summaryrefslogtreecommitdiff
path: root/src/openvpn/cryptoapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/openvpn/cryptoapi.c')
-rw-r--r--src/openvpn/cryptoapi.c465
1 files changed, 465 insertions, 0 deletions
diff --git a/src/openvpn/cryptoapi.c b/src/openvpn/cryptoapi.c
new file mode 100644
index 0000000..b7fc11e
--- /dev/null
+++ b/src/openvpn/cryptoapi.c
@@ -0,0 +1,465 @@
+/*
+ * Copyright (c) 2004 Peter 'Luna' Runestig <peter@runestig.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifi-
+ * cation, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright no-
+ * tice, this list of conditions and the following disclaimer in the do-
+ * cumentation and/or other materials provided with the distribution.
+ *
+ * o The names of the contributors may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LI-
+ * ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN-
+ * TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV-
+ * ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI-
+ * LITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#ifdef ENABLE_CRYPTOAPI
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <windows.h>
+#include <wincrypt.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <assert.h>
+
+/* MinGW w32api 3.17 is still incomplete when it comes to CryptoAPI while
+ * MinGW32-w64 defines all macros used. This is a hack around that problem.
+ */
+#ifndef CERT_SYSTEM_STORE_LOCATION_SHIFT
+#define CERT_SYSTEM_STORE_LOCATION_SHIFT 16
+#endif
+#ifndef CERT_SYSTEM_STORE_CURRENT_USER_ID
+#define CERT_SYSTEM_STORE_CURRENT_USER_ID 1
+#endif
+#ifndef CERT_SYSTEM_STORE_CURRENT_USER
+#define CERT_SYSTEM_STORE_CURRENT_USER (CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+#endif
+#ifndef CERT_STORE_READONLY_FLAG
+#define CERT_STORE_READONLY_FLAG 0x00008000
+#endif
+#ifndef CERT_STORE_OPEN_EXISTING_FLAG
+#define CERT_STORE_OPEN_EXISTING_FLAG 0x00004000
+#endif
+
+/* Size of an SSL signature: MD5+SHA1 */
+#define SSL_SIG_LENGTH 36
+
+/* try to funnel any Windows/CryptoAPI error messages to OpenSSL ERR_... */
+#define ERR_LIB_CRYPTOAPI (ERR_LIB_USER + 69) /* 69 is just a number... */
+#define CRYPTOAPIerr(f) err_put_ms_error(GetLastError(), (f), __FILE__, __LINE__)
+#define CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE 100
+#define CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE 101
+#define CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY 102
+#define CRYPTOAPI_F_CRYPT_CREATE_HASH 103
+#define CRYPTOAPI_F_CRYPT_GET_HASH_PARAM 104
+#define CRYPTOAPI_F_CRYPT_SET_HASH_PARAM 105
+#define CRYPTOAPI_F_CRYPT_SIGN_HASH 106
+#define CRYPTOAPI_F_LOAD_LIBRARY 107
+#define CRYPTOAPI_F_GET_PROC_ADDRESS 108
+
+static ERR_STRING_DATA CRYPTOAPI_str_functs[] = {
+ { ERR_PACK(ERR_LIB_CRYPTOAPI, 0, 0), "microsoft cryptoapi"},
+ { ERR_PACK(0, CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE, 0), "CertOpenSystemStore" },
+ { ERR_PACK(0, CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE, 0), "CertFindCertificateInStore" },
+ { ERR_PACK(0, CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY, 0), "CryptAcquireCertificatePrivateKey" },
+ { ERR_PACK(0, CRYPTOAPI_F_CRYPT_CREATE_HASH, 0), "CryptCreateHash" },
+ { ERR_PACK(0, CRYPTOAPI_F_CRYPT_GET_HASH_PARAM, 0), "CryptGetHashParam" },
+ { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SET_HASH_PARAM, 0), "CryptSetHashParam" },
+ { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SIGN_HASH, 0), "CryptSignHash" },
+ { ERR_PACK(0, CRYPTOAPI_F_LOAD_LIBRARY, 0), "LoadLibrary" },
+ { ERR_PACK(0, CRYPTOAPI_F_GET_PROC_ADDRESS, 0), "GetProcAddress" },
+ { 0, NULL }
+};
+
+typedef struct _CAPI_DATA {
+ const CERT_CONTEXT *cert_context;
+ HCRYPTPROV crypt_prov;
+ DWORD key_spec;
+ BOOL free_crypt_prov;
+} CAPI_DATA;
+
+static char *ms_error_text(DWORD ms_err)
+{
+ LPVOID lpMsgBuf = NULL;
+ char *rv = NULL;
+
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, ms_err,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
+ (LPTSTR) &lpMsgBuf, 0, NULL);
+ if (lpMsgBuf) {
+ char *p;
+ rv = strdup(lpMsgBuf);
+ LocalFree(lpMsgBuf);
+ /* trim to the left */
+ if (rv)
+ for (p = rv + strlen(rv) - 1; p >= rv; p--) {
+ if (isspace(*p))
+ *p = '\0';
+ else
+ break;
+ }
+ }
+ return rv;
+}
+
+static void err_put_ms_error(DWORD ms_err, int func, const char *file, int line)
+{
+ static int init = 0;
+# define ERR_MAP_SZ 16
+ static struct {
+ int err;
+ DWORD ms_err; /* I don't think we get more than 16 *different* errors */
+ } err_map[ERR_MAP_SZ]; /* in here, before we give up the whole thing... */
+ int i;
+
+ if (ms_err == 0)
+ /* 0 is not an error */
+ return;
+ if (!init) {
+ ERR_load_strings(ERR_LIB_CRYPTOAPI, CRYPTOAPI_str_functs);
+ memset(&err_map, 0, sizeof(err_map));
+ init++;
+ }
+ /* since MS error codes are 32 bit, and the ones in the ERR_... system is
+ * only 12, we must have a mapping table between them. */
+ for (i = 0; i < ERR_MAP_SZ; i++) {
+ if (err_map[i].ms_err == ms_err) {
+ ERR_PUT_error(ERR_LIB_CRYPTOAPI, func, err_map[i].err, file, line);
+ break;
+ } else if (err_map[i].ms_err == 0 ) {
+ /* end of table, add new entry */
+ ERR_STRING_DATA *esd = calloc(2, sizeof(*esd));
+ if (esd == NULL)
+ break;
+ err_map[i].ms_err = ms_err;
+ err_map[i].err = esd->error = i + 100;
+ esd->string = ms_error_text(ms_err);
+ ERR_load_strings(ERR_LIB_CRYPTOAPI, esd);
+ ERR_PUT_error(ERR_LIB_CRYPTOAPI, func, err_map[i].err, file, line);
+ break;
+ }
+ }
+}
+
+/* encrypt */
+static int rsa_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ /* I haven't been able to trigger this one, but I want to know if it happens... */
+ assert(0);
+
+ return 0;
+}
+
+/* verify arbitrary data */
+static int rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ /* I haven't been able to trigger this one, but I want to know if it happens... */
+ assert(0);
+
+ return 0;
+}
+
+/* sign arbitrary data */
+static int rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ CAPI_DATA *cd = (CAPI_DATA *) rsa->meth->app_data;
+ HCRYPTHASH hash;
+ DWORD hash_size, len, i;
+ unsigned char *buf;
+
+ if (cd == NULL) {
+ RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_PASSED_NULL_PARAMETER);
+ return 0;
+ }
+ if (padding != RSA_PKCS1_PADDING) {
+ /* AFAICS, CryptSignHash() *always* uses PKCS1 padding. */
+ RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
+ return 0;
+ }
+ /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would
+ * be way to straightforward for M$, I guess... So we have to do it this
+ * tricky way instead, by creating a "Hash", and load the already-made hash
+ * from 'from' into it. */
+ /* For now, we only support NID_md5_sha1 */
+ if (flen != SSL_SIG_LENGTH) {
+ RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_INVALID_MESSAGE_LENGTH);
+ return 0;
+ }
+ if (!CryptCreateHash(cd->crypt_prov, CALG_SSL3_SHAMD5, 0, 0, &hash)) {
+ CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_CREATE_HASH);
+ return 0;
+ }
+ len = sizeof(hash_size);
+ if (!CryptGetHashParam(hash, HP_HASHSIZE, (BYTE *) &hash_size, &len, 0)) {
+ CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_GET_HASH_PARAM);
+ CryptDestroyHash(hash);
+ return 0;
+ }
+ if ((int) hash_size != flen) {
+ RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_INVALID_MESSAGE_LENGTH);
+ CryptDestroyHash(hash);
+ return 0;
+ }
+ if (!CryptSetHashParam(hash, HP_HASHVAL, (BYTE * ) from, 0)) {
+ CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SET_HASH_PARAM);
+ CryptDestroyHash(hash);
+ return 0;
+ }
+
+ len = RSA_size(rsa);
+ buf = malloc(len);
+ if (buf == NULL) {
+ RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_MALLOC_FAILURE);
+ CryptDestroyHash(hash);
+ return 0;
+ }
+ if (!CryptSignHash(hash, cd->key_spec, NULL, 0, buf, &len)) {
+ CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SIGN_HASH);
+ CryptDestroyHash(hash);
+ free(buf);
+ return 0;
+ }
+ /* and now, we have to reverse the byte-order in the result from CryptSignHash()... */
+ for (i = 0; i < len; i++)
+ to[i] = buf[len - i - 1];
+ free(buf);
+
+ CryptDestroyHash(hash);
+ return len;
+}
+
+/* decrypt */
+static int rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ /* I haven't been able to trigger this one, but I want to know if it happens... */
+ assert(0);
+
+ return 0;
+}
+
+/* called at RSA_new */
+static int init(RSA *rsa)
+{
+
+ return 0;
+}
+
+/* called at RSA_free */
+static int finish(RSA *rsa)
+{
+ CAPI_DATA *cd = (CAPI_DATA *) rsa->meth->app_data;
+
+ if (cd == NULL)
+ return 0;
+ if (cd->crypt_prov && cd->free_crypt_prov)
+ CryptReleaseContext(cd->crypt_prov, 0);
+ if (cd->cert_context)
+ CertFreeCertificateContext(cd->cert_context);
+ free(rsa->meth->app_data);
+ free((char *) rsa->meth);
+ rsa->meth = NULL;
+ return 1;
+}
+
+static const CERT_CONTEXT *find_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store)
+{
+ /* Find, and use, the desired certificate from the store. The
+ * 'cert_prop' certificate search string can look like this:
+ * SUBJ:<certificate substring to match>
+ * THUMB:<certificate thumbprint hex value>, e.g.
+ * THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28
+ */
+ const CERT_CONTEXT *rv = NULL;
+
+ if (!strncmp(cert_prop, "SUBJ:", 5)) {
+ /* skip the tag */
+ cert_prop += 5;
+ rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ 0, CERT_FIND_SUBJECT_STR_A, cert_prop, NULL);
+
+ } else if (!strncmp(cert_prop, "THUMB:", 6)) {
+ unsigned char hash[255];
+ char *p;
+ int i, x = 0;
+ CRYPT_HASH_BLOB blob;
+
+ /* skip the tag */
+ cert_prop += 6;
+ for (p = (char *) cert_prop, i = 0; *p && i < sizeof(hash); i++) {
+ if (*p >= '0' && *p <= '9')
+ x = (*p - '0') << 4;
+ else if (*p >= 'A' && *p <= 'F')
+ x = (*p - 'A' + 10) << 4;
+ else if (*p >= 'a' && *p <= 'f')
+ x = (*p - 'a' + 10) << 4;
+ if (!*++p) /* unexpected end of string */
+ break;
+ if (*p >= '0' && *p <= '9')
+ x += *p - '0';
+ else if (*p >= 'A' && *p <= 'F')
+ x += *p - 'A' + 10;
+ else if (*p >= 'a' && *p <= 'f')
+ x += *p - 'a' + 10;
+ hash[i] = x;
+ /* skip any space(s) between hex numbers */
+ for (p++; *p && *p == ' '; p++);
+ }
+ blob.cbData = i;
+ blob.pbData = (unsigned char *) &hash;
+ rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ 0, CERT_FIND_HASH, &blob, NULL);
+
+ }
+
+ return rv;
+}
+
+int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
+{
+ HCERTSTORE cs;
+ X509 *cert = NULL;
+ RSA *rsa = NULL, *pub_rsa;
+ CAPI_DATA *cd = calloc(1, sizeof(*cd));
+ RSA_METHOD *my_rsa_method = calloc(1, sizeof(*my_rsa_method));
+
+ if (cd == NULL || my_rsa_method == NULL) {
+ SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+ /* search CURRENT_USER first, then LOCAL_MACHINE */
+ cs = CertOpenStore((LPCSTR) CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER |
+ CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, L"MY");
+ if (cs == NULL) {
+ CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE);
+ goto err;
+ }
+ cd->cert_context = find_certificate_in_store(cert_prop, cs);
+ CertCloseStore(cs, 0);
+ if (!cd->cert_context) {
+ cs = CertOpenStore((LPCSTR) CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE |
+ CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, L"MY");
+ if (cs == NULL) {
+ CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE);
+ goto err;
+ }
+ cd->cert_context = find_certificate_in_store(cert_prop, cs);
+ CertCloseStore(cs, 0);
+ if (cd->cert_context == NULL) {
+ CRYPTOAPIerr(CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE);
+ goto err;
+ }
+ }
+
+ /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
+ cert = d2i_X509(NULL, (const unsigned char **) &cd->cert_context->pbCertEncoded,
+ cd->cert_context->cbCertEncoded);
+ if (cert == NULL) {
+ SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
+ goto err;
+ }
+
+ /* set up stuff to use the private key */
+ if (!CryptAcquireCertificatePrivateKey(cd->cert_context, CRYPT_ACQUIRE_COMPARE_KEY_FLAG,
+ NULL, &cd->crypt_prov, &cd->key_spec, &cd->free_crypt_prov)) {
+ /* if we don't have a smart card reader here, and we try to access a
+ * smart card certificate, we get:
+ * "Error 1223: The operation was canceled by the user." */
+ CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY);
+ goto err;
+ }
+ /* here we don't need to do CryptGetUserKey() or anything; all necessary key
+ * info is in cd->cert_context, and then, in cd->crypt_prov. */
+
+ my_rsa_method->name = "Microsoft CryptoAPI RSA Method";
+ my_rsa_method->rsa_pub_enc = rsa_pub_enc;
+ my_rsa_method->rsa_pub_dec = rsa_pub_dec;
+ my_rsa_method->rsa_priv_enc = rsa_priv_enc;
+ my_rsa_method->rsa_priv_dec = rsa_priv_dec;
+ /* my_rsa_method->init = init; */
+ my_rsa_method->finish = finish;
+ my_rsa_method->flags = RSA_METHOD_FLAG_NO_CHECK;
+ my_rsa_method->app_data = (char *) cd;
+
+ rsa = RSA_new();
+ if (rsa == NULL) {
+ SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
+ /* cert->cert_info->key->pkey is NULL until we call SSL_CTX_use_certificate(),
+ * so we do it here then... */
+ if (!SSL_CTX_use_certificate(ssl_ctx, cert))
+ goto err;
+ /* the public key */
+ pub_rsa = cert->cert_info->key->pkey->pkey.rsa;
+ /* SSL_CTX_use_certificate() increased the reference count in 'cert', so
+ * we decrease it here with X509_free(), or it will never be cleaned up. */
+ X509_free(cert);
+ cert = NULL;
+
+ /* I'm not sure about what we have to fill in in the RSA, trying out stuff... */
+ /* rsa->n indicates the key size */
+ rsa->n = BN_dup(pub_rsa->n);
+ rsa->flags |= RSA_FLAG_EXT_PKEY;
+ if (!RSA_set_method(rsa, my_rsa_method))
+ goto err;
+
+ if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx, rsa))
+ goto err;
+ /* SSL_CTX_use_RSAPrivateKey() increased the reference count in 'rsa', so
+ * we decrease it here with RSA_free(), or it will never be cleaned up. */
+ RSA_free(rsa);
+ return 1;
+
+ err:
+ if (cert)
+ X509_free(cert);
+ if (rsa)
+ RSA_free(rsa);
+ else {
+ if (my_rsa_method)
+ free(my_rsa_method);
+ if (cd) {
+ if (cd->free_crypt_prov && cd->crypt_prov)
+ CryptReleaseContext(cd->crypt_prov, 0);
+ if (cd->cert_context)
+ CertFreeCertificateContext(cd->cert_context);
+ free(cd);
+ }
+ }
+ return 0;
+}
+
+#else
+#ifdef _MSC_VER /* Dummy function needed to avoid empty file compiler warning in Microsoft VC */
+static void dummy (void) {}
+#endif
+#endif /* WIN32 */