[Openvpn-devel,2/2] TLS v1.2 support for cryptoapicert -- RSA only

Message ID 1515378076-5774-3-git-send-email-selva.nair@gmail.com
State Superseded
Headers show
Series
  • Make cryptoapicert work with TLS 1.2
Related show

Commit Message

Selva Nair Jan. 8, 2018, 2:21 a.m.
From: Selva Nair <selva.nair@gmail.com>

- If an NCRYPT handle for the private key can be obtained, use
  NCryptSignHash from the Cryptography NG API to sign the hash.

  This should work for all keys in the Windows certifiate stores
  but may fail for keys in a legacy token, for example. In such
  cases, we disable TLS v1.2 and fall back to the current
  behaviour. A warning is logged unless TLS version is already
  restricted to <= 1.1

Signed-off-by: Selva Nair <selva.nair@gmail.com>
---
Tested on Windows 10 with openssl 1.0.1r and 1.1.0g
with certificate/key in Windows user store

 src/openvpn/Makefile.am |  2 +-
 src/openvpn/cryptoapi.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++---
 src/openvpn/options.c   | 18 -----------
 3 files changed, 83 insertions(+), 23 deletions(-)

Comments

Steffan Karger Jan. 17, 2018, 2:20 p.m. | #1
Hi,

Some preliminary comments:

