Hi,

On 25-02-18 06:02, selva.n...@gmail.com wrote:
> From: Selva Nair <selva.n...@gmail.com>
> 
> Requires openssl 1.1.0 or higher
> 
> Signed-off-by: Selva Nair <selva.n...@gmail.com>
> ---
> v3 changes:
>  - check return value of ECDSA_SIG_set0
>  - ensure buffer size needed by i2d_ECDSA_SIG does not exceed the expected
>    capacity of the sig buffer
>  - Fix a typo and add contextual info to a debug message
>  - Remove an superflous cast leftover from an older version
> v4 changes:
>  - Add ECDSA_SIG_free() cleanup calls missed in v3 (2 places).
> 
>  src/openvpn/cryptoapi.c | 210 
> +++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 209 insertions(+), 1 deletion(-)
> 
> diff --git a/src/openvpn/cryptoapi.c b/src/openvpn/cryptoapi.c
> index 1097286..7e211f0 100644
> --- a/src/openvpn/cryptoapi.c
> +++ b/src/openvpn/cryptoapi.c
> @@ -1,5 +1,6 @@
>  /*
>   * Copyright (c) 2004 Peter 'Luna' Runestig <pe...@runestig.com>
> + * Copyright (c) 2018 Selva Nair <selva.n...@gmail.com>
>   * All rights reserved.
>   *
>   * Redistribution and use in source and binary forms, with or without modifi-
> @@ -101,6 +102,9 @@ static ERR_STRING_DATA CRYPTOAPI_str_functs[] = {
>      { 0, NULL }
>  };
>  
> +/* index for storing external data in EC_KEY: < 0 means uninitialized */
> +static int ec_data_idx = -1;
> +
>  typedef struct _CAPI_DATA {
>      const CERT_CONTEXT *cert_context;
>      HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov;
> @@ -394,6 +398,201 @@ finish(RSA *rsa)
>      return 1;
>  }
>  
> +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(OPENSSL_NO_EC)
> +
> +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;
> +}
> +
> +/**
> + * Helper to convert ECDSA signature returned by NCryptSignHash
> + * to an ECDSA_SIG structure.
> + * On entry 'buf[]' of length len contains r and s concatenated.
> + * Returns a newly allocated ECDSA_SIG or NULL (on error).
> + */
> +static ECDSA_SIG *
> +ecdsa_bin2sig(unsigned char *buf, int len)
> +{
> +    ECDSA_SIG *ecsig = NULL;
> +    DWORD rlen = len/2;
> +    BIGNUM *r = BN_bin2bn(buf, rlen, NULL);
> +    BIGNUM *s = BN_bin2bn(buf+rlen, rlen, NULL);
> +    if (!r || !s)
> +    {
> +        goto err;
> +    }
> +    ecsig = ECDSA_SIG_new(); /* in openssl 1.1 this does not allocate r, s */
> +    if (!ecsig)
> +    {
> +        goto err;
> +    }
> +    if (!ECDSA_SIG_set0(ecsig, r, s)) /* ecsig takes ownership of r and s */
> +    {
> +        ECDSA_SIG_free(ecsig);
> +        goto err;
> +    }
> +    return ecsig;
> +err:
> +    BN_free(r); /* it is ok to free NULL BN */
> +    BN_free(s);
> +    return NULL;
> +}
> +
> +/** 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);
> +        CRYPTOAPIerr(CRYPTOAPI_F_NCRYPT_SIGN_HASH);
> +    }
> +    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: 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;
> +
> +    if (cd->key_spec != CERT_NCRYPT_KEY_SPEC)
> +    {
> +        msg(M_NONFATAL, "ERROR: cryptoapicert with only legacy private key 
> handle available."
> +                    " EC certificate not supported.");
> +        goto err;
> +    }
> +    /* 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 /* OPENSSL_VERSION_NUMBER >= 1.1.0 */
> +
>  static const CERT_CONTEXT *
>  find_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store)
>  {
> @@ -642,9 +841,18 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, 
> const char *cert_prop)
>              goto err;
>          }
>      }
> +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(OPENSSL_NO_EC)
> +    else if (EVP_PKEY_id(pkey) == EVP_PKEY_EC)
> +    {
> +        if (!ssl_ctx_set_eckey(ssl_ctx, cd, pkey))
> +        {
> +            goto err;
> +        }
> +    }
> +#endif /* OPENSSL_VERSION_NUMBER >= 1.1.0 */
>      else
>      {
> -        msg(M_WARN, "cryptoapicert requires an RSA certificate");
> +        msg(M_WARN, "WARNING: cryptoapicert: certificate type not 
> supported");
>          goto err;
>      }
>      CAPI_DATA_free(cd); /* this will do a ref_count-- */
> 

Thanks, changes wrt v2 look good, so:

Acked-by: Steffan Karger <stef...@karger.me>

-Steffan

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to