diff options
Diffstat (limited to 'src/lib/crypt')
| -rw-r--r-- | src/lib/crypt/openssl.c | 1190 | ||||
| -rw-r--r-- | src/lib/crypt/openssl.h | 99 |
2 files changed, 1125 insertions, 164 deletions
diff --git a/src/lib/crypt/openssl.c b/src/lib/crypt/openssl.c index 291a3418..71a69c1c 100644 --- a/src/lib/crypt/openssl.c +++ b/src/lib/crypt/openssl.c @@ -23,6 +23,10 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ +#define _POSIX_C_SOURCE 200809L + +#include <config.h> + #include <ouroboros/errno.h> #include <ouroboros/crypt.h> #include <ouroboros/hash.h> @@ -32,31 +36,239 @@ #include <openssl/evp.h> #include <openssl/bio.h> #include <openssl/ec.h> +#include <openssl/err.h> +#include <openssl/kdf.h> #include <openssl/pem.h> #include <openssl/sha.h> +#include <openssl/provider.h> #include <openssl/x509v3.h> #include <openssl/x509_vfy.h> #include <assert.h> +#include <stdio.h> + +#define IS_EC_GROUP(str) (strcmp(str, "EC") == 0) +#define IS_DH_GROUP(str) (strcmp(str, "DH") == 0) + +#define HKDF_INFO_DHE "o7s-ossl-dhe" +#define HKDF_INFO_ENCAP "o7s-ossl-encap" +#define HKDF_SALT_LEN 32 /* SHA-256 output size */ + +struct ossl_crypt_ctx { + EVP_CIPHER_CTX * evp_ctx; + const EVP_CIPHER * cipher; + uint8_t * key; + int ivsz; + int tagsz; +}; + +struct kdf_info { + buffer_t secret; + int nid; + buffer_t salt; + buffer_t info; + buffer_t key; +}; + +/* Convert hash NID to OpenSSL digest name string for HKDF */ +static const char * hash_nid_to_digest_name(int nid) +{ + const EVP_MD * md; + const char * name; + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return "SHA256"; /* fallback to SHA-256 */ + + name = EVP_MD_get0_name(md); + if (name == NULL) + return "SHA256"; /* fallback to SHA-256 */ + + return name; +} + +/* Extract public key bytes from a key pair for salt derivation */ +static int get_pk_bytes_from_key(EVP_PKEY * key, + buffer_t * pk) +{ + const char * name; + int ret; + + assert(key != NULL); + assert(pk != NULL); + + name = EVP_PKEY_get0_type_name(key); + if (name == NULL) + goto fail_name; + + if (IS_HYBRID_KEM(name)) { + pk->len = EVP_PKEY_get1_encoded_public_key(key, &pk->data); + if (pk->len == 0) + goto fail_name; + } else { + /* Pure ML-KEM: use DER encoding to match encap */ + pk->data = NULL; + ret = i2d_PUBKEY(key, &pk->data); + if (ret <= 0) + goto fail_name; + pk->len = (size_t) ret; + } + + return 0; + fail_name: + return -ECRYPT; +} + +/* Derive salt from public key bytes by hashing them */ +static int derive_salt_from_pk_bytes(buffer_t pk, + uint8_t * salt, + size_t salt_len) +{ + uint8_t hash[EVP_MAX_MD_SIZE]; + unsigned hash_len; + + assert(pk.data != NULL); + assert(salt != NULL); + + if (EVP_Digest(pk.data, pk.len, hash, &hash_len, + EVP_sha256(), NULL) != 1) + goto fail_digest; + + memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len); + + return 0; + fail_digest: + return -ECRYPT; +} + +/* Derive salt from two public key byte buffers (DHE) in canonical order */ +static int derive_salt_from_pk_bytes_dhe(buffer_t local, + buffer_t remote, + uint8_t * salt, + size_t salt_len) +{ + uint8_t * concat; + size_t concat_len; + uint8_t hash[EVP_MAX_MD_SIZE]; + unsigned hash_len; + size_t min_len; + int cmp; + + assert(local.data != NULL); + assert(remote.data != NULL); + assert(salt != NULL); + + concat_len = local.len + remote.len; + concat = OPENSSL_malloc(concat_len); + if (concat == NULL) + goto fail_malloc; + + /* Canonical order: compare and concatenate smaller first */ + min_len = local.len < remote.len ? local.len : remote.len; + cmp = memcmp(local.data, remote.data, min_len); + if (cmp < 0 || (cmp == 0 && local.len < remote.len)) { + memcpy(concat, local.data, local.len); + memcpy(concat + local.len, remote.data, remote.len); + } else { + memcpy(concat, remote.data, remote.len); + memcpy(concat + remote.len, local.data, local.len); + } + + if (EVP_Digest(concat, concat_len, hash, &hash_len, + EVP_sha256(), NULL) != 1) + goto fail_digest; + + OPENSSL_free(concat); + + memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len); + + return 0; + fail_digest: + OPENSSL_free(concat); + fail_malloc: + return -ECRYPT; +} + +/* Derive key using HKDF */ +#define OPc_u_str OSSL_PARAM_construct_utf8_string +#define OPc_o_str OSSL_PARAM_construct_octet_string +static int derive_key_hkdf(struct kdf_info * ki) +{ + EVP_KDF * kdf; + EVP_KDF_CTX * kctx; + OSSL_PARAM params[5]; + const char * digest; + int idx; + + digest = hash_nid_to_digest_name(ki->nid); + + kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + if (kdf == NULL) + goto fail_fetch; + + kctx = EVP_KDF_CTX_new(kdf); + if (kctx == NULL) + goto fail_ctx; + + idx = 0; + params[idx++] = OPc_u_str("digest", (char *) digest, 0); + params[idx++] = OPc_o_str("key", ki->secret.data, ki->secret.len); + params[idx++] = OPc_o_str("salt", ki->salt.data, ki->salt.len); + params[idx++] = OPc_o_str("info", ki->info.data, ki->info.len); + + params[idx] = OSSL_PARAM_construct_end(); + + if (EVP_KDF_derive(kctx, ki->key.data, ki->key.len, params) != 1) + goto fail_derive; + + EVP_KDF_CTX_free(kctx); + EVP_KDF_free(kdf); + + return 0; + + fail_derive: + EVP_KDF_CTX_free(kctx); + fail_ctx: + EVP_KDF_free(kdf); + fail_fetch: + return -ECRYPT; +} /* * Derive the common secret from - * - your public key pair (kp) - * - the remote public key (pub). + * - your public key pair (pkp) + * - the remote public key bytes (remote_pk). * Store it in a preallocated buffer (s). */ -static int __openssl_ecdh_derive_secret(EVP_PKEY * kp, - EVP_PKEY * pub, - uint8_t * s) +static int __openssl_dhe_derive(EVP_PKEY * pkp, + EVP_PKEY * pub, + buffer_t remote_pk, + int kdf, + uint8_t * s) { - EVP_PKEY_CTX * ctx; - int ret; - uint8_t * secret; - size_t secret_len; - - ctx = EVP_PKEY_CTX_new(kp, NULL); + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + buffer_t local_pk; + int ret; + uint8_t * secret; + size_t secret_len; + uint8_t salt_buf[HKDF_SALT_LEN]; + + /* Extract local public key bytes */ + local_pk.data = NULL; + ret = i2d_PUBKEY(pkp, &local_pk.data); + if (ret <= 0) + goto fail_local; + local_pk.len = (size_t) ret; + + /* Derive salt from both public keys */ + if (derive_salt_from_pk_bytes_dhe(local_pk, remote_pk, salt_buf, + HKDF_SALT_LEN) < 0) + goto fail_salt; + + ctx = EVP_PKEY_CTX_new(pkp, NULL); if (ctx == NULL) - goto fail_new; + goto fail_salt; ret = EVP_PKEY_derive_init(ctx); if (ret != 1) @@ -81,37 +293,78 @@ static int __openssl_ecdh_derive_secret(EVP_PKEY * kp, if (ret != 1) goto fail_derive; - /* Hash the secret for use as AES key. */ - mem_hash(HASH_SHA3_256, s, secret, secret_len); + ki.nid = kdf; + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.info.len = strlen(HKDF_INFO_DHE); + ki.info.data = (uint8_t *) HKDF_INFO_DHE; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt_buf; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); OPENSSL_free(secret); EVP_PKEY_CTX_free(ctx); + OPENSSL_free(local_pk.data); + + if (ret != 0) + return ret; return 0; fail_derive: OPENSSL_free(secret); fail_ctx: EVP_PKEY_CTX_free(ctx); - fail_new: + fail_salt: + OPENSSL_free(local_pk.data); + fail_local: return -ECRYPT; } -static int __openssl_ecdh_gen_key(void ** kp) +static int __openssl_dhe_gen_key(const char * algo, + EVP_PKEY ** kp) { EVP_PKEY_CTX * ctx = NULL; EVP_PKEY_CTX * kctx = NULL; EVP_PKEY * params = NULL; + int nid; + int type; int ret; - ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + assert(algo != NULL); + assert(kp != NULL); + + nid = OBJ_txt2nid(algo); + if (nid == NID_undef) + return -ECRYPT; + + /* X25519 and X448: direct keygen context */ + if (nid == EVP_PKEY_X25519 || nid == EVP_PKEY_X448) { + kctx = EVP_PKEY_CTX_new_id(nid, NULL); + if (kctx == NULL) + goto fail_kctx; + + goto keygen; + } + /* EC and FFDHE: parameter generation first */ + type = (strncmp(algo, "ffdhe", 5) == 0) ? EVP_PKEY_DH : EVP_PKEY_EC; + + ctx = EVP_PKEY_CTX_new_id(type, NULL); if (ctx == NULL) - goto fail_new_id; + goto fail_ctx; ret = EVP_PKEY_paramgen_init(ctx); if (ret != 1) goto fail_paramgen; - ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1); + if (type == EVP_PKEY_EC) + ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid); + else /* EVP_PKEY_DH */ + ret = EVP_PKEY_CTX_set_dh_nid(ctx, nid); + if (ret != 1) goto fail_paramgen; @@ -121,187 +374,556 @@ static int __openssl_ecdh_gen_key(void ** kp) kctx = EVP_PKEY_CTX_new(params, NULL); if (kctx == NULL) - goto fail_keygen_init; + goto fail_kctx; + EVP_PKEY_free(params); + EVP_PKEY_CTX_free(ctx); + keygen: ret = EVP_PKEY_keygen_init(kctx); if (ret != 1) goto fail_keygen; - ret = EVP_PKEY_keygen(kctx, (EVP_PKEY **) kp); + ret = EVP_PKEY_keygen(kctx, kp); if (ret != 1) goto fail_keygen; - EVP_PKEY_free(params); EVP_PKEY_CTX_free(kctx); - EVP_PKEY_CTX_free(ctx); return 0; + fail_keygen: EVP_PKEY_CTX_free(kctx); - fail_keygen_init: - EVP_PKEY_free(params); + return -ECRYPT; + fail_kctx: + if (params != NULL) + EVP_PKEY_free(params); fail_paramgen: - EVP_PKEY_CTX_free(ctx); - fail_new_id: + if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + fail_ctx: + return -ECRYPT; +} + +static int __openssl_kem_gen_key(const char * algo, + EVP_PKEY ** kp) +{ + EVP_PKEY_CTX * kctx; + int ret; + + assert(algo != NULL); + assert(kp != NULL); + + /* PQC KEM (ML-KEM-512, ML-KEM-768, ML-KEM-1024) or hybrid */ + kctx = EVP_PKEY_CTX_new_from_name(NULL, algo, NULL); + if (kctx == NULL) + goto fail_kctx; + + ret = EVP_PKEY_keygen_init(kctx); + if (ret != 1) + goto fail_keygen; + + ret = EVP_PKEY_keygen(kctx, kp); + if (ret != 1) + goto fail_keygen; + + EVP_PKEY_CTX_free(kctx); + + return 0; + + fail_keygen: + EVP_PKEY_CTX_free(kctx); + fail_kctx: return -ECRYPT; } -ssize_t openssl_ecdh_pkp_create(void ** pkp, - uint8_t * pk) +/* Determine hybrid KEM algorithm from raw key/ciphertext length */ +static const char * __openssl_hybrid_algo_from_len(size_t len) +{ + switch(len) { + case X25519MLKEM768_PKSZ: + return "X25519MLKEM768"; + case X25519MLKEM768_CTSZ: + return "X25519MLKEM768"; + case X448MLKEM1024_PKSZ: + return "X448MLKEM1024"; + default: + break; + } + + return NULL; +} + +static int __openssl_kex_gen_key(const char * algo, + EVP_PKEY ** kp) { - uint8_t * pos; - ssize_t len; + assert(algo != NULL); + assert(kp != NULL); + + /* Dispatch based on algorithm name prefix */ + if (IS_KEM_ALGORITHM(algo)) + return __openssl_kem_gen_key(algo, kp); + + return __openssl_dhe_gen_key(algo, kp); +} +ssize_t openssl_pkp_create(const char * algo, + EVP_PKEY ** pkp, + uint8_t * pk) +{ + uint8_t * pos; + buffer_t raw; + ssize_t len; + + assert(algo != NULL); assert(pkp != NULL); assert(*pkp == NULL); assert(pk != NULL); - if (__openssl_ecdh_gen_key(pkp) < 0) + if (__openssl_kex_gen_key(algo, pkp) < 0) goto fail_key; - pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */ - len = i2d_PUBKEY(*pkp, &pos); - if (len < 0) - goto fail_pubkey; + if (IS_HYBRID_KEM(algo)) { /* Raw encode hybrid KEM */ + raw.len = EVP_PKEY_get1_encoded_public_key(*pkp, &raw.data); + if (raw.len == 0) + goto fail_pubkey; + + memcpy(pk, raw.data, raw.len); + OPENSSL_free(raw.data); + + return (ssize_t) raw.len; + } else { /* DER encode standard algorithms */ + pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */ + len = i2d_PUBKEY(*pkp, &pos); + if (len < 0) + goto fail_pubkey; - return len; + return len; + } fail_pubkey: EVP_PKEY_free(*pkp); fail_key: return -ECRYPT; } -void openssl_ecdh_pkp_destroy(void * pkp) +/* Common KEM encapsulation - pub key and salt already prepared */ +static ssize_t __openssl_kem_encap(EVP_PKEY * pub, + uint8_t * salt, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + uint8_t * secret; + size_t secret_len; + size_t ct_len; + int ret; + + ctx = EVP_PKEY_CTX_new(pub, NULL); + if (ctx == NULL) + goto fail_ctx; + + ret = EVP_PKEY_encapsulate_init(ctx, NULL); + if (ret != 1) + goto fail_encap; + + /* Get required lengths */ + ret = EVP_PKEY_encapsulate(ctx, NULL, &ct_len, NULL, &secret_len); + if (ret != 1 || ct_len > MSGBUFSZ) + goto fail_encap; + + /* Allocate buffer for secret */ + secret = OPENSSL_malloc(secret_len); + if (secret == NULL) + goto fail_encap; + + /* Perform encapsulation */ + ret = EVP_PKEY_encapsulate(ctx, ct, &ct_len, secret, &secret_len); + if (ret != 1) + goto fail_secret; + + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.nid = kdf; + ki.info.len = strlen(HKDF_INFO_ENCAP); + ki.info.data = (uint8_t *) HKDF_INFO_ENCAP; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); + + OPENSSL_free(secret); + EVP_PKEY_CTX_free(ctx); + + if (ret != 0) + return -ECRYPT; + + return (ssize_t) ct_len; + + fail_secret: + OPENSSL_free(secret); + fail_encap: + EVP_PKEY_CTX_free(ctx); + fail_ctx: + return -ECRYPT; +} + +/* ML-KEM encapsulation - DER-encoded public key */ +ssize_t openssl_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY * pub; + uint8_t * pos; + uint8_t salt[HKDF_SALT_LEN]; + ssize_t ret; + + assert(pk.data != NULL); + assert(ct != NULL); + assert(s != NULL); + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + pos = pk.data; + pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); + if (pub == NULL) + goto fail_salt; + + ret = __openssl_kem_encap(pub, salt, ct, kdf, s); + + EVP_PKEY_free(pub); + + return ret; + fail_salt: + return -ECRYPT; +} + +/* Hybrid KEM encapsulation: raw-encoded public key */ +ssize_t openssl_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY * pub; + const char * algo; + uint8_t salt[HKDF_SALT_LEN]; + ssize_t ret; + + assert(pk.data != NULL); + assert(ct != NULL); + assert(s != NULL); + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + algo = __openssl_hybrid_algo_from_len(pk.len); + if (algo == NULL) + goto fail_salt; + + pub = EVP_PKEY_new_raw_public_key_ex(NULL, algo, NULL, + pk.data, pk.len); + if (pub == NULL) + goto fail_salt; + + ret = __openssl_kem_encap(pub, salt, ct, kdf, s); + + EVP_PKEY_free(pub); + + return ret; + fail_salt: + return -ECRYPT; +} + +/* KEM decapsulation - used by party that generated the keypair */ +int openssl_kem_decap(EVP_PKEY * priv, + buffer_t ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + buffer_t pk; + uint8_t * secret; + size_t secret_len; + int ret; + uint8_t salt[HKDF_SALT_LEN]; + + /* Extract public key bytes from private key */ + if (get_pk_bytes_from_key(priv, &pk) < 0) + goto fail_pk; + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + ctx = EVP_PKEY_CTX_new(priv, NULL); + if (ctx == NULL) + goto fail_salt; + + ret = EVP_PKEY_decapsulate_init(ctx, NULL); + if (ret != 1) + goto fail_ctx; + + /* Get required secret length */ + ret = EVP_PKEY_decapsulate(ctx, NULL, &secret_len, ct.data, ct.len); + if (ret != 1) + goto fail_ctx; + + /* Allocate buffer for secret */ + secret = OPENSSL_malloc(secret_len); + if (secret == NULL) + goto fail_ctx; + + /* Perform decapsulation */ + ret = EVP_PKEY_decapsulate(ctx, secret, &secret_len, ct.data, ct.len); + if (ret != 1) + goto fail_secret; + + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.nid = kdf; + ki.info.len = strlen(HKDF_INFO_ENCAP); + ki.info.data = (uint8_t *) HKDF_INFO_ENCAP; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); + + OPENSSL_free(secret); + EVP_PKEY_CTX_free(ctx); + OPENSSL_free(pk.data); + + if (ret != 0) + return ret; + + return 0; + + fail_secret: + OPENSSL_free(secret); + fail_ctx: + EVP_PKEY_CTX_free(ctx); + fail_salt: + OPENSSL_free(pk.data); + fail_pk: + return -ECRYPT; +} + +void openssl_pkp_destroy(EVP_PKEY * pkp) +{ + EVP_PKEY_free(pkp); +} + +int __openssl_get_curve(EVP_PKEY * pub, + char * algo) +{ + int ret; + size_t len = KEX_ALGO_BUFSZ; + + ret = EVP_PKEY_get_utf8_string_param(pub, "group", algo, len, &len); + return ret == 1 ? 0 : -ECRYPT; +} + +int openssl_get_algo_from_pk_der(buffer_t pk, + char * algo) +{ + uint8_t * pos; + EVP_PKEY * pub; + char * type_str; + + assert(pk.data != NULL); + assert(algo != NULL); + + pos = pk.data; + pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); + if (pub == NULL) + goto fail_decode; + + type_str = (char *) EVP_PKEY_get0_type_name(pub); + if (type_str == NULL) + goto fail_pub; + + strcpy(algo, type_str); + + if ((IS_EC_GROUP(algo) || IS_DH_GROUP(algo)) && + __openssl_get_curve(pub, algo) < 0) + goto fail_pub; + + EVP_PKEY_free(pub); + return 0; + + fail_pub: + EVP_PKEY_free(pub); + fail_decode: + return -ECRYPT; +} + +int openssl_get_algo_from_pk_raw(buffer_t pk, + char * algo) { - EVP_PKEY_free((EVP_PKEY *) pkp); + const char * hybrid_algo; + + assert(pk.data != NULL); + assert(algo != NULL); + + hybrid_algo = __openssl_hybrid_algo_from_len(pk.len); + if (hybrid_algo == NULL) + return -ECRYPT; + + strcpy(algo, hybrid_algo); + + return 0; } -int openssl_ecdh_derive(void * pkp, - buffer_t pk, - uint8_t * s) +int openssl_dhe_derive(EVP_PKEY * pkp, + buffer_t pk, + int kdf, + uint8_t * s) { uint8_t * pos; EVP_PKEY * pub; + assert(pkp != NULL); + assert(pk.data != NULL); + assert(s != NULL); + + /* X.509 DER decoding for DHE */ pos = pk.data; /* d2i_PUBKEY increments pos, don't use key ptr! */ pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); if (pub == NULL) - goto fail_pubkey; + goto fail_decode; - if (__openssl_ecdh_derive_secret(pkp, pub, s) < 0) - goto fail_key; + if (__openssl_dhe_derive(pkp, pub, pk, kdf, s) < 0) + goto fail_derive; EVP_PKEY_free(pub); return 0; - fail_pubkey: + fail_derive: EVP_PKEY_free(pub); - fail_key: + fail_decode: return -ECRYPT; } -/* - * AES encryption calls. If FRCT is disabled, we should generate a - * 128-bit random IV and append it to the packet. If the flow is - * reliable, we could initialize the context once, and consider the - * stream a single encrypted message to avoid initializing the - * encryption context for each packet. - */ - -int openssl_encrypt(void * ctx, - uint8_t * key, - buffer_t in, - buffer_t * out) +int openssl_encrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out) { - uint8_t * ptr; - uint8_t * iv; - int in_sz; - int out_sz; - int tmp_sz; - int ret; + uint8_t * ptr; + uint8_t * iv; + int in_sz; + int out_sz; + int tmp_sz; + int ret; + + assert(ctx != NULL); in_sz = (int) in.len; - out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + IVSZ); + out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + \ + ctx->ivsz + ctx->tagsz); if (out->data == NULL) goto fail_malloc; iv = out->data; - ptr = out->data + IVSZ; + ptr = out->data + ctx->ivsz; - if (random_buffer(iv, IVSZ) < 0) - goto fail_iv; + if (random_buffer(iv, ctx->ivsz) < 0) + goto fail_encrypt; - EVP_CIPHER_CTX_reset(ctx); + EVP_CIPHER_CTX_reset(ctx->evp_ctx); - ret = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv); + ret = EVP_EncryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, ctx->key, iv); if (ret != 1) - goto fail_iv; + goto fail_encrypt; - ret = EVP_EncryptUpdate(ctx, ptr, &tmp_sz, in.data, in_sz); + ret = EVP_EncryptUpdate(ctx->evp_ctx, ptr, &tmp_sz, in.data, in_sz); if (ret != 1) goto fail_encrypt; out_sz = tmp_sz; - ret = EVP_EncryptFinal_ex(ctx, ptr + tmp_sz, &tmp_sz); + ret = EVP_EncryptFinal_ex(ctx->evp_ctx, ptr + tmp_sz, &tmp_sz); if (ret != 1) goto fail_encrypt; out_sz += tmp_sz; - EVP_CIPHER_CTX_cleanup(ctx); + /* For AEAD ciphers, get and append the authentication tag */ + if (ctx->tagsz > 0) { + ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_GET_TAG, + ctx->tagsz, ptr + out_sz); + if (ret != 1) + goto fail_encrypt; + out_sz += ctx->tagsz; + } assert(out_sz >= in_sz); - out->len = (size_t) out_sz + IVSZ; + out->len = (size_t) out_sz + ctx->ivsz; return 0; fail_encrypt: - EVP_CIPHER_CTX_cleanup(ctx); - fail_iv: free(out->data); fail_malloc: clrbuf(*out); return -ECRYPT; } -int openssl_decrypt(void * ctx, - uint8_t * key, - buffer_t in, - buffer_t * out) +int openssl_decrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out) { - uint8_t * ptr; - uint8_t * iv; - uint8_t * input; - int ret; - int out_sz; - int in_sz; - int tmp_sz; - - in_sz = (int) in.len - IVSZ; - if (in_sz < 0) + uint8_t * ptr; + uint8_t * iv; + uint8_t * input; + int ret; + int out_sz; + int in_sz; + int tmp_sz; + + assert(ctx != NULL); + + in_sz = (int) in.len - ctx->ivsz; + if (in_sz < ctx->tagsz) return -ECRYPT; - out->data = malloc(in_sz); + in_sz -= ctx->tagsz; + + out->data = malloc(in_sz + EVP_MAX_BLOCK_LENGTH); if (out->data == NULL) goto fail_malloc; iv = in.data; ptr = out->data; - input = in.data + IVSZ; + input = in.data + ctx->ivsz; - EVP_CIPHER_CTX_reset(ctx); + EVP_CIPHER_CTX_reset(ctx->evp_ctx); - ret = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv); + ret = EVP_DecryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, ctx->key, iv); if (ret != 1) - goto fail_decrypt_init; + goto fail_decrypt; + + /* For AEAD ciphers, set the expected authentication tag */ + if (ctx->tagsz > 0) { + uint8_t * tag = input + in_sz; + ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_SET_TAG, + ctx->tagsz, tag); + if (ret != 1) + goto fail_decrypt; + } - ret = EVP_DecryptUpdate(ctx, ptr, &tmp_sz, input, in_sz); + ret = EVP_DecryptUpdate(ctx->evp_ctx, ptr, &tmp_sz, input, in_sz); if (ret != 1) goto fail_decrypt; out_sz = tmp_sz; - ret = EVP_DecryptFinal_ex(ctx, ptr + tmp_sz, &tmp_sz); + ret = EVP_DecryptFinal_ex(ctx->evp_ctx, ptr + tmp_sz, &tmp_sz); if (ret != 1) goto fail_decrypt; @@ -313,22 +935,65 @@ int openssl_decrypt(void * ctx, return 0; fail_decrypt: - EVP_CIPHER_CTX_cleanup(ctx); - fail_decrypt_init: free(out->data); fail_malloc: clrbuf(*out); return -ECRYPT; } -void * openssl_crypt_create_ctx(void) +struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk) { - return (void *) EVP_CIPHER_CTX_new(); + struct ossl_crypt_ctx * ctx; + + assert(sk != NULL); + assert(sk->key != NULL); + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) + goto fail_malloc; + + memset(ctx, 0, sizeof(*ctx)); + + ctx->key = OPENSSL_secure_malloc(SYMMKEYSZ); + if (ctx->key == NULL) + goto fail_key; + + memcpy(ctx->key, sk->key, SYMMKEYSZ); + + ctx->cipher = EVP_get_cipherbynid(sk->nid); + if (ctx->cipher == NULL) + goto fail_cipher; + + ctx->ivsz = EVP_CIPHER_iv_length(ctx->cipher); + + /* Set tag size for AEAD ciphers (GCM, CCM, OCB, ChaCha20-Poly1305) */ + if (EVP_CIPHER_flags(ctx->cipher) & EVP_CIPH_FLAG_AEAD_CIPHER) + ctx->tagsz = 16; /* Standard AEAD tag length (128 bits) */ + + ctx->evp_ctx = EVP_CIPHER_CTX_new(); + if (ctx->evp_ctx == NULL) + goto fail_cipher; + + return ctx; + + fail_cipher: + OPENSSL_secure_clear_free(ctx->key, SYMMKEYSZ); + fail_key: + free(ctx); + fail_malloc: + return NULL; } -void openssl_crypt_destroy_ctx(void * ctx) +void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx) { - EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *) ctx); + if (ctx == NULL) + return; + + if (ctx->key != NULL) + OPENSSL_secure_clear_free(ctx->key, SYMMKEYSZ); + + EVP_CIPHER_CTX_free(ctx->evp_ctx); + free(ctx); } /* AUTHENTICATION */ @@ -442,14 +1107,24 @@ int openssl_load_privkey_file(const char * path, { FILE * fp; EVP_PKEY * pkey; + unsigned long err; + char errbuf[256]; fp = fopen(path, "r"); - if (fp == NULL) + if (fp == NULL) { + fprintf(stderr, "Failed to open %s\n", path); goto fail_file; + } pkey = PEM_read_PrivateKey(fp, NULL, NULL, ""); - if (pkey == NULL) + if (pkey == NULL) { + err = ERR_get_error(); + ERR_error_string_n(err, errbuf, sizeof(errbuf)); + fprintf(stderr, + "OpenSSL error loading privkey from %s: %s\n", + path, errbuf); goto fail_key; + } fclose(fp); @@ -518,6 +1193,48 @@ int openssl_load_pubkey_file(const char * path, return -1; } +int openssl_load_pubkey_file_to_der(const char * path, + buffer_t * buf) +{ + FILE * fp; + EVP_PKEY * pkey; + int ret; + + assert(path != NULL); + assert(buf != NULL); + + memset(buf, 0, sizeof(*buf)); + + fp = fopen(path, "r"); + if (fp == NULL) + goto fail_file; + + pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + if (pkey == NULL) + goto fail_key; + + fclose(fp); + + /* Extract public key bytes in DER format */ + ret = get_pk_bytes_from_key(pkey, buf); + + EVP_PKEY_free(pkey); + + if (ret < 0) + goto fail_extract; + + return 0; + + fail_extract: + clrbuf(*buf); + return -1; + fail_key: + fclose(fp); + fail_file: + clrbuf(*buf); + return -1; +} + int openssl_load_pubkey_str(const char * str, void ** key) { @@ -547,28 +1264,123 @@ int openssl_load_pubkey_str(const char * str, return -1; } -int openssl_cmp_key(const void * key1, - const void * key2) +int openssl_load_pubkey_raw_file(const char * path, + buffer_t * buf) +{ + FILE * fp; + uint8_t tmp_buf[MSGBUFSZ]; + size_t bytes_read; + const char * algo; + + assert(path != NULL); + assert(buf != NULL); + + fp = fopen(path, "rb"); + if (fp == NULL) + goto fail_file; + + bytes_read = fread(tmp_buf, 1, MSGBUFSZ, fp); + if (bytes_read == 0) + goto fail_read; + + /* Validate that this is a known hybrid KEM format */ + algo = __openssl_hybrid_algo_from_len(bytes_read); + if (algo == NULL) + goto fail_read; + + buf->data = malloc(bytes_read); + if (buf->data == NULL) + goto fail_malloc; + + memcpy(buf->data, tmp_buf, bytes_read); + buf->len = bytes_read; + + return 0; + + fail_malloc: + fail_read: + fclose(fp); + fail_file: + clrbuf(*buf); + return -1; +} + +/* Determine hybrid KEM algorithm from raw private key length */ +static const char * __openssl_hybrid_algo_from_sk_len(size_t len) +{ + switch(len) { + case X25519MLKEM768_SKSZ: + return "X25519MLKEM768"; + case X448MLKEM1024_SKSZ: + return "X448MLKEM1024"; + default: + break; + } + + return NULL; +} + +int openssl_load_privkey_raw_file(const char * path, + void ** key) { - EVP_PKEY * pkey1; - EVP_PKEY * pkey2; + FILE * fp; + uint8_t tmp_buf[4096]; + size_t bytes_read; + const char * algo; + EVP_PKEY * pkey; + + assert(path != NULL); + assert(key != NULL); + + fp = fopen(path, "rb"); + if (fp == NULL) + goto fail_file; + + bytes_read = fread(tmp_buf, 1, sizeof(tmp_buf), fp); + fclose(fp); + + if (bytes_read == 0) + goto fail_read; + + /* Determine algorithm from key size */ + algo = __openssl_hybrid_algo_from_sk_len(bytes_read); + if (algo == NULL) + goto fail_read; + + pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algo, NULL, + tmp_buf, bytes_read); + /* Clear sensitive data from stack */ + OPENSSL_cleanse(tmp_buf, bytes_read); + if (pkey == NULL) + goto fail_read; + + *key = (void *) pkey; + + return 0; + + fail_read: + fail_file: + *key = NULL; + return -1; +} + +int openssl_cmp_key(const EVP_PKEY * key1, + const EVP_PKEY * key2) +{ assert(key1 != NULL); assert(key2 != NULL); - pkey1 = (EVP_PKEY *) key1; - pkey2 = (EVP_PKEY *) key2; - #if OPENSSL_VERSION_NUMBER >= 0x30000000L - return EVP_PKEY_eq(pkey1, pkey2) == 1 ? 0 : -1; + return EVP_PKEY_eq(key1, key2) == 1 ? 0 : -1; #else - return EVP_PKEY_cmp(pkey1, pkey2) == 1 ? 0 : -1; + return EVP_PKEY_cmp(key1, key2) == 1 ? 0 : -1; #endif } -void openssl_free_key(void * key) +void openssl_free_key(EVP_PKEY * key) { - EVP_PKEY_free((EVP_PKEY *) key); + EVP_PKEY_free(key); } int openssl_check_crt_name(void * crt, @@ -600,6 +1412,41 @@ int openssl_check_crt_name(void * crt, return -1; } +int openssl_get_crt_name(void * crt, + char * name) +{ + char * subj; + char * cn; + char * end; + X509 * xcrt; + + xcrt = (X509 *) crt; + + subj = X509_NAME_oneline(X509_get_subject_name(xcrt), NULL, 0); + if (subj == NULL) + goto fail_subj; + + cn = strstr(subj, "CN="); + if (cn == NULL) + goto fail_cn; + + cn += 3; /* Skip "CN=" */ + + /* Find end of CN (comma or slash for next field) */ + end = strpbrk(cn, ",/"); + if (end != NULL) + *end = '\0'; + + strcpy(name, cn); + free(subj); + + return 0; + fail_cn: + free(subj); + fail_subj: + return -1; +} + int openssl_crt_str(const void * crt, char * str) { @@ -704,37 +1551,48 @@ int openssl_verify_crt(void * store, return -1; } -int openssl_sign(void * pkp, +static const EVP_MD * select_md(EVP_PKEY * pkey, + int nid) +{ + if (EVP_PKEY_get_id(pkey) < 0) + return NULL; /* Provider-based (PQC) */ + + if (nid == NID_undef) + return NULL; /* Classical requires explicit nid */ + + return EVP_get_digestbynid(nid); +} + +int openssl_sign(EVP_PKEY * pkp, + int nid, buffer_t msg, buffer_t * sig) { - EVP_PKEY * pkey; - EVP_MD_CTX * mdctx; - size_t required; + EVP_MD_CTX * mdctx; + const EVP_MD * md; + size_t required; assert(pkp != NULL); assert(sig != NULL); - pkey = (EVP_PKEY *) pkp; - mdctx = EVP_MD_CTX_new(); if (!mdctx) goto fail_ctx; - if (EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) - goto fail_digest; + md = select_md(pkp, nid); - if (EVP_DigestSignUpdate(mdctx, msg.data, msg.len) != 1) + if (EVP_DigestSignInit(mdctx, NULL, md, NULL, pkp) != 1) goto fail_digest; - if (EVP_DigestSignFinal(mdctx, NULL, &required) != 1) + /* Get required signature buffer size */ + if (EVP_DigestSign(mdctx, NULL, &required, msg.data, msg.len) != 1) goto fail_digest; sig->data = malloc(required); if (sig->data == NULL) goto fail_digest; - if (EVP_DigestSignFinal(mdctx, sig->data, &required) != 1) + if (EVP_DigestSign(mdctx, sig->data, &required, msg.data, msg.len) != 1) goto fail_sign; sig->len = required; @@ -751,29 +1609,27 @@ int openssl_sign(void * pkp, return -1; } -int openssl_verify_sig(void * pk, - buffer_t msg, - buffer_t sig) +int openssl_verify_sig(EVP_PKEY * pk, + int nid, + buffer_t msg, + buffer_t sig) { - EVP_PKEY * pkey; - EVP_MD_CTX * mdctx; - int ret; + EVP_MD_CTX * mdctx; + const EVP_MD * md; + int ret; assert(pk != NULL); - pkey = (EVP_PKEY *) pk; - mdctx = EVP_MD_CTX_new(); if (!mdctx) goto fail_ctx; - if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) - goto fail_digest; + md = select_md(pk, nid); - if (EVP_DigestVerifyUpdate(mdctx, msg.data, msg.len) != 1) + if (EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pk) != 1) goto fail_digest; - ret = EVP_DigestVerifyFinal(mdctx, sig.data, sig.len); + ret = EVP_DigestVerify(mdctx, sig.data, sig.len, msg.data, msg.len); if (ret != 1) goto fail_digest; @@ -786,3 +1642,55 @@ int openssl_verify_sig(void * pk, clrbuf(sig); return -1; } + +ssize_t openssl_md_digest(int nid, + buffer_t in, + uint8_t * out) +{ + const EVP_MD * md; + unsigned int len; + + assert(in.data != NULL); + assert(out != NULL); + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return -1; + + if (EVP_Digest(in.data, in.len, out, &len, md, NULL) != 1) + return -1; + + return (ssize_t) len; +} + +ssize_t openssl_md_len(int nid) +{ + const EVP_MD * md; + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return -1; + + return (ssize_t) EVP_MD_get_size(md); +} + +int openssl_secure_malloc_init(size_t max, + size_t guard) +{ + return CRYPTO_secure_malloc_init(max, guard) == 1 ? 0 : -1; +} + +void openssl_secure_malloc_fini(void) +{ + CRYPTO_secure_malloc_done(); +} + +void * openssl_secure_malloc(size_t size) +{ + return OPENSSL_secure_malloc(size); +} + +void openssl_secure_free(void * ptr) +{ + OPENSSL_secure_free(ptr); +} diff --git a/src/lib/crypt/openssl.h b/src/lib/crypt/openssl.h index d4ee73b9..c28d0b4d 100644 --- a/src/lib/crypt/openssl.h +++ b/src/lib/crypt/openssl.h @@ -26,28 +26,52 @@ #ifndef OUROBOROS_LIB_CRYPT_OPENSSL_H #define OUROBOROS_LIB_CRYPT_OPENSSL_H -ssize_t openssl_ecdh_pkp_create(void ** pkp, - uint8_t * pk); +struct ossl_crypt_ctx; -void openssl_ecdh_pkp_destroy(void * pkp); +ssize_t openssl_pkp_create(const char * algo, + EVP_PKEY ** pkp, + uint8_t * pk); -int openssl_ecdh_derive(void * pkp, - buffer_t pk, - uint8_t * s); +void openssl_pkp_destroy(EVP_PKEY * pkp); -int openssl_encrypt(void * ctx, - uint8_t * key, - buffer_t in, - buffer_t * out); +int openssl_dhe_derive(EVP_PKEY * pkp, + buffer_t pk, + int kdf_nid, + uint8_t * s); -int openssl_decrypt(void * ctx, - uint8_t * key, - buffer_t in, - buffer_t * out); +ssize_t openssl_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); -void * openssl_crypt_create_ctx(void); +/* no X509 DER support yet for DHKEM public keys */ +ssize_t openssl_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); -void openssl_crypt_destroy_ctx(void * ctx); +int openssl_kem_decap(EVP_PKEY * priv, + buffer_t ct, + int kdf_nid, + uint8_t * s); + +int openssl_get_algo_from_pk_der(buffer_t pk, + char * algo); + +int openssl_get_algo_from_pk_raw(buffer_t pk, + char * algo); + +int openssl_encrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out); + +int openssl_decrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out); + +struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk); + +void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx); /* AUTHENTICATION */ @@ -76,15 +100,25 @@ int openssl_load_pubkey_file(const char * path, int openssl_load_pubkey_str(const char * str, void ** key); +int openssl_load_pubkey_file_to_der(const char * path, + buffer_t * buf); +int openssl_load_pubkey_raw_file(const char * path, + buffer_t * buf); + +int openssl_load_privkey_raw_file(const char * path, + void ** key); -int openssl_cmp_key(const void * key1, - const void * key2); +int openssl_cmp_key(const EVP_PKEY * key1, + const EVP_PKEY * key2); -void openssl_free_key(void * key); +void openssl_free_key(EVP_PKEY * key); int openssl_check_crt_name(void * crt, const char * name); +int openssl_get_crt_name(void * crt, + char * name); + int openssl_crt_str(const void * crt, char * str); @@ -101,12 +135,31 @@ int openssl_auth_add_crt_to_store(void * store, int openssl_verify_crt(void * store, void * crt); -int openssl_sign(void * pkp, +int openssl_sign(EVP_PKEY * pkp, + int md_nid, buffer_t msg, buffer_t * sig); -int openssl_verify_sig(void * pk, - buffer_t msg, - buffer_t sig); +int openssl_verify_sig(EVP_PKEY * pk, + int md_nid, + buffer_t msg, + buffer_t sig); + +ssize_t openssl_md_digest(int md_nid, + buffer_t in, + uint8_t * out); + +ssize_t openssl_md_len(int md_nid); + +/* Secure memory allocation */ +int openssl_secure_malloc_init(size_t max, + size_t guard); + +void openssl_secure_malloc_fini(void); + +void * openssl_secure_malloc(size_t size); + +void openssl_secure_free(void * ptr, + size_t size); #endif /* OUROBOROS_LIB_CRYPT_OPENSSL_H */ |