On 08-01-18 03:21, selva.nair@gmail.com wrote:
> From: Selva Nair <selva.nair@gmail.com>
> 
> - If an NCRYPT handle for the private key can be obtained, use
>   NCryptSignHash from the Cryptography NG API to sign the hash.
> 
>   This should work for all keys in the Windows certifiate stores
>   but may fail for keys in a legacy token, for example. In such
>   cases, we disable TLS v1.2 and fall back to the current
>   behaviour. A warning is logged unless TLS version is already
>   restricted to <= 1.1
> 
> Signed-off-by: Selva Nair <selva.nair@gmail.com>
> ---
> Tested on Windows 10 with openssl 1.0.1r and 1.1.0g
> with certificate/key in Windows user store
> 
>  src/openvpn/Makefile.am |  2 +-
>  src/openvpn/cryptoapi.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++---
>  src/openvpn/options.c   | 18 -----------
>  3 files changed, 83 insertions(+), 23 deletions(-)
> 
> diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
> index fcc22d6..1a2f42e 100644
> --- a/src/openvpn/Makefile.am
> +++ b/src/openvpn/Makefile.am
> @@ -132,5 +132,5 @@ openvpn_LDADD = \
>  	$(OPTIONAL_DL_LIBS)
>  if WIN32
>  openvpn_SOURCES += openvpn_win32_resources.rc block_dns.c block_dns.h
> -openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4
> +openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt
>  endif
> diff --git a/src/openvpn/cryptoapi.c b/src/openvpn/cryptoapi.c
> index 7052e4e..cd3f023 100644
> --- a/src/openvpn/cryptoapi.c
> +++ b/src/openvpn/cryptoapi.c
> @@ -42,6 +42,7 @@
>  #include <openssl/err.h>
>  #include <windows.h>
>  #include <wincrypt.h>
> +#include <ncrypt.h>
>  #include <stdio.h>
>  #include <ctype.h>
>  #include <assert.h>
> @@ -68,6 +69,14 @@
>  #define CERT_STORE_OPEN_EXISTING_FLAG 0x00004000
>  #endif
>  
>  /* Size of an SSL signature: MD5+SHA1 */
>  #define SSL_SIG_LENGTH  36
>  
> @@ -83,6 +92,7 @@
>  #define CRYPTOAPI_F_CRYPT_SIGN_HASH                         106
>  #define CRYPTOAPI_F_LOAD_LIBRARY                            107
>  #define CRYPTOAPI_F_GET_PROC_ADDRESS                        108
> +#define CRYPTOAPI_F_NCRYPT_SIGN_HASH                        109
>  
>  static ERR_STRING_DATA CRYPTOAPI_str_functs[] = {
>      { ERR_PACK(ERR_LIB_CRYPTOAPI, 0, 0),                                    "microsoft cryptoapi"},
> @@ -95,12 +105,13 @@ static ERR_STRING_DATA CRYPTOAPI_str_functs[] = {
>      { 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" },
> +    { ERR_PACK(0, CRYPTOAPI_F_NCRYPT_SIGN_HASH, 0),                         "NCryptSignHash" },
>      { 0, NULL }
>  };
>  
>  typedef struct _CAPI_DATA {
>      const CERT_CONTEXT *cert_context;
> -    HCRYPTPROV crypt_prov;
> +    HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov;
>      DWORD key_spec;
>      BOOL free_crypt_prov;
>  } CAPI_DATA;
> @@ -210,6 +221,41 @@ rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, in
>      return 0;
>  }
>  
> +/**
> + * 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.
> + * For now we support only RSA and the padding is assumed to be PKCS1 v1.5
> + */
> +static int
> +priv_enc_CNG(const CAPI_DATA *cd, const unsigned char *from, int flen,
> +              unsigned char *to, int tlen, int padding)
> +{
> +    NCRYPT_KEY_HANDLE hkey = cd->crypt_prov;
> +    DWORD len;
> +    ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC);
> +
> +    msg(M_INFO, "Signing hash using CNG: data size = %d", flen);

M_INFO is probably a bit verbose here.

> +    /* The hash OID is already in 'from'.  So set the hash algorithm
> +     * in the padding info struct to NULL.
> +     */
> +    BCRYPT_PKCS1_PADDING_INFO padinfo = {NULL};
> +    DWORD status;
> +
> +    status = NCryptSignHash(hkey, padding? &padinfo : NULL, (BYTE*) from, flen,
> +                            to, tlen, &len, padding? BCRYPT_PAD_PKCS1 : 0);
> +    if (status != ERROR_SUCCESS)
> +    {
> +        SetLastError(status);
> +        CRYPTOAPIerr(CRYPTOAPI_F_NCRYPT_SIGN_HASH);
> +        len = 0;
> +    }
> +
> +    /* Unlike CAPI, CNG signature is in big endian order. No reversing needed. */
> +    return len;
> +}
> +
>  /* sign arbitrary data */
>  static int
>  rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
> @@ -230,6 +276,11 @@ rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, i
>          RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
>          return 0;
>      }
> +    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
> +    {
> +        return priv_enc_CNG(cd, from, flen, to, RSA_size(rsa), padding);
> +    }
> +
>      /* 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
> @@ -322,7 +373,14 @@ finish(RSA *rsa)
>      }
>      if (cd->crypt_prov && cd->free_crypt_prov)
>      {
> -        CryptReleaseContext(cd->crypt_prov, 0);
> +        if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
> +        {
> +            NCryptFreeObject(cd->crypt_prov);
> +        }
> +        else
> +        {
> +            CryptReleaseContext(cd->crypt_prov, 0);
> +        }
>      }
>      if (cd->cert_context)
>      {
> @@ -458,7 +516,9 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
>      }
>  
>      /* set up stuff to use the private key */
> -    if (!CryptAcquireCertificatePrivateKey(cd->cert_context, CRYPT_ACQUIRE_COMPARE_KEY_FLAG,
> +    /* We prefer to get an NCRYPT key handle so that TLS1.2 can be supported */
> +    DWORD flags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG|CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG;
> +    if (!CryptAcquireCertificatePrivateKey(cd->cert_context, flags,
>                                             NULL, &cd->crypt_prov, &cd->key_spec, &cd->free_crypt_prov))

Some rewrapping of the arguments to no longer exceed 80 chars would be
nice if you touch this anyway.

>      {
>          /* if we don't have a smart card reader here, and we try to access a
> @@ -470,6 +530,17 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
>      /* 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.  */
>  
> +    /* If we do not have an NCRYPT key handle disable TLS v1.2 */
> +    if (cd->key_spec != CERT_NCRYPT_KEY_SPEC)
> +    {
> +        if (!(SSL_CTX_get_options(ssl_ctx) & SSL_OP_NO_TLSv1_2))
> +        {

This no longer works for OpenSSL 1.1, we'll need the patch from
https://patchwork.openvpn.net/patch/160/ (awaiting review), and then use
the SSL_CTX_{get,set}_max_proto_version functions instead.

> +            msg(M_WARN, "WARNING: cryptapicert: only legacy private key handle is available."
> +                        "Disabling TLS v1.2");
> +            SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_2);
> +        }
> +    }
> +
>      my_rsa_method = RSA_meth_new("Microsoft Cryptography API RSA Method",
>                                    RSA_METHOD_FLAG_NO_CHECK);
>      check_malloc_return(my_rsa_method);
> @@ -551,7 +622,14 @@ err:
>          {
>              if (cd->free_crypt_prov && cd->crypt_prov)
>              {
> -                CryptReleaseContext(cd->crypt_prov, 0);
> +                if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
> +                {
> +                    NCryptFreeObject(cd->crypt_prov);
> +                }
> +                else
> +                {
> +                    CryptReleaseContext(cd->crypt_prov, 0);
> +                }
>              }
>              if (cd->cert_context)
>              {
> diff --git a/src/openvpn/options.c b/src/openvpn/options.c
> index 7be5f38..e4fbade 100644
> --- a/src/openvpn/options.c
> +++ b/src/openvpn/options.c
> @@ -3018,24 +3018,6 @@ options_postprocess_mutate(struct options *o)
>      }
>  #endif
>  
> -#ifdef ENABLE_CRYPTOAPI
> -    if (o->cryptoapi_cert)
> -    {
> -        const int tls_version_max =
> -            (o->ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT)
> -            &SSLF_TLS_VERSION_MAX_MASK;
> -
> -        if (tls_version_max == TLS_VER_UNSPEC || tls_version_max > TLS_VER_1_1)
> -        {
> -            msg(M_WARN, "Warning: cryptapicert used, setting maximum TLS "
> -                "version to 1.1.");
> -            o->ssl_flags &= ~(SSLF_TLS_VERSION_MAX_MASK
> -                              <<SSLF_TLS_VERSION_MAX_SHIFT);
> -            o->ssl_flags |= (TLS_VER_1_1 << SSLF_TLS_VERSION_MAX_SHIFT);
> -        }
> -    }
> -#endif /* ENABLE_CRYPTOAPI */
> -
>  #if P2MP
>      /*
>       * Save certain parms before modifying options via --pull
> 

Otherwise this looks good on first sight.  I'll try to do some building
and testing later.

-Steffan

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Selva Nair Jan. 17, 2018, 3:28 p.m. | #2
Hi,

Thanks for the comments

On Wed, Jan 17, 2018 at 9:20 AM, Steffan Karger
<steffan.karger@fox-it.com> wrote:
> Hi,
>
> Some preliminary comments:
>
> On 08-01-18 03:21, selva.nair@gmail.com wrote:
>> From: Selva Nair <selva.nair@gmail.com>
>>
>> - If an NCRYPT handle for the private key can be obtained, use
>>   NCryptSignHash from the Cryptography NG API to sign the hash.
>>
>>   This should work for all keys in the Windows certifiate stores
>>   but may fail for keys in a legacy token, for example. In such
>>   cases, we disable TLS v1.2 and fall back to the current
>>   behaviour. A warning is logged unless TLS version is already
>>   restricted to <= 1.1
>>
>>

.. snipped..

>> +static int
>> +priv_enc_CNG(const CAPI_DATA *cd, const unsigned char *from, int flen,
>> +              unsigned char *to, int tlen, int padding)
>> +{
>> +    NCRYPT_KEY_HANDLE hkey = cd->crypt_prov;
>> +    DWORD len;
>> +    ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC);
>> +
>> +    msg(M_INFO, "Signing hash using CNG: data size = %d", flen);
>
> M_INFO is probably a bit verbose here.

Yeah, that was more for testing -- will use one of the TLS debug flags -- we
really need to make M_DEBUG print only at some high verbosity.

...


>>      /* 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
>> @@ -322,7 +373,14 @@ finish(RSA *rsa)
>>      }
>>      if (cd->crypt_prov && cd->free_crypt_prov)
>>      {
>> -        CryptReleaseContext(cd->crypt_prov, 0);
>> +        if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
>> +        {
>> +            NCryptFreeObject(cd->crypt_prov);
>> +        }
>> +        else
>> +        {
>> +            CryptReleaseContext(cd->crypt_prov, 0);
>> +        }
>>      }
>>      if (cd->cert_context)
>>      {
>> @@ -458,7 +516,9 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
>>      }
>>
>>      /* set up stuff to use the private key */
>> -    if (!CryptAcquireCertificatePrivateKey(cd->cert_context, CRYPT_ACQUIRE_COMPARE_KEY_FLAG,
>> +    /* We prefer to get an NCRYPT key handle so that TLS1.2 can be supported */
>> +    DWORD flags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG|CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG;
>> +    if (!CryptAcquireCertificatePrivateKey(cd->cert_context, flags,
>>                                             NULL, &cd->crypt_prov, &cd->key_spec, &cd->free_crypt_prov))
>
> Some rewrapping of the arguments to no longer exceed 80 chars would be
> nice if you touch this anyway.

done :)

>
>>      {
>>          /* if we don't have a smart card reader here, and we try to access a
>> @@ -470,6 +530,17 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
>>      /* 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.  */
>>
>> +    /* If we do not have an NCRYPT key handle disable TLS v1.2 */
>> +    if (cd->key_spec != CERT_NCRYPT_KEY_SPEC)
>> +    {
>> +        if (!(SSL_CTX_get_options(ssl_ctx) & SSL_OP_NO_TLSv1_2))
>> +        {
>
> This no longer works for OpenSSL 1.1, we'll need the patch from
> https://patchwork.openvpn.net/patch/160/ (awaiting review), and then use
> the SSL_CTX_{get,set}_max_proto_version functions instead.

Good point.

Strangely enough it works in some cases -- I had tested with a non-NCRYPT key in
a legacy token and got restricted to TLS1.1. But point noted. And,  anyway,
set_max_proto_version is better, as otherwise we'll have to touch this again
when TLS 1.3 is out.

Will take care of it in v2.

Selva

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

Patch

diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index fcc22d6..1a2f42e 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -132,5 +132,5 @@  openvpn_LDADD = \
 	$(OPTIONAL_DL_LIBS)
 if WIN32
 openvpn_SOURCES += openvpn_win32_resources.rc block_dns.c block_dns.h
-openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4
+openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt
 endif
diff --git a/src/openvpn/cryptoapi.c b/src/openvpn/cryptoapi.c
index 7052e4e..cd3f023 100644
--- a/src/openvpn/cryptoapi.c
+++ b/src/openvpn/cryptoapi.c
@@ -42,6 +42,7 @@ 
 #include <openssl/err.h>
 #include <windows.h>
 #include <wincrypt.h>
+#include <ncrypt.h>
 #include <stdio.h>
 #include <ctype.h>
 #include <assert.h>
@@ -68,6 +69,14 @@ 
 #define CERT_STORE_OPEN_EXISTING_FLAG 0x00004000
 #endif
 
 /* Size of an SSL signature: MD5+SHA1 */
 #define SSL_SIG_LENGTH  36
 
@@ -83,6 +92,7 @@ 
 #define CRYPTOAPI_F_CRYPT_SIGN_HASH                         106
 #define CRYPTOAPI_F_LOAD_LIBRARY                            107
 #define CRYPTOAPI_F_GET_PROC_ADDRESS                        108
+#define CRYPTOAPI_F_NCRYPT_SIGN_HASH                        109
 
 static ERR_STRING_DATA CRYPTOAPI_str_functs[] = {
     { ERR_PACK(ERR_LIB_CRYPTOAPI, 0, 0),                                    "microsoft cryptoapi"},
@@ -95,12 +105,13 @@  static ERR_STRING_DATA CRYPTOAPI_str_functs[] = {
     { 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" },
+    { ERR_PACK(0, CRYPTOAPI_F_NCRYPT_SIGN_HASH, 0),                         "NCryptSignHash" },
     { 0, NULL }
 };
 
 typedef struct _CAPI_DATA {
     const CERT_CONTEXT *cert_context;
-    HCRYPTPROV crypt_prov;
+    HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov;
     DWORD key_spec;
     BOOL free_crypt_prov;
 } CAPI_DATA;
@@ -210,6 +221,41 @@  rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, in
     return 0;
 }
 
+/**
+ * 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.
+ * For now we support only RSA and the padding is assumed to be PKCS1 v1.5
+ */
+static int
+priv_enc_CNG(const CAPI_DATA *cd, const unsigned char *from, int flen,
+              unsigned char *to, int tlen, int padding)
+{
+    NCRYPT_KEY_HANDLE hkey = cd->crypt_prov;
+    DWORD len;
+    ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC);
+
+    msg(M_INFO, "Signing hash using CNG: data size = %d", flen);
+
+    /* The hash OID is already in 'from'.  So set the hash algorithm
+     * in the padding info struct to NULL.
+     */
+    BCRYPT_PKCS1_PADDING_INFO padinfo = {NULL};
+    DWORD status;
+
+    status = NCryptSignHash(hkey, padding? &padinfo : NULL, (BYTE*) from, flen,
+                            to, tlen, &len, padding? BCRYPT_PAD_PKCS1 : 0);
+    if (status != ERROR_SUCCESS)
+    {
+        SetLastError(status);
+        CRYPTOAPIerr(CRYPTOAPI_F_NCRYPT_SIGN_HASH);
+        len = 0;
+    }
+
+    /* Unlike CAPI, CNG signature is in big endian order. No reversing needed. */
+    return len;
+}
+
 /* sign arbitrary data */
 static int
 rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
@@ -230,6 +276,11 @@  rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, i
         RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
         return 0;
     }
+    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+    {
+        return priv_enc_CNG(cd, from, flen, to, RSA_size(rsa), padding);
+    }
+
     /* 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
@@ -322,7 +373,14 @@  finish(RSA *rsa)
     }
     if (cd->crypt_prov && cd->free_crypt_prov)
     {
-        CryptReleaseContext(cd->crypt_prov, 0);
+        if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+        {
+            NCryptFreeObject(cd->crypt_prov);
+        }
+        else
+        {
+            CryptReleaseContext(cd->crypt_prov, 0);
+        }
     }
     if (cd->cert_context)
     {
@@ -458,7 +516,9 @@  SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
     }
 
     /* set up stuff to use the private key */
-    if (!CryptAcquireCertificatePrivateKey(cd->cert_context, CRYPT_ACQUIRE_COMPARE_KEY_FLAG,
+    /* We prefer to get an NCRYPT key handle so that TLS1.2 can be supported */
+    DWORD flags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG|CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG;
+    if (!CryptAcquireCertificatePrivateKey(cd->cert_context, flags,
                                            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
@@ -470,6 +530,17 @@  SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
     /* 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.  */
 
+    /* If we do not have an NCRYPT key handle disable TLS v1.2 */
+    if (cd->key_spec != CERT_NCRYPT_KEY_SPEC)
+    {
+        if (!(SSL_CTX_get_options(ssl_ctx) & SSL_OP_NO_TLSv1_2))
+        {
+            msg(M_WARN, "WARNING: cryptapicert: only legacy private key handle is available."
+                        "Disabling TLS v1.2");
+            SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_2);
+        }
+    }
+
     my_rsa_method = RSA_meth_new("Microsoft Cryptography API RSA Method",
                                   RSA_METHOD_FLAG_NO_CHECK);
     check_malloc_return(my_rsa_method);
@@ -551,7 +622,14 @@  err:
         {
             if (cd->free_crypt_prov && cd->crypt_prov)
             {
-                CryptReleaseContext(cd->crypt_prov, 0);
+                if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+                {
+                    NCryptFreeObject(cd->crypt_prov);
+                }
+                else
+                {
+                    CryptReleaseContext(cd->crypt_prov, 0);
+                }
             }
             if (cd->cert_context)
             {
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 7be5f38..e4fbade 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -3018,24 +3018,6 @@  options_postprocess_mutate(struct options *o)
     }
 #endif
 
-#ifdef ENABLE_CRYPTOAPI
-    if (o->cryptoapi_cert)
-    {
-        const int tls_version_max =
-            (o->ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT)
-            &SSLF_TLS_VERSION_MAX_MASK;
-
-        if (tls_version_max == TLS_VER_UNSPEC || tls_version_max > TLS_VER_1_1)
-        {
-            msg(M_WARN, "Warning: cryptapicert used, setting maximum TLS "
-                "version to 1.1.");
-            o->ssl_flags &= ~(SSLF_TLS_VERSION_MAX_MASK
-                              <<SSLF_TLS_VERSION_MAX_SHIFT);
-            o->ssl_flags |= (TLS_VER_1_1 << SSLF_TLS_VERSION_MAX_SHIFT);
-        }
-    }
-#endif /* ENABLE_CRYPTOAPI */
-
 #if P2MP
     /*
      * Save certain parms before modifying options via --pull