cosmetics
[tomato.git] / release / src / router / openvpn / cryptoapi.c
blob8fb538713ba37543e6ad44bb326294f2c81446c4
1 /*
2 * Copyright (c) 2004 Peter 'Luna' Runestig <peter@runestig.com>
3 * All rights reserved.
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
17 * permission.
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.
31 #include "syshead.h"
33 #if defined(WIN32) && defined(USE_CRYPTO) && defined(USE_SSL)
35 #include <openssl/ssl.h>
36 #include <openssl/err.h>
37 #include <windows.h>
38 #include <wincrypt.h>
39 #include <stdio.h>
40 #include <ctype.h>
41 #include <assert.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;
56 #else
57 #define OpenVPNCryptAcquireCertificatePrivateKey CryptAcquireCertificatePrivateKey
58 #endif
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" },
87 { 0, NULL }
90 typedef struct _CAPI_DATA {
91 const CERT_CONTEXT *cert_context;
92 HCRYPTPROV crypt_prov;
93 DWORD key_spec;
94 BOOL free_crypt_prov;
95 } CAPI_DATA;
97 static char *ms_error_text(DWORD ms_err)
99 LPVOID lpMsgBuf = NULL;
100 char *rv = NULL;
102 FormatMessage(
103 FORMAT_MESSAGE_ALLOCATE_BUFFER |
104 FORMAT_MESSAGE_FROM_SYSTEM |
105 FORMAT_MESSAGE_IGNORE_INSERTS,
106 NULL, ms_err,
107 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
108 (LPTSTR) &lpMsgBuf, 0, NULL);
109 if (lpMsgBuf) {
110 char *p;
111 rv = strdup(lpMsgBuf);
112 LocalFree(lpMsgBuf);
113 /* trim to the left */
114 if (rv)
115 for (p = rv + strlen(rv) - 1; p >= rv; p--) {
116 if (isspace(*p))
117 *p = '\0';
118 else
119 break;
122 return rv;
125 static void err_put_ms_error(DWORD ms_err, int func, const char *file, int line)
127 static int init = 0;
128 # define ERR_MAP_SZ 16
129 static struct {
130 int err;
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... */
133 int i;
135 if (ms_err == 0)
136 /* 0 is not an error */
137 return;
138 if (!init) {
139 ERR_load_strings(ERR_LIB_CRYPTOAPI, CRYPTOAPI_str_functs);
140 memset(&err_map, 0, sizeof(err_map));
141 init++;
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);
148 break;
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));
152 if (esd == NULL)
153 break;
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);
159 break;
164 /* encrypt */
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... */
168 assert(0);
170 return 0;
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... */
177 assert(0);
179 return 0;
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;
186 HCRYPTHASH hash;
187 DWORD hash_size, len, i;
188 unsigned char *buf;
190 if (cd == NULL) {
191 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_PASSED_NULL_PARAMETER);
192 return 0;
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);
197 return 0;
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);
206 return 0;
208 if (!CryptCreateHash(cd->crypt_prov, CALG_SSL3_SHAMD5, 0, 0, &hash)) {
209 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_CREATE_HASH);
210 return 0;
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);
216 return 0;
218 if ((int) hash_size != flen) {
219 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_INVALID_MESSAGE_LENGTH);
220 CryptDestroyHash(hash);
221 return 0;
223 if (!CryptSetHashParam(hash, HP_HASHVAL, (BYTE * ) from, 0)) {
224 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SET_HASH_PARAM);
225 CryptDestroyHash(hash);
226 return 0;
229 len = RSA_size(rsa);
230 buf = malloc(len);
231 if (buf == NULL) {
232 RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_MALLOC_FAILURE);
233 CryptDestroyHash(hash);
234 return 0;
236 if (!CryptSignHash(hash, cd->key_spec, NULL, 0, buf, &len)) {
237 CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SIGN_HASH);
238 CryptDestroyHash(hash);
239 free(buf);
240 return 0;
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];
245 free(buf);
247 CryptDestroyHash(hash);
248 return len;
251 /* decrypt */
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... */
255 assert(0);
257 return 0;
260 /* called at RSA_new */
261 static int init(RSA *rsa)
264 return 0;
267 /* called at RSA_free */
268 static int finish(RSA *rsa)
270 CAPI_DATA *cd = (CAPI_DATA *) rsa->meth->app_data;
272 if (cd == NULL)
273 return 0;
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);
280 rsa->meth = NULL;
281 return 1;
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)) {
295 /* skip the tag */
296 cert_prop += 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];
302 char *p;
303 int i, x = 0;
304 CRYPT_HASH_BLOB blob;
306 /* skip the tag */
307 cert_prop += 6;
308 for (p = (char *) cert_prop, i = 0; *p && i < sizeof(hash); i++) {
309 if (*p >= '0' && *p <= '9')
310 x = (*p - '0') << 4;
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 */
316 break;
317 if (*p >= '0' && *p <= '9')
318 x += *p - '0';
319 else if (*p >= 'A' && *p <= 'F')
320 x += *p - 'A' + 10;
321 else if (*p >= 'a' && *p <= 'f')
322 x += *p - 'a' + 10;
323 hash[i] = x;
324 /* skip any space(s) between hex numbers */
325 for (p++; *p && *p == ' '; p++);
327 blob.cbData = i;
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);
334 return rv;
337 int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
339 HCERTSTORE cs;
340 X509 *cert = NULL;
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);
347 goto err;
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");
352 if (cs == NULL) {
353 CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE);
354 goto err;
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");
361 if (cs == NULL) {
362 CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE);
363 goto err;
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);
369 goto err;
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);
376 if (cert == NULL) {
377 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
378 goto err;
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);
389 goto err;
392 if (OpenVPNCryptAcquireCertificatePrivateKey == NULL) {
393 OpenVPNCryptAcquireCertificatePrivateKey = GetProcAddress(crypt32dll,
394 "CryptAcquireCertificatePrivateKey");
395 if (OpenVPNCryptAcquireCertificatePrivateKey == NULL) {
396 CRYPTOAPIerr(CRYPTOAPI_F_GET_PROC_ADDRESS);
397 goto err;
400 #endif
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);
407 goto err;
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;
422 rsa = RSA_new();
423 if (rsa == NULL) {
424 SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
425 goto err;
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))
431 goto err;
432 /* the public key */
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. */
436 X509_free(cert);
437 cert = NULL;
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))
444 goto err;
446 if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx, rsa))
447 goto err;
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. */
450 RSA_free(rsa);
451 return 1;
453 err:
454 if (cert)
455 X509_free(cert);
456 if (rsa)
457 RSA_free(rsa);
458 else {
459 if (my_rsa_method)
460 free(my_rsa_method);
461 if (cd) {
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);
466 free(cd);
469 return 0;
472 #else
473 static void dummy (void) {}
474 #endif /* WIN32 */