diff options
Diffstat (limited to 'libmariadb/libmariadb/secure/schannel_certs.c')
-rw-r--r-- | libmariadb/libmariadb/secure/schannel_certs.c | 854 |
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); + } +} |