1/* 2 * Copyright (c) 2004 Peter 'Luna' Runestig <peter@runestig.com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without modifi- 6 * cation, are permitted provided that the following conditions are met: 7 * 8 * o Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 11 * o Redistributions in binary form must reproduce the above copyright no- 12 * tice, this list of conditions and the following disclaimer in the do- 13 * cumentation and/or other materials provided with the distribution. 14 * 15 * o The names of the contributors may not be used to endorse or promote 16 * products derived from this software without specific prior written 17 * permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LI- 23 * ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN- 24 * TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV- 26 * ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI- 27 * LITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#ifdef HAVE_CONFIG_H 32#include "config.h" 33#elif defined(_MSC_VER) 34#include "config-msvc.h" 35#endif 36 37#include "syshead.h" 38 39#ifdef ENABLE_CRYPTOAPI 40 41#include <openssl/ssl.h> 42#include <openssl/err.h> 43#include <windows.h> 44#include <wincrypt.h> 45#include <stdio.h> 46#include <ctype.h> 47#include <assert.h> 48 49/* MinGW w32api 3.17 is still incomplete when it comes to CryptoAPI while 50 * MinGW32-w64 defines all macros used. This is a hack around that problem. 51 */ 52#ifndef CERT_SYSTEM_STORE_LOCATION_SHIFT 53#define CERT_SYSTEM_STORE_LOCATION_SHIFT 16 54#endif 55#ifndef CERT_SYSTEM_STORE_CURRENT_USER_ID 56#define CERT_SYSTEM_STORE_CURRENT_USER_ID 1 57#endif 58#ifndef CERT_SYSTEM_STORE_CURRENT_USER 59#define CERT_SYSTEM_STORE_CURRENT_USER (CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT) 60#endif 61#ifndef CERT_STORE_READONLY_FLAG 62#define CERT_STORE_READONLY_FLAG 0x00008000 63#endif 64#ifndef CERT_STORE_OPEN_EXISTING_FLAG 65#define CERT_STORE_OPEN_EXISTING_FLAG 0x00004000 66#endif 67 68/* Size of an SSL signature: MD5+SHA1 */ 69#define SSL_SIG_LENGTH 36 70 71/* try to funnel any Windows/CryptoAPI error messages to OpenSSL ERR_... */ 72#define ERR_LIB_CRYPTOAPI (ERR_LIB_USER + 69) /* 69 is just a number... */ 73#define CRYPTOAPIerr(f) err_put_ms_error(GetLastError(), (f), __FILE__, __LINE__) 74#define CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE 100 75#define CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE 101 76#define CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY 102 77#define CRYPTOAPI_F_CRYPT_CREATE_HASH 103 78#define CRYPTOAPI_F_CRYPT_GET_HASH_PARAM 104 79#define CRYPTOAPI_F_CRYPT_SET_HASH_PARAM 105 80#define CRYPTOAPI_F_CRYPT_SIGN_HASH 106 81#define CRYPTOAPI_F_LOAD_LIBRARY 107 82#define CRYPTOAPI_F_GET_PROC_ADDRESS 108 83 84static ERR_STRING_DATA CRYPTOAPI_str_functs[] = { 85 { ERR_PACK(ERR_LIB_CRYPTOAPI, 0, 0), "microsoft cryptoapi"}, 86 { ERR_PACK(0, CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE, 0), "CertOpenSystemStore" }, 87 { ERR_PACK(0, CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE, 0), "CertFindCertificateInStore" }, 88 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY, 0), "CryptAcquireCertificatePrivateKey" }, 89 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_CREATE_HASH, 0), "CryptCreateHash" }, 90 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_GET_HASH_PARAM, 0), "CryptGetHashParam" }, 91 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SET_HASH_PARAM, 0), "CryptSetHashParam" }, 92 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SIGN_HASH, 0), "CryptSignHash" }, 93 { ERR_PACK(0, CRYPTOAPI_F_LOAD_LIBRARY, 0), "LoadLibrary" }, 94 { ERR_PACK(0, CRYPTOAPI_F_GET_PROC_ADDRESS, 0), "GetProcAddress" }, 95 { 0, NULL } 96}; 97 98typedef struct _CAPI_DATA { 99 const CERT_CONTEXT *cert_context; 100 HCRYPTPROV crypt_prov; 101 DWORD key_spec; 102 BOOL free_crypt_prov; 103} CAPI_DATA; 104 105static char *ms_error_text(DWORD ms_err) 106{ 107 LPVOID lpMsgBuf = NULL; 108 char *rv = NULL; 109 110 FormatMessage( 111 FORMAT_MESSAGE_ALLOCATE_BUFFER | 112 FORMAT_MESSAGE_FROM_SYSTEM | 113 FORMAT_MESSAGE_IGNORE_INSERTS, 114 NULL, ms_err, 115 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ 116 (LPTSTR) &lpMsgBuf, 0, NULL); 117 if (lpMsgBuf) { 118 char *p; 119 rv = strdup(lpMsgBuf); 120 LocalFree(lpMsgBuf); 121 /* trim to the left */ 122 if (rv) 123 for (p = rv + strlen(rv) - 1; p >= rv; p--) { 124 if (isspace(*p)) 125 *p = '\0'; 126 else 127 break; 128 } 129 } 130 return rv; 131} 132 133static void err_put_ms_error(DWORD ms_err, int func, const char *file, int line) 134{ 135 static int init = 0; 136# define ERR_MAP_SZ 16 137 static struct { 138 int err; 139 DWORD ms_err; /* I don't think we get more than 16 *different* errors */ 140 } err_map[ERR_MAP_SZ]; /* in here, before we give up the whole thing... */ 141 int i; 142 143 if (ms_err == 0) 144 /* 0 is not an error */ 145 return; 146 if (!init) { 147 ERR_load_strings(ERR_LIB_CRYPTOAPI, CRYPTOAPI_str_functs); 148 memset(&err_map, 0, sizeof(err_map)); 149 init++; 150 } 151 /* since MS error codes are 32 bit, and the ones in the ERR_... system is 152 * only 12, we must have a mapping table between them. */ 153 for (i = 0; i < ERR_MAP_SZ; i++) { 154 if (err_map[i].ms_err == ms_err) { 155 ERR_PUT_error(ERR_LIB_CRYPTOAPI, func, err_map[i].err, file, line); 156 break; 157 } else if (err_map[i].ms_err == 0 ) { 158 /* end of table, add new entry */ 159 ERR_STRING_DATA *esd = calloc(2, sizeof(*esd)); 160 if (esd == NULL) 161 break; 162 err_map[i].ms_err = ms_err; 163 err_map[i].err = esd->error = i + 100; 164 esd->string = ms_error_text(ms_err); 165 ERR_load_strings(ERR_LIB_CRYPTOAPI, esd); 166 ERR_PUT_error(ERR_LIB_CRYPTOAPI, func, err_map[i].err, file, line); 167 break; 168 } 169 } 170} 171 172/* encrypt */ 173static int rsa_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) 174{ 175 /* I haven't been able to trigger this one, but I want to know if it happens... */ 176 assert(0); 177 178 return 0; 179} 180 181/* verify arbitrary data */ 182static int rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) 183{ 184 /* I haven't been able to trigger this one, but I want to know if it happens... */ 185 assert(0); 186 187 return 0; 188} 189 190/* sign arbitrary data */ 191static int rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) 192{ 193 CAPI_DATA *cd = (CAPI_DATA *) rsa->meth->app_data; 194 HCRYPTHASH hash; 195 DWORD hash_size, len, i; 196 unsigned char *buf; 197 198 if (cd == NULL) { 199 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_PASSED_NULL_PARAMETER); 200 return 0; 201 } 202 if (padding != RSA_PKCS1_PADDING) { 203 /* AFAICS, CryptSignHash() *always* uses PKCS1 padding. */ 204 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE); 205 return 0; 206 } 207 /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would 208 * be way to straightforward for M$, I guess... So we have to do it this 209 * tricky way instead, by creating a "Hash", and load the already-made hash 210 * from 'from' into it. */ 211 /* For now, we only support NID_md5_sha1 */ 212 if (flen != SSL_SIG_LENGTH) { 213 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_INVALID_MESSAGE_LENGTH); 214 return 0; 215 } 216 if (!CryptCreateHash(cd->crypt_prov, CALG_SSL3_SHAMD5, 0, 0, &hash)) { 217 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_CREATE_HASH); 218 return 0; 219 } 220 len = sizeof(hash_size); 221 if (!CryptGetHashParam(hash, HP_HASHSIZE, (BYTE *) &hash_size, &len, 0)) { 222 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_GET_HASH_PARAM); 223 CryptDestroyHash(hash); 224 return 0; 225 } 226 if ((int) hash_size != flen) { 227 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_INVALID_MESSAGE_LENGTH); 228 CryptDestroyHash(hash); 229 return 0; 230 } 231 if (!CryptSetHashParam(hash, HP_HASHVAL, (BYTE * ) from, 0)) { 232 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SET_HASH_PARAM); 233 CryptDestroyHash(hash); 234 return 0; 235 } 236 237 len = RSA_size(rsa); 238 buf = malloc(len); 239 if (buf == NULL) { 240 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_MALLOC_FAILURE); 241 CryptDestroyHash(hash); 242 return 0; 243 } 244 if (!CryptSignHash(hash, cd->key_spec, NULL, 0, buf, &len)) { 245 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SIGN_HASH); 246 CryptDestroyHash(hash); 247 free(buf); 248 return 0; 249 } 250 /* and now, we have to reverse the byte-order in the result from CryptSignHash()... */ 251 for (i = 0; i < len; i++) 252 to[i] = buf[len - i - 1]; 253 free(buf); 254 255 CryptDestroyHash(hash); 256 return len; 257} 258 259/* decrypt */ 260static int rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) 261{ 262 /* I haven't been able to trigger this one, but I want to know if it happens... */ 263 assert(0); 264 265 return 0; 266} 267 268/* called at RSA_new */ 269static int init(RSA *rsa) 270{ 271 272 return 0; 273} 274 275/* called at RSA_free */ 276static int finish(RSA *rsa) 277{ 278 CAPI_DATA *cd = (CAPI_DATA *) rsa->meth->app_data; 279 280 if (cd == NULL) 281 return 0; 282 if (cd->crypt_prov && cd->free_crypt_prov) 283 CryptReleaseContext(cd->crypt_prov, 0); 284 if (cd->cert_context) 285 CertFreeCertificateContext(cd->cert_context); 286 free(rsa->meth->app_data); 287 free((char *) rsa->meth); 288 rsa->meth = NULL; 289 return 1; 290} 291 292static const CERT_CONTEXT *find_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store) 293{ 294 /* Find, and use, the desired certificate from the store. The 295 * 'cert_prop' certificate search string can look like this: 296 * SUBJ:<certificate substring to match> 297 * THUMB:<certificate thumbprint hex value>, e.g. 298 * THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28 299 */ 300 const CERT_CONTEXT *rv = NULL; 301 302 if (!strncmp(cert_prop, "SUBJ:", 5)) { 303 /* skip the tag */ 304 cert_prop += 5; 305 rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 306 0, CERT_FIND_SUBJECT_STR_A, cert_prop, NULL); 307 308 } else if (!strncmp(cert_prop, "THUMB:", 6)) { 309 unsigned char hash[255]; 310 char *p; 311 int i, x = 0; 312 CRYPT_HASH_BLOB blob; 313 314 /* skip the tag */ 315 cert_prop += 6; 316 for (p = (char *) cert_prop, i = 0; *p && i < sizeof(hash); i++) { 317 if (*p >= '0' && *p <= '9') 318 x = (*p - '0') << 4; 319 else if (*p >= 'A' && *p <= 'F') 320 x = (*p - 'A' + 10) << 4; 321 else if (*p >= 'a' && *p <= 'f') 322 x = (*p - 'a' + 10) << 4; 323 if (!*++p) /* unexpected end of string */ 324 break; 325 if (*p >= '0' && *p <= '9') 326 x += *p - '0'; 327 else if (*p >= 'A' && *p <= 'F') 328 x += *p - 'A' + 10; 329 else if (*p >= 'a' && *p <= 'f') 330 x += *p - 'a' + 10; 331 hash[i] = x; 332 /* skip any space(s) between hex numbers */ 333 for (p++; *p && *p == ' '; p++); 334 } 335 blob.cbData = i; 336 blob.pbData = (unsigned char *) &hash; 337 rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 338 0, CERT_FIND_HASH, &blob, NULL); 339 340 } 341 342 return rv; 343} 344 345int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop) 346{ 347 HCERTSTORE cs; 348 X509 *cert = NULL; 349 RSA *rsa = NULL, *pub_rsa; 350 CAPI_DATA *cd = calloc(1, sizeof(*cd)); 351 RSA_METHOD *my_rsa_method = calloc(1, sizeof(*my_rsa_method)); 352 353 if (cd == NULL || my_rsa_method == NULL) { 354 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE); 355 goto err; 356 } 357 /* search CURRENT_USER first, then LOCAL_MACHINE */ 358 cs = CertOpenStore((LPCSTR) CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER | 359 CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, L"MY"); 360 if (cs == NULL) { 361 CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE); 362 goto err; 363 } 364 cd->cert_context = find_certificate_in_store(cert_prop, cs); 365 CertCloseStore(cs, 0); 366 if (!cd->cert_context) { 367 cs = CertOpenStore((LPCSTR) CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE | 368 CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, L"MY"); 369 if (cs == NULL) { 370 CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE); 371 goto err; 372 } 373 cd->cert_context = find_certificate_in_store(cert_prop, cs); 374 CertCloseStore(cs, 0); 375 if (cd->cert_context == NULL) { 376 CRYPTOAPIerr(CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE); 377 goto err; 378 } 379 } 380 381 /* cert_context->pbCertEncoded is the cert X509 DER encoded. */ 382 cert = d2i_X509(NULL, (const unsigned char **) &cd->cert_context->pbCertEncoded, 383 cd->cert_context->cbCertEncoded); 384 if (cert == NULL) { 385 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB); 386 goto err; 387 } 388 389 /* set up stuff to use the private key */ 390 if (!CryptAcquireCertificatePrivateKey(cd->cert_context, CRYPT_ACQUIRE_COMPARE_KEY_FLAG, 391 NULL, &cd->crypt_prov, &cd->key_spec, &cd->free_crypt_prov)) { 392 /* if we don't have a smart card reader here, and we try to access a 393 * smart card certificate, we get: 394 * "Error 1223: The operation was canceled by the user." */ 395 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY); 396 goto err; 397 } 398 /* here we don't need to do CryptGetUserKey() or anything; all necessary key 399 * info is in cd->cert_context, and then, in cd->crypt_prov. */ 400 401 my_rsa_method->name = "Microsoft CryptoAPI RSA Method"; 402 my_rsa_method->rsa_pub_enc = rsa_pub_enc; 403 my_rsa_method->rsa_pub_dec = rsa_pub_dec; 404 my_rsa_method->rsa_priv_enc = rsa_priv_enc; 405 my_rsa_method->rsa_priv_dec = rsa_priv_dec; 406 /* my_rsa_method->init = init; */ 407 my_rsa_method->finish = finish; 408 my_rsa_method->flags = RSA_METHOD_FLAG_NO_CHECK; 409 my_rsa_method->app_data = (char *) cd; 410 411 rsa = RSA_new(); 412 if (rsa == NULL) { 413 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE); 414 goto err; 415 } 416 417 /* cert->cert_info->key->pkey is NULL until we call SSL_CTX_use_certificate(), 418 * so we do it here then... */ 419 if (!SSL_CTX_use_certificate(ssl_ctx, cert)) 420 goto err; 421 /* the public key */ 422 pub_rsa = cert->cert_info->key->pkey->pkey.rsa; 423 /* SSL_CTX_use_certificate() increased the reference count in 'cert', so 424 * we decrease it here with X509_free(), or it will never be cleaned up. */ 425 X509_free(cert); 426 cert = NULL; 427 428 /* I'm not sure about what we have to fill in in the RSA, trying out stuff... */ 429 /* rsa->n indicates the key size */ 430 rsa->n = BN_dup(pub_rsa->n); 431 rsa->flags |= RSA_FLAG_EXT_PKEY; 432 if (!RSA_set_method(rsa, my_rsa_method)) 433 goto err; 434 435 if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx, rsa)) 436 goto err; 437 /* SSL_CTX_use_RSAPrivateKey() increased the reference count in 'rsa', so 438 * we decrease it here with RSA_free(), or it will never be cleaned up. */ 439 RSA_free(rsa); 440 return 1; 441 442 err: 443 if (cert) 444 X509_free(cert); 445 if (rsa) 446 RSA_free(rsa); 447 else { 448 if (my_rsa_method) 449 free(my_rsa_method); 450 if (cd) { 451 if (cd->free_crypt_prov && cd->crypt_prov) 452 CryptReleaseContext(cd->crypt_prov, 0); 453 if (cd->cert_context) 454 CertFreeCertificateContext(cd->cert_context); 455 free(cd); 456 } 457 } 458 return 0; 459} 460 461#else 462#ifdef _MSC_VER /* Dummy function needed to avoid empty file compiler warning in Microsoft VC */ 463static void dummy (void) {} 464#endif 465#endif /* WIN32 */ 466