2 * Copyright (c) 2004 Peter 'Luna' Runestig <peter@runestig.com>
5 * Redistribution and use in source and binary forms, with or without modifi-
6 * cation, are permitted provided that the following conditions are met:
8 * o Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
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.
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
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.
33 #if defined(WIN32) && defined(USE_CRYPTO) && defined(USE_SSL)
35 #include <openssl/ssl.h>
36 #include <openssl/err.h>
43 #ifdef __MINGW32_VERSION
44 /* MinGW w32api is incomplete when it comes to CryptoAPI, as per version 3.1
45 * anyway. This is a hack around that problem. */
46 #define CALG_SSL3_SHAMD5 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SSL3SHAMD5)
47 #define CERT_SYSTEM_STORE_LOCATION_SHIFT 16
48 #define CERT_SYSTEM_STORE_CURRENT_USER_ID 1
49 #define CERT_SYSTEM_STORE_CURRENT_USER (CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
50 #define CERT_STORE_READONLY_FLAG 0x00008000
51 #define CERT_STORE_OPEN_EXISTING_FLAG 0x00004000
52 #define CRYPT_ACQUIRE_COMPARE_KEY_FLAG 0x00000004
53 static HINSTANCE crypt32dll
= NULL
;
54 static BOOL
WINAPI (*CryptAcquireCertificatePrivateKey
) (PCCERT_CONTEXT pCert
, DWORD dwFlags
,
55 void *pvReserved
, HCRYPTPROV
*phCryptProv
, DWORD
*pdwKeySpec
, BOOL
*pfCallerFreeProv
) = NULL
;
58 /* Size of an SSL signature: MD5+SHA1 */
59 #define SSL_SIG_LENGTH 36
61 /* try to funnel any Windows/CryptoAPI error messages to OpenSSL ERR_... */
62 #define ERR_LIB_CRYPTOAPI (ERR_LIB_USER + 69) /* 69 is just a number... */
63 #define CRYPTOAPIerr(f) err_put_ms_error(GetLastError(), (f), __FILE__, __LINE__)
64 #define CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE 100
65 #define CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE 101
66 #define CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY 102
67 #define CRYPTOAPI_F_CRYPT_CREATE_HASH 103
68 #define CRYPTOAPI_F_CRYPT_GET_HASH_PARAM 104
69 #define CRYPTOAPI_F_CRYPT_SET_HASH_PARAM 105
70 #define CRYPTOAPI_F_CRYPT_SIGN_HASH 106
71 #define CRYPTOAPI_F_LOAD_LIBRARY 107
72 #define CRYPTOAPI_F_GET_PROC_ADDRESS 108
74 static ERR_STRING_DATA CRYPTOAPI_str_functs
[] = {
75 { ERR_PACK(ERR_LIB_CRYPTOAPI
, 0, 0), "microsoft cryptoapi"},
76 { ERR_PACK(0, CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE
, 0), "CertOpenSystemStore" },
77 { ERR_PACK(0, CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE
, 0), "CertFindCertificateInStore" },
78 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY
, 0), "CryptAcquireCertificatePrivateKey" },
79 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_CREATE_HASH
, 0), "CryptCreateHash" },
80 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_GET_HASH_PARAM
, 0), "CryptGetHashParam" },
81 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SET_HASH_PARAM
, 0), "CryptSetHashParam" },
82 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SIGN_HASH
, 0), "CryptSignHash" },
83 { ERR_PACK(0, CRYPTOAPI_F_LOAD_LIBRARY
, 0), "LoadLibrary" },
84 { ERR_PACK(0, CRYPTOAPI_F_GET_PROC_ADDRESS
, 0), "GetProcAddress" },
88 typedef struct _CAPI_DATA
{
89 const CERT_CONTEXT
*cert_context
;
90 HCRYPTPROV crypt_prov
;
95 static char *ms_error_text(DWORD ms_err
)
97 LPVOID lpMsgBuf
= NULL
;
101 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
102 FORMAT_MESSAGE_FROM_SYSTEM
|
103 FORMAT_MESSAGE_IGNORE_INSERTS
,
105 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), /* Default language */
106 (LPTSTR
) &lpMsgBuf
, 0, NULL
);
109 rv
= strdup(lpMsgBuf
);
111 /* trim to the left */
113 for (p
= rv
+ strlen(rv
) - 1; p
>= rv
; p
--) {
123 static void err_put_ms_error(DWORD ms_err
, int func
, const char *file
, int line
)
126 # define ERR_MAP_SZ 16
129 DWORD ms_err
; /* I don't think we get more than 16 *different* errors */
130 } err_map
[ERR_MAP_SZ
]; /* in here, before we give up the whole thing... */
134 /* 0 is not an error */
137 ERR_load_strings(ERR_LIB_CRYPTOAPI
, CRYPTOAPI_str_functs
);
138 memset(&err_map
, 0, sizeof(err_map
));
141 /* since MS error codes are 32 bit, and the ones in the ERR_... system is
142 * only 12, we must have a mapping table between them. */
143 for (i
= 0; i
< ERR_MAP_SZ
; i
++) {
144 if (err_map
[i
].ms_err
== ms_err
) {
145 ERR_PUT_error(ERR_LIB_CRYPTOAPI
, func
, err_map
[i
].err
, file
, line
);
147 } else if (err_map
[i
].ms_err
== 0 ) {
148 /* end of table, add new entry */
149 ERR_STRING_DATA
*esd
= calloc(2, sizeof(*esd
));
152 err_map
[i
].ms_err
= ms_err
;
153 err_map
[i
].err
= esd
->error
= i
+ 100;
154 esd
->string
= ms_error_text(ms_err
);
155 ERR_load_strings(ERR_LIB_CRYPTOAPI
, esd
);
156 ERR_PUT_error(ERR_LIB_CRYPTOAPI
, func
, err_map
[i
].err
, file
, line
);
163 static int rsa_pub_enc(int flen
, const unsigned char *from
, unsigned char *to
, RSA
*rsa
, int padding
)
165 /* I haven't been able to trigger this one, but I want to know if it happens... */
171 /* verify arbitrary data */
172 static int rsa_pub_dec(int flen
, const unsigned char *from
, unsigned char *to
, RSA
*rsa
, int padding
)
174 /* I haven't been able to trigger this one, but I want to know if it happens... */
180 /* sign arbitrary data */
181 static int rsa_priv_enc(int flen
, const unsigned char *from
, unsigned char *to
, RSA
*rsa
, int padding
)
183 CAPI_DATA
*cd
= (CAPI_DATA
*) rsa
->meth
->app_data
;
185 DWORD hash_size
, len
, i
;
189 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, ERR_R_PASSED_NULL_PARAMETER
);
192 if (padding
!= RSA_PKCS1_PADDING
) {
193 /* AFAICS, CryptSignHash() *always* uses PKCS1 padding. */
194 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, RSA_R_UNKNOWN_PADDING_TYPE
);
197 /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would
198 * be way to straightforward for M$, I guess... So we have to do it this
199 * tricky way instead, by creating a "Hash", and load the already-made hash
200 * from 'from' into it. */
201 /* For now, we only support NID_md5_sha1 */
202 if (flen
!= SSL_SIG_LENGTH
) {
203 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, RSA_R_INVALID_MESSAGE_LENGTH
);
206 if (!CryptCreateHash(cd
->crypt_prov
, CALG_SSL3_SHAMD5
, 0, 0, &hash
)) {
207 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_CREATE_HASH
);
210 len
= sizeof(hash_size
);
211 if (!CryptGetHashParam(hash
, HP_HASHSIZE
, (BYTE
*) &hash_size
, &len
, 0)) {
212 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_GET_HASH_PARAM
);
213 CryptDestroyHash(hash
);
216 if ((int) hash_size
!= flen
) {
217 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, RSA_R_INVALID_MESSAGE_LENGTH
);
218 CryptDestroyHash(hash
);
221 if (!CryptSetHashParam(hash
, HP_HASHVAL
, (BYTE
* ) from
, 0)) {
222 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SET_HASH_PARAM
);
223 CryptDestroyHash(hash
);
230 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, ERR_R_MALLOC_FAILURE
);
231 CryptDestroyHash(hash
);
234 if (!CryptSignHash(hash
, cd
->key_spec
, NULL
, 0, buf
, &len
)) {
235 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SIGN_HASH
);
236 CryptDestroyHash(hash
);
240 /* and now, we have to reverse the byte-order in the result from CryptSignHash()... */
241 for (i
= 0; i
< len
; i
++)
242 to
[i
] = buf
[len
- i
- 1];
245 CryptDestroyHash(hash
);
250 static int rsa_priv_dec(int flen
, const unsigned char *from
, unsigned char *to
, RSA
*rsa
, int padding
)
252 /* I haven't been able to trigger this one, but I want to know if it happens... */
258 /* called at RSA_new */
259 static int init(RSA
*rsa
)
265 /* called at RSA_free */
266 static int finish(RSA
*rsa
)
268 CAPI_DATA
*cd
= (CAPI_DATA
*) rsa
->meth
->app_data
;
272 if (cd
->crypt_prov
&& cd
->free_crypt_prov
)
273 CryptReleaseContext(cd
->crypt_prov
, 0);
274 if (cd
->cert_context
)
275 CertFreeCertificateContext(cd
->cert_context
);
276 free(rsa
->meth
->app_data
);
277 free((char *) rsa
->meth
);
282 static const CERT_CONTEXT
*find_certificate_in_store(const char *cert_prop
, HCERTSTORE cert_store
)
284 /* Find, and use, the desired certificate from the store. The
285 * 'cert_prop' certificate search string can look like this:
286 * SUBJ:<certificate substring to match>
287 * THUMB:<certificate thumbprint hex value>, e.g.
288 * THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28
290 const CERT_CONTEXT
*rv
= NULL
;
292 if (!strncmp(cert_prop
, "SUBJ:", 5)) {
295 rv
= CertFindCertificateInStore(cert_store
, X509_ASN_ENCODING
| PKCS_7_ASN_ENCODING
,
296 0, CERT_FIND_SUBJECT_STR_A
, cert_prop
, NULL
);
298 } else if (!strncmp(cert_prop
, "THUMB:", 6)) {
299 unsigned char hash
[255];
302 CRYPT_HASH_BLOB blob
;
306 for (p
= (char *) cert_prop
, i
= 0; *p
&& i
< sizeof(hash
); i
++) {
307 if (*p
>= '0' && *p
<= '9')
309 else if (*p
>= 'A' && *p
<= 'F')
310 x
= (*p
- 'A' + 10) << 4;
311 else if (*p
>= 'a' && *p
<= 'f')
312 x
= (*p
- 'a' + 10) << 4;
313 if (!*++p
) /* unexpected end of string */
315 if (*p
>= '0' && *p
<= '9')
317 else if (*p
>= 'A' && *p
<= 'F')
319 else if (*p
>= 'a' && *p
<= 'f')
322 /* skip any space(s) between hex numbers */
323 for (p
++; *p
&& *p
== ' '; p
++);
326 blob
.pbData
= (unsigned char *) &hash
;
327 rv
= CertFindCertificateInStore(cert_store
, X509_ASN_ENCODING
| PKCS_7_ASN_ENCODING
,
328 0, CERT_FIND_HASH
, &blob
, NULL
);
335 int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX
*ssl_ctx
, const char *cert_prop
)
339 RSA
*rsa
= NULL
, *pub_rsa
;
340 CAPI_DATA
*cd
= calloc(1, sizeof(*cd
));
341 RSA_METHOD
*my_rsa_method
= calloc(1, sizeof(*my_rsa_method
));
343 if (cd
== NULL
|| my_rsa_method
== NULL
) {
344 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE
, ERR_R_MALLOC_FAILURE
);
347 /* search CURRENT_USER first, then LOCAL_MACHINE */
348 cs
= CertOpenStore((LPCSTR
) CERT_STORE_PROV_SYSTEM
, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER
|
349 CERT_STORE_OPEN_EXISTING_FLAG
| CERT_STORE_READONLY_FLAG
, L
"MY");
351 CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE
);
354 cd
->cert_context
= find_certificate_in_store(cert_prop
, cs
);
355 CertCloseStore(cs
, 0);
356 if (!cd
->cert_context
) {
357 cs
= CertOpenStore((LPCSTR
) CERT_STORE_PROV_SYSTEM
, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE
|
358 CERT_STORE_OPEN_EXISTING_FLAG
| CERT_STORE_READONLY_FLAG
, L
"MY");
360 CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE
);
363 cd
->cert_context
= find_certificate_in_store(cert_prop
, cs
);
364 CertCloseStore(cs
, 0);
365 if (cd
->cert_context
== NULL
) {
366 CRYPTOAPIerr(CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE
);
371 /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
372 cert
= d2i_X509(NULL
, (const unsigned char **) &cd
->cert_context
->pbCertEncoded
,
373 cd
->cert_context
->cbCertEncoded
);
375 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE
, ERR_R_ASN1_LIB
);
379 /* set up stuff to use the private key */
380 #ifdef __MINGW32_VERSION
381 /* MinGW w32api is incomplete when it comes to CryptoAPI, as per version 3.1
382 * anyway. This is a hack around that problem. */
383 if (crypt32dll
== NULL
) {
384 crypt32dll
= LoadLibrary("crypt32");
385 if (crypt32dll
== NULL
) {
386 CRYPTOAPIerr(CRYPTOAPI_F_LOAD_LIBRARY
);
390 if (CryptAcquireCertificatePrivateKey
== NULL
) {
391 CryptAcquireCertificatePrivateKey
= GetProcAddress(crypt32dll
,
392 "CryptAcquireCertificatePrivateKey");
393 if (CryptAcquireCertificatePrivateKey
== NULL
) {
394 CRYPTOAPIerr(CRYPTOAPI_F_GET_PROC_ADDRESS
);
399 if (!CryptAcquireCertificatePrivateKey(cd
->cert_context
, CRYPT_ACQUIRE_COMPARE_KEY_FLAG
,
400 NULL
, &cd
->crypt_prov
, &cd
->key_spec
, &cd
->free_crypt_prov
)) {
401 /* if we don't have a smart card reader here, and we try to access a
402 * smart card certificate, we get:
403 * "Error 1223: The operation was canceled by the user." */
404 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY
);
407 /* here we don't need to do CryptGetUserKey() or anything; all necessary key
408 * info is in cd->cert_context, and then, in cd->crypt_prov. */
410 my_rsa_method
->name
= "Microsoft CryptoAPI RSA Method";
411 my_rsa_method
->rsa_pub_enc
= rsa_pub_enc
;
412 my_rsa_method
->rsa_pub_dec
= rsa_pub_dec
;
413 my_rsa_method
->rsa_priv_enc
= rsa_priv_enc
;
414 my_rsa_method
->rsa_priv_dec
= rsa_priv_dec
;
415 /* my_rsa_method->init = init; */
416 my_rsa_method
->finish
= finish
;
417 my_rsa_method
->flags
= RSA_METHOD_FLAG_NO_CHECK
;
418 my_rsa_method
->app_data
= (char *) cd
;
422 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE
, ERR_R_MALLOC_FAILURE
);
426 /* cert->cert_info->key->pkey is NULL until we call SSL_CTX_use_certificate(),
427 * so we do it here then... */
428 if (!SSL_CTX_use_certificate(ssl_ctx
, cert
))
431 pub_rsa
= cert
->cert_info
->key
->pkey
->pkey
.rsa
;
432 /* SSL_CTX_use_certificate() increased the reference count in 'cert', so
433 * we decrease it here with X509_free(), or it will never be cleaned up. */
437 /* I'm not sure about what we have to fill in in the RSA, trying out stuff... */
438 /* rsa->n indicates the key size */
439 rsa
->n
= BN_dup(pub_rsa
->n
);
440 rsa
->flags
|= RSA_FLAG_EXT_PKEY
;
441 if (!RSA_set_method(rsa
, my_rsa_method
))
444 if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx
, rsa
))
446 /* SSL_CTX_use_RSAPrivateKey() increased the reference count in 'rsa', so
447 * we decrease it here with RSA_free(), or it will never be cleaned up. */
460 if (cd
->free_crypt_prov
&& cd
->crypt_prov
)
461 CryptReleaseContext(cd
->crypt_prov
, 0);
462 if (cd
->cert_context
)
463 CertFreeCertificateContext(cd
->cert_context
);
471 static void dummy (void) {}