mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-09 14:20:51 +02:00
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:
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user