summaryrefslogtreecommitdiffstats
path: root/libmariadb/libmariadb/secure/schannel_certs.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmariadb/libmariadb/secure/schannel_certs.c')
-rw-r--r--libmariadb/libmariadb/secure/schannel_certs.c854
1 files changed, 854 insertions, 0 deletions
diff --git a/libmariadb/libmariadb/secure/schannel_certs.c b/libmariadb/libmariadb/secure/schannel_certs.c
new file mode 100644
index 00000000..1bcfb852
--- /dev/null
+++ b/libmariadb/libmariadb/secure/schannel_certs.c
@@ -0,0 +1,854 @@
+/************************************************************************************
+ Copyright (C) 2019 MariaDB
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not see <http://www.gnu.org/licenses>
+ or write to the Free Software Foundation, Inc.,
+ 51 Franklin St., Fifth Floor, Boston, MA 02110, USA
+
+ *************************************************************************************/
+
+ /*
+ This module contain X509 certificate handling on Windows.
+ PEM parsing, loading client certificate and key, server certificate validation
+ */
+
+ /*
+ CERT_CHAIN_ENGINE_CONFIG has additional members in Windows 8.1
+ To allow client to be work on pre-8.1 Windows, compile
+ with corresponding _WIN32_WINNT
+ */
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0601
+#endif
+
+#include "schannel_certs.h"
+#include <malloc.h>
+#include <stdio.h>
+#include <string.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <winhttp.h>
+#include <assert.h>
+#include "win32_errmsg.h"
+
+ /*
+ Return GetLastError(), or, if this unexpectedly gives success,
+ return ERROR_INTERNAL_ERROR.
+
+ Background - in several cases in this module we return GetLastError()
+ after an Windows function fails. However, we do not want the function to
+ return success, even if GetLastError() was suddenly 0.
+ */
+static DWORD get_last_error()
+{
+ DWORD ret = GetLastError();
+ if (ret)
+ return ret;
+
+ // We generally expect last error to be set API fails.
+ // thus the debug assertion-
+ assert(0);
+ return ERROR_INTERNAL_ERROR;
+}
+
+#define FAIL(...) \
+ do{\
+ status = get_last_error();\
+ ma_format_win32_error(errmsg, errmsg_len, status, __VA_ARGS__);\
+ goto cleanup;\
+ } while (0)
+
+/*
+ Load file into memory. Add null terminator at the end, so it will be a valid C string.
+*/
+static char* pem_file_to_string(const char* file, char* errmsg, size_t errmsg_len)
+{
+ LARGE_INTEGER file_size;
+ size_t file_bufsize = 0;
+ size_t total_bytes_read = 0;
+ char* file_buffer = NULL;
+ SECURITY_STATUS status = SEC_E_OK;
+
+ HANDLE file_handle = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (file_handle == INVALID_HANDLE_VALUE)
+ {
+ FAIL("failed to open file '%s'", file);
+ }
+
+ if (!GetFileSizeEx(file_handle, &file_size))
+ {
+ FAIL("GetFileSizeEx failed on '%s'", file);
+ }
+
+ if (file_size.QuadPart > ULONG_MAX - 1)
+ {
+ SetLastError(SEC_E_INVALID_PARAMETER);
+ FAIL("file '%s' too large", file);
+ }
+
+ file_bufsize = (size_t)file_size.QuadPart;
+ file_buffer = (char*)LocalAlloc(0,file_bufsize + 1);
+ if (!file_buffer)
+ {
+ FAIL("LocalAlloc(0,%zu) failed", file_bufsize + 1);
+ }
+
+ while (total_bytes_read < file_bufsize)
+ {
+ DWORD bytes_to_read = (DWORD)(file_bufsize - total_bytes_read);
+ DWORD bytes_read = 0;
+
+ if (!ReadFile(file_handle, file_buffer + total_bytes_read,
+ bytes_to_read, &bytes_read, NULL))
+ {
+ FAIL("ReadFile() failed to read file '%s'", file);
+ }
+ if (bytes_read == 0)
+ {
+ /* Premature EOF -- adjust the bufsize to the new value */
+ file_bufsize = total_bytes_read;
+ }
+ else
+ {
+ total_bytes_read += bytes_read;
+ }
+ }
+
+ /* Null terminate the buffer */
+ file_buffer[file_bufsize] = '\0';
+
+cleanup:
+ if (file_handle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(file_handle);
+ }
+ if (status)
+ {
+ /* Some error happened. */
+ LocalFree(file_buffer);
+ file_buffer = NULL;
+ }
+ return file_buffer;
+}
+
+
+// Structure for parsing BEGIN/END sections inside pem.
+typedef struct _pem_type_desc
+{
+ const char* begin_tag;
+ size_t begin_tag_len;
+ const char* end_tag;
+ size_t end_tag_len;
+} pem_type_desc;
+
+#define BEGIN_TAG(x) "-----BEGIN " x "-----"
+#define END_TAG(x) "\n-----END " x "-----"
+#define PEM_SECTION(tag) {BEGIN_TAG(tag), sizeof(BEGIN_TAG(tag))-1, END_TAG(tag), sizeof(END_TAG(tag))-1}
+
+typedef enum {
+ PEM_TYPE_CERTIFICATE = 0,
+ PEM_TYPE_X509_CRL,
+ PEM_TYPE_RSA_PRIVATE_KEY,
+ PEM_TYPE_PRIVATE_KEY
+} PEM_TYPE;
+
+static const pem_type_desc pem_sections[] = {
+ PEM_SECTION("CERTIFICATE"),
+ PEM_SECTION("X509 CRL"),
+ PEM_SECTION("RSA PRIVATE KEY"),
+ PEM_SECTION("PRIVATE KEY")
+};
+
+/*
+ Locate a substring in pem for given type,
+ e.g section between BEGIN CERTIFICATE and END CERTIFICATE
+ in PEMs base64 format, with header and footer.
+
+ output parameters 'begin' and 'end' are set upon return.
+ it is possible that functions returns 'begin' != NULL but
+ 'end' = NULL. This is generally a format error, meaning that
+ the end tag was not found
+*/
+void pem_locate(char* pem_str,
+ PEM_TYPE type,
+ char** begin,
+ char** end)
+{
+ *begin = NULL;
+ *end = NULL;
+ char c;
+
+ const pem_type_desc* desc = &pem_sections[type];
+ *begin = strstr(pem_str, desc->begin_tag);
+ if (!(*begin))
+ return;
+
+ // We expect newline after the
+ // begin tag, LF or CRLF
+ c = (*begin)[desc->begin_tag_len];
+
+ if (c != '\r' && c != '\n')
+ {
+ *begin = NULL;
+ return;
+ }
+
+ *end = strstr(*begin + desc->begin_tag_len + 1, desc->end_tag);
+ if (!*end)
+ return; // error, end marker not found
+
+ (*end) += desc->end_tag_len;
+ return;
+}
+
+
+/*
+ Add certificates, or CRLs from a PEM file to Wincrypt store
+*/
+static SECURITY_STATUS add_certs_to_store(
+ HCERTSTORE trust_store,
+ const char* file,
+ PEM_TYPE type,
+ char* errmsg,
+ size_t errmsg_len)
+{
+ char* file_buffer = NULL;
+ char* cur = NULL;
+ SECURITY_STATUS status = SEC_E_OK;
+ CRL_CONTEXT* crl_context = NULL;
+ CERT_CONTEXT* cert_context = NULL;
+ char* begin;
+ char* end;
+
+ file_buffer = pem_file_to_string(file, errmsg, errmsg_len);
+ if (!file_buffer)
+ goto cleanup;
+
+ for (cur = file_buffer; ; cur = end)
+ {
+ pem_locate(cur, type, &begin, &end);
+
+ if (!begin)
+ break;
+
+ if (!end)
+ {
+ SetLastError(SEC_E_INVALID_PARAMETER);
+ FAIL("Invalid PEM file '%s', missing end marker corresponding to begin marker '%s' at offset %zu",
+ file, pem_sections[type].begin_tag, (size_t)(begin - file_buffer));
+ }
+ CERT_BLOB cert_blob;
+ void* context = NULL;
+ DWORD actual_content_type = 0;
+
+ cert_blob.pbData = (BYTE*)begin;
+ cert_blob.cbData = (DWORD)(end - begin);
+ if (!CryptQueryObject(
+ CERT_QUERY_OBJECT_BLOB, &cert_blob,
+ CERT_QUERY_CONTENT_FLAG_CERT | CERT_QUERY_CONTENT_FLAG_CRL,
+ CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type,
+ NULL, NULL, NULL, (const void**)&context))
+ {
+ FAIL("failed to extract certificate from PEM file '%s'",file);
+ }
+
+ if (!context)
+ {
+ SetLastError(SEC_E_INTERNAL_ERROR);
+ FAIL("unexpected result from CryptQueryObject(),cert_context is NULL"
+ " after successful completion, file '%s'",
+ file);
+ }
+
+ if (actual_content_type == CERT_QUERY_CONTENT_CERT)
+ {
+ CERT_CONTEXT* cert_context = (CERT_CONTEXT*)context;
+ if (!CertAddCertificateContextToStore(
+ trust_store, cert_context,
+ CERT_STORE_ADD_ALWAYS, NULL))
+ {
+ FAIL("CertAddCertificateContextToStore failed");
+ }
+ }
+ else if (actual_content_type == CERT_QUERY_CONTENT_CRL)
+ {
+ CRL_CONTEXT* crl_context = (CRL_CONTEXT*)context;
+ if (!CertAddCRLContextToStore(
+ trust_store, crl_context,
+ CERT_STORE_ADD_ALWAYS, NULL))
+ {
+ FAIL("CertAddCRLContextToStore() failed");
+ }
+ }
+ }
+cleanup:
+ LocalFree(file_buffer);
+ if (cert_context)
+ CertFreeCertificateContext(cert_context);
+ if (crl_context)
+ CertFreeCRLContext(crl_context);
+ return status;
+}
+
+/*
+Add a directory to store, i.e try to load all files.
+(extract certificates and add them to store)
+
+@return 0 on success, error only if directory is invalid.
+*/
+SECURITY_STATUS add_dir_to_store(HCERTSTORE trust_store, const char* dir,
+ PEM_TYPE type, char* errmsg, size_t errmsg_len)
+{
+ WIN32_FIND_DATAA ffd;
+ char path[MAX_PATH];
+ char pattern[MAX_PATH];
+ DWORD dwAttr;
+ HANDLE hFind = INVALID_HANDLE_VALUE;
+ SECURITY_STATUS status = SEC_E_OK;
+
+ if ((dwAttr = GetFileAttributes(dir)) == INVALID_FILE_ATTRIBUTES)
+ {
+ SetLastError(SEC_E_INVALID_PARAMETER);
+ FAIL("directory '%s' does not exist", dir);
+ }
+ if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ SetLastError(SEC_E_INVALID_PARAMETER);
+ FAIL("'%s' is not a directory", dir);
+ }
+ sprintf_s(pattern, sizeof(pattern), "%s\\*", dir);
+ hFind = FindFirstFile(pattern, &ffd);
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ FAIL("FindFirstFile(%s) failed",pattern);
+ }
+ do
+ {
+ if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ continue;
+ sprintf_s(path, sizeof(path), "%s\\%s", dir, ffd.cFileName);
+
+ // ignore error from add_certs_to_store(), not all file
+ // maybe PEM.
+ add_certs_to_store(trust_store, path, type, errmsg,
+ errmsg_len);
+ } while (FindNextFile(hFind, &ffd) != 0);
+
+cleanup:
+ if (hFind != INVALID_HANDLE_VALUE)
+ FindClose(hFind);
+
+ return status;
+}
+
+/* Count certificates in store. */
+static int count_certificates(HCERTSTORE store)
+{
+ int num_certs = 0;
+ PCCERT_CONTEXT c = NULL;
+
+ while ((c = CertEnumCertificatesInStore(store, c)))
+ num_certs++;
+
+ return num_certs;
+}
+
+/**
+ Creates certificate store with user defined CA chain and/or CRL.
+ Loads PEM certificate from files or directories.
+
+ If only CRLFile/CRLPath is defined, the "system" store is duplicated,
+ and new CRLs are added to it.
+
+ If CAFile/CAPAth is defined, then new empty store is created, and CAs
+ (and CRLs, if defined), are added to it.
+
+ The function throws an error, if none of the files in CAFile/CAPath have a valid certificate.
+ It is also an error if CRLFile does not exist.
+*/
+SECURITY_STATUS schannel_create_store(
+ const char* CAFile,
+ const char* CAPath,
+ const char* CRLFile,
+ const char* CRLPath,
+ HCERTSTORE* out_store,
+ char* errmsg,
+ size_t errmsg_len)
+{
+
+ HCERTSTORE store = NULL;
+ HCERTSTORE system_store = NULL;
+ int status = SEC_E_OK;
+
+ *out_store = NULL;
+ if (!CAFile && !CAPath && !CRLFile && !CRLPath)
+ {
+ /* Nothing to do, caller will use default store*/
+ *out_store = NULL;
+ return SEC_E_OK;
+ }
+ if (CAFile || CAPath)
+ {
+ /* Open the certificate store */
+ store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, (HCRYPTPROV)NULL,
+ CERT_STORE_CREATE_NEW_FLAG, NULL);
+ if (!store)
+ {
+ FAIL("CertOpenStore failed for memory store");
+ }
+ }
+ else if (CRLFile || CRLPath)
+ {
+ /* Only CRL was provided, copy system store, add revocation list to
+ * it. */
+ system_store =
+ CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, (HCRYPTPROV_LEGACY)NULL,
+ CERT_SYSTEM_STORE_CURRENT_USER, L"MY");
+ if (!system_store)
+ {
+ FAIL("CertOpenStore failed for system store");
+ }
+
+ store = CertDuplicateStore(system_store);
+ if (!store)
+ {
+ FAIL("CertDuplicateStore failed");
+ }
+ }
+
+ if (CAFile)
+ {
+ status = add_certs_to_store(store, CAFile,
+ PEM_TYPE_CERTIFICATE, errmsg, errmsg_len);
+ if (status)
+ goto cleanup;
+ }
+ if (CAPath)
+ {
+ status = add_dir_to_store(store, CAPath,
+ PEM_TYPE_CERTIFICATE, errmsg, errmsg_len);
+ if (status)
+ goto cleanup;
+ }
+
+ if ((CAFile || CAPath) && store && !count_certificates(store))
+ {
+ SetLastError(SEC_E_INVALID_PARAMETER);
+ FAIL("no valid certificates were found, CAFile='%s', CAPath='%s'",
+ CAFile ? CAFile : "<not set>", CAPath ? CAPath : "<not set>");
+ }
+
+ if (CRLFile)
+ {
+ status = add_certs_to_store(store, CRLFile, PEM_TYPE_X509_CRL,
+ errmsg, errmsg_len);
+ }
+ if (CRLPath)
+ {
+ status = add_dir_to_store(store, CRLPath, PEM_TYPE_X509_CRL,
+ errmsg, errmsg_len);
+ }
+
+cleanup:
+ if (system_store)
+ CertCloseStore(system_store, 0);
+ if (status && store)
+ {
+ CertCloseStore(store, 0);
+ store = NULL;
+ }
+ *out_store = store;
+ return status;
+}
+
+/*
+ The main verification logic.
+ Taken almost completely from Windows 2003 Platform SDK 2003
+ (Samples\Security\SSPI\SSL\WebClient.c)
+
+ The only difference here is is usage of custom store
+ and chain engine.
+*/
+static SECURITY_STATUS VerifyServerCertificate(
+ PCCERT_CONTEXT pServerCert,
+ HCERTSTORE hStore,
+ LPWSTR pwszServerName,
+ DWORD dwRevocationCheckFlags,
+ DWORD dwVerifyFlags,
+ LPSTR errmsg,
+ size_t errmsg_len)
+{
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA polExtra;
+ CERT_CHAIN_POLICY_PARA PolicyPara;
+ CERT_CHAIN_POLICY_STATUS PolicyStatus;
+ CERT_CHAIN_PARA ChainPara;
+ HCERTCHAINENGINE hChainEngine = NULL;
+ PCCERT_CHAIN_CONTEXT pChainContext = NULL;
+ LPSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH,
+ szOID_SERVER_GATED_CRYPTO,
+ szOID_SGC_NETSCAPE };
+ DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR);
+ SECURITY_STATUS status = SEC_E_OK;
+
+ if (pServerCert == NULL)
+ {
+ SetLastError(SEC_E_WRONG_PRINCIPAL);
+ FAIL("Invalid parameter pServerCert passed to VerifyServerCertificate");
+ }
+
+ ZeroMemory(&ChainPara, sizeof(ChainPara));
+ ChainPara.cbSize = sizeof(ChainPara);
+ ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages;
+ ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;
+
+ if (hStore)
+ {
+ CERT_CHAIN_ENGINE_CONFIG EngineConfig = { 0 };
+ EngineConfig.cbSize = sizeof(EngineConfig);
+ EngineConfig.hExclusiveRoot = hStore;
+ if (!CertCreateCertificateChainEngine(&EngineConfig, &hChainEngine))
+ {
+ FAIL("CertCreateCertificateChainEngine failed");
+ }
+ }
+
+ if (!CertGetCertificateChain(
+ hChainEngine,
+ pServerCert,
+ NULL,
+ pServerCert->hCertStore,
+ &ChainPara,
+ dwRevocationCheckFlags,
+ NULL,
+ &pChainContext))
+ {
+ FAIL("CertGetCertificateChain failed");
+ goto cleanup;
+ }
+
+ // Validate certificate chain.
+ ZeroMemory(&polExtra, sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA));
+ polExtra.cbStruct = sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA);
+ polExtra.dwAuthType = AUTHTYPE_SERVER;
+ polExtra.fdwChecks = dwVerifyFlags;
+ polExtra.pwszServerName = pwszServerName;
+
+ memset(&PolicyPara, 0, sizeof(PolicyPara));
+ PolicyPara.cbSize = sizeof(PolicyPara);
+ PolicyPara.pvExtraPolicyPara = &polExtra;
+
+ memset(&PolicyStatus, 0, sizeof(PolicyStatus));
+ PolicyStatus.cbSize = sizeof(PolicyStatus);
+
+ if (!CertVerifyCertificateChainPolicy(
+ CERT_CHAIN_POLICY_SSL,
+ pChainContext,
+ &PolicyPara,
+ &PolicyStatus))
+ {
+ FAIL("CertVerifyCertificateChainPolicy failed");
+ }
+
+ if (PolicyStatus.dwError)
+ {
+ SetLastError(PolicyStatus.dwError);
+ FAIL("Server certificate validation failed");
+ }
+
+cleanup:
+ if (hChainEngine)
+ {
+ CertFreeCertificateChainEngine(hChainEngine);
+ }
+ if (pChainContext)
+ {
+ CertFreeCertificateChain(pChainContext);
+ }
+ return status;
+}
+
+
+void schannel_free_store(HCERTSTORE store)
+{
+ if (store)
+ CertCloseStore(store, 0);
+}
+
+
+/*
+Verify server certificate against a wincrypt store
+@return 0 - success, otherwise error occurred.
+*/
+SECURITY_STATUS schannel_verify_server_certificate(
+ const CERT_CONTEXT* cert,
+ HCERTSTORE store,
+ BOOL check_revocation,
+ const char* server_name,
+ BOOL check_server_name,
+ char* errmsg,
+ size_t errmsg_len)
+{
+ SECURITY_STATUS status = SEC_E_OK;
+ wchar_t* wserver_name = NULL;
+ DWORD dwVerifyFlags;
+ DWORD dwRevocationFlags;
+
+ if (check_server_name)
+ {
+ int cchServerName = (int)strlen(server_name) + 1;
+ wserver_name = (wchar_t*)LocalAlloc(0,sizeof(wchar_t) * cchServerName);
+ if (!wserver_name)
+ {
+ FAIL("LocalAlloc() failed");
+ }
+ if (MultiByteToWideChar(CP_UTF8, 0, server_name, cchServerName, wserver_name, cchServerName) < 0)
+ {
+ FAIL("MultiByteToWideChar() failed");
+ }
+ }
+
+ dwVerifyFlags = 0;
+ dwRevocationFlags = 0;
+ if (check_revocation)
+ dwRevocationFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT | CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
+ if (!check_server_name)
+ dwVerifyFlags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
+
+ status = VerifyServerCertificate(cert, store, wserver_name ? wserver_name : L"SERVER_NAME",
+ dwRevocationFlags, dwVerifyFlags, errmsg, errmsg_len);
+
+cleanup:
+ LocalFree(wserver_name);
+ return status;
+}
+
+
+/* Attach private key (in PEM format) to client certificate */
+static SECURITY_STATUS load_private_key(CERT_CONTEXT* cert, char* private_key_str, size_t len, char* errmsg, size_t errmsg_len)
+{
+ DWORD derlen = (DWORD)len;
+ BYTE* derbuf = NULL;
+ DWORD keyblob_len = 0;
+ BYTE* keyblob = NULL;
+ HCRYPTPROV hProv = 0;
+ HCRYPTKEY hKey = 0;
+ CERT_KEY_CONTEXT cert_key_context = { 0 };
+ PCRYPT_PRIVATE_KEY_INFO pki = NULL;
+ DWORD pki_len = 0;
+ SECURITY_STATUS status = SEC_E_OK;
+
+ derbuf = LocalAlloc(0, derlen);
+ if (!derbuf)
+ {
+ FAIL("LocalAlloc failed");
+ }
+
+ if (!CryptStringToBinaryA(private_key_str, (DWORD)len, CRYPT_STRING_BASE64HEADER, derbuf, &derlen, NULL, NULL))
+ {
+ FAIL("Failed to convert BASE64 private key");
+ }
+
+ /*
+ To accommodate for both "BEGIN PRIVATE KEY" vs "BEGIN RSA PRIVATE KEY"
+ sections in PEM, we try to decode with PKCS_PRIVATE_KEY_INFO first,
+ and, if it fails, with PKCS_RSA_PRIVATE_KEY flag.
+ */
+ if (CryptDecodeObjectEx(
+ X509_ASN_ENCODING,
+ PKCS_PRIVATE_KEY_INFO,
+ derbuf, derlen,
+ CRYPT_DECODE_ALLOC_FLAG,
+ NULL, &pki, &pki_len))
+ {
+ // convert private key info to RSA private key blob
+ if (!CryptDecodeObjectEx(
+ X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ PKCS_RSA_PRIVATE_KEY,
+ pki->PrivateKey.pbData,
+ pki->PrivateKey.cbData,
+ CRYPT_DECODE_ALLOC_FLAG,
+ NULL, &keyblob, &keyblob_len))
+ {
+ FAIL("Failed to parse private key");
+ }
+ }
+ else if (!CryptDecodeObjectEx(
+ X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ PKCS_RSA_PRIVATE_KEY,
+ derbuf, derlen,
+ CRYPT_DECODE_ALLOC_FLAG, NULL,
+ &keyblob, &keyblob_len))
+ {
+ FAIL("Failed to parse private key");
+ }
+
+ if (!CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
+ {
+ FAIL("CryptAcquireContext failed");
+ }
+
+ if (!CryptImportKey(hProv, keyblob, keyblob_len, 0, 0, (HCRYPTKEY*)&hKey))
+ {
+ FAIL("CryptImportKey failed");
+ }
+ cert_key_context.hCryptProv = hProv;
+ cert_key_context.dwKeySpec = AT_KEYEXCHANGE;
+ cert_key_context.cbSize = sizeof(cert_key_context);
+
+ /* assign private key to certificate context */
+ if (!CertSetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID,
+ CERT_STORE_NO_CRYPT_RELEASE_FLAG,
+ &cert_key_context))
+ {
+ FAIL("CertSetCertificateContextProperty failed");
+ }
+
+cleanup:
+ LocalFree(derbuf);
+ LocalFree(keyblob);
+ LocalFree(pki);
+ if (hKey)
+ CryptDestroyKey(hKey);
+ if (status)
+ {
+ if (hProv)
+ CryptReleaseContext(hProv, 0);
+ }
+ return status;
+}
+
+/*
+ Given PEM strings for certificate and private key,
+ create a client certificate*
+*/
+static CERT_CONTEXT* create_client_certificate_mem(
+ char* cert_file_content,
+ char* key_file_content,
+ char* errmsg,
+ size_t errmsg_len)
+{
+ CERT_CONTEXT* ctx = NULL;
+ char* begin;
+ char* end;
+ CERT_BLOB cert_blob;
+ DWORD actual_content_type = 0;
+ SECURITY_STATUS status = SEC_E_OK;
+
+ /* Parse certificate */
+ pem_locate(cert_file_content, PEM_TYPE_CERTIFICATE,
+ &begin, &end);
+
+ if (!begin || !end)
+ {
+ SetLastError(SEC_E_INVALID_PARAMETER);
+ FAIL("Client certificate not found in PEM file");
+ }
+
+ cert_blob.pbData = (BYTE*)begin;
+ cert_blob.cbData = (DWORD)(end - begin);
+ if (!CryptQueryObject(
+ CERT_QUERY_OBJECT_BLOB, &cert_blob,
+ CERT_QUERY_CONTENT_FLAG_CERT,
+ CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type,
+ NULL, NULL, NULL, (const void**)&ctx))
+ {
+ FAIL("Can't parse client certficate");
+ }
+
+ /* Parse key */
+ PEM_TYPE types[] = { PEM_TYPE_RSA_PRIVATE_KEY, PEM_TYPE_PRIVATE_KEY };
+ for (int i = 0; i < sizeof(types) / sizeof(types[0]); i++)
+ {
+ pem_locate(key_file_content, types[i], &begin, &end);
+ if (begin && end)
+ {
+ /* Assign key to certificate.*/
+ status = load_private_key(ctx, begin, (end - begin), errmsg, errmsg_len);
+ goto cleanup;
+ }
+ }
+
+ if (!begin || !end)
+ {
+ SetLastError(SEC_E_INVALID_PARAMETER);
+ FAIL("Client private key not found in PEM");
+ }
+
+cleanup:
+ if (status && ctx)
+ {
+ CertFreeCertificateContext(ctx);
+ ctx = NULL;
+ }
+ return ctx;
+}
+
+
+/* Given cert and key, as PEM file names, create a client certificate */
+CERT_CONTEXT* schannel_create_cert_context(char* cert_file, char* key_file, char* errmsg, size_t errmsg_len)
+{
+ CERT_CONTEXT* ctx = NULL;
+ char* key_file_content = NULL;
+ char* cert_file_content = NULL;
+
+ cert_file_content = pem_file_to_string(cert_file, errmsg, errmsg_len);
+
+ if (!cert_file_content)
+ goto cleanup;
+
+ if (cert_file == key_file)
+ {
+ key_file_content = cert_file_content;
+ }
+ else
+ {
+ key_file_content = pem_file_to_string(key_file, errmsg, errmsg_len);
+ if (!key_file_content)
+ goto cleanup;
+ }
+
+ ctx = create_client_certificate_mem(cert_file_content, key_file_content, errmsg, errmsg_len);
+
+cleanup:
+ LocalFree(cert_file_content);
+ if (cert_file != key_file)
+ LocalFree(key_file_content);
+
+ return ctx;
+}
+
+/*
+ Free certificate, and all resources, created by schannel_create_cert_context()
+*/
+void schannel_free_cert_context(const CERT_CONTEXT* cert)
+{
+ /* release provider handle which was acquires in load_private_key() */
+ CERT_KEY_CONTEXT cert_key_context = { 0 };
+ cert_key_context.cbSize = sizeof(cert_key_context);
+ DWORD cbData = sizeof(CERT_KEY_CONTEXT);
+ HCRYPTPROV hProv = 0;
+
+ if (CertGetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID, &cert_key_context, &cbData))
+ {
+ hProv = cert_key_context.hCryptProv;
+ }
+ CertFreeCertificateContext(cert);
+ if (hProv)
+ {
+ CryptReleaseContext(cert_key_context.hCryptProv, 0);
+ }
+}