@@ -101,6 +101,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;
@@ -395,6 +398,190 @@ 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 contcatenated.
+ * 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;
+ }
+ ECDSA_SIG_set0(ecsig, r, s); /* ecsig takes ownership of r and s */
+ 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, "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;
+
+ s = ecdsa_sign_sig(dgst, dgstlen, NULL, NULL, ec);
+ if (s == NULL)
+ {
+ *siglen = 0;
+ return 0;
+ }
+
+ /* convert internal signature structure 's' to DER encoded byte array in sig */
+ *siglen = i2d_ECDSA_SIG((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)
{
@@ -639,9 +826,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;
}
cd->ref_count--; /* so that cd will get freed with the private key */