From: Selva Nair <selva.n...@gmail.com> - Require xkey-provider (thus OpenSSL 3.01+) for --cryptoapicert
Note: Ideally we should also make ENABLE_CRYPTOAPI conditional on HAVE_XKEY_PROVIDER but that looks hard unless we can agree to move HAVE_XKEY_PROVIDER to configure/config.h. v2: use "binary" instead of "version" in the error message Signed-off-by: Selva Nair <selva.n...@gmail.com> --- src/openvpn/cryptoapi.c | 555 +--------------------------------------- src/openvpn/options.c | 2 +- 2 files changed, 11 insertions(+), 546 deletions(-) diff --git a/src/openvpn/cryptoapi.c b/src/openvpn/cryptoapi.c index e3c0bc99..eafef1b1 100644 --- a/src/openvpn/cryptoapi.c +++ b/src/openvpn/cryptoapi.c @@ -55,17 +55,17 @@ #include "xkey_common.h" #ifndef HAVE_XKEY_PROVIDER -/* index for storing external data in EC_KEY: < 0 means uninitialized */ -static int ec_data_idx = -1; -/* Global EVP_PKEY_METHOD used to override the sign operation */ -static EVP_PKEY_METHOD *pmethod; -static int (*default_pkey_sign_init) (EVP_PKEY_CTX *ctx); -static int (*default_pkey_sign) (EVP_PKEY_CTX *ctx, unsigned char *sig, - size_t *siglen, const unsigned char *tbs, size_t tbslen); -#else /* ifndef HAVE_XKEY_PROVIDER */ +int +SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop) +{ + msg(M_NONFATAL, "ERROR: this binary was built without cryptoapicert support"); + return 0; +} + +#else /* HAVE_XKEY_PROVIDER */ + static XKEY_EXTERNAL_SIGN_fn xkey_cng_sign; -#endif /* HAVE_XKEY_PROVIDER */ typedef struct _CAPI_DATA { const CERT_CONTEXT *cert_context; @@ -146,127 +146,6 @@ CAPI_DATA_free(CAPI_DATA *cd) free(cd); } -#ifndef HAVE_XKEY_PROVIDER - -/* Translate OpenSSL padding type to CNG padding type - * Returns 0 for unknown/unsupported padding. - */ -static DWORD -cng_padding_type(int padding) -{ - DWORD pad = 0; - - switch (padding) - { - case RSA_NO_PADDING: - break; - - case RSA_PKCS1_PADDING: - pad = BCRYPT_PAD_PKCS1; - break; - - case RSA_PKCS1_PSS_PADDING: - pad = BCRYPT_PAD_PSS; - break; - - default: - msg(M_WARN|M_INFO, "cryptoapicert: unknown OpenSSL padding type %d.", - padding); - } - - return pad; -} - -/** - * Sign the hash in 'from' using NCryptSignHash(). This requires an NCRYPT - * key handle in cd->crypt_prov. On return the signature is in 'to'. Returns - * the length of the signature or 0 on error. - * This is used only for RSA and padding should be BCRYPT_PAD_PKCS1 or - * BCRYPT_PAD_PSS. - * If the hash_algo is not NULL, PKCS #1 DigestInfo header gets added - * to |from|, else it is signed as is. Use NULL for MD5 + SHA1 hash used - * in TLS 1.1 and earlier. - * In case of PSS padding, |saltlen| should specify the size of salt to use. - * If |to| is NULL returns the required buffer size. - */ -static int -priv_enc_CNG(const CAPI_DATA *cd, const wchar_t *hash_algo, const unsigned char *from, - int flen, unsigned char *to, int tlen, DWORD padding, DWORD saltlen) -{ - NCRYPT_KEY_HANDLE hkey = cd->crypt_prov; - DWORD len = 0; - ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC); - - DWORD status; - - msg(D_LOW, "Signing hash using CNG: data size = %d padding = %lu", flen, padding); - - if (padding == BCRYPT_PAD_PKCS1) - { - BCRYPT_PKCS1_PADDING_INFO padinfo = {hash_algo}; - status = NCryptSignHash(hkey, &padinfo, (BYTE *)from, flen, - to, tlen, &len, padding); - } - else if (padding == BCRYPT_PAD_PSS) - { - BCRYPT_PSS_PADDING_INFO padinfo = {hash_algo, saltlen}; - status = NCryptSignHash(hkey, &padinfo, (BYTE *)from, flen, - to, tlen, &len, padding); - } - else - { - msg(M_NONFATAL, "Error in cryptoapicert: Unknown padding type"); - return 0; - } - - if (status != ERROR_SUCCESS) - { - SetLastError(status); - msg(M_NONFATAL|M_ERRNO, "Error in cryptoapicert: NCryptSignHash failed"); - len = 0; - } - - /* Unlike CAPI, CNG signature is in big endian order. No reversing needed. */ - return len; -} - -/* called at RSA_free */ -static int -rsa_finish(RSA *rsa) -{ - const RSA_METHOD *rsa_meth = RSA_get_method(rsa); - CAPI_DATA *cd = (CAPI_DATA *) RSA_meth_get0_app_data(rsa_meth); - - if (cd == NULL) - { - return 0; - } - CAPI_DATA_free(cd); - RSA_meth_free((RSA_METHOD *) rsa_meth); - return 1; -} - -static EC_KEY_METHOD *ec_method = NULL; - -/** EC_KEY_METHOD callback: called when the key is freed */ -static void -ec_finish(EC_KEY *ec) -{ - EC_KEY_METHOD_free(ec_method); - ec_method = NULL; - CAPI_DATA *cd = EC_KEY_get_ex_data(ec, ec_data_idx); - CAPI_DATA_free(cd); - EC_KEY_set_ex_data(ec, ec_data_idx, NULL); -} - -/** EC_KEY_METHOD callback sign_setup(): we do nothing here */ -static int -ecdsa_sign_setup(EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp) -{ - return 1; -} -#endif /* HAVE_XKEY_PROVIDER */ - /** * Helper to convert ECDSA signature returned by NCryptSignHash * to an ECDSA_SIG structure. @@ -301,141 +180,6 @@ err: return NULL; } -#ifndef HAVE_XKEY_PROVIDER - -/** EC_KEY_METHOD callback sign_sig(): sign and return an ECDSA_SIG pointer. */ -static ECDSA_SIG * -ecdsa_sign_sig(const unsigned char *dgst, int dgstlen, - const BIGNUM *in_kinv, const BIGNUM *in_r, EC_KEY *ec) -{ - ECDSA_SIG *ecsig = NULL; - CAPI_DATA *cd = (CAPI_DATA *)EC_KEY_get_ex_data(ec, ec_data_idx); - - ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC); - - NCRYPT_KEY_HANDLE hkey = cd->crypt_prov; - BYTE buf[512]; /* large enough buffer for signature to avoid malloc */ - DWORD len = _countof(buf); - - msg(D_LOW, "Cryptoapi: signing hash using EC key: data size = %d", dgstlen); - - DWORD status = NCryptSignHash(hkey, NULL, (BYTE *)dgst, dgstlen, (BYTE *)buf, len, &len, 0); - if (status != ERROR_SUCCESS) - { - SetLastError(status); - msg(M_NONFATAL|M_ERRNO, "Error in cryptoapticert: NCryptSignHash failed"); - } - else - { - /* NCryptSignHash returns r, s concatenated in buf[] */ - ecsig = ecdsa_bin2sig(buf, len); - } - return ecsig; -} - -/** EC_KEY_METHOD callback sign(): sign and return a DER encoded signature */ -static int -ecdsa_sign(int type, const unsigned char *dgst, int dgstlen, unsigned char *sig, - unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *ec) -{ - ECDSA_SIG *s; - - *siglen = 0; - s = ecdsa_sign_sig(dgst, dgstlen, NULL, NULL, ec); - if (s == NULL) - { - return 0; - } - - /* convert internal signature structure 's' to DER encoded byte array in sig */ - int len = i2d_ECDSA_SIG(s, NULL); - if (len > ECDSA_size(ec)) - { - ECDSA_SIG_free(s); - msg(M_NONFATAL, "Error in cryptoapicert: DER encoded ECDSA signature is too long (%d bytes)", len); - return 0; - } - *siglen = i2d_ECDSA_SIG(s, &sig); - ECDSA_SIG_free(s); - - return 1; -} - -static int -ssl_ctx_set_eckey(SSL_CTX *ssl_ctx, CAPI_DATA *cd, EVP_PKEY *pkey) -{ - EC_KEY *ec = NULL; - EVP_PKEY *privkey = NULL; - - /* create a method struct with default callbacks filled in */ - ec_method = EC_KEY_METHOD_new(EC_KEY_OpenSSL()); - if (!ec_method) - { - goto err; - } - - /* We only need to set finish among init methods, and sign methods */ - EC_KEY_METHOD_set_init(ec_method, NULL, ec_finish, NULL, NULL, NULL, NULL); - EC_KEY_METHOD_set_sign(ec_method, ecdsa_sign, ecdsa_sign_setup, ecdsa_sign_sig); - - ec = EC_KEY_dup(EVP_PKEY_get0_EC_KEY(pkey)); - if (!ec) - { - goto err; - } - if (!EC_KEY_set_method(ec, ec_method)) - { - goto err; - } - - /* get an index to store cd as external data */ - if (ec_data_idx < 0) - { - ec_data_idx = EC_KEY_get_ex_new_index(0, "cryptapicert ec key", NULL, NULL, NULL); - if (ec_data_idx < 0) - { - goto err; - } - } - EC_KEY_set_ex_data(ec, ec_data_idx, cd); - - /* cd assigned to ec as ex_data, increase its refcount */ - cd->ref_count++; - - privkey = EVP_PKEY_new(); - if (!EVP_PKEY_assign_EC_KEY(privkey, ec)) - { - EC_KEY_free(ec); - goto err; - } - /* from here on ec will get freed with privkey */ - - if (!SSL_CTX_use_PrivateKey(ssl_ctx, privkey)) - { - goto err; - } - EVP_PKEY_free(privkey); /* this will dn_ref or free ec as well */ - return 1; - -err: - if (privkey) - { - EVP_PKEY_free(privkey); - } - else if (ec) - { - EC_KEY_free(ec); - } - if (ec_method) /* do always set ec_method = NULL after freeing it */ - { - EC_KEY_METHOD_free(ec_method); - ec_method = NULL; - } - return 0; -} - -#endif /* !HAVE_XKEY_PROVIDER */ - static const CERT_CONTEXT * find_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store) { @@ -541,254 +285,6 @@ out: return rv; } -#ifndef HAVE_XKEY_PROVIDER - -static const CAPI_DATA * -retrieve_capi_data(EVP_PKEY *pkey) -{ - const CAPI_DATA *cd = NULL; - - if (pkey && EVP_PKEY_id(pkey) == EVP_PKEY_RSA) - { - RSA *rsa = EVP_PKEY_get0_RSA(pkey); - if (rsa) - { - cd = (CAPI_DATA *)RSA_meth_get0_app_data(RSA_get_method(rsa)); - } - } - return cd; -} - -static int -pkey_rsa_sign_init(EVP_PKEY_CTX *ctx) -{ - msg(D_LOW, "cryptoapicert: enter pkey_rsa_sign_init"); - - EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx); - - if (pkey && retrieve_capi_data(pkey)) - { - return 1; /* Return success */ - } - else if (default_pkey_sign_init) /* Not our key. Call the default method */ - { - return default_pkey_sign_init(ctx); - } - return 1; -} - -/** - * Implementation of EVP_PKEY_sign() using CNG: sign the digest in |tbs| - * and save the the signature in |sig| and its size in |*siglen|. - * If |sig| is NULL the required buffer size is returned in |*siglen|. - * Returns value is 1 on success, 0 or a negative integer on error. - */ -static int -pkey_rsa_sign(EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen, - const unsigned char *tbs, size_t tbslen) -{ - EVP_PKEY *pkey = NULL; - const CAPI_DATA *cd = NULL; - EVP_MD *md = NULL; - const wchar_t *alg = NULL; - - int padding = 0; - int hashlen = 0; - int saltlen = 0; - - pkey = EVP_PKEY_CTX_get0_pkey(ctx); - if (pkey) - { - cd = retrieve_capi_data(pkey); - } - - /* - * We intercept all sign requests, not just the one's for our key. - * Check the key and call the saved OpenSSL method for unknown keys. - */ - if (!pkey || !cd) - { - if (default_pkey_sign) - { - return default_pkey_sign(ctx, sig, siglen, tbs, tbslen); - } - else /* This should not happen */ - { - msg(M_FATAL, "Error in cryptoapicert: Unknown key and no default sign operation to fallback on"); - return -1; - } - } - - if (!EVP_PKEY_CTX_get_rsa_padding(ctx, &padding)) - { - padding = RSA_PKCS1_PADDING; /* Default padding for RSA */ - } - - if (EVP_PKEY_CTX_get_signature_md(ctx, &md)) - { - hashlen = EVP_MD_size(md); - alg = cng_hash_algo(EVP_MD_type(md)); - - /* - * alg == NULL indicates legacy MD5+SHA1 hash, else alg should be a valid - * digest algorithm. - */ - if (alg && wcscmp(alg, L"UNKNOWN") == 0) - { - msg(M_NONFATAL, "Error in cryptoapicert: Unknown hash algorithm <%d>", EVP_MD_type(md)); - return -1; - } - } - else - { - msg(M_NONFATAL, "Error in cryptoapicert: could not determine the signature digest algorithm"); - return -1; - } - - if (tbslen != (size_t)hashlen) - { - msg(M_NONFATAL, "Error in cryptoapicert: data size does not match hash"); - return -1; - } - - /* If padding is PSS, determine parameters to pass to CNG */ - if (padding == RSA_PKCS1_PSS_PADDING) - { - /* - * Ensure the digest type for signature and mask generation match. - * In CNG there is no option to specify separate hash functions for - * the two, but OpenSSL supports it. However, I have not seen the - * two being different in practice. Also the recommended practice is - * to use the same for both (rfc 8017 sec 8.1). - */ - EVP_MD *mgf1md; - if (!EVP_PKEY_CTX_get_rsa_mgf1_md(ctx, &mgf1md) - || EVP_MD_type(mgf1md) != EVP_MD_type(md)) - { - msg(M_NONFATAL, "Error in cryptoapicert: Unknown MGF1 digest type or does" - " not match the signature digest type."); - return -1; - } - - if (!EVP_PKEY_CTX_get_rsa_pss_saltlen(ctx, &saltlen)) - { - msg(M_WARN|M_INFO, "cryptoapicert: unable to get the salt length from context." - " Using the default value."); - saltlen = -1; - } - - /* - * In OpenSSL saltlen = -1 indicates to use the size of the digest as - * size of the salt. A value of -2 or -3 indicates maximum salt length - * that will fit. See RSA_padding_add_PKCS1_PSS_mgf1() of OpenSSL. - */ - if (saltlen == -1) - { - saltlen = hashlen; - } - else if (saltlen < 0) - { - const RSA *rsa = EVP_PKEY_get0_RSA(pkey); - saltlen = RSA_size(rsa) - hashlen - 2; /* max salt length for RSASSA-PSS */ - if (RSA_bits(rsa) &0x7) /* number of bits in the key not a multiple of 8 */ - { - saltlen--; - } - } - - if (saltlen < 0) - { - msg(M_NONFATAL, "Error in cryptoapicert: invalid salt length (%d). Digest too large for keysize?", saltlen); - return -1; - } - msg(D_LOW, "cryptoapicert: PSS padding using saltlen = %d", saltlen); - } - - msg(D_LOW, "cryptoapicert: calling priv_enc_CNG with alg = %ls", alg); - *siglen = priv_enc_CNG(cd, alg, tbs, (int)tbslen, sig, (int)*siglen, - cng_padding_type(padding), (DWORD)saltlen); - - return (*siglen == 0) ? 0 : 1; -} - -static int -ssl_ctx_set_rsakey(SSL_CTX *ssl_ctx, CAPI_DATA *cd, EVP_PKEY *pkey) -{ - RSA *rsa = NULL; - RSA_METHOD *my_rsa_method = NULL; - EVP_PKEY *privkey = NULL; - int ret = 0; - - my_rsa_method = RSA_meth_new("Microsoft Cryptography API RSA Method", - RSA_METHOD_FLAG_NO_CHECK); - check_malloc_return(my_rsa_method); - RSA_meth_set_finish(my_rsa_method, rsa_finish); /* we use this callback to cleanup CAPI_DATA */ - RSA_meth_set0_app_data(my_rsa_method, cd); - - /* pmethod is global -- initialize only if NULL */ - if (!pmethod) - { - pmethod = EVP_PKEY_meth_new(EVP_PKEY_RSA, 0); - if (!pmethod) - { - msg(M_NONFATAL, "Error in cryptoapicert: failed to create EVP_PKEY_METHOD"); - return 0; - } - const EVP_PKEY_METHOD *default_pmethod = EVP_PKEY_meth_find(EVP_PKEY_RSA); - EVP_PKEY_meth_copy(pmethod, default_pmethod); - - /* We want to override only sign_init() and sign() */ - EVP_PKEY_meth_set_sign(pmethod, pkey_rsa_sign_init, pkey_rsa_sign); - EVP_PKEY_meth_add0(pmethod); - - /* Keep a copy of the default sign and sign_init methods */ - - EVP_PKEY_meth_get_sign(default_pmethod, &default_pkey_sign_init, - &default_pkey_sign); - } - - rsa = EVP_PKEY_get1_RSA(pkey); - - RSA_set_flags(rsa, RSA_flags(rsa) | RSA_FLAG_EXT_PKEY); - if (!RSA_set_method(rsa, my_rsa_method)) - { - goto cleanup; - } - my_rsa_method = NULL; /* we do not want to free it in cleanup */ - cd->ref_count++; /* with method, cd gets assigned to the key as well */ - - privkey = EVP_PKEY_new(); - if (!EVP_PKEY_assign_RSA(privkey, rsa)) - { - goto cleanup; - } - rsa = NULL; /* privkey has taken ownership */ - - if (!SSL_CTX_use_PrivateKey(ssl_ctx, privkey)) - { - goto cleanup; - } - ret = 1; - -cleanup: - if (rsa) - { - RSA_free(rsa); - } - if (my_rsa_method) - { - RSA_meth_free(my_rsa_method); - } - if (privkey) - { - EVP_PKEY_free(privkey); - } - - return ret; -} - -#else /* HAVE_XKEY_PROVIDER */ - /** Sign hash in tbs using EC key in cd and NCryptSignHash */ static int xkey_cng_ec_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs, @@ -937,8 +433,6 @@ xkey_cng_sign(void *handle, unsigned char *sig, size_t *siglen, const unsigned c } } -#endif /* HAVE_XKEY_PROVIDER */ - static char * get_cert_name(const CERT_CONTEXT *cc, struct gc_arena *gc) { @@ -1043,45 +537,16 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop) X509_free(cert); cert = NULL; -#ifdef HAVE_XKEY_PROVIDER - EVP_PKEY *privkey = xkey_load_generic_key(tls_libctx, cd, pkey, xkey_cng_sign, (XKEY_PRIVKEY_FREE_fn *) CAPI_DATA_free); SSL_CTX_use_PrivateKey(ssl_ctx, privkey); gc_free(&gc); return 1; /* do not free cd -- its kept by xkey provider */ -#else /* ifdef HAVE_XKEY_PROVIDER */ - - if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA) - { - if (!ssl_ctx_set_rsakey(ssl_ctx, cd, pkey)) - { - goto err; - } - } - else if (EVP_PKEY_id(pkey) == EVP_PKEY_EC) - { - if (!ssl_ctx_set_eckey(ssl_ctx, cd, pkey)) - { - goto err; - } - } - else - { - msg(M_WARN|M_INFO, "WARNING: cryptoapicert: key type <%d> not supported", - EVP_PKEY_id(pkey)); - goto err; - } - CAPI_DATA_free(cd); /* this will do a ref_count-- */ - gc_free(gc); - return 1; - -#endif /* HAVE_XKEY_PROVIDER */ - err: CAPI_DATA_free(cd); gc_free(&gc); return 0; } +#endif /* HAVE_XKEY_PROVIDER */ #endif /* _WIN32 */ diff --git a/src/openvpn/options.c b/src/openvpn/options.c index f24af3d7..e18b3b39 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -8864,7 +8864,7 @@ add_option(struct options *options, listend->next = newlist; } } -#ifdef ENABLE_CRYPTOAPI +#if defined(ENABLE_CRYPTOAPI) && defined(HAVE_XKEY_PROVIDER) else if (streq(p[0], "cryptoapicert") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); -- 2.34.1 _______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel