From 0fdf96512613574591f501d63fe49495ba40e1d5 Mon Sep 17 00:00:00 2001 From: Gilles Vollant Date: Sun, 15 Sep 2019 14:37:36 +0200 Subject: schannel: support .P12 or .PFX client certificates Used with curl command line option like this: --cert : --cert-type p12 Closes #5193 --- lib/vtls/schannel.c | 158 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 34 deletions(-) diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 64719fe70..8bf598c07 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -585,11 +585,12 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) /* client certificate */ if(data->set.ssl.cert) { DWORD cert_store_name; - TCHAR *cert_store_path; + TCHAR *cert_store_path = NULL; TCHAR *cert_thumbprint_str; CRYPT_HASH_BLOB cert_thumbprint; BYTE cert_thumbprint_data[CERT_THUMBPRINT_DATA_LEN]; HCERTSTORE cert_store; + FILE *fInCert = NULL; TCHAR *cert_path = Curl_convert_UTF8_to_tchar(data->set.ssl.cert); if(!cert_path) @@ -597,54 +598,143 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) result = get_cert_location(cert_path, &cert_store_name, &cert_store_path, &cert_thumbprint_str); - if(result != CURLE_OK) { - failf(data, "schannel: Failed to get certificate location for %s", - cert_path); + if((result != CURLE_OK) && (data->set.ssl.cert[0]!='\0')) + fInCert = fopen(data->set.ssl.cert, "rb"); + + if((result != CURLE_OK) && (fInCert == NULL)) { + failf(data, "schannel: Failed to get certificate location" + " or file for %s", + data->set.ssl.cert); Curl_unicodefree(cert_path); return result; } - cert_store = - CertOpenStore(CURL_CERT_STORE_PROV_SYSTEM, 0, - (HCRYPTPROV)NULL, - CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name, - cert_store_path); - if(!cert_store) { - failf(data, "schannel: Failed to open cert store %x %s, " - "last error is %x", - cert_store_name, cert_store_path, GetLastError()); - free(cert_store_path); + if(fInCert) { + /* Reading a .P12 or .pfx file, like the example at bottom of + https://social.msdn.microsoft.com/Forums/windowsdesktop/ + en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5 + */ + void *certdata = NULL; + long filesize = 0; + CRYPT_DATA_BLOB datablob; + WCHAR* pszPassword; + size_t pwd_len = 0; + int str_w_len = 0; + int continue_reading = fseek(fInCert, 0, SEEK_END) == 0; + if(continue_reading) + filesize = ftell(fInCert); + if(filesize < 0) + continue_reading = 0; + if(continue_reading) + continue_reading = fseek(fInCert, 0, SEEK_SET) == 0; + if(continue_reading) + certdata = malloc(((size_t)filesize) + 1); + if((certdata == NULL) || + ((int) fread(certdata, (size_t)filesize, 1, fInCert) != 1)) + continue_reading = 0; + fclose(fInCert); Curl_unicodefree(cert_path); - return CURLE_SSL_CERTPROBLEM; - } - free(cert_store_path); - cert_thumbprint.pbData = cert_thumbprint_data; - cert_thumbprint.cbData = CERT_THUMBPRINT_DATA_LEN; + if(!continue_reading) { + failf(data, "schannel: Failed to read cert file %s", + data->set.ssl.cert); + free(certdata); + return CURLE_SSL_CERTPROBLEM; + } - if(!CryptStringToBinary(cert_thumbprint_str, CERT_THUMBPRINT_STR_LEN, - CRYPT_STRING_HEX, - cert_thumbprint_data, &cert_thumbprint.cbData, - NULL, NULL)) { - Curl_unicodefree(cert_path); - return CURLE_SSL_CERTPROBLEM; - } + /* Convert key-pair data to the in-memory certificate store */ + datablob.pbData = (BYTE*)certdata; + datablob.cbData = (DWORD)filesize; + + if(data->set.ssl.key_passwd != NULL) + pwd_len = strlen(data->set.ssl.key_passwd); + pszPassword = (WCHAR*)malloc(sizeof(WCHAR)*(pwd_len + 1)); + if(pwd_len > 0) + str_w_len = + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + data->set.ssl.key_passwd, (int)pwd_len, + pszPassword, (int)(pwd_len + 1)); + + if((str_w_len >= 0) && (str_w_len <= (int)pwd_len)) + pszPassword[str_w_len] = 0; + else + pszPassword[0] = 0; + + cert_store = PFXImportCertStore(&datablob, pszPassword, 0); + free(pszPassword); + free(certdata); + if(cert_store == NULL) { + DWORD errorcode = GetLastError(); + if(errorcode == ERROR_INVALID_PASSWORD) + failf(data, "schannel: Failed to import cert file %s, " + "password is bad", data->set.ssl.cert); + else + failf(data, "schannel: Failed to import cert file %s, " + "last error is 0x%x", data->set.ssl.cert, errorcode); + return CURLE_SSL_CERTPROBLEM; + } - client_certs[0] = CertFindCertificateInStore( - cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, - CERT_FIND_HASH, &cert_thumbprint, NULL); + client_certs[0] = CertFindCertificateInStore( + cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, + CERT_FIND_ANY, NULL, NULL); - Curl_unicodefree(cert_path); + if(client_certs[0] == NULL) { + failf(data, "schannel: Failed to get certificat from file %s" + ", last error is 0x%x", + data->set.ssl.cert, GetLastError()); + CertCloseStore(cert_store, 0); + return CURLE_SSL_CERTPROBLEM; + } - if(client_certs[0]) { schannel_cred.cCreds = 1; schannel_cred.paCred = client_certs; } else { - /* CRYPT_E_NOT_FOUND / E_INVALIDARG */ - return CURLE_SSL_CERTPROBLEM; - } + cert_store = + CertOpenStore(CURL_CERT_STORE_PROV_SYSTEM, 0, + (HCRYPTPROV)NULL, + CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name, + cert_store_path); + if(!cert_store) { + failf(data, "schannel: Failed to open cert store %x %s, " + "last error is 0x%x", + cert_store_name, cert_store_path, GetLastError()); + free(cert_store_path); + Curl_unicodefree(cert_path); + return CURLE_SSL_CERTPROBLEM; + } + free(cert_store_path); + + cert_thumbprint.pbData = cert_thumbprint_data; + cert_thumbprint.cbData = CERT_THUMBPRINT_DATA_LEN; + + if(!CryptStringToBinary(cert_thumbprint_str, + CERT_THUMBPRINT_STR_LEN, + CRYPT_STRING_HEX, + cert_thumbprint_data, + &cert_thumbprint.cbData, + NULL, NULL)) { + Curl_unicodefree(cert_path); + CertCloseStore(cert_store, 0); + return CURLE_SSL_CERTPROBLEM; + } + + client_certs[0] = CertFindCertificateInStore( + cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, + CERT_FIND_HASH, &cert_thumbprint, NULL); + + Curl_unicodefree(cert_path); + if(client_certs[0]) { + schannel_cred.cCreds = 1; + schannel_cred.paCred = client_certs; + } + else { + /* CRYPT_E_NOT_FOUND / E_INVALIDARG */ + CertCloseStore(cert_store, 0); + return CURLE_SSL_CERTPROBLEM; + } + } CertCloseStore(cert_store, 0); } #else -- cgit v1.2.1