Skip to content

Replace liboqs SPHINCS+ with SLH-DSA in certificate layer#10261

Open
Frauschi wants to merge 1 commit intowolfSSL:masterfrom
Frauschi:slh-dsa
Open

Replace liboqs SPHINCS+ with SLH-DSA in certificate layer#10261
Frauschi wants to merge 1 commit intowolfSSL:masterfrom
Frauschi:slh-dsa

Conversation

@Frauschi
Copy link
Copy Markdown
Contributor

Replace the liboqs-based pre-standardization SPHINCS+ implementation with the native FIPS 205 SLH-DSA implementation across the certificate/ASN.1/X.509 layers. All liboqs SPHINCS+ code is removed.

This enables SLH-DSA for certificate chain authentication (CA certificates signed with SLH-DSA, certificate verification). TLS 1.3 entity authentication via CertificateVerify with SLH-DSA will be added in a follow-up PR.

Follows RFC 9909 (X.509 Algorithm Identifiers for SLH-DSA).

Changes:

  • New DER codec for SLH-DSA (PrivateKeyDecode, PublicKeyDecode, KeyToDer, PrivateKeyToDer, PublicKeyToDer) with RFC 9909 compliant encoding (bare OCTET STRING, 4*n bytes = SK.seed || SK.prf || PK.seed || PK.root, no nested wrapper) and OID auto-detection across all six SHAKE parameter sets.
  • 12 standardized NIST OIDs (6 SHA2 + 6 SHAKE) per RFC 9909. OID collision mechanism cleaned up since NIST OIDs don't collide.
  • Complete ASN.1 layer replacement (~500 lines in asn.c) and X.509 public-key handling in x509.c.
  • SHA2-SLH-DSA OIDs are recognized but the native backend is SHAKE-only; Private/PublicKeyDecode and ConfirmSignature return NOT_COMPILED_IN for SHA2 variants so callers get an informative error instead of a generic parse failure.
  • wc_GetKeyOID selects its placeholder parameter from whatever SHAKE variant is compiled in, so auto-detect works when specific variants (e.g. SHAKE128F) are disabled.
  • wc_SlhDsaKey_PublicKeyDecode saves and restores key->params and *inOutIdx on ImportPublic failure, matching PrivateKeyDecode.
  • DER round-trip and on-disk decode tests (bench_slhdsa_shake*_key.der fixtures regenerated with wolfSSL's own encoder, guaranteed RFC 9909 compliant) guard against future encoding drift.
  • SLH-DSA test cert chain generated with OpenSSL 3.5.
  • DYNAMIC_TYPE_SPHINCS = 98 kept as RESERVED with a tombstone comment for ABI stability; new code should use DYNAMIC_TYPE_SLHDSA (107).
  • All build system / IDE project files updated; SPHINCS+ sources, headers, and test data removed.

If #9843 is merged ahead of this (probably the case), then this PR will be updated to properly incorporate the SHA2-based SLH-DSA variants.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 20, 2026

MemBrowse Memory Report

gcc-arm-cortex-m4

  • FLASH: .rodata.CSWTCH.742 +6 B, .text +64 B (+0.0%, 195,473 B / 262,144 B, total: 75% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .rodata.CSWTCH.670 +6 B (+0.0%, 63,545 B / 262,144 B, total: 24% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .rodata.CSWTCH.671 +6 B (+0.0%, 59,195 B / 262,144 B, total: 23% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .rodata.CSWTCH.724 +6 B (+0.0%, 119,488 B / 262,144 B, total: 46% used)

@Frauschi Frauschi force-pushed the slh-dsa branch 3 times, most recently from ff56841 to 0c02b5f Compare April 20, 2026 17:44
Replace the liboqs-based pre-standardization SPHINCS+ implementation
with the native FIPS 205 SLH-DSA implementation across the
certificate/ASN.1/X.509 layers. All liboqs SPHINCS+ code is removed.

This enables SLH-DSA for certificate chain authentication (CA
certificates signed with SLH-DSA, certificate verification). TLS 1.3
entity authentication via CertificateVerify with SLH-DSA will be added
in a follow-up PR.

Follows RFC 9909 (X.509 Algorithm Identifiers for SLH-DSA).

Changes:
- New DER codec for SLH-DSA (PrivateKeyDecode, PublicKeyDecode,
  KeyToDer, PrivateKeyToDer, PublicKeyToDer) with RFC 9909 compliant
  encoding (bare OCTET STRING, 4*n bytes = SK.seed || SK.prf ||
  PK.seed || PK.root, no nested wrapper) and OID auto-detection across
  all six SHAKE parameter sets.
- 12 standardized NIST OIDs (6 SHA2 + 6 SHAKE) per RFC 9909. OID
  collision mechanism cleaned up since NIST OIDs don't collide.
- Complete ASN.1 layer replacement (~500 lines in asn.c) and X.509
  public-key handling in x509.c.
- SHA2-SLH-DSA OIDs are recognized but the native backend is
  SHAKE-only; Private/PublicKeyDecode and ConfirmSignature return
  NOT_COMPILED_IN for SHA2 variants so callers get an informative
  error instead of a generic parse failure.
- wc_GetKeyOID selects its placeholder parameter from whatever SHAKE
  variant is compiled in, so auto-detect works when specific variants
  (e.g. SHAKE128F) are disabled.
- wc_SlhDsaKey_PublicKeyDecode saves and restores key->params and
  *inOutIdx on ImportPublic failure, matching PrivateKeyDecode.
- DER round-trip and on-disk decode tests (bench_slhdsa_shake*_key.der
  fixtures regenerated with wolfSSL's own encoder, guaranteed
  RFC 9909 compliant) guard against future encoding drift.
- SLH-DSA test cert chain generated with OpenSSL 3.5.
- DYNAMIC_TYPE_SPHINCS = 98 kept as RESERVED with a tombstone comment
  for ABI stability; new code should use DYNAMIC_TYPE_SLHDSA (107).
- All build system / IDE project files updated; SPHINCS+ sources,
  headers, and test data removed.
Copy link
Copy Markdown

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #10261

Scan targets checked: wolfcrypt-bugs, wolfssl-bugs, wolfssl-src
Failed targets: wolfcrypt-src

No new issues found in the changed files. ✅

Frauschi pushed a commit to Frauschi/wolfssl that referenced this pull request Apr 22, 2026
Fix pre-existing wc_Falcon_KeyToDer pubKeyLen typo: it was passing
FALCON_LEVELx_KEY_SIZE (secret-key size) as the pubKeyLen argument to
SetAsymKeyDer, producing DER with padding/junk bytes instead of the
real public key. Now passes FALCON_LEVELx_PUB_KEY_SIZE.

Restore the "Note for some CPUs smaller than 32 bit..." header comment
to the oid_sum.h generator so it survives regeneration. Was silently
dropped by the previous regen.

Make Falcon private-key decode accept both wire formats:

  * wc_Falcon_PrivateKeyDecode no longer routes the full DER back
    through parse_private_key's legacy OCTET(OCTET(priv||pub)) parser.
    After DecodeAsymKey extracts privKey and pubKey separately, either
    use them directly (RFC 5958, as oqs-provider emits) or split the
    concatenated priv||pub if the legacy double-OCTET wrapping is
    present.
  * ProcessBufferTryDecodeFalcon now auto-detects the level via the
    OID (by trying each level through wc_Falcon_PrivateKeyDecode),
    and falls back to wc_falcon_import_private_only only when the DER
    length actually matches a Falcon raw-blob size. The previous
    length-based level guess erroneously matched Falcon-1024 against
    an ML-DSA-65 seed-priv PKCS8, masking the correct Dilithium
    dispatch.

Minor: sweep "see mlkem.h" comments in Espressif user_settings.h
templates to "see wc_mlkem.h" to match the renamed header, and point
the INSTALL SPHINCS+ note at wolfSSL#10261 where the native SLH-DSA
replacement is landing.

Verified end-to-end against oqs-provider 0.10.0 on OpenSSL 3.0.13:
 * Four-way X.509 cert matrix (oqs<->wolfSSL, level 1 + 5) passes.
 * wolfSSL_CTX_use_PrivateKey_file loads both oqs-provider RFC 5958
   PEM keys and wolfSSL legacy-format DER bench keys.
 * make check passes with --enable-falcon --with-liboqs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants