diff --git a/providers/implementations/encode_decode/build.info b/providers/implementations/encode_decode/build.info index a5a93cc072c50..a1529fdf12582 100644 --- a/providers/implementations/encode_decode/build.info +++ b/providers/implementations/encode_decode/build.info @@ -26,3 +26,7 @@ ENDIF IF[{- !$disabled{'ml-kem'} -}] SOURCE[$DECODER_GOAL]=ml_kem_codecs.c ENDIF + +IF[{- !$disabled{'ml-dsa'} || !$disabled{'ml-kem'} -}] + SOURCE[$DECODER_GOAL]=ml_common_codecs.c +ENDIF diff --git a/providers/implementations/encode_decode/ml_common_codecs.c b/providers/implementations/encode_decode/ml_common_codecs.c new file mode 100644 index 0000000000000..773550c9fb931 --- /dev/null +++ b/providers/implementations/encode_decode/ml_common_codecs.c @@ -0,0 +1,92 @@ +/* + * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include +#include +#include +#include "ml_common_codecs.h" + +static int pref_cmp(const void *va, const void *vb) +{ + const ML_COMMON_PKCS8_FMT_PREF *a = va; + const ML_COMMON_PKCS8_FMT_PREF *b = vb; + + /* + * Zeros sort last, otherwise the sort is in increasing order. + * + * The preferences are small enough to ensure the comparison is transitive + * as required by qsort(3). When overflow or underflow is possible, the + * correct transitive comparison would be: (b < a) - (a < b). + */ + if (a->pref > 0 && b->pref > 0) + return a->pref - b->pref; + /* A preference of 0 is "larger" than (sorts after) any nonzero value. */ + return b->pref - a->pref; +} + +ML_COMMON_PKCS8_FMT_PREF * +ossl_ml_common_pkcs8_fmt_order(const char *algorithm_name, + const ML_COMMON_PKCS8_FMT *p8fmt, + const char *direction, const char *formats) +{ + ML_COMMON_PKCS8_FMT_PREF *ret; + int i, count = 0; + const char *fmt = formats, *end; + const char *sep = "\t ,"; + + /* Reserve an extra terminal slot with fmt == NULL */ + if ((ret = OPENSSL_zalloc((NUM_PKCS8_FORMATS + 1) * sizeof(*ret))) == NULL) + return NULL; + + /* Entries that match a format will get a non-zero preference. */ + for (i = 0; i < NUM_PKCS8_FORMATS; ++i) { + ret[i].fmt = &p8fmt[i]; + ret[i].pref = 0; + } + + /* Default to compile-time table order when none specified. */ + if (formats == NULL) + return ret; + + /* + * Formats are case-insensitive, separated by spaces, tabs or commas. + * Duplicate formats are allowed, the first occurence determines the order. + */ + do { + if (*(fmt += strspn(fmt, sep)) == '\0') + break; + end = fmt + strcspn(fmt, sep); + for (i = 0; i < NUM_PKCS8_FORMATS; ++i) { + /* Skip slots already selected or with a different name. */ + if (ret[i].pref > 0 + || OPENSSL_strncasecmp(ret[i].fmt->p8_name, + fmt, (end - fmt)) != 0) + continue; + /* First time match */ + ret[i].pref = ++count; + break; + } + fmt = end; + } while (count < NUM_PKCS8_FORMATS); + + /* No formats matched, raise an error */ + if (count == 0) { + OPENSSL_free(ret); + ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_DSA_NO_FORMAT, + "no %s private key %s formats are enabled", + algorithm_name, direction); + return NULL; + } + /* Sort by preference, with 0's last */ + qsort(ret, NUM_PKCS8_FORMATS, sizeof(*ret), pref_cmp); + /* Terminate the list at first unselected entry, perhaps reserved slot. */ + ret[count].fmt = NULL; + return ret; +} diff --git a/providers/implementations/encode_decode/ml_common_codecs.h b/providers/implementations/encode_decode/ml_common_codecs.h new file mode 100644 index 0000000000000..4bf618ea0b792 --- /dev/null +++ b/providers/implementations/encode_decode/ml_common_codecs.h @@ -0,0 +1,98 @@ +/* + * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef PROV_ML_COMMON_CODECS_H +# define PROV_ML_COMMON_CODECS_H +# pragma once + +# include +# include "crypto/ml_dsa.h" +# include "prov/provider_ctx.h" + + /*- + * The DER ASN.1 encoding of ML-DSA and ML-KEM public keys prepends 22 bytes + * to the encoded public key: + * + * - 4 byte outer sequence tag and length + * - 2 byte algorithm sequence tag and length + * - 2 byte algorithm OID tag and length + * - 9 byte algorithm OID (from NIST CSOR OID arc) + * - 4 byte bit string tag and length + * - 1 bitstring lead byte + */ +# define ML_COMMON_SPKI_OVERHEAD 22 +typedef struct { + const uint8_t asn1_prefix[ML_COMMON_SPKI_OVERHEAD]; +} ML_COMMON_SPKI_FMT; + +/*- +* For each parameter set we support a few PKCS#8 input formats, three + * corresponding to the "either or both" variants of: + * + * ML-DSA-PrivateKey ::= CHOICE { + * seed [0] IMPLICIT OCTET STRING (SIZE (32)), + * expandedKey OCTET STRING (SIZE (2560 | 4032 | 4896)), + * both SEQUENCE { + * seed OCTET STRING (SIZE (32)), + * expandedKey OCTET STRING (SIZE (2560 | 4032 | 4896)) } } + * + * ML-KEM-PrivateKey ::= CHOICE { + * seed [0] IMPLICIT OCTET STRING (SIZE (64)), + * expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)), + * both SEQUENCE { + * seed OCTET STRING (SIZE (64)), + * expandedKey OCTET STRING SIZE ((1632 | 2400 | 3168)) } } + * + * one more for a historical OQS encoding: + * + * - OQS private + public key: OCTET STRING + * (The public key is ignored, just as with PKCS#8 v2.) + * + * and two more that are the minimal IETF non-ASN.1 seed encoding: + * + * - Bare seed (just the 32 or 64 bytes) + * - Bare priv (just the key bytes) + * + * A length of zero means that particular field is absent. + * + * The p8_shift is 0 when the top-level tag+length occupy four bytes, 2 when + * they occupy two by†es, and 4 when no tag is used at all. + */ +#define NUM_PKCS8_FORMATS 6 + +typedef struct { + const char *p8_name; /* Format name */ + size_t p8_bytes; /* Total P8 encoding length */ + int p8_shift; /* 4 - (top-level tag + len) */ + uint32_t p8_magic; /* The tag + len value */ + uint16_t seed_magic; /* Interior tag + len for the seed */ + size_t seed_offset; /* Seed offset from start */ + size_t seed_length; /* Seed bytes */ + uint32_t priv_magic; /* Interior tag + len for the key */ + size_t priv_offset; /* Key offset from start */ + size_t priv_length; /* Key bytes */ + size_t pub_offset; /* Pubkey offset */ + size_t pub_length; /* Pubkey bytes */ +} ML_COMMON_PKCS8_FMT; + +typedef struct { + const ML_COMMON_SPKI_FMT *spkifmt; + const ML_COMMON_PKCS8_FMT *p8fmt; +} ML_COMMON_CODEC; + +typedef struct { + const ML_COMMON_PKCS8_FMT *fmt; + int pref; +} ML_COMMON_PKCS8_FMT_PREF; + +ML_COMMON_PKCS8_FMT_PREF * +ossl_ml_common_pkcs8_fmt_order(const char *algorithm_name, + const ML_COMMON_PKCS8_FMT *p8fmt, + const char *direction, const char *formats); +#endif diff --git a/providers/implementations/encode_decode/ml_dsa_codecs.c b/providers/implementations/encode_decode/ml_dsa_codecs.c index e7be0094cc686..a0944d8a11c49 100644 --- a/providers/implementations/encode_decode/ml_dsa_codecs.c +++ b/providers/implementations/encode_decode/ml_dsa_codecs.c @@ -18,56 +18,18 @@ /*- * Tables describing supported ASN.1 input/output formats. - * For each parameter set we support a few PKCS#8 input formats, three - * corresponding to the "either or both" variants of: - * - * ML-DSA-PrivateKey ::= CHOICE { - * seed [0] IMPLICIT OCTET STRING (SIZE (32)), - * expandedKey OCTET STRING (SIZE (2560 | 4032 | 4896)), - * both SEQUENCE { - * seed OCTET STRING (SIZE (32)), - * expandedKey OCTET STRING (SIZE (2560 | 4032 | 4896)) } } - * - * one more for a historical OQS encoding: - * - * - OQS private + public key: OCTET STRING - * (The public key is ignored, just as with PKCS#8 v2.) - * - * and two more that are the minimal IETF non-ASN.1 seed encoding: - * - * - Bare seed (just the 32 bytes) - * - Bare priv (just the key bytes) - * - * A length of zero means that particular field is absent. - * - * The p8_shift is 0 when the top-level tag+length occupy four bytes, 2 when - * they occupy two by†es, and 4 when no tag is used at all. - * - * On output the PKCS8 info table order is important: - * - When we have a seed we'll use the first entry with a non-zero seed offset. - * - Otherwise, the first entry with a zero seed offset. - * - * As written, when possible, we prefer to output both the seed and private - * key, otherwise, just the private key ([1] IMPLICIT OCTET STRING form). - * - * The various lengths in the PKCS#8 tag/len fields could have been left - * zeroed, and filled in on the fly from the algorithm parameters, but that - * makes the code more complex, so a choice was made to embed them directly - * into the tables. Had they been zeroed, one table could cover all three - * ML-DSA parameter sets. */ -#define NUM_PKCS8_FORMATS 6 /*- * ML-DSA-44: * Public key bytes: 1312 (0x0520) * Private key bytes: 2560 (0x0a00) */ -static const ML_DSA_SPKI_FMT ml_dsa_44_spkifmt = { +static const ML_COMMON_SPKI_FMT ml_dsa_44_spkifmt = { { 0x30, 0x82, 0x05, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x03, 0x82, 0x05, 0x21, 0x00, } }; -static const ML_DSA_PKCS8_FMT ml_dsa_44_p8fmt[NUM_PKCS8_FORMATS] = { +static const ML_COMMON_PKCS8_FMT ml_dsa_44_p8fmt[NUM_PKCS8_FORMATS] = { { "seed-priv", 0x0a2a, 0, 0x30820a26, 0x0420, 6, 0x20, 0x04820a00, 0x2a, 0x0a00, 0, 0, }, { "priv-only", 0x0a04, 0, 0x04820a00, 0, 0, 0, 0, 0x04, 0x0a00, 0, 0, }, { "oqskeypair", 0x0f24, 0, 0x04820f20, 0, 0, 0, 0, 0x04, 0x0a00, 0x0a04, 0x0520 }, @@ -81,11 +43,11 @@ static const ML_DSA_PKCS8_FMT ml_dsa_44_p8fmt[NUM_PKCS8_FORMATS] = { * Public key bytes: 1952 (0x07a0) * Private key bytes: 4032 (0x0fc0) */ -static const ML_DSA_SPKI_FMT ml_dsa_65_spkifmt = { +static const ML_COMMON_SPKI_FMT ml_dsa_65_spkifmt = { { 0x30, 0x82, 0x07, 0xb2, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12, 0x03, 0x82, 0x07, 0xa1, 0x00, } }; -static const ML_DSA_PKCS8_FMT ml_dsa_65_p8fmt[NUM_PKCS8_FORMATS] = { +static const ML_COMMON_PKCS8_FMT ml_dsa_65_p8fmt[NUM_PKCS8_FORMATS] = { { "seed-priv", 0x0fea, 0, 0x30820fe6, 0x0420, 6, 0x20, 0x04820fc0, 0x2a, 0x0fc0, 0, 0, }, { "priv-only", 0x0fc4, 0, 0x04820fc0, 0, 0, 0, 0, 0x04, 0x0fc0, 0, 0, }, { "oqskeypair", 0x1764, 0, 0x04821760, 0, 0, 0, 0, 0x04, 0x0fc0, 0x0fc4, 0x07a0 }, @@ -99,11 +61,11 @@ static const ML_DSA_PKCS8_FMT ml_dsa_65_p8fmt[NUM_PKCS8_FORMATS] = { * Public key bytes: 2592 (0x0a20) * Private key bytes: 4896 (0x1320) */ -static const ML_DSA_SPKI_FMT ml_dsa_87_spkifmt = { +static const ML_COMMON_SPKI_FMT ml_dsa_87_spkifmt = { { 0x30, 0x82, 0x0a, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13, 0x03, 0x82, 0x0a, 0x21, 0x00, } }; -static const ML_DSA_PKCS8_FMT ml_dsa_87_p8fmt[NUM_PKCS8_FORMATS] = { +static const ML_COMMON_PKCS8_FMT ml_dsa_87_p8fmt[NUM_PKCS8_FORMATS] = { { "seed-priv", 0x134a, 0, 0x30821346, 0x0420, 6, 0x20, 0x04821320, 0x2a, 0x1320, 0, 0, }, { "priv-only", 0x1324, 0, 0x04821320, 0, 0, 0, 0, 0x04, 0x1320, 0, 0, }, { "oqskeypair", 0x1d44, 0, 0x04821d40, 0, 0, 0, 0, 0x04, 0x1320, 0x1324, 0x0a20 }, @@ -120,14 +82,14 @@ static const ML_DSA_PKCS8_FMT ml_dsa_87_p8fmt[NUM_PKCS8_FORMATS] = { /* * Per-variant fixed parameters */ -static const ML_DSA_CODEC codecs[3] = { +static const ML_COMMON_CODEC codecs[3] = { { &ml_dsa_44_spkifmt, ml_dsa_44_p8fmt }, { &ml_dsa_65_spkifmt, ml_dsa_65_p8fmt }, { &ml_dsa_87_spkifmt, ml_dsa_87_p8fmt } }; /* Retrieve the parameters of one of the ML-DSA variants */ -static const ML_DSA_CODEC *ml_dsa_get_codec(int evp_type) +static const ML_COMMON_CODEC *ml_dsa_get_codec(int evp_type) { switch (evp_type) { case EVP_PKEY_ML_DSA_44: @@ -140,101 +102,23 @@ static const ML_DSA_CODEC *ml_dsa_get_codec(int evp_type) return NULL; } -static int pref_cmp(const void *va, const void *vb) -{ - const ML_DSA_PKCS8_FMT_PREF *a = va; - const ML_DSA_PKCS8_FMT_PREF *b = vb; - - /* - * Zeros sort last, otherwise the sort is in increasing order. - * - * The preferences are small enough to ensure the comparison is transitive - * as required by qsort(3). When overflow or underflow is possible, the - * correct transitive comparison would be: (b < a) - (a < b). - */ - if (a->pref > 0 && b->pref > 0) - return a->pref - b->pref; - /* A preference of 0 is "larger" than (sorts after) any nonzero value. */ - return b->pref - a->pref; -} - -static -ML_DSA_PKCS8_FMT_PREF *vp8_order(const char *algorithm_name, - const ML_DSA_PKCS8_FMT *p8fmt, - const char *direction, const char *formats) -{ - ML_DSA_PKCS8_FMT_PREF *ret; - int i, count = 0; - const char *fmt = formats, *end; - const char *sep = "\t ,"; - - /* Reserve an extra terminal slot with fmt == NULL */ - if ((ret = OPENSSL_zalloc((NUM_PKCS8_FORMATS + 1) * sizeof(*ret))) == NULL) - return NULL; - - /* Entries that match a format will get a non-zero preference. */ - for (i = 0; i < NUM_PKCS8_FORMATS; ++i) { - ret[i].fmt = &p8fmt[i]; - ret[i].pref = 0; - } - - /* Default to compile-time table order when none specified. */ - if (formats == NULL) - return ret; - - /* - * Formats are case-insensitive, separated by spaces, tabs or commas. - * Duplicate formats are allowed, the first occurence determines the order. - */ - do { - if (*(fmt += strspn(fmt, sep)) == '\0') - break; - end = fmt + strcspn(fmt, sep); - for (i = 0; i < NUM_PKCS8_FORMATS; ++i) { - /* Skip slots already selected or with a different name. */ - if (ret[i].pref > 0 - || OPENSSL_strncasecmp(ret[i].fmt->p8_name, - fmt, (end - fmt)) != 0) - continue; - /* First time match */ - ret[i].pref = ++count; - break; - } - fmt = end; - } while (count < NUM_PKCS8_FORMATS); - - /* No formats matched, raise an error */ - if (count == 0) { - OPENSSL_free(ret); - ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_DSA_NO_FORMAT, - "no %s private key %s formats are enabled", - algorithm_name, direction); - return NULL; - } - /* Sort by preference, with 0's last */ - qsort(ret, NUM_PKCS8_FORMATS, sizeof(*ret), pref_cmp); - /* Terminate the list at first unselected entry, perhaps reserved slot. */ - ret[count].fmt = NULL; - return ret; -} - ML_DSA_KEY * ossl_ml_dsa_d2i_PUBKEY(const uint8_t *pk, int pk_len, int evp_type, PROV_CTX *provctx, const char *propq) { OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx); - const ML_DSA_CODEC *codec; + const ML_COMMON_CODEC *codec; const ML_DSA_PARAMS *params; ML_DSA_KEY *ret; if ((params = ossl_ml_dsa_params_get(evp_type)) == NULL || (codec = ml_dsa_get_codec(evp_type)) == NULL) return NULL; - if (pk_len != ML_DSA_SPKI_OVERHEAD + (ossl_ssize_t) params->pk_len - || memcmp(pk, codec->spkifmt->asn1_prefix, ML_DSA_SPKI_OVERHEAD) != 0) + if (pk_len != ML_COMMON_SPKI_OVERHEAD + (ossl_ssize_t) params->pk_len + || memcmp(pk, codec->spkifmt->asn1_prefix, ML_COMMON_SPKI_OVERHEAD) != 0) return NULL; - pk_len -= ML_DSA_SPKI_OVERHEAD; - pk += ML_DSA_SPKI_OVERHEAD; + pk_len -= ML_COMMON_SPKI_OVERHEAD; + pk += ML_COMMON_SPKI_OVERHEAD; if ((ret = ossl_ml_dsa_key_new(libctx, propq, evp_type)) == NULL) return NULL; @@ -257,9 +141,9 @@ ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen, { OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx); const ML_DSA_PARAMS *v; - const ML_DSA_CODEC *codec; - ML_DSA_PKCS8_FMT_PREF *vp8_alloc = NULL, *slot; - const ML_DSA_PKCS8_FMT *p8fmt; + const ML_COMMON_CODEC *codec; + ML_COMMON_PKCS8_FMT_PREF *fmt_slots = NULL, *slot; + const ML_COMMON_PKCS8_FMT *p8fmt; ML_DSA_KEY *key = NULL, *ret = NULL; PKCS8_PRIV_KEY_INFO *p8inf = NULL; const uint8_t *buf, *pos; @@ -289,8 +173,9 @@ ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen, /* Get the list of enabled decoders. Their order is not important here. */ formats = ossl_prov_ctx_get_param( provctx, OSSL_PKEY_PARAM_ML_DSA_INPUT_FORMATS, NULL); - vp8_alloc = vp8_order(v->alg, codec->p8fmt, "input", formats); - if (vp8_alloc == NULL) + fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->alg, codec->p8fmt, + "input", formats); + if (fmt_slots == NULL) goto end; /* Parameters must be absent. */ @@ -306,7 +191,7 @@ ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen, /* Find the matching p8 info slot, that also has the expected length. */ pos = OPENSSL_load_u32_be(&magic, buf); - for (slot = vp8_alloc; (p8fmt = slot->fmt) != NULL; ++slot) { + for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot) { if (len != (ossl_ssize_t)p8fmt->p8_bytes) continue; if (p8fmt->p8_shift == sizeof(magic) @@ -384,7 +269,7 @@ ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen, ret = key; end: - OPENSSL_free(vp8_alloc); + OPENSSL_free(fmt_slots); PKCS8_PRIV_KEY_INFO_free(p8inf); if (ret == NULL) ossl_ml_dsa_key_free(key); @@ -413,9 +298,9 @@ int ossl_ml_dsa_i2d_prvkey(const ML_DSA_KEY *key, uint8_t **out, PROV_CTX *provctx) { const ML_DSA_PARAMS *params = ossl_ml_dsa_key_params(key); - const ML_DSA_CODEC *codec; - ML_DSA_PKCS8_FMT_PREF *vp8_alloc, *slot; - const ML_DSA_PKCS8_FMT *p8fmt; + const ML_COMMON_CODEC *codec; + ML_COMMON_PKCS8_FMT_PREF *fmt_slots, *slot; + const ML_COMMON_PKCS8_FMT *p8fmt; uint8_t *buf = NULL, *pos; const char *formats; int len = ML_DSA_SEED_BYTES; @@ -436,12 +321,13 @@ int ossl_ml_dsa_i2d_prvkey(const ML_DSA_KEY *key, uint8_t **out, formats = ossl_prov_ctx_get_param( provctx, OSSL_PKEY_PARAM_ML_DSA_OUTPUT_FORMATS, NULL); - vp8_alloc = vp8_order(params->alg, codec->p8fmt, "output", formats); - if (vp8_alloc == NULL) + fmt_slots = ossl_ml_common_pkcs8_fmt_order(params->alg, codec->p8fmt, + "output", formats); + if (fmt_slots == NULL) return 0; /* If we don't have a seed, skip seedful entries */ - for (slot = vp8_alloc; (p8fmt = slot->fmt) != NULL; ++slot) + for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot) if (seed != NULL || p8fmt->seed_length == 0) break; /* No matching table entries, give up */ @@ -523,7 +409,7 @@ int ossl_ml_dsa_i2d_prvkey(const ML_DSA_KEY *key, uint8_t **out, } end: - OPENSSL_free(vp8_alloc); + OPENSSL_free(fmt_slots); if (ret == 0) OPENSSL_free(buf); return ret; diff --git a/providers/implementations/encode_decode/ml_dsa_codecs.h b/providers/implementations/encode_decode/ml_dsa_codecs.h index 86a4303a0c8da..c0c2e842a2421 100644 --- a/providers/implementations/encode_decode/ml_dsa_codecs.h +++ b/providers/implementations/encode_decode/ml_dsa_codecs.h @@ -15,73 +15,7 @@ # include # include "crypto/ml_dsa.h" # include "prov/provider_ctx.h" - - /*- - * The DER ASN.1 encoding of ML-KEM (and ML-DSA) public keys prepends 22 bytes - * to the encoded public key: - * - * - 4 byte outer sequence tag and length - * - 2 byte algorithm sequence tag and length - * - 2 byte algorithm OID tag and length - * - 9 byte algorithm OID (from NIST CSOR OID arc) - * - 4 byte bit string tag and length - * - 1 bitstring lead byte - */ -# define ML_DSA_SPKI_OVERHEAD 22 -typedef struct { - const uint8_t asn1_prefix[ML_DSA_SPKI_OVERHEAD]; -} ML_DSA_SPKI_FMT; - -/*- -* For each parameter set we support a few PKCS#8 input formats, three - * corresponding to the "either or both" variants of: - * - * ML-DSA-PrivateKey ::= CHOICE { - * seed [0] IMPLICIT OCTET STRING (SIZE (32)), - * expandedKey OCTET STRING (SIZE (2560 | 4032 | 4896)), - * both SEQUENCE { - * seed OCTET STRING (SIZE (32)), - * expandedKey OCTET STRING (SIZE (2560 | 4032 | 4896)) } } - * - * one more for a historical OQS encoding: - * - * - OQS private + public key: OCTET STRING - * (The public key is ignored, just as with PKCS#8 v2.) - * - * and two more that are the minimal IETF non-ASN.1 seed encoding: - * - * - Bare seed (just the 32 bytes) - * - Bare priv (just the key bytes) - * - * A length of zero means that particular field is absent. - * - * The p8_shift is 0 when the top-level tag+length occupy four bytes, 2 when - * they occupy two by†es, and 4 when no tag is used at all. - */ -typedef struct { - const char *p8_name; /* Format name */ - size_t p8_bytes; /* Total P8 encoding length */ - int p8_shift; /* 4 - (top-level tag + len) */ - uint32_t p8_magic; /* The tag + len value */ - uint16_t seed_magic; /* Interior tag + len for the seed */ - size_t seed_offset; /* Seed offset from start */ - size_t seed_length; /* Seed bytes */ - uint32_t priv_magic; /* Interior tag + len for the key */ - size_t priv_offset; /* Key offset from start */ - size_t priv_length; /* Key bytes */ - size_t pub_offset; /* Pubkey offset */ - size_t pub_length; /* Pubkey bytes */ -} ML_DSA_PKCS8_FMT; - -typedef struct { - const ML_DSA_SPKI_FMT *spkifmt; - const ML_DSA_PKCS8_FMT *p8fmt; -} ML_DSA_CODEC; - -typedef struct { - const ML_DSA_PKCS8_FMT *fmt; - int pref; -} ML_DSA_PKCS8_FMT_PREF; +# include "ml_common_codecs.h" __owur ML_DSA_KEY *ossl_ml_dsa_d2i_PUBKEY(const uint8_t *pubenc, int publen, diff --git a/providers/implementations/encode_decode/ml_kem_codecs.c b/providers/implementations/encode_decode/ml_kem_codecs.c index 46fe811048014..7a50210a7d246 100644 --- a/providers/implementations/encode_decode/ml_kem_codecs.c +++ b/providers/implementations/encode_decode/ml_kem_codecs.c @@ -15,58 +15,18 @@ #include "internal/encoder.h" #include "ml_kem_codecs.h" -/*- - * Tables describing supported ASN.1 input/output formats. - * For each parameter set we support a few PKCS#8 input formats, three - * corresponding to the "either or both" variants of: - * - * ML-KEM-PrivateKey ::= CHOICE { - * seed [0] IMPLICIT OCTET STRING (SIZE (64)), - * expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)), - * both SEQUENCE { - * seed OCTET STRING (SIZE (64)), - * expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)) } } - * - * one more for a historical OQS encoding: - * - * - OQS private + public key: OCTET STRING - * (The public key is ignored, just as with PKCS#8 v2.) - * - * and two more that are the minimal IETF non-ASN.1 seed encoding: - * - * - Bare seed (just the 64 bytes) - * - Bare priv (just the key bytes) - * - * A length of zero means that particular field is absent. - * - * The p8_shift is 0 when the top-level tag+length occupy four bytes, 2 when - * they occupy two by†es, and 4 when no tag is used at all. - * - * On output the PKCS8 info table order is important: - * - When we have a seed we'll use the first entry with a non-zero seed offset. - * - Otherwise, the first entry with a zero seed offset. - * - * As written, when possible, we prefer to output both the seed and private - * key, otherwise, just the private key. - * - * The various lengths in the PKCS#8 tag/len fields could have been left - * zeroed, and filled in on the fly from the algorithm parameters, but that - * makes the code more complex, so a choice was made to embed them directly - * into the tables. Had they been zeroed, one table could cover all three - * ML-KEM parameter sets. - */ -#define NUM_PKCS8_FORMATS 6 +/* Tables describing supported ASN.1 input/output formats. */ /*- * ML-KEM-512: * Public key bytes: 800 (0x0320) * Private key bytes: 1632 (0x0660) */ -static const ML_KEM_SPKI_FMT ml_kem_512_spkifmt = { +static const ML_COMMON_SPKI_FMT ml_kem_512_spkifmt = { { 0x30, 0x82, 0x03, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01, 0x03, 0x82, 0x03, 0x21, 0x00, } }; -static const ML_KEM_PKCS8_FMT ml_kem_512_p8fmt[NUM_PKCS8_FORMATS] = { +static const ML_COMMON_PKCS8_FMT ml_kem_512_p8fmt[NUM_PKCS8_FORMATS] = { { "seed-priv", 0x06aa, 0, 0x308206a6, 0x0440, 6, 0x40, 0x04820660, 0x4a, 0x0660, 0, 0 }, { "priv-only", 0x0664, 0, 0x04820660, 0, 0, 0, 0, 0x04, 0x0660, 0, 0 }, { "oqskeypair", 0x0984, 0, 0x04820980, 0, 0, 0, 0, 0x04, 0x0660, 0x0664, 0x0320 }, @@ -80,11 +40,11 @@ static const ML_KEM_PKCS8_FMT ml_kem_512_p8fmt[NUM_PKCS8_FORMATS] = { * Public key bytes: 1184 (0x04a0) * Private key bytes: 2400 (0x0960) */ -static const ML_KEM_SPKI_FMT ml_kem_768_spkifmt = { +static const ML_COMMON_SPKI_FMT ml_kem_768_spkifmt = { { 0x30, 0x82, 0x04, 0xb2, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02, 0x03, 0x82, 0x04, 0xa1, 0x00, } }; -static const ML_KEM_PKCS8_FMT ml_kem_768_p8fmt[NUM_PKCS8_FORMATS] = { +static const ML_COMMON_PKCS8_FMT ml_kem_768_p8fmt[NUM_PKCS8_FORMATS] = { { "seed-priv", 0x09aa, 0, 0x308209a6, 0x0440, 6, 0x40, 0x04820960, 0x4a, 0x0960, 0, 0, }, { "priv-only", 0x0964, 0, 0x04820960, 0, 0, 0, 0, 0x04, 0x0960, 0, 0, }, { "oqskeypair", 0x0e04, 0, 0x04820e00, 0, 0, 0, 0, 0x04, 0x0960, 0x0964, 0x04a0 }, @@ -98,11 +58,11 @@ static const ML_KEM_PKCS8_FMT ml_kem_768_p8fmt[NUM_PKCS8_FORMATS] = { * Private key bytes: 3168 (0x0c60) * Public key bytes: 1568 (0x0620) */ -static const ML_KEM_SPKI_FMT ml_kem_1024_spkifmt = { +static const ML_COMMON_SPKI_FMT ml_kem_1024_spkifmt = { { 0x30, 0x82, 0x06, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03, 0x03, 0x82, 0x06, 0x21, 0x00, } }; -static const ML_KEM_PKCS8_FMT ml_kem_1024_p8fmt[NUM_PKCS8_FORMATS] = { +static const ML_COMMON_PKCS8_FMT ml_kem_1024_p8fmt[NUM_PKCS8_FORMATS] = { { "seed-priv", 0x0caa, 0, 0x30820ca6, 0x0440, 6, 0x40, 0x04820c60, 0x4a, 0x0c60, 0, 0 }, { "priv-only", 0x0c64, 0, 0x04820c60, 0, 0, 0, 0, 0x04, 0x0c60, 0, 0 }, { "oqskeypair", 0x1284, 0, 0x04821280, 0, 0, 0, 0, 0x04, 0x0c60, 0x0c64, 0x0620 }, @@ -119,14 +79,14 @@ static const ML_KEM_PKCS8_FMT ml_kem_1024_p8fmt[NUM_PKCS8_FORMATS] = { /* * Per-variant fixed parameters */ -static const ML_KEM_CODEC codecs[3] = { +static const ML_COMMON_CODEC codecs[3] = { { &ml_kem_512_spkifmt, ml_kem_512_p8fmt }, { &ml_kem_768_spkifmt, ml_kem_768_p8fmt }, { &ml_kem_1024_spkifmt, ml_kem_1024_p8fmt } }; /* Retrieve the parameters of one of the ML-KEM variants */ -static const ML_KEM_CODEC *ml_kem_get_codec(int evp_type) +static const ML_COMMON_CODEC *ml_kem_get_codec(int evp_type) { switch (evp_type) { case EVP_PKEY_ML_KEM_512: @@ -139,103 +99,25 @@ static const ML_KEM_CODEC *ml_kem_get_codec(int evp_type) return NULL; } -static int pref_cmp(const void *va, const void *vb) -{ - const ML_KEM_PKCS8_FMT_PREF *a = va; - const ML_KEM_PKCS8_FMT_PREF *b = vb; - - /* - * Zeros sort last, otherwise the sort is in increasing order. - * - * The preferences are small enough to ensure the comparison is transitive - * as required by qsort(3). When overflow or underflow is possible, the - * correct transitive comparison would be: (b < a) - (a < b). - */ - if (a->pref > 0 && b->pref > 0) - return a->pref - b->pref; - /* A preference of 0 is "larger" than (sorts after) any nonzero value. */ - return b->pref - a->pref; -} - -static -ML_KEM_PKCS8_FMT_PREF *vp8_order(const char *algorithm_name, - const ML_KEM_PKCS8_FMT *p8fmt, - const char *direction, const char *formats) -{ - ML_KEM_PKCS8_FMT_PREF *ret; - int i, count = 0; - const char *fmt = formats, *end; - const char *sep = "\t ,"; - - /* Reserve an extra terminal slot with fmt == NULL */ - if ((ret = OPENSSL_zalloc((NUM_PKCS8_FORMATS + 1) * sizeof(*ret))) == NULL) - return NULL; - - /* Entries that match a format will get a non-zero preference. */ - for (i = 0; i < NUM_PKCS8_FORMATS; ++i) { - ret[i].fmt = &p8fmt[i]; - ret[i].pref = 0; - } - - /* Default to compile-time table order when none specified. */ - if (formats == NULL) - return ret; - - /* - * Formats are case-insensitive, separated by spaces, tabs or commas. - * Duplicate formats are allowed, the first occurence determines the order. - */ - do { - if (*(fmt += strspn(fmt, sep)) == '\0') - break; - end = fmt + strcspn(fmt, sep); - for (i = 0; i < NUM_PKCS8_FORMATS; ++i) { - /* Skip slots already selected or with a different name. */ - if (ret[i].pref > 0 - || OPENSSL_strncasecmp(ret[i].fmt->p8_name, - fmt, (end - fmt)) != 0) - continue; - /* First time match */ - ret[i].pref = ++count; - break; - } - fmt = end; - } while (count < NUM_PKCS8_FORMATS); - - /* No formats matched, raise an error */ - if (count == 0) { - OPENSSL_free(ret); - ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_KEM_NO_FORMAT, - "no %s private key %s formats are enabled", - algorithm_name, direction); - return NULL; - } - /* Sort by preference, with 0's last */ - qsort(ret, NUM_PKCS8_FORMATS, sizeof(*ret), pref_cmp); - /* Terminate the list at first unselected entry, perhaps reserved slot. */ - ret[count].fmt = NULL; - return ret; -} - ML_KEM_KEY * ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen, int evp_type, PROV_CTX *provctx, const char *propq) { OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx); const ML_KEM_VINFO *v; - const ML_KEM_CODEC *codec; - const ML_KEM_SPKI_FMT *vspki; + const ML_COMMON_CODEC *codec; + const ML_COMMON_SPKI_FMT *vspki; ML_KEM_KEY *ret; if ((v = ossl_ml_kem_get_vinfo(evp_type)) == NULL || (codec = ml_kem_get_codec(evp_type)) == NULL) return NULL; vspki = codec->spkifmt; - if (publen != ML_KEM_SPKI_OVERHEAD + (ossl_ssize_t) v->pubkey_bytes - || memcmp(pubenc, vspki->asn1_prefix, ML_KEM_SPKI_OVERHEAD) != 0) + if (publen != ML_COMMON_SPKI_OVERHEAD + (ossl_ssize_t) v->pubkey_bytes + || memcmp(pubenc, vspki->asn1_prefix, ML_COMMON_SPKI_OVERHEAD) != 0) return NULL; - publen -= ML_KEM_SPKI_OVERHEAD; - pubenc += ML_KEM_SPKI_OVERHEAD; + publen -= ML_COMMON_SPKI_OVERHEAD; + pubenc += ML_COMMON_SPKI_OVERHEAD; if ((ret = ossl_ml_kem_key_new(libctx, propq, evp_type)) == NULL) return NULL; @@ -258,9 +140,9 @@ ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen, { OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx); const ML_KEM_VINFO *v; - const ML_KEM_CODEC *codec; - ML_KEM_PKCS8_FMT_PREF *fmt_slots = NULL, *slot; - const ML_KEM_PKCS8_FMT *p8fmt; + const ML_COMMON_CODEC *codec; + ML_COMMON_PKCS8_FMT_PREF *fmt_slots = NULL, *slot; + const ML_COMMON_PKCS8_FMT *p8fmt; ML_KEM_KEY *key = NULL, *ret = NULL; PKCS8_PRIV_KEY_INFO *p8inf = NULL; const uint8_t *buf, *pos; @@ -288,8 +170,8 @@ ossl_ml_kem_d2i_PKCS8(const uint8_t *prvenc, int prvlen, /* Get the list of enabled decoders. Their order is not important here. */ formats = ossl_prov_ctx_get_param( provctx, OSSL_PKEY_PARAM_ML_KEM_INPUT_FORMATS, NULL); - fmt_slots = vp8_order(v->algorithm_name, codec->p8fmt, - "input", formats); + fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->algorithm_name, codec->p8fmt, + "input", formats); if (fmt_slots == NULL) goto end; @@ -426,9 +308,9 @@ int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, uint8_t **out, PROV_CTX *provctx) { const ML_KEM_VINFO *v = key->vinfo; - const ML_KEM_CODEC *codec; - ML_KEM_PKCS8_FMT_PREF *fmt_slots, *slot; - const ML_KEM_PKCS8_FMT *p8fmt; + const ML_COMMON_CODEC *codec; + ML_COMMON_PKCS8_FMT_PREF *fmt_slots, *slot; + const ML_COMMON_PKCS8_FMT *p8fmt; uint8_t *buf = NULL, *pos; const char *formats; int len = ML_KEM_SEED_BYTES; @@ -447,8 +329,8 @@ int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, uint8_t **out, formats = ossl_prov_ctx_get_param( provctx, OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS, NULL); - fmt_slots = vp8_order(v->algorithm_name, codec->p8fmt, - "output", formats); + fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->algorithm_name, codec->p8fmt, + "output", formats); if (fmt_slots == NULL) return 0; diff --git a/providers/implementations/encode_decode/ml_kem_codecs.h b/providers/implementations/encode_decode/ml_kem_codecs.h index aa9e77e360d52..b8a22201ab171 100644 --- a/providers/implementations/encode_decode/ml_kem_codecs.h +++ b/providers/implementations/encode_decode/ml_kem_codecs.h @@ -11,76 +11,11 @@ # define PROV_ML_KEM_CODECS_H # pragma once -# include -# include "crypto/ml_kem.h" -# include "prov/provider_ctx.h" - - /*- - * The DER ASN.1 encoding of ML-KEM (and ML-DSA) public keys prepends 22 bytes - * to the encoded public key: - * - * - 4 byte outer sequence tag and length - * - 2 byte algorithm sequence tag and length - * - 2 byte algorithm OID tag and length - * - 9 byte algorithm OID (from NIST CSOR OID arc) - * - 4 byte bit string tag and length - * - 1 bitstring lead byte - */ -# define ML_KEM_SPKI_OVERHEAD 22 -typedef struct { - const uint8_t asn1_prefix[ML_KEM_SPKI_OVERHEAD]; -} ML_KEM_SPKI_FMT; - -/*- - * For each parameter set we support a few PKCS#8 input formats, three - * corresponding to the "either or both" variants of: - * - * ML-KEM-PrivateKey ::= CHOICE { - * seed [0] IMPLICIT OCTET STRING (SIZE (64)), - * expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)), - * both SEQUENCE { - * seed OCTET STRING (SIZE (64)), - * expandedKey OCTET STRING SIZE ((1632 | 2400 | 3168)) } } - * - * one more for a historical OQS encoding: - * - * - OQS private + public key: OCTET STRING - * (The public key is ignored, just as with PKCS#8 v2.) - * - * and two more that are the minimal IETF non-ASN.1 seed encoding: - * - * - Bare seed (just the 32 bytes) - * - Bare priv (just the key bytes) - * - * A length of zero means that particular field is absent. - * - * The p8_shift is 0 when the top-level tag+length occupy four bytes, 2 when - * they occupy two by†es, and 4 when no tag is used at all. - */ -typedef struct { - const char *p8_name; /* Format name */ - size_t p8_bytes; /* Total P8 encoding length */ - int p8_shift; /* 4 - (top-level tag + len) */ - uint32_t p8_magic; /* The tag + len value */ - uint16_t seed_magic; /* Interior tag + len for the seed */ - size_t seed_offset; /* Seed offset from start */ - size_t seed_length; /* Seed bytes */ - uint32_t priv_magic; /* Interior tag + len for the key */ - size_t priv_offset; /* Key offset from start */ - size_t priv_length; /* Key bytes */ - size_t pub_offset; /* Pubkey offset */ - size_t pub_length; /* Pubkey bytes */ -} ML_KEM_PKCS8_FMT; - -typedef struct { - const ML_KEM_SPKI_FMT *spkifmt; - const ML_KEM_PKCS8_FMT *p8fmt; -} ML_KEM_CODEC; - -typedef struct { - const ML_KEM_PKCS8_FMT *fmt; - int pref; -} ML_KEM_PKCS8_FMT_PREF; +# ifndef OPENSSL_NO_ML_KEM +# include +# include "crypto/ml_kem.h" +# include "prov/provider_ctx.h" +# include "ml_common_codecs.h" __owur ML_KEM_KEY *ossl_ml_kem_d2i_PUBKEY(const uint8_t *pubenc, int publen, @@ -100,4 +35,5 @@ __owur int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, unsigned char **out, PROV_CTX *provctx); +# endif /* OPENSSL_NO_ML_KEM */ #endif /* PROV_ML_KEM_CODECS_H */