@@ -7352,132 +7352,164 @@ static int slhdsa_param_to_keytype(enum SlhDsaParam param)
73527352 }
73537353}
73547354
7355- /* Decode a DER-encoded SLH-DSA private key.
7355+ /* Map OID key type back to SlhDsaParam. */
7356+ static int slhdsa_keytype_to_param (int keytype )
7357+ {
7358+ switch (keytype ) {
7359+ #ifndef WOLFSSL_SLHDSA_NO_128
7360+ #ifndef WOLFSSL_SLHDSA_NO_SMALL
7361+ case SLH_DSA_SHAKE_128Sk : return SLHDSA_SHAKE128S ;
7362+ #endif
7363+ #ifndef WOLFSSL_SLHDSA_NO_FAST
7364+ case SLH_DSA_SHAKE_128Fk : return SLHDSA_SHAKE128F ;
7365+ #endif
7366+ #endif
7367+ #ifndef WOLFSSL_SLHDSA_NO_192
7368+ #ifndef WOLFSSL_SLHDSA_NO_SMALL
7369+ case SLH_DSA_SHAKE_192Sk : return SLHDSA_SHAKE192S ;
7370+ #endif
7371+ #ifndef WOLFSSL_SLHDSA_NO_FAST
7372+ case SLH_DSA_SHAKE_192Fk : return SLHDSA_SHAKE192F ;
7373+ #endif
7374+ #endif
7375+ #ifndef WOLFSSL_SLHDSA_NO_256
7376+ #ifndef WOLFSSL_SLHDSA_NO_SMALL
7377+ case SLH_DSA_SHAKE_256Sk : return SLHDSA_SHAKE256S ;
7378+ #endif
7379+ #ifndef WOLFSSL_SLHDSA_NO_FAST
7380+ case SLH_DSA_SHAKE_256Fk : return SLHDSA_SHAKE256F ;
7381+ #endif
7382+ #endif
7383+ default :
7384+ return BAD_FUNC_ARG ;
7385+ }
7386+ }
7387+
7388+ /* Find SlhDsaParameters entry for a given param enum. */
7389+ static const SlhDsaParameters * slhdsa_find_params (enum SlhDsaParam param )
7390+ {
7391+ int i ;
7392+ for (i = 0 ; i < SLHDSA_PARAM_LEN ; i ++ ) {
7393+ if (SlhDsaParams [i ].param == param ) {
7394+ return & SlhDsaParams [i ];
7395+ }
7396+ }
7397+ return NULL ;
7398+ }
7399+
7400+ /* Decode a DER-encoded SLH-DSA private key (PKCS#8 / OneAsymmetricKey).
7401+ *
7402+ * RFC 9909 Section 6: The privateKey OCTET STRING contains the raw
7403+ * concatenation SK.seed || SK.prf || PK.seed || PK.root (4*n bytes)
7404+ * directly, without a nested OCTET STRING wrapper. This differs from
7405+ * Ed25519/Ed448 which wrap the key in an additional OCTET STRING.
73567406 *
7357- * The parameter set is detected from the OID in the DER encoding. On
7358- * success, key->params is updated to match the detected parameter set.
7359- * The key must be initialized (via wc_SlhDsaKey_Init) before calling this
7360- * function; the initial parameter is used only as a placeholder.
7407+ * The parameter set is detected from the AlgorithmIdentifier OID.
7408+ * On success, key->params is updated to match the detected parameter set.
73617409 *
73627410 * @param [in] input DER-encoded key data.
73637411 * @param [in, out] inOutIdx Index into input, updated on return.
73647412 * @param [in, out] key SLH-DSA key. Parameter set is auto-detected.
73657413 * @param [in] inSz Size of input in bytes.
73667414 * @return 0 on success.
73677415 * @return BAD_FUNC_ARG when input, inOutIdx, or key is NULL.
7368- * @return ASN_PARSE_E when OID does not match any supported SLH-DSA param .
7416+ * @return ASN_PARSE_E when the DER cannot be parsed as an SLH-DSA key .
73697417 */
73707418int wc_SlhDsaKey_PrivateKeyDecode (const byte * input , word32 * inOutIdx ,
73717419 SlhDsaKey * key , word32 inSz )
73727420{
7373- int ret = ASN_PARSE_E ;
7374- byte privKey [WC_SLHDSA_MAX_PRIV_LEN ];
7375- byte pubKey [WC_SLHDSA_MAX_PUB_LEN ];
7376- word32 privKeyLen = 0 ;
7377- word32 pubKeyLen = 0 ;
7378- word32 savedIdx ;
7379- int i ;
7380- const SlhDsaParameters * matched = NULL ;
7421+ int ret = 0 ;
7422+ int length ;
7423+ int version ;
7424+ word32 oid = 0 ;
7425+ int privSz ;
7426+ int paramId ;
7427+ const SlhDsaParameters * params ;
73817428
73827429 if ((input == NULL ) || (inOutIdx == NULL ) || (key == NULL ) || (inSz == 0 )) {
73837430 return BAD_FUNC_ARG ;
73847431 }
73857432
7386- savedIdx = * inOutIdx ;
7433+ /* Parse PKCS#8 OneAsymmetricKey wrapper:
7434+ * SEQUENCE { version, AlgorithmIdentifier { OID }, OCTET STRING { key } }
7435+ */
7436+ if (GetSequence (input , inOutIdx , & length , inSz ) < 0 ) {
7437+ return ASN_PARSE_E ;
7438+ }
73877439
7388- /* Try each compiled-in parameter set until one matches the OID in the
7389- * DER encoding. DecodeAsymKey validates the OID against the requested
7390- * keytype, so a mismatch returns an error without consuming input. */
7391- for (i = 0 ; i < SLHDSA_PARAM_LEN ; i ++ ) {
7392- int keytype = slhdsa_param_to_keytype (SlhDsaParams [i ].param );
7393- word32 idx = savedIdx ;
7394- word32 pkLen = (word32 )sizeof (privKey );
7395- word32 pbLen = (word32 )sizeof (pubKey );
7440+ if (GetMyVersion (input , inOutIdx , & version , inSz ) < 0 ) {
7441+ return ASN_PARSE_E ;
7442+ }
7443+ if (version != 0 ) {
7444+ return ASN_PARSE_E ;
7445+ }
73967446
7397- if ( keytype < 0 ) {
7398- continue ;
7399- }
7447+ if ( GetAlgoId ( input , inOutIdx , & oid , oidKeyType , inSz ) < 0 ) {
7448+ return ASN_PARSE_E ;
7449+ }
74007450
7401- ret = DecodeAsymKey (input , & idx , inSz , privKey , & pkLen ,
7402- pubKey , & pbLen , keytype );
7403- if (ret == 0 ) {
7404- /* OID matched - record detected parameter set. */
7405- matched = & SlhDsaParams [i ];
7406- * inOutIdx = idx ;
7407- privKeyLen = pkLen ;
7408- pubKeyLen = pbLen ;
7409- break ;
7410- }
7451+ /* Map the OID to an SLH-DSA parameter set. */
7452+ paramId = slhdsa_keytype_to_param ((int )oid );
7453+ if (paramId < 0 ) {
7454+ return ASN_PARSE_E ;
7455+ }
7456+ params = slhdsa_find_params ((enum SlhDsaParam )paramId );
7457+ if (params == NULL ) {
7458+ return ASN_PARSE_E ;
74117459 }
74127460
7413- if (ret == 0 && matched != NULL ) {
7414- /* Point the key at the detected parameter set so subsequent import
7415- * and size queries use the right values. */
7416- key -> params = matched ;
7461+ /* RFC 9909: privateKey is a bare OCTET STRING containing the raw key
7462+ * (4*n bytes). No nested OCTET STRING wrapping. */
7463+ if (GetOctetString (input , inOutIdx , & privSz , inSz ) < 0 ) {
7464+ return ASN_PARSE_E ;
7465+ }
74177466
7418- if (pubKeyLen == 0 ) {
7419- ret = wc_SlhDsaKey_ImportPrivate (key , privKey , privKeyLen );
7420- }
7421- else {
7422- /* Private key includes public key in SLH-DSA: reconstruct full
7423- * key = sk_seed || sk_prf || pk_seed || pk_root */
7424- int n = key -> params -> n ;
7425- if ((int )privKeyLen == n * 4 ) {
7426- /* Full private key already includes public portion. */
7427- ret = wc_SlhDsaKey_ImportPrivate (key , privKey , privKeyLen );
7428- }
7429- else if ((int )privKeyLen == n * 2 && (int )pubKeyLen == n * 2 ) {
7430- /* sk_seed || sk_prf separate from pk_seed || pk_root. */
7431- byte combined [WC_SLHDSA_MAX_PRIV_LEN ];
7432- XMEMCPY (combined , privKey , privKeyLen );
7433- XMEMCPY (combined + privKeyLen , pubKey , pubKeyLen );
7434- ret = wc_SlhDsaKey_ImportPrivate (key , combined ,
7435- privKeyLen + pubKeyLen );
7436- ForceZero (combined , sizeof (combined ));
7437- }
7438- else {
7439- ret = BAD_FUNC_ARG ;
7440- }
7441- }
7467+ if (privSz != params -> n * 4 ) {
7468+ return ASN_PARSE_E ;
7469+ }
7470+
7471+ /* Update the key's parameter set to the detected one. */
7472+ key -> params = params ;
7473+
7474+ /* Import the raw private key: SK.seed || SK.prf || PK.seed || PK.root */
7475+ ret = wc_SlhDsaKey_ImportPrivate (key , input + * inOutIdx , (word32 )privSz );
7476+ if (ret == 0 ) {
7477+ * inOutIdx += (word32 )privSz ;
74427478 }
74437479
7444- ForceZero (privKey , sizeof (privKey ));
74457480 return ret ;
74467481}
74477482
7448- /* Decode a DER-encoded SLH-DSA public key.
7483+ /* Decode a DER-encoded SLH-DSA public key (SubjectPublicKeyInfo) .
74497484 *
7450- * The parameter set is detected from the OID in the DER encoding. On
7451- * success, key->params is updated to match the detected parameter set.
7485+ * The parameter set is detected from the AlgorithmIdentifier OID.
7486+ * On success, key->params is updated to match the detected parameter set.
74527487 *
74537488 * @param [in] input DER-encoded key data.
74547489 * @param [in, out] inOutIdx Index into input, updated on return.
74557490 * @param [in, out] key SLH-DSA key. Parameter set is auto-detected.
74567491 * @param [in] inSz Size of input in bytes.
74577492 * @return 0 on success.
74587493 * @return BAD_FUNC_ARG when input, inOutIdx, or key is NULL.
7459- * @return ASN_PARSE_E when OID does not match any supported SLH-DSA param .
7494+ * @return ASN_PARSE_E when the DER cannot be parsed as an SLH-DSA key .
74607495 */
74617496int wc_SlhDsaKey_PublicKeyDecode (const byte * input , word32 * inOutIdx ,
74627497 SlhDsaKey * key , word32 inSz )
74637498{
74647499 int ret = ASN_PARSE_E ;
74657500 byte pubKey [WC_SLHDSA_MAX_PUB_LEN ];
74667501 word32 pubKeyLen = 0 ;
7467- word32 savedIdx ;
74687502 int i ;
74697503 const SlhDsaParameters * matched = NULL ;
74707504
74717505 if ((input == NULL ) || (inOutIdx == NULL ) || (key == NULL ) || (inSz == 0 )) {
74727506 return BAD_FUNC_ARG ;
74737507 }
74747508
7475- savedIdx = * inOutIdx ;
7476-
7477- /* Try each compiled-in parameter set until one matches the OID. */
7509+ /* Try each compiled-in parameter set against the OID in the SPKI. */
74787510 for (i = 0 ; i < SLHDSA_PARAM_LEN ; i ++ ) {
74797511 int keytype = slhdsa_param_to_keytype (SlhDsaParams [i ].param );
7480- word32 idx = savedIdx ;
7512+ word32 idx = * inOutIdx ;
74817513 word32 pkLen = (word32 )sizeof (pubKey );
74827514
74837515 if (keytype < 0 ) {
@@ -7538,18 +7570,27 @@ int wc_SlhDsaKey_PublicKeyToDer(SlhDsaKey* key, byte* output, word32 inLen,
75387570 return ret ;
75397571}
75407572
7541- /* Encode an SLH-DSA key (private + public) to DER.
7573+ /* Encode an SLH-DSA private key to DER (PKCS#8 / OneAsymmetricKey).
7574+ *
7575+ * RFC 9909: The privateKey OCTET STRING contains the raw 4*n bytes
7576+ * (SK.seed || SK.prf || PK.seed || PK.root) directly, without a nested
7577+ * OCTET STRING wrapper. This differs from Ed25519/Ed448 which use a
7578+ * double OCTET STRING wrapping.
7579+ *
7580+ * Pass NULL for output to get the required buffer size.
75427581 *
75437582 * @param [in] key SLH-DSA key object.
7544- * @param [out] output Buffer to put encoded data in.
7583+ * @param [out] output Buffer to put encoded data in (or NULL for size) .
75457584 * @param [in] inLen Size of buffer in bytes.
75467585 * @return Size of encoded data in bytes on success.
75477586 * @return BAD_FUNC_ARG when key is NULL.
7587+ * @return BUFFER_E when output buffer is too small.
75487588 */
75497589int wc_SlhDsaKey_KeyToDer (SlhDsaKey * key , byte * output , word32 inLen )
75507590{
75517591 int keytype ;
75527592 int n ;
7593+ word32 privSz , algoSz , verSz , seqSz , sz ;
75537594
75547595 if ((key == NULL ) || (key -> params == NULL )) {
75557596 return BAD_FUNC_ARG ;
@@ -7561,24 +7602,48 @@ int wc_SlhDsaKey_KeyToDer(SlhDsaKey* key, byte* output, word32 inLen)
75617602 }
75627603
75637604 n = key -> params -> n ;
7564- /* Private key portion: sk_seed || sk_prf (first 2*n bytes)
7565- * Public key portion: pk_seed || pk_root (last 2*n bytes) */
7566- return SetAsymKeyDer (key -> sk , (word32 )(n * 2 ), key -> sk + n * 2 ,
7567- (word32 )(n * 2 ), output , inLen , keytype );
7605+ /* RFC 9909: bare OCTET STRING containing 4*n raw key bytes */
7606+ privSz = SetOctetString ((word32 )(n * 4 ), NULL ) + (word32 )(n * 4 );
7607+ algoSz = SetAlgoID (keytype , NULL , oidKeyType , 0 );
7608+ verSz = 3 ;
7609+ seqSz = SetSequence (verSz + algoSz + privSz , NULL );
7610+ sz = seqSz + verSz + algoSz + privSz ;
7611+
7612+ if (output == NULL ) {
7613+ return (int )sz ;
7614+ }
7615+ if (sz > inLen ) {
7616+ return BUFFER_E ;
7617+ }
7618+
7619+ {
7620+ word32 idx = 0 ;
7621+ idx += SetSequence (verSz + algoSz + privSz , output + idx );
7622+ SetMyVersion (0 , output + idx , FALSE);
7623+ idx += verSz ;
7624+ idx += SetAlgoID (keytype , output + idx , oidKeyType , 0 );
7625+ idx += SetOctetString ((word32 )(n * 4 ), output + idx );
7626+ XMEMCPY (output + idx , key -> sk , (word32 )(n * 4 ));
7627+ idx += (word32 )(n * 4 );
7628+ return (int )idx ;
7629+ }
75687630}
75697631
7570- /* Encode an SLH-DSA private key only to DER.
7632+ /* Encode an SLH-DSA private key only (sk_seed || sk_prf) to DER.
7633+ * Same PKCS#8 format but with only the secret portion (2*n bytes).
75717634 *
75727635 * @param [in] key SLH-DSA key object.
7573- * @param [out] output Buffer to put encoded data in.
7636+ * @param [out] output Buffer to put encoded data in (or NULL for size) .
75747637 * @param [in] inLen Size of buffer in bytes.
75757638 * @return Size of encoded data in bytes on success.
75767639 * @return BAD_FUNC_ARG when key is NULL.
7640+ * @return BUFFER_E when output buffer is too small.
75777641 */
75787642int wc_SlhDsaKey_PrivateKeyToDer (SlhDsaKey * key , byte * output , word32 inLen )
75797643{
75807644 int keytype ;
75817645 int n ;
7646+ word32 privSz , algoSz , verSz , seqSz , sz ;
75827647
75837648 if ((key == NULL ) || (key -> params == NULL )) {
75847649 return BAD_FUNC_ARG ;
@@ -7590,8 +7655,30 @@ int wc_SlhDsaKey_PrivateKeyToDer(SlhDsaKey* key, byte* output, word32 inLen)
75907655 }
75917656
75927657 n = key -> params -> n ;
7593- return SetAsymKeyDer (key -> sk , (word32 )(n * 2 ), NULL , 0 , output , inLen ,
7594- keytype );
7658+ privSz = SetOctetString ((word32 )(n * 2 ), NULL ) + (word32 )(n * 2 );
7659+ algoSz = SetAlgoID (keytype , NULL , oidKeyType , 0 );
7660+ verSz = 3 ;
7661+ seqSz = SetSequence (verSz + algoSz + privSz , NULL );
7662+ sz = seqSz + verSz + algoSz + privSz ;
7663+
7664+ if (output == NULL ) {
7665+ return (int )sz ;
7666+ }
7667+ if (sz > inLen ) {
7668+ return BUFFER_E ;
7669+ }
7670+
7671+ {
7672+ word32 idx = 0 ;
7673+ idx += SetSequence (verSz + algoSz + privSz , output + idx );
7674+ SetMyVersion (0 , output + idx , FALSE);
7675+ idx += verSz ;
7676+ idx += SetAlgoID (keytype , output + idx , oidKeyType , 0 );
7677+ idx += SetOctetString ((word32 )(n * 2 ), output + idx );
7678+ XMEMCPY (output + idx , key -> sk , (word32 )(n * 2 ));
7679+ idx += (word32 )(n * 2 );
7680+ return (int )idx ;
7681+ }
75957682}
75967683#endif /* WC_ENABLE_ASYM_KEY_EXPORT */
75977684
0 commit comments