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 (*OpenVPNCryptAcquireCertificatePrivateKey
) (PCCERT_CONTEXT pCert
, DWORD dwFlags
,
55 void *pvReserved
, HCRYPTPROV
*phCryptProv
, DWORD
*pdwKeySpec
, BOOL
*pfCallerFreeProv
) = NULL
;
57 #define OpenVPNCryptAcquireCertificatePrivateKey CryptAcquireCertificatePrivateKey
60 /* Size of an SSL signature: MD5+SHA1 */
61 #define SSL_SIG_LENGTH 36
63 /* try to funnel any Windows/CryptoAPI error messages to OpenSSL ERR_... */
64 #define ERR_LIB_CRYPTOAPI (ERR_LIB_USER + 69) /* 69 is just a number... */
65 #define CRYPTOAPIerr(f) err_put_ms_error(GetLastError(), (f), __FILE__, __LINE__)
66 #define CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE 100
67 #define CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE 101
68 #define CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY 102
69 #define CRYPTOAPI_F_CRYPT_CREATE_HASH 103
70 #define CRYPTOAPI_F_CRYPT_GET_HASH_PARAM 104
71 #define CRYPTOAPI_F_CRYPT_SET_HASH_PARAM 105
72 #define CRYPTOAPI_F_CRYPT_SIGN_HASH 106
73 #define CRYPTOAPI_F_LOAD_LIBRARY 107
74 #define CRYPTOAPI_F_GET_PROC_ADDRESS 108
76 static ERR_STRING_DATA CRYPTOAPI_str_functs
[] = {
77 { ERR_PACK(ERR_LIB_CRYPTOAPI
, 0, 0), "microsoft cryptoapi"},
78 { ERR_PACK(0, CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE
, 0), "CertOpenSystemStore" },
79 { ERR_PACK(0, CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE
, 0), "CertFindCertificateInStore" },
80 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY
, 0), "CryptAcquireCertificatePrivateKey" },
81 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_CREATE_HASH
, 0), "CryptCreateHash" },
82 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_GET_HASH_PARAM
, 0), "CryptGetHashParam" },
83 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SET_HASH_PARAM
, 0), "CryptSetHashParam" },
84 { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SIGN_HASH
, 0), "CryptSignHash" },
85 { ERR_PACK(0, CRYPTOAPI_F_LOAD_LIBRARY
, 0), "LoadLibrary" },
86 { ERR_PACK(0, CRYPTOAPI_F_GET_PROC_ADDRESS
, 0), "GetProcAddress" },
90 typedef struct _CAPI_DATA
{
91 const CERT_CONTEXT
*cert_context
;
92 HCRYPTPROV crypt_prov
;
97 static char *ms_error_text(DWORD ms_err
)
99 LPVOID lpMsgBuf
= NULL
;
103 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
104 FORMAT_MESSAGE_FROM_SYSTEM
|
105 FORMAT_MESSAGE_IGNORE_INSERTS
,
107 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), /* Default language */
108 (LPTSTR
) &lpMsgBuf
, 0, NULL
);
111 rv
= strdup(lpMsgBuf
);
113 /* trim to the left */
115 for (p
= rv
+ strlen(rv
) - 1; p
>= rv
; p
--) {
125 static void err_put_ms_error(DWORD ms_err
, int func
, const char *file
, int line
)
128 # define ERR_MAP_SZ 16
131 DWORD ms_err
; /* I don't think we get more than 16 *different* errors */
132 } err_map
[ERR_MAP_SZ
]; /* in here, before we give up the whole thing... */
136 /* 0 is not an error */
139 ERR_load_strings(ERR_LIB_CRYPTOAPI
, CRYPTOAPI_str_functs
);
140 memset(&err_map
, 0, sizeof(err_map
));
143 /* since MS error codes are 32 bit, and the ones in the ERR_... system is
144 * only 12, we must have a mapping table between them. */
145 for (i
= 0; i
< ERR_MAP_SZ
; i
++) {
146 if (err_map
[i
].ms_err
== ms_err
) {
147 ERR_PUT_error(ERR_LIB_CRYPTOAPI
, func
, err_map
[i
].err
, file
, line
);
149 } else if (err_map
[i
].ms_err
== 0 ) {
150 /* end of table, add new entry */
151 ERR_STRING_DATA
*esd
= calloc(2, sizeof(*esd
));
154 err_map
[i
].ms_err
= ms_err
;
155 err_map
[i
].err
= esd
->error
= i
+ 100;
156 esd
->string
= ms_error_text(ms_err
);
157 ERR_load_strings(ERR_LIB_CRYPTOAPI
, esd
);
158 ERR_PUT_error(ERR_LIB_CRYPTOAPI
, func
, err_map
[i
].err
, file
, line
);
165 static int rsa_pub_enc(int flen
, const unsigned char *from
, unsigned char *to
, RSA
*rsa
, int padding
)
167 /* I haven't been able to trigger this one, but I want to know if it happens... */
173 /* verify arbitrary data */
174 static int rsa_pub_dec(int flen
, const unsigned char *from
, unsigned char *to
, RSA
*rsa
, int padding
)
176 /* I haven't been able to trigger this one, but I want to know if it happens... */
182 /* sign arbitrary data */
183 static int rsa_priv_enc(int flen
, const unsigned char *from
, unsigned char *to
, RSA
*rsa
, int padding
)
185 CAPI_DATA
*cd
= (CAPI_DATA
*) rsa
->meth
->app_data
;
187 DWORD hash_size
, len
, i
;
191 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, ERR_R_PASSED_NULL_PARAMETER
);
194 if (padding
!= RSA_PKCS1_PADDING
) {
195 /* AFAICS, CryptSignHash() *always* uses PKCS1 padding. */
196 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, RSA_R_UNKNOWN_PADDING_TYPE
);
199 /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would
200 * be way to straightforward for M$, I guess... So we have to do it this
201 * tricky way instead, by creating a "Hash", and load the already-made hash
202 * from 'from' into it. */
203 /* For now, we only support NID_md5_sha1 */
204 if (flen
!= SSL_SIG_LENGTH
) {
205 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, RSA_R_INVALID_MESSAGE_LENGTH
);
208 if (!CryptCreateHash(cd
->crypt_prov
, CALG_SSL3_SHAMD5
, 0, 0, &hash
)) {
209 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_CREATE_HASH
);
212 len
= sizeof(hash_size
);
213 if (!CryptGetHashParam(hash
, HP_HASHSIZE
, (BYTE
*) &hash_size
, &len
, 0)) {
214 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_GET_HASH_PARAM
);
215 CryptDestroyHash(hash
);
218 if ((int) hash_size
!= flen
) {
219 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, RSA_R_INVALID_MESSAGE_LENGTH
);
220 CryptDestroyHash(hash
);
223 if (!CryptSetHashParam(hash
, HP_HASHVAL
, (BYTE
* ) from
, 0)) {
224 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SET_HASH_PARAM
);
225 CryptDestroyHash(hash
);
232 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT
, ERR_R_MALLOC_FAILURE
);
233 CryptDestroyHash(hash
);
236 if (!CryptSignHash(hash
, cd
->key_spec
, NULL
, 0, buf
, &len
)) {
237 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SIGN_HASH
);
238 CryptDestroyHash(hash
);
242 /* and now, we have to reverse the byte-order in the result from CryptSignHash()... */
243 for (i
= 0; i
< len
; i
++)
244 to
[i
] = buf
[len
- i
- 1];
247 CryptDestroyHash(hash
);
252 static int rsa_priv_dec(int flen
, const unsigned char *from
, unsigned char *to
, RSA
*rsa
, int padding
)
254 /* I haven't been able to trigger this one, but I want to know if it happens... */
260 /* called at RSA_new */
261 static int init(RSA
*rsa
)
267 /* called at RSA_free */
268 static int finish(RSA
*rsa
)
270 CAPI_DATA
*cd
= (CAPI_DATA
*) rsa
->meth
->app_data
;
274 if (cd
->crypt_prov
&& cd
->free_crypt_prov
)
275 CryptReleaseContext(cd
->crypt_prov
, 0);
276 if (cd
->cert_context
)
277 CertFreeCertificateContext(cd
->cert_context
);
278 free(rsa
->meth
->app_data
);
279 free((char *) rsa
->meth
);
284 static const CERT_CONTEXT
*find_certificate_in_store(const char *cert_prop
, HCERTSTORE cert_store
)
286 /* Find, and use, the desired certificate from the store. The
287 * 'cert_prop' certificate search string can look like this:
288 * SUBJ:<certificate substring to match>
289 * THUMB:<certificate thumbprint hex value>, e.g.
290 * THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28
292 const CERT_CONTEXT
*rv
= NULL
;
294 if (!strncmp(cert_prop
, "SUBJ:", 5)) {
297 rv
= CertFindCertificateInStore(cert_store
, X509_ASN_ENCODING
| PKCS_7_ASN_ENCODING
,
298 0, CERT_FIND_SUBJECT_STR_A
, cert_prop
, NULL
);
300 } else if (!strncmp(cert_prop
, "THUMB:", 6)) {
301 unsigned char hash
[255];
304 CRYPT_HASH_BLOB blob
;
308 for (p
= (char *) cert_prop
, i
= 0; *p
&& i
< sizeof(hash
); i
++) {
309 if (*p
>= '0' && *p
<= '9')
311 else if (*p
>= 'A' && *p
<= 'F')
312 x
= (*p
- 'A' + 10) << 4;
313 else if (*p
>= 'a' && *p
<= 'f')
314 x
= (*p
- 'a' + 10) << 4;
315 if (!*++p
) /* unexpected end of string */
317 if (*p
>= '0' && *p
<= '9')
319 else if (*p
>= 'A' && *p
<= 'F')
321 else if (*p
>= 'a' && *p
<= 'f')
324 /* skip any space(s) between hex numbers */
325 for (p
++; *p
&& *p
== ' '; p
++);
328 blob
.pbData
= (unsigned char *) &hash
;
329 rv
= CertFindCertificateInStore(cert_store
, X509_ASN_ENCODING
| PKCS_7_ASN_ENCODING
,
330 0, CERT_FIND_HASH
, &blob
, NULL
);
337 int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX
*ssl_ctx
, const char *cert_prop
)
341 RSA
*rsa
= NULL
, *pub_rsa
;
342 CAPI_DATA
*cd
= calloc(1, sizeof(*cd
));
343 RSA_METHOD
*my_rsa_method
= calloc(1, sizeof(*my_rsa_method
));
345 if (cd
== NULL
|| my_rsa_method
== NULL
) {
346 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE
, ERR_R_MALLOC_FAILURE
);
349 /* search CURRENT_USER first, then LOCAL_MACHINE */
350 cs
= CertOpenStore((LPCSTR
) CERT_STORE_PROV_SYSTEM
, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER
|
351 CERT_STORE_OPEN_EXISTING_FLAG
| CERT_STORE_READONLY_FLAG
, L
"MY");
353 CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE
);
356 cd
->cert_context
= find_certificate_in_store(cert_prop
, cs
);
357 CertCloseStore(cs
, 0);
358 if (!cd
->cert_context
) {
359 cs
= CertOpenStore((LPCSTR
) CERT_STORE_PROV_SYSTEM
, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE
|
360 CERT_STORE_OPEN_EXISTING_FLAG
| CERT_STORE_READONLY_FLAG
, L
"MY");
362 CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE
);
365 cd
->cert_context
= find_certificate_in_store(cert_prop
, cs
);
366 CertCloseStore(cs
, 0);
367 if (cd
->cert_context
== NULL
) {
368 CRYPTOAPIerr(CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE
);
373 /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
374 cert
= d2i_X509(NULL
, (const unsigned char **) &cd
->cert_context
->pbCertEncoded
,
375 cd
->cert_context
->cbCertEncoded
);
377 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE
, ERR_R_ASN1_LIB
);
381 /* set up stuff to use the private key */
382 #ifdef __MINGW32_VERSION
383 /* MinGW w32api is incomplete when it comes to CryptoAPI, as per version 3.1
384 * anyway. This is a hack around that problem. */
385 if (crypt32dll
== NULL
) {
386 crypt32dll
= LoadLibrary("crypt32");
387 if (crypt32dll
== NULL
) {
388 CRYPTOAPIerr(CRYPTOAPI_F_LOAD_LIBRARY
);
392 if (OpenVPNCryptAcquireCertificatePrivateKey
== NULL
) {
393 OpenVPNCryptAcquireCertificatePrivateKey
= GetProcAddress(crypt32dll
,
394 "CryptAcquireCertificatePrivateKey");
395 if (OpenVPNCryptAcquireCertificatePrivateKey
== NULL
) {
396 CRYPTOAPIerr(CRYPTOAPI_F_GET_PROC_ADDRESS
);
401 if (!OpenVPNCryptAcquireCertificatePrivateKey(cd
->cert_context
, CRYPT_ACQUIRE_COMPARE_KEY_FLAG
,
402 NULL
, &cd
->crypt_prov
, &cd
->key_spec
, &cd
->free_crypt_prov
)) {
403 /* if we don't have a smart card reader here, and we try to access a
404 * smart card certificate, we get:
405 * "Error 1223: The operation was canceled by the user." */
406 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY
);
409 /* here we don't need to do CryptGetUserKey() or anything; all necessary key
410 * info is in cd->cert_context, and then, in cd->crypt_prov. */
412 my_rsa_method
->name
= "Microsoft CryptoAPI RSA Method";
413 my_rsa_method
->rsa_pub_enc
= rsa_pub_enc
;
414 my_rsa_method
->rsa_pub_dec
= rsa_pub_dec
;
415 my_rsa_method
->rsa_priv_enc
= rsa_priv_enc
;
416 my_rsa_method
->rsa_priv_dec
= rsa_priv_dec
;
417 /* my_rsa_method->init = init; */
418 my_rsa_method
->finish
= finish
;
419 my_rsa_method
->flags
= RSA_METHOD_FLAG_NO_CHECK
;
420 my_rsa_method
->app_data
= (char *) cd
;
424 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE
, ERR_R_MALLOC_FAILURE
);
428 /* cert->cert_info->key->pkey is NULL until we call SSL_CTX_use_certificate(),
429 * so we do it here then... */
430 if (!SSL_CTX_use_certificate(ssl_ctx
, cert
))
433 pub_rsa
= cert
->cert_info
->key
->pkey
->pkey
.rsa
;
434 /* SSL_CTX_use_certificate() increased the reference count in 'cert', so
435 * we decrease it here with X509_free(), or it will never be cleaned up. */
439 /* I'm not sure about what we have to fill in in the RSA, trying out stuff... */
440 /* rsa->n indicates the key size */
441 rsa
->n
= BN_dup(pub_rsa
->n
);
442 rsa
->flags
|= RSA_FLAG_EXT_PKEY
;
443 if (!RSA_set_method(rsa
, my_rsa_method
))
446 if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx
, rsa
))
448 /* SSL_CTX_use_RSAPrivateKey() increased the reference count in 'rsa', so
449 * we decrease it here with RSA_free(), or it will never be cleaned up. */
462 if (cd
->free_crypt_prov
&& cd
->crypt_prov
)
463 CryptReleaseContext(cd
->crypt_prov
, 0);
464 if (cd
->cert_context
)
465 CertFreeCertificateContext(cd
->cert_context
);
473 static void dummy (void) {}