projects/linux/crypto/ecc_openssl.c (473 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "ecc_openssl.h"
#include "openssl_check.h"
#include "asn1/ecc_der_util.h"
#include "common/unused.h"
#include "crypto/hash.h"
/**
* Initialize a public key instance from a private key.
*
* @param key The private key instance to covert to a public key.
* @param dup Flag indicating if a new key instance should be created for the public key.
*
* @return The public key instance or null if there was an error.
*/
static EVP_PKEY* ecc_openssl_convert_private_to_public (EVP_PKEY *key, bool dup)
{
EVP_PKEY *ec_pub = NULL;
uint8_t *pub_key = NULL;
const uint8_t *parse_key;
int key_length;
/* Create a key context that only contains the public key by encoding just the public key from
* the parsed key pair then decoding that into a new key context. */
key_length = i2d_PUBKEY (key, &pub_key);
if (key_length < 0) {
return NULL;
}
parse_key = pub_key;
ec_pub = d2i_PUBKEY (NULL, &parse_key, key_length);
if (ec_pub && !dup) {
EVP_PKEY_free (key);
}
OPENSSL_free (pub_key);
return ec_pub;
}
int ecc_openssl_init_key_pair (const struct ecc_engine *engine, const uint8_t *key,
size_t key_length, struct ecc_private_key *priv_key, struct ecc_public_key *pub_key)
{
EVP_PKEY *ec_priv = NULL;
int status;
#if OPENSSL_IS_VERSION_3
const int ERROR_NOT_EC = 0x1e08010c;
#else
const int ERROR_NOT_EC = 0xd0680a8;
#endif
if ((engine == NULL) || (key == NULL) || (key_length == 0)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
if (!priv_key && !pub_key) {
return 0;
}
if (priv_key) {
memset (priv_key, 0, sizeof (struct ecc_private_key));
}
if (pub_key) {
memset (pub_key, 0, sizeof (struct ecc_public_key));
}
ERR_clear_error ();
ec_priv = d2i_PrivateKey (EVP_PKEY_EC, NULL, &key, key_length);
if (ec_priv == NULL) {
status = ERR_get_error ();
if (status == ERROR_NOT_EC) {
status = ECC_ENGINE_NOT_EC_KEY;
}
else {
status = -status;
}
goto err_load;
}
if (pub_key) {
pub_key->context = ecc_openssl_convert_private_to_public (ec_priv, (priv_key));
if (pub_key->context == NULL) {
status = -ERR_get_error ();
goto err_pubkey;
}
}
if (priv_key) {
priv_key->context = ec_priv;
}
return 0;
err_pubkey:
EVP_PKEY_free (ec_priv);
err_load:
return status;
}
int ecc_openssl_init_public_key (const struct ecc_engine *engine, const uint8_t *key,
size_t key_length, struct ecc_public_key *pub_key)
{
EVP_PKEY *ec_pub = NULL;
int status;
if ((engine == NULL) || (key == NULL) || (key_length == 0) || (pub_key == NULL)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
memset (pub_key, 0, sizeof (struct ecc_public_key));
ERR_clear_error ();
ec_pub = d2i_PUBKEY (NULL, &key, key_length);
if (ec_pub == NULL) {
return -ERR_get_error ();
}
if (EVP_PKEY_base_id (ec_pub) != EVP_PKEY_EC) {
EVP_PKEY_free (ec_pub);
return ECC_ENGINE_NOT_EC_KEY;
}
pub_key->context = ec_pub;
status = 0;
return status;
}
#ifdef ECC_ENABLE_GENERATE_KEY_PAIR
int ecc_openssl_generate_derived_key_pair (const struct ecc_engine *engine, const uint8_t *priv,
size_t key_length, struct ecc_private_key *priv_key, struct ecc_public_key *pub_key)
{
uint8_t der[ECC_DER_MAX_PRIVATE_NO_PUB_LENGTH];
int der_length;
if ((engine == NULL) || (priv == NULL) || (key_length == 0)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
/* It seems possible to use EVP_PKEY_fromdata (introduced in version 3.0) to generate a specific
* private key context, but that path requires the caller to also have the public key. Encoding
* the private key as DER and parsing it causes OpenSSL to calculate the public key on it's own,
* which makes this code much simpler. This approach also works equally well for any version of
* OpenSSL. */
der_length = ecc_der_encode_private_key (priv, NULL, NULL, key_length, der, sizeof (der));
if (ROT_IS_ERROR (der_length)) {
return ECC_ENGINE_UNSUPPORTED_KEY_LENGTH;
}
return ecc_openssl_init_key_pair (engine, der, der_length, priv_key, pub_key);
}
int ecc_openssl_generate_key_pair (const struct ecc_engine *engine, size_t key_length,
struct ecc_private_key *priv_key, struct ecc_public_key *pub_key)
{
EVP_PKEY *ec_priv = NULL;
EVP_PKEY_CTX *ctx = NULL;
int status;
if (engine == NULL) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
if (!priv_key && !pub_key) {
return 0;
}
if (priv_key) {
memset (priv_key, 0, sizeof (struct ecc_private_key));
}
if (pub_key) {
memset (pub_key, 0, sizeof (struct ecc_public_key));
}
ERR_clear_error ();
ctx = EVP_PKEY_CTX_new_id (EVP_PKEY_EC, NULL);
if (ctx == NULL) {
return -ERR_get_error ();
}
status = EVP_PKEY_keygen_init (ctx);
if (status != 1) {
status = -ERR_get_error ();
goto exit;
}
switch (key_length) {
case ECC_KEY_LENGTH_256:
status = EVP_PKEY_CTX_set_ec_paramgen_curve_nid (ctx, NID_X9_62_prime256v1);
break;
#if ECC_MAX_KEY_LENGTH >= ECC_KEY_LENGTH_384
case ECC_KEY_LENGTH_384:
status = EVP_PKEY_CTX_set_ec_paramgen_curve_nid (ctx, NID_secp384r1);
break;
#if ECC_MAX_KEY_LENGTH >= ECC_KEY_LENGTH_521
case ECC_KEY_LENGTH_521:
status = EVP_PKEY_CTX_set_ec_paramgen_curve_nid (ctx, NID_secp521r1);
break;
#endif
#endif
default:
status = ECC_ENGINE_UNSUPPORTED_KEY_LENGTH;
goto exit;
}
if (status != 1) {
status = -ERR_get_error ();
goto exit;
}
status = EVP_PKEY_keygen (ctx, &ec_priv);
if (status != 1) {
status = -ERR_get_error ();
goto exit;
}
if (pub_key) {
pub_key->context = ecc_openssl_convert_private_to_public (ec_priv, (priv_key));
if (pub_key->context == NULL) {
status = -ERR_get_error ();
EVP_PKEY_free (ec_priv);
return status;
}
}
if (priv_key) {
priv_key->context = ec_priv;
}
status = 0;
exit:
EVP_PKEY_CTX_free (ctx);
return status;
}
#endif
void ecc_openssl_release_key_pair (const struct ecc_engine *engine,
struct ecc_private_key *priv_key, struct ecc_public_key *pub_key)
{
UNUSED (engine);
if (priv_key) {
EVP_PKEY_free ((EVP_PKEY*) priv_key->context);
memset (priv_key, 0, sizeof (struct ecc_private_key));
}
if (pub_key) {
EVP_PKEY_free ((EVP_PKEY*) pub_key->context);
memset (pub_key, 0, sizeof (struct ecc_public_key));
}
}
int ecc_openssl_get_signature_max_length (const struct ecc_engine *engine,
const struct ecc_private_key *key)
{
if ((engine == NULL) || (key == NULL)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
return EVP_PKEY_size ((EVP_PKEY*) key->context);
}
/**
* Check an EC key context to see if contains a private key.
*
* @param key The key to check.
*
* @return 1 if it contains a private key, 0 if not, or an error code.
*/
static int ecc_openssl_has_private_key (EVP_PKEY *key)
{
#if OPENSSL_IS_VERSION_3
EVP_PKEY_CTX *ctx = NULL;
int status;
ctx = EVP_PKEY_CTX_new (key, NULL);
if (ctx == NULL) {
return -ERR_get_error ();
}
status = EVP_PKEY_private_check (ctx);
EVP_PKEY_CTX_free (ctx);
if (status != 1) {
return 0;
}
return 1;
#else
EC_KEY *ec = EVP_PKEY_get0_EC_KEY (key);
return (EC_KEY_get0_private_key (ec) != NULL);
#endif
}
#ifdef ECC_ENABLE_GENERATE_KEY_PAIR
int ecc_openssl_get_private_key_der (const struct ecc_engine *engine,
const struct ecc_private_key *key, uint8_t **der, size_t *length)
{
int status;
if (der == NULL) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
*der = NULL;
if ((engine == NULL) || (key == NULL) || (length == NULL)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
ERR_clear_error ();
status = ecc_openssl_has_private_key ((EVP_PKEY*) key->context);
if (status != 1) {
if (status == 0) {
return ECC_ENGINE_NOT_PRIVATE_KEY;
}
else {
return status;
}
}
#if OPENSSL_IS_VERSION_3
status = EVP_PKEY_set_int_param ((EVP_PKEY*) key->context, "include-public", 1);
if (status != 1) {
return -ERR_get_error ();
}
#else
{
EC_KEY *ec = EVP_PKEY_get0_EC_KEY ((EVP_PKEY*) key->context);
status = EC_KEY_get_enc_flags (ec);
EC_KEY_set_enc_flags (ec, (status & ~EC_PKEY_NO_PUBKEY));
}
#endif
status = i2d_PrivateKey ((EVP_PKEY*) key->context, der);
if (status >= 0) {
*length = status;
status = 0;
}
else {
status = -ERR_get_error ();
}
return 0;
}
int ecc_openssl_get_public_key_der (const struct ecc_engine *engine,
const struct ecc_public_key *key, uint8_t **der, size_t *length)
{
int status;
if (der == NULL) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
*der = NULL;
if ((engine == NULL) || (key == NULL) || (length == NULL)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
ERR_clear_error ();
status = ecc_openssl_has_private_key ((EVP_PKEY*) key->context);
if (status != 0) {
if (status == 1) {
return ECC_ENGINE_NOT_PUBLIC_KEY;
}
else {
return status;
}
}
status = i2d_PUBKEY ((EVP_PKEY*) key->context, der);
if (status >= 0) {
*length = status;
status = 0;
}
else {
status = -ERR_get_error ();
}
return 0;
}
#endif
int ecc_openssl_sign (const struct ecc_engine *engine, const struct ecc_private_key *key,
const uint8_t *digest, size_t length, const struct rng_engine *rng, uint8_t *signature,
size_t sig_length)
{
EVP_PKEY_CTX *ctx = NULL;
const EVP_MD *sig_algo;
size_t out_len = sig_length;
int status;
if ((engine == NULL) || (key == NULL) || (digest == NULL) || (signature == NULL) ||
(length == 0)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
if ((int) sig_length < ecc_openssl_get_signature_max_length (engine, key)) {
return ECC_ENGINE_SIG_BUFFER_TOO_SMALL;
}
switch (length) {
case SHA256_HASH_LENGTH:
sig_algo = EVP_sha256 ();
break;
case SHA384_HASH_LENGTH:
sig_algo = EVP_sha384 ();
break;
case SHA512_HASH_LENGTH:
sig_algo = EVP_sha512 ();
break;
default:
return ECC_ENGINE_UNSUPPORTED_HASH_TYPE;
}
ERR_clear_error ();
ctx = EVP_PKEY_CTX_new ((EVP_PKEY*) key->context, NULL);
if (ctx == NULL) {
return -ERR_get_error ();
}
status = EVP_PKEY_sign_init (ctx);
if (status != 1) {
status = -ERR_get_error ();
goto exit;
}
status = EVP_PKEY_CTX_set_signature_md (ctx, sig_algo);
if (status != 1) {
status = -ERR_get_error ();
goto exit;
}
if (rng == NULL) {
status = EVP_PKEY_sign (ctx, signature, &out_len, digest, length);
if (status != 1) {
status = -ERR_get_error ();
}
}
else {
/* TODO: It's not clear how to leverage the EVP API to control the random number generation
* for use in the signing operation. This implementation isn't used in any scenario where
* it's necessary to use an external RNG. Defer this work until it becomes more
* relevant. */
status = ECC_ENGINE_UNSUPPORTED_OPERATION;
}
exit:
EVP_PKEY_CTX_free (ctx);
return (status == 1) ? (int) out_len : status;
}
int ecc_openssl_verify (const struct ecc_engine *engine, const struct ecc_public_key *key,
const uint8_t *digest, size_t length, const uint8_t *signature, size_t sig_length)
{
EVP_PKEY_CTX *ctx = NULL;
const EVP_MD *sig_algo;
int status;
if ((engine == NULL) || (key == NULL) || (digest == NULL) || (signature == NULL) ||
(length == 0) || (sig_length == 0)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
switch (length) {
case SHA256_HASH_LENGTH:
sig_algo = EVP_sha256 ();
break;
case SHA384_HASH_LENGTH:
sig_algo = EVP_sha384 ();
break;
case SHA512_HASH_LENGTH:
sig_algo = EVP_sha512 ();
break;
default:
return ECC_ENGINE_UNSUPPORTED_HASH_TYPE;
}
ERR_clear_error ();
ctx = EVP_PKEY_CTX_new ((EVP_PKEY*) key->context, NULL);
if (ctx == NULL) {
return -ERR_get_error ();
}
status = EVP_PKEY_verify_init (ctx);
if (status != 1) {
return -ERR_get_error ();
}
status = EVP_PKEY_CTX_set_signature_md (ctx, sig_algo);
if (status != 1) {
return -ERR_get_error ();
}
status = EVP_PKEY_verify (ctx, signature,
ecc_der_get_ecdsa_signature_length (signature, sig_length), digest, length);
EVP_PKEY_CTX_free (ctx);
return (status == 1) ? 0 : ECC_ENGINE_BAD_SIGNATURE;
}
#ifdef ECC_ENABLE_ECDH
int ecc_openssl_get_shared_secret_max_length (const struct ecc_engine *engine,
const struct ecc_private_key *key)
{
if ((engine == NULL) || (key == NULL)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
return (EVP_PKEY_bits ((EVP_PKEY*) key->context) + 7) / 8;
}
int ecc_openssl_compute_shared_secret (const struct ecc_engine *engine,
const struct ecc_private_key *priv_key, const struct ecc_public_key *pub_key, uint8_t *secret,
size_t length)
{
EVP_PKEY_CTX *ctx = NULL;
int status;
if ((engine == NULL) || (priv_key == NULL) || (pub_key == NULL) || (secret == NULL)) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
if ((int) length < ecc_openssl_get_shared_secret_max_length (engine, priv_key)) {
return ECC_ENGINE_SECRET_BUFFER_TOO_SMALL;
}
ERR_clear_error ();
ctx = EVP_PKEY_CTX_new ((EVP_PKEY*) priv_key->context, NULL);
if (ctx == NULL) {
return -ERR_get_error ();
}
status = EVP_PKEY_derive_init (ctx);
if (status != 1) {
return -ERR_get_error ();
}
status = EVP_PKEY_derive_set_peer (ctx, (EVP_PKEY*) pub_key->context);
if (status != 1) {
return -ERR_get_error ();
}
status = EVP_PKEY_derive (ctx, secret, &length);
EVP_PKEY_CTX_free (ctx);
return (status == 1) ? length : -ERR_get_error ();
}
#endif
/**
* Initialize an instance for running ECC operations using OpenSSL.
*
* @param engine The ECC engine to initialize.
*
* @return 0 if the engine was successfully initialized or an error code.
*/
int ecc_openssl_init (struct ecc_engine_openssl *engine)
{
if (engine == NULL) {
return ECC_ENGINE_INVALID_ARGUMENT;
}
memset (engine, 0, sizeof (struct ecc_engine_openssl));
engine->base.init_key_pair = ecc_openssl_init_key_pair;
engine->base.init_public_key = ecc_openssl_init_public_key;
#ifdef ECC_ENABLE_GENERATE_KEY_PAIR
engine->base.generate_derived_key_pair = ecc_openssl_generate_derived_key_pair;
engine->base.generate_key_pair = ecc_openssl_generate_key_pair;
#endif
engine->base.release_key_pair = ecc_openssl_release_key_pair;
engine->base.get_signature_max_length = ecc_openssl_get_signature_max_length;
#ifdef ECC_ENABLE_GENERATE_KEY_PAIR
engine->base.get_private_key_der = ecc_openssl_get_private_key_der;
engine->base.get_public_key_der = ecc_openssl_get_public_key_der;
#endif
engine->base.sign = ecc_openssl_sign;
engine->base.verify = ecc_openssl_verify;
#ifdef ECC_ENABLE_ECDH
engine->base.get_shared_secret_max_length = ecc_openssl_get_shared_secret_max_length;
engine->base.compute_shared_secret = ecc_openssl_compute_shared_secret;
#endif
return 0;
}
/**
* Release an OpenSSL ECC engine.
*
* @param engine The ECC engine to release.
*/
void ecc_openssl_release (const struct ecc_engine_openssl *engine)
{
UNUSED (engine);
}