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