Merge pull request #10852 from stenslae/fix-mldsa-privkeydecode-no-asn1

Fix ML-DSA level auto-detection in WOLFSSL_MLDSA_NO_ASN1 builds
This commit is contained in:
Tobias Frauenschläger
2026-07-08 10:03:34 +02:00
committed by GitHub
4 changed files with 508 additions and 90 deletions
+9 -1
View File
@@ -225,7 +225,15 @@ jobs:
{"name": "mldsa-verify-only-dynamic-keys", "minutes": 1.2,
"configure": ["--disable-intelasm",
"--enable-dilithium=44,65,87,verify-only",
"CPPFLAGS=-DWOLFSSL_MLDSA_DYNAMIC_KEYS"]}
"CPPFLAGS=-DWOLFSSL_MLDSA_DYNAMIC_KEYS"]},
{"name": "mldsa-no-asn1-opensslextra", "minutes": 1.2,
"configure": ["--enable-intelasm", "--enable-sp-asm",
"--enable-dilithium=yes", "--enable-opensslextra",
"CPPFLAGS=-DWOLFSSL_MLDSA_NO_ASN1"]},
{"name": "mldsa-no-asn1-fips204-draft-opensslextra", "minutes": 1.2,
"configure": ["--enable-intelasm", "--enable-sp-asm",
"--enable-dilithium=yes,draft", "--enable-opensslextra",
"CPPFLAGS=-DWOLFSSL_MLDSA_NO_ASN1"]}
]
EOF
.github/scripts/parallel-make-check.py \
+364 -11
View File
@@ -18751,10 +18751,14 @@ static int test_wolfSSL_d2i_SSL_SESSION_bounds_check(void)
#ifndef NO_BIO
#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_MLDSA)
/* Verify wc_MlDsaKey auto detects the expected ML-DSA level from the OID
* in a SPKI / PKCS#8 DER buffer. Returns 0 on match. */
/* Guarded to match its callers' build conditions, else unused. */
#if !defined(WOLFSSL_MLDSA_NO_VERIFY) || \
(!defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_MLDSA_NO_SIGN))
/* Verify wc_MlDsaKey auto-detects expectedLevel from a SPKI/PKCS#8 DER
* buffer. If presetLevel is non-zero, set it on the key first to also
* exercise the match/mismatch check. Returns 0 on match. */
static int check_mldsa_der_level(const byte* der, word32 derSz,
byte expectedLevel, int isPrivate)
byte expectedLevel, int isPrivate, byte presetLevel)
{
wc_MlDsaKey key;
word32 idx = 0;
@@ -18764,15 +18768,31 @@ static int check_mldsa_der_level(const byte* der, word32 derSz,
(void)isPrivate;
#endif
if ((rc = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID)) != 0) {
rc = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID);
if (rc != 0) {
return rc;
}
#if defined(WOLFSSL_MLDSA_PRIVATE_KEY)
if (presetLevel != 0) {
rc = wc_MlDsaKey_SetParams(&key, presetLevel);
}
if (rc != 0) {
wc_MlDsaKey_Free(&key);
return rc;
}
#if defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1)
if (isPrivate) {
rc = wc_MlDsaKey_PrivateKeyDecode(&key, der, derSz, &idx);
}
else
#elif defined(WOLFSSL_MLDSA_PRIVATE_KEY) && defined(WOLFSSL_MLDSA_NO_ASN1)
if (isPrivate) {
/* No PrivateKeyDecode without ASN.1 support. */
rc = NOT_COMPILED_IN;
}
else
#endif
{
rc = wc_MlDsaKey_PublicKeyDecode(&key, der, derSz, &idx);
@@ -18790,6 +18810,292 @@ static int check_mldsa_der_level(const byte* der, word32 derSz,
return rc;
}
#endif /* !WOLFSSL_MLDSA_NO_VERIFY ||
* (!WOLFSSL_MLDSA_NO_ASN1 && !WOLFSSL_MLDSA_NO_SIGN) */
#if defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_NO_ML_DSA_44) && \
!defined(WOLFSSL_NO_ML_DSA_65) && !defined(WOLFSSL_MLDSA_NO_VERIFY)
/* Cover the level-preset match/mismatch branches in
* wc_MlDsaKey_PublicKeyDecode for WOLFSSL_MLDSA_NO_ASN1 builds. */
static int test_mldsa_der_level_preset(void)
{
EXPECT_DECLS;
/* Preset level matches the level encoded in the DER: succeeds. */
ExpectIntEQ(check_mldsa_der_level(mldsa44_pub_spki,
sizeof_mldsa44_pub_spki, WC_ML_DSA_44, 0, WC_ML_DSA_44), 0);
/* Preset level does not match the level encoded in the DER: rejected. */
ExpectIntEQ(check_mldsa_der_level(mldsa65_pub_spki,
sizeof_mldsa65_pub_spki, WC_ML_DSA_65, 0, WC_ML_DSA_44),
ASN_PARSE_E);
/* Private-key DER decode is rejected when ASN.1 support is compiled
* out. */
#if !defined(WOLFSSL_MLDSA_NO_SIGN)
ExpectIntEQ(check_mldsa_der_level(mldsa44_priv_only,
sizeof_mldsa44_priv_only, WC_ML_DSA_44, 1, 0), NOT_COMPILED_IN);
#endif
return EXPECT_RESULT();
}
#if defined(WOLFSSL_MLDSA_FIPS204_DRAFT)
/* Build a FIPS 204 draft SPKI from a standard one by swapping in the
* 11-byte draft OID and fixing up the SEQUENCE lengths, then decode it.
* Verifies the resulting level against expectedLevel. Returns the decode
* result, or -1 if decode succeeded but the level is wrong. */
static int check_mldsa_draft_der_level(const byte* base, word32 baseSz,
const byte* draftOid, byte expectedLevel, byte presetLevel)
{
wc_MlDsaKey key;
byte* der = NULL;
word32 derSz = baseSz + 2;
word32 idx = 0;
word32 outerLen = 0;
int rc;
/* All standard ML-DSA SPKIs in certs_test.h use this fixed header:
* SEQUENCE (0x30 0x82 hh ll) SEQUENCE (0x30 0x0B) OID (0x06 0x09 ...) */
if ((baseSz < 17) || (base[0] != 0x30) || (base[1] != 0x82) ||
(base[4] != 0x30) || (base[5] != 0x0B) || (base[6] != 0x06) ||
(base[7] != 0x09)) {
return BAD_FUNC_ARG;
}
der = (byte*)XMALLOC(derSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (der == NULL) {
return MEMORY_E;
}
/* Outer SEQUENCE grows by two bytes for the longer draft OID. */
outerLen = ((word32)base[2] << 8) + base[3] + 2;
der[0] = 0x30;
der[1] = 0x82;
der[2] = (byte)(outerLen >> 8);
der[3] = (byte)outerLen;
der[4] = 0x30; /* AlgorithmIdentifier SEQUENCE */
der[5] = 0x0D;
der[6] = 0x06; /* OID */
der[7] = 0x0B;
XMEMCPY(der + 8, draftOid, 11);
/* BIT STRING onward is unchanged. */
XMEMCPY(der + 19, base + 17, baseSz - 17);
rc = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID);
if (rc == 0) {
if (presetLevel != 0) {
rc = wc_MlDsaKey_SetParams(&key, presetLevel);
}
if (rc == 0) {
rc = wc_MlDsaKey_PublicKeyDecode(&key, der, derSz, &idx);
}
if ((rc == 0) && ((key.params == NULL) ||
(key.params->level != expectedLevel))) {
rc = -1;
}
wc_MlDsaKey_Free(&key);
}
XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER);
return rc;
}
/* Cover draft-OID auto-detection and preset match/mismatch in
* mldsa_oid_to_level(). */
static int test_mldsa_der_level_draft_oid(void)
{
EXPECT_DECLS;
static const byte draftOid44[] = {
0x2b, 0x06, 0x01, 0x04, 0x01, 0x02, 0x82, 0x0b, 0x0c, 0x04, 0x04
};
static const byte draftOid65[] = {
0x2b, 0x06, 0x01, 0x04, 0x01, 0x02, 0x82, 0x0b, 0x0c, 0x06, 0x05
};
#ifndef WOLFSSL_NO_ML_DSA_87
static const byte draftOid87[] = {
0x2b, 0x06, 0x01, 0x04, 0x01, 0x02, 0x82, 0x0b, 0x0c, 0x08, 0x07
};
#endif
/* Auto-detect the draft level from the draft OID. */
ExpectIntEQ(check_mldsa_draft_der_level(mldsa44_pub_spki,
sizeof_mldsa44_pub_spki, draftOid44, WC_ML_DSA_44_DRAFT, 0), 0);
ExpectIntEQ(check_mldsa_draft_der_level(mldsa65_pub_spki,
sizeof_mldsa65_pub_spki, draftOid65, WC_ML_DSA_65_DRAFT, 0), 0);
#ifndef WOLFSSL_NO_ML_DSA_87
ExpectIntEQ(check_mldsa_draft_der_level(mldsa87_pub_spki,
sizeof_mldsa87_pub_spki, draftOid87, WC_ML_DSA_87_DRAFT, 0), 0);
#endif
/* Preset draft level matches the draft OID: succeeds. */
ExpectIntEQ(check_mldsa_draft_der_level(mldsa44_pub_spki,
sizeof_mldsa44_pub_spki, draftOid44, WC_ML_DSA_44_DRAFT,
WC_ML_DSA_44_DRAFT), 0);
/* Preset standard level does not match a draft OID: rejected. */
ExpectIntEQ(check_mldsa_draft_der_level(mldsa44_pub_spki,
sizeof_mldsa44_pub_spki, draftOid44, WC_ML_DSA_44_DRAFT,
WC_ML_DSA_44), ASN_PARSE_E);
/* Preset draft level does not match a different draft OID: rejected. */
ExpectIntEQ(check_mldsa_draft_der_level(mldsa44_pub_spki,
sizeof_mldsa44_pub_spki, draftOid44, WC_ML_DSA_44_DRAFT,
WC_ML_DSA_65_DRAFT), ASN_PARSE_E);
/* Preset draft level does not match a standard (non-draft) OID:
* rejected. */
ExpectIntEQ(check_mldsa_der_level(mldsa44_pub_spki,
sizeof_mldsa44_pub_spki, WC_ML_DSA_44, 0, WC_ML_DSA_44_DRAFT),
ASN_PARSE_E);
return EXPECT_RESULT();
}
#endif /* WOLFSSL_MLDSA_FIPS204_DRAFT */
#endif /* WOLFSSL_MLDSA_NO_ASN1 && !WOLFSSL_NO_ML_DSA_44 &&
* !WOLFSSL_NO_ML_DSA_65 && !WOLFSSL_MLDSA_NO_VERIFY */
#if defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_NO_ML_DSA_44) && \
!defined(WOLFSSL_MLDSA_NO_VERIFY)
/* Copy mldsa44_pub_spki, apply a mutation and/or truncation to derSz, then
* decode. mutateOff < 0 means no byte mutation. Returns the decode result. */
static int mldsa_der_mutate_and_decode(int mutateOff, byte mutateVal,
word32 derSz)
{
byte der[sizeof(mldsa44_pub_spki)] = {0};
int rc;
/* Clamp so static analysis sees der's bounds respected. */
if (derSz > sizeof(der)) {
derSz = sizeof(der);
}
XMEMCPY(der, mldsa44_pub_spki, derSz);
if ((mutateOff >= 0) && ((word32)mutateOff < sizeof(der))) {
der[mutateOff] = mutateVal;
}
rc = check_mldsa_der_level(der, derSz, WC_ML_DSA_44, 0, 0);
return rc;
}
/* Malformed/truncated DER coverage for the WOLFSSL_MLDSA_NO_ASN1 SPKI
* parser's bounds checks and zero-length BIT STRING guard. */
static int test_mldsa_der_level_preset_malformed(void)
{
EXPECT_DECLS;
word32 fullSz = (word32)sizeof_mldsa44_pub_spki;
/* Empty buffer is rejected before any parsing is attempted. */
ExpectIntEQ(mldsa_der_mutate_and_decode(-1, 0, 0), BAD_FUNC_ARG);
/* Truncated buffers at various lengths, all rejected by
* mldsa_get_der_length()'s bounds check. */
ExpectIntEQ(mldsa_der_mutate_and_decode(-1, 0, 1), ASN_PARSE_E);
ExpectIntEQ(mldsa_der_mutate_and_decode(-1, 0, 4), ASN_PARSE_E);
ExpectIntEQ(mldsa_der_mutate_and_decode(-1, 0, 6), ASN_PARSE_E);
ExpectIntEQ(mldsa_der_mutate_and_decode(-1, 0, 10), ASN_PARSE_E);
ExpectIntEQ(mldsa_der_mutate_and_decode(-1, 0, 19), ASN_PARSE_E);
ExpectIntEQ(mldsa_der_mutate_and_decode(-1, 0, fullSz - 1), ASN_PARSE_E);
/* Wrong outer SEQUENCE tag. */
ExpectIntEQ(mldsa_der_mutate_and_decode(0, 0x31, fullSz), ASN_PARSE_E);
/* Wrong inner SEQUENCE (AlgorithmIdentifier) tag. */
ExpectIntEQ(mldsa_der_mutate_and_decode(4, 0x02, fullSz), ASN_PARSE_E);
/* Wrong OID tag. */
ExpectIntEQ(mldsa_der_mutate_and_decode(6, 0x04, fullSz), ASN_PARSE_E);
/* Wrong BIT STRING tag. */
ExpectIntEQ(mldsa_der_mutate_and_decode(17, 0x04, fullSz), ASN_PARSE_E);
/* Non-zero unused-bits byte in the BIT STRING. */
ExpectIntEQ(mldsa_der_mutate_and_decode(21, 0x01, fullSz), ASN_PARSE_E);
/* BIT STRING length claims more bytes than remain in the buffer. */
ExpectIntEQ(mldsa_der_mutate_and_decode(20, 0x7F, fullSz), ASN_PARSE_E);
/* Well-formed OID TLV whose content matches no known OID, exercising
* mldsa_oid_to_level()'s "no match" fallback. */
ExpectIntEQ(mldsa_der_mutate_and_decode(16, 0x00, fullSz), ASN_PARSE_E);
/* Zero-length BIT STRING (tag 0x03, length 0x00, no unused-bits byte).
* Without the length == 0 guard this reads one byte past the end. */
{
byte der[17];
der[0] = 0x30; /* outer SEQUENCE tag */
der[1] = 0x0F; /* outer length: inner SEQUENCE (13) + BIT STRING (2) */
der[2] = 0x30; /* inner SEQUENCE (AlgorithmIdentifier) tag */
der[3] = 0x0B; /* inner length: OID tag+length+content (11) */
der[4] = 0x06; /* OID tag */
der[5] = 0x09; /* OID length */
XMEMCPY(der + 6, mldsa44_pub_spki + 8, 9); /* OID content */
der[15] = 0x03; /* BIT STRING tag */
der[16] = 0x00; /* BIT STRING length 0 */
ExpectIntEQ(check_mldsa_der_level(der, sizeof(der), WC_ML_DSA_44,
0, 0), ASN_PARSE_E);
}
/* Outer SEQUENCE claims 4 bytes more than the BIT STRING fills; only
* the idx + length == outerEnd check at the end catches this. */
{
byte der[sizeof(mldsa44_pub_spki) + 4];
word32 outerLen;
XMEMCPY(der, mldsa44_pub_spki, sizeof(mldsa44_pub_spki));
XMEMSET(der + sizeof(mldsa44_pub_spki), 0, 4);
/* Outer SEQUENCE uses the long form (0x30 0x82 hh ll); inflate
* the declared length by 4 to claim the trailing padding as
* part of the outer SEQUENCE's content. */
outerLen = ((word32)der[2] << 8) + der[3] + 4;
der[2] = (byte)(outerLen >> 8);
der[3] = (byte)outerLen;
ExpectIntEQ(check_mldsa_der_level(der, sizeof(der), WC_ML_DSA_44,
0, 0), ASN_PARSE_E);
}
return EXPECT_RESULT();
}
/* Verify wc_MlDsaKey_PublicKeyDecode() honors a non-zero starting
* *inOutIdx and shifts the output index by the same offset, rather than
* assuming the DER starts at 0 or double-adding the offset. */
static int test_mldsa_der_level_nonzero_idx(void)
{
EXPECT_DECLS;
wc_MlDsaKey key;
byte der[4 + sizeof(mldsa44_pub_spki)];
word32 idx;
word32 baseIdx = 0;
int rc;
rc = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID);
ExpectIntEQ(rc, 0);
if (rc == 0) {
ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, mldsa44_pub_spki,
(word32)sizeof_mldsa44_pub_spki, &baseIdx), 0);
wc_MlDsaKey_Free(&key);
}
XMEMSET(der, 0xFF, 4);
XMEMCPY(der + 4, mldsa44_pub_spki, sizeof(mldsa44_pub_spki));
idx = 4;
rc = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID);
ExpectIntEQ(rc, 0);
if (rc == 0) {
ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, sizeof(der),
&idx), 0);
ExpectIntEQ((int)idx, (int)(baseIdx + 4));
wc_MlDsaKey_Free(&key);
}
return EXPECT_RESULT();
}
#endif /* WOLFSSL_MLDSA_NO_ASN1 && !WOLFSSL_NO_ML_DSA_44 &&
* !WOLFSSL_MLDSA_NO_VERIFY */
#endif /* OPENSSL_EXTRA && WOLFSSL_HAVE_MLDSA */
static int test_wolfSSL_d2i_PUBKEY(void)
@@ -18858,7 +19164,7 @@ defined(OPENSSL_EXTRA) && defined(WOLFSSL_DH_EXTRA)
ExpectNotNull(pkey = d2i_PUBKEY_bio(bio, NULL));
ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM);
ExpectIntEQ(check_mldsa_der_level(mldsa44_pub_spki,
sizeof_mldsa44_pub_spki, WC_ML_DSA_44, 0), 0);
sizeof_mldsa44_pub_spki, WC_ML_DSA_44, 0, 0), 0);
EVP_PKEY_free(pkey);
pkey = NULL;
#endif
@@ -18877,7 +19183,7 @@ defined(OPENSSL_EXTRA) && defined(WOLFSSL_DH_EXTRA)
ExpectNotNull(pkey = d2i_PUBKEY_bio(bio, NULL));
ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM);
ExpectIntEQ(check_mldsa_der_level(mldsa65_pub_spki,
sizeof_mldsa65_pub_spki, WC_ML_DSA_65, 0), 0);
sizeof_mldsa65_pub_spki, WC_ML_DSA_65, 0, 0), 0);
EVP_PKEY_free(pkey);
pkey = NULL;
#endif
@@ -18896,7 +19202,7 @@ defined(OPENSSL_EXTRA) && defined(WOLFSSL_DH_EXTRA)
ExpectNotNull(pkey = d2i_PUBKEY_bio(bio, NULL));
ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM);
ExpectIntEQ(check_mldsa_der_level(mldsa87_pub_spki,
sizeof_mldsa87_pub_spki, WC_ML_DSA_87, 0), 0);
sizeof_mldsa87_pub_spki, WC_ML_DSA_87, 0, 0), 0);
EVP_PKEY_free(pkey);
pkey = NULL;
#endif
@@ -19028,6 +19334,7 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
BIO_free(bio);
bio = NULL;
#ifndef WOLFSSL_MLDSA_NO_ASN1
/* ML-DSA-44 PrivateKey test (LAMPS PKCS#8 priv-only DER) */
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
ExpectIntGT(BIO_write(bio, mldsa44_priv_only,
@@ -19035,7 +19342,7 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
ExpectNotNull(pkey = d2i_PrivateKey_bio(bio, NULL));
ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM);
ExpectIntEQ(check_mldsa_der_level(mldsa44_priv_only,
sizeof_mldsa44_priv_only, WC_ML_DSA_44, 1), 0);
sizeof_mldsa44_priv_only, WC_ML_DSA_44, 1, 0), 0);
EVP_PKEY_free(pkey);
pkey = NULL;
BIO_free(bio);
@@ -19065,6 +19372,16 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
BIO_free(bio);
bio = NULL;
#endif
#else /* WOLFSSL_MLDSA_NO_ASN1 */
/* d2i_PrivateKey_bio must reject DER private keys without ASN.1
* support. */
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
ExpectIntGT(BIO_write(bio, mldsa44_priv_only,
sizeof_mldsa44_priv_only), 0);
ExpectNull(pkey = d2i_PrivateKey_bio(bio, NULL));
BIO_free(bio);
bio = NULL;
#endif /* !WOLFSSL_MLDSA_NO_ASN1 */
#endif
#if !defined(WOLFSSL_NO_ML_DSA_65)
@@ -19079,6 +19396,7 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
BIO_free(bio);
bio = NULL;
#ifndef WOLFSSL_MLDSA_NO_ASN1
/* ML-DSA-65 PrivateKey test (LAMPS PKCS#8 priv-only DER) */
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
ExpectIntGT(BIO_write(bio, mldsa65_priv_only,
@@ -19086,7 +19404,7 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
ExpectNotNull(pkey = d2i_PrivateKey_bio(bio, NULL));
ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM);
ExpectIntEQ(check_mldsa_der_level(mldsa65_priv_only,
sizeof_mldsa65_priv_only, WC_ML_DSA_65, 1), 0);
sizeof_mldsa65_priv_only, WC_ML_DSA_65, 1, 0), 0);
EVP_PKEY_free(pkey);
pkey = NULL;
BIO_free(bio);
@@ -19116,6 +19434,16 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
BIO_free(bio);
bio = NULL;
#endif
#else /* WOLFSSL_MLDSA_NO_ASN1 */
/* d2i_PrivateKey_bio must reject DER private keys without ASN.1
* support. */
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
ExpectIntGT(BIO_write(bio, mldsa65_priv_only,
sizeof_mldsa65_priv_only), 0);
ExpectNull(pkey = d2i_PrivateKey_bio(bio, NULL));
BIO_free(bio);
bio = NULL;
#endif /* !WOLFSSL_MLDSA_NO_ASN1 */
#endif
#if !defined(WOLFSSL_NO_ML_DSA_87)
@@ -19130,6 +19458,7 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
BIO_free(bio);
bio = NULL;
#ifndef WOLFSSL_MLDSA_NO_ASN1
/* ML-DSA-87 PrivateKey test (LAMPS PKCS#8 priv-only DER) */
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
ExpectIntGT(BIO_write(bio, mldsa87_priv_only,
@@ -19137,7 +19466,7 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
ExpectNotNull(pkey = d2i_PrivateKey_bio(bio, NULL));
ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM);
ExpectIntEQ(check_mldsa_der_level(mldsa87_priv_only,
sizeof_mldsa87_priv_only, WC_ML_DSA_87, 1), 0);
sizeof_mldsa87_priv_only, WC_ML_DSA_87, 1, 0), 0);
EVP_PKEY_free(pkey);
pkey = NULL;
BIO_free(bio);
@@ -19167,6 +19496,16 @@ static int test_wolfSSL_d2i_PrivateKeys_bio(void)
BIO_free(bio);
bio = NULL;
#endif
#else /* WOLFSSL_MLDSA_NO_ASN1 */
/* d2i_PrivateKey_bio must reject DER private keys without ASN.1
* support. */
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
ExpectIntGT(BIO_write(bio, mldsa87_priv_only,
sizeof_mldsa87_priv_only), 0);
ExpectNull(pkey = d2i_PrivateKey_bio(bio, NULL));
BIO_free(bio);
bio = NULL;
#endif /* !WOLFSSL_MLDSA_NO_ASN1 */
#endif
#endif /* WOLFSSL_HAVE_MLDSA && !NO_SIGN */
@@ -35428,6 +35767,20 @@ TEST_CASE testCases[] = {
TEST_DECL(test_wolfSSL_d2i_and_i2d_PublicKey_ecc),
#ifndef NO_BIO
TEST_DECL(test_wolfSSL_d2i_PUBKEY),
#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_MLDSA) && \
defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_NO_ML_DSA_44) && \
!defined(WOLFSSL_NO_ML_DSA_65) && !defined(WOLFSSL_MLDSA_NO_VERIFY)
TEST_DECL(test_mldsa_der_level_preset),
#if defined(WOLFSSL_MLDSA_FIPS204_DRAFT)
TEST_DECL(test_mldsa_der_level_draft_oid),
#endif
#endif
#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_MLDSA) && \
defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_NO_ML_DSA_44) && \
!defined(WOLFSSL_MLDSA_NO_VERIFY)
TEST_DECL(test_mldsa_der_level_preset_malformed),
TEST_DECL(test_mldsa_der_level_nonzero_idx),
#endif
#endif
TEST_DECL(test_wolfSSL_d2i_and_i2d_DSAparams),
TEST_DECL(test_wolfSSL_i2d_PrivateKey),
+10 -1
View File
@@ -980,6 +980,9 @@ static int d2iTryFalconKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem,
* size-keyed, so each level is tried in turn. DER input is decoded once,
* letting the decoder auto-detect the level from the OID.
*
* Under WOLFSSL_MLDSA_NO_ASN1 there is no PKCS#8 decoder, so a DER private
* key is always treated as not this key type; only raw bytes are accepted.
*
* @param [in, out] out On in, an EVP PKEY or NULL.
* On out, an EVP PKEY or NULL.
* @param [in] mem Memory containing key data.
@@ -1041,11 +1044,17 @@ static int d2iTryMlDsaKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem,
WC_FREE_VAR_EX(mldsa, NULL, DYNAMIC_TYPE_MLDSA);
return 0;
}
#if defined(WOLFSSL_MLDSA_PRIVATE_KEY)
#if defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1)
if (priv) {
rc = wc_MlDsaKey_PrivateKeyDecode(mldsa, mem, inSz, &keyIdx);
}
else
#elif defined(WOLFSSL_MLDSA_PRIVATE_KEY) && defined(WOLFSSL_MLDSA_NO_ASN1)
if (priv) {
/* No PrivateKeyDecode without ASN.1 support. */
rc = NOT_COMPILED_IN;
}
else
#endif
{
rc = wc_MlDsaKey_PublicKeyDecode(mldsa, mem, inSz, &keyIdx);
+125 -77
View File
@@ -12451,6 +12451,66 @@ static int mldsa_check_type(const byte* input, word32* inOutIdx, byte type,
return ret;
}
/* Map an AlgorithmIdentifier OID byte string to a security level.
* Used to auto-detect the level when it is not already set on the key. */
static int mldsa_oid_to_level(const byte* oid, word32 oidLen, byte* level)
{
int ret = WC_NO_ERR_TRACE(ASN_PARSE_E);
#if defined(WOLFSSL_MLDSA_FIPS204_DRAFT)
#ifndef WOLFSSL_NO_ML_DSA_44
if ((oidLen == sizeof(mldsa_oid_44)) &&
(XMEMCMP(oid, mldsa_oid_44, oidLen) == 0)) {
*level = WC_ML_DSA_44_DRAFT;
ret = 0;
}
#endif
#ifndef WOLFSSL_NO_ML_DSA_65
#ifndef WOLFSSL_NO_ML_DSA_44
else
#endif
if ((oidLen == sizeof(mldsa_oid_65)) &&
(XMEMCMP(oid, mldsa_oid_65, oidLen) == 0)) {
*level = WC_ML_DSA_65_DRAFT;
ret = 0;
}
#endif
#ifndef WOLFSSL_NO_ML_DSA_87
#if !defined(WOLFSSL_NO_ML_DSA_44) || !defined(WOLFSSL_NO_ML_DSA_65)
else
#endif
if ((oidLen == sizeof(mldsa_oid_87)) &&
(XMEMCMP(oid, mldsa_oid_87, oidLen) == 0)) {
*level = WC_ML_DSA_87_DRAFT;
ret = 0;
}
#endif
#endif /* WOLFSSL_MLDSA_FIPS204_DRAFT */
#ifndef WOLFSSL_NO_ML_DSA_44
if ((ret != 0) && (oidLen == sizeof(ml_dsa_oid_44)) &&
(XMEMCMP(oid, ml_dsa_oid_44, oidLen) == 0)) {
*level = WC_ML_DSA_44;
ret = 0;
}
#endif
#ifndef WOLFSSL_NO_ML_DSA_65
if ((ret != 0) && (oidLen == sizeof(ml_dsa_oid_65)) &&
(XMEMCMP(oid, ml_dsa_oid_65, oidLen) == 0)) {
*level = WC_ML_DSA_65;
ret = 0;
}
#endif
#ifndef WOLFSSL_NO_ML_DSA_87
if ((ret != 0) && (oidLen == sizeof(ml_dsa_oid_87)) &&
(XMEMCMP(oid, ml_dsa_oid_87, oidLen) == 0)) {
*level = WC_ML_DSA_87;
ret = 0;
}
#endif
return ret;
}
#endif /* WOLFSSL_MLDSA_NO_ASN1 */
/* Decode the DER encoded ML-DSA public key.
@@ -12466,13 +12526,13 @@ static int mldsa_check_type(const byte* input, word32* inOutIdx, byte type,
* input, the level will be detected from the DER
* file based on the algorithm OID, appropriately
* decoded, then updated in the key structure on
* output. Auto-detection of the security level is
* not supported if compiled for FIPS 204
* draft mode.
* output. Auto-detection is not supported for
* FIPS 204 draft mode unless WOLFSSL_MLDSA_NO_ASN1
* is also defined.
* @param [in] inSz Total size of data in array.
* @return 0 on success.
* @return BAD_FUNC_ARG when input, inOutIdx or key is NULL or inSz is 0.
* @return BAD_FUNC_ARG when level not set.
* @return BAD_FUNC_ARG when level not set and auto-detection unsupported.
* @return Other negative on parse error.
*/
int wc_MlDsaKey_PublicKeyDecode(wc_MlDsaKey* key, const byte* input,
@@ -12495,9 +12555,10 @@ int wc_MlDsaKey_PublicKeyDecode(wc_MlDsaKey* key, const byte* input,
int keyType = 0;
#else
int length = 0;
unsigned char* oid = NULL;
const unsigned char* oid = NULL;
word32 oidLen = 0;
word32 idx = 0;
word32 idx = *inOutIdx;
word32 outerEnd = 0;
#endif
/* Start again. */
@@ -12547,59 +12608,8 @@ int wc_MlDsaKey_PublicKeyDecode(wc_MlDsaKey* key, const byte* input,
}
}
#else
/* Get OID sum for level. */
#if defined(WOLFSSL_MLDSA_FIPS204_DRAFT)
if (key->params == NULL) {
ret = BAD_FUNC_ARG;
}
else
#ifndef WOLFSSL_NO_ML_DSA_44
if (key->params->level == WC_ML_DSA_44_DRAFT) {
oid = mldsa_oid_44;
oidLen = (word32)sizeof(mldsa_oid_44);
}
else
#endif
#ifndef WOLFSSL_NO_ML_DSA_65
if (key->params->level == WC_ML_DSA_65_DRAFT) {
oid = mldsa_oid_65;
oidLen = (word32)sizeof(mldsa_oid_65);
}
else
#endif
#ifndef WOLFSSL_NO_ML_DSA_87
if (key->params->level == WC_ML_DSA_87_DRAFT) {
oid = mldsa_oid_87;
oidLen = (word32)sizeof(mldsa_oid_87);
}
else
#endif
#endif
#ifndef WOLFSSL_NO_ML_DSA_44
if (key->level == WC_ML_DSA_44) {
oid = ml_dsa_oid_44;
oidLen = (word32)sizeof(ml_dsa_oid_44);
}
else
#endif
#ifndef WOLFSSL_NO_ML_DSA_65
if (key->level == WC_ML_DSA_65) {
oid = ml_dsa_oid_65;
oidLen = (word32)sizeof(ml_dsa_oid_65);
}
else
#endif
#ifndef WOLFSSL_NO_ML_DSA_87
if (key->level == WC_ML_DSA_87) {
oid = ml_dsa_oid_87;
oidLen = (word32)sizeof(ml_dsa_oid_87);
}
else
#endif
{
/* Level not set. */
ret = BAD_FUNC_ARG;
}
/* Parse: SEQUENCE { SEQUENCE { OID } BIT STRING }, detecting the
* level from the OID. */
if (ret == 0) {
ret = mldsa_check_type(input, &idx, 0x30, inSz);
}
@@ -12607,43 +12617,81 @@ int wc_MlDsaKey_PublicKeyDecode(wc_MlDsaKey* key, const byte* input,
ret = mldsa_get_der_length(input, &idx, &length, inSz);
}
if (ret == 0) {
ret = mldsa_check_type(input, &idx, 0x30, inSz);
}
if (ret == 0) {
ret = mldsa_get_der_length(input, &idx, &length, inSz);
}
if (ret == 0) {
ret = mldsa_check_type(input, &idx, 0x06, inSz);
}
if (ret == 0) {
ret = mldsa_get_der_length(input, &idx, &length, inSz);
}
if (ret == 0) {
if (((word32)length != oidLen) ||
(XMEMCMP(input + idx, oid, oidLen) != 0)) {
outerEnd = idx + (word32)length;
/* Reject trailing bytes after the outer SEQUENCE. */
if (outerEnd != inSz) {
ret = ASN_PARSE_E;
}
}
if (ret == 0) {
ret = mldsa_check_type(input, &idx, 0x30, outerEnd);
}
if (ret == 0) {
ret = mldsa_get_der_length(input, &idx, &length, outerEnd);
}
if (ret == 0) {
ret = mldsa_check_type(input, &idx, 0x06, outerEnd);
}
if (ret == 0) {
ret = mldsa_get_der_length(input, &idx, &length, outerEnd);
}
if (ret == 0) {
oid = input + idx;
oidLen = (word32)length;
idx += oidLen;
}
if (ret == 0) {
ret = mldsa_check_type(input, &idx, 0x03, inSz);
byte detectedLevel = 0;
byte curLevel = 0;
ret = mldsa_oid_to_level(oid, oidLen, &detectedLevel);
#if defined(WOLFSSL_MLDSA_FIPS204_DRAFT)
if ((ret == 0) && (key->params != NULL)) {
curLevel = key->params->level;
}
#else
if (ret == 0) {
curLevel = key->level;
}
#endif
if (ret == 0) {
if (curLevel == 0) {
/* Level not set by caller - use the detected one. */
ret = wc_MlDsaKey_SetParams(key, detectedLevel);
}
else if (curLevel != detectedLevel) {
ret = ASN_PARSE_E;
}
}
}
if (ret == 0) {
ret = mldsa_get_der_length(input, &idx, &length, inSz);
ret = mldsa_check_type(input, &idx, 0x03, outerEnd);
}
if (ret == 0) {
if ((input[idx] != 0) || (length == 0)) {
ret = mldsa_get_der_length(input, &idx, &length, outerEnd);
}
if ((ret == 0) && (length == 0)) {
/* Reject before reading input[idx] below, else a
* zero-length BIT STRING causes a one-byte over-read. */
ret = ASN_PARSE_E;
}
if (ret == 0) {
if (input[idx] != 0) {
ret = ASN_PARSE_E;
}
idx++;
length--;
}
if ((ret == 0) && ((idx + (word32)length) != outerEnd)) {
/* BIT STRING must exactly fill the outer SEQUENCE. */
ret = ASN_PARSE_E;
}
if (ret == 0) {
/* This is the raw point data compressed or uncompressed. */
pubKeyLen = (word32)length;
pubKey = input + idx;
*inOutIdx += idx;
*inOutIdx = idx;
}
#endif
if (ret == 0) {