ADR 0014: SubjectPublicKeyInfo / PKCS#8 encoding with placeholder OIDs (preview)
Status: Accepted (preview)
Context
The v1 library serializes hybrid keys as a versioned, fixed-size byte blob with an algorithm-id byte (see SPEC and ADR 0007). That format is simple and self-describing for callers that stay inside the PostQuantum.Hybrid ecosystem.
But many downstream stores expect ASN.1-framed material:
- Embedding a public key inside an X.509 certificate template needs a
SubjectPublicKeyInfo. - Persisting a private key in a PKCS#8-aware key store needs a
PrivateKeyInfo. - Some KMS / HSM APIs round-trip via PKCS#8 only.
The IETF LAMPS WG has drafts (draft-ietf-lamps-pq-composite-kem and
draft-ietf-lamps-pq-composite-sig) that will eventually assign
official OIDs for hybrid composite constructions. Those OIDs are not
yet final; both drafts are still being revised.
Decision
Ship the framing now, OIDs as preview placeholders:
HybridKemPublicKey.ExportSubjectPublicKeyInfo()andImportSubjectPublicKeyInfo(ReadOnlySpan<byte>)round-trip the v1 wire blob inside a valid X.509SubjectPublicKeyInfoenvelope.HybridKemPrivateKey.ExportPkcs8PrivateKey()andImportPkcs8PrivateKey(ReadOnlySpan<byte>)round-trip the v1 wire blob inside a valid PKCS#8PrivateKeyInfoenvelope.- Same four methods on
HybridSignaturePublicKey/HybridSignaturePrivateKey.
The algorithm OIDs live under the IANA Example PEN
1.3.6.1.4.1.32473 (RFC 5612). Specifically:
| Construction | OID |
|---|---|
| Hybrid KEM X25519+ML-KEM-768, HKDF-SHA256 combiner | 1.3.6.1.4.1.32473.1.1.1 |
| Hybrid KEM X25519+ML-KEM-768, X-Wing combiner | 1.3.6.1.4.1.32473.1.1.2 |
| Hybrid signature Ed25519+ML-DSA-65 | 1.3.6.1.4.1.32473.1.2.1 |
The inner key material inside the BIT STRING (SPKI) or OCTET STRING
(PKCS#8) is the existing PostQuantum.Hybrid wire format, byte-for-byte.
Decoding therefore reduces to "read the inner bytes, hand them to the
existing Import path."
Rationale
- Structurally valid framing matters. A consumer that just needs
to embed a hybrid public key inside an X.509 extension or an
encrypted PKCS#8 store does not need IETF-allocated OIDs to do so —
any registered OID works. RFC 5612 explicitly carved out
1.3.6.1.4.1.32473for exactly this purpose. - The encoder is
System.Formats.Asn1-based. No new dependency, no hand-written ASN.1 parser, and the output passes through third-party DER decoders (the test suite confirms this againstAsnReaderdirectly). - Replacement is a non-issue when LAMPS finalizes. The OID
constants live in
Internal.Pkcs8SpkiCodec; flipping them and bumping the algorithm-id list is a single-file change. The wire shape of the inner bytes does not change.
Consequences
- New public surface on each of the four key types:
ExportSubjectPublicKeyInfo()/ImportSubjectPublicKeyInfo()on public-key types;ExportPkcs8PrivateKey()/ImportPkcs8PrivateKey()on private-key types. - XML docs explicitly call out the preview status and the placeholder OID story.
- When LAMPS finalizes, the OIDs change and existing blobs at the preview OIDs continue to decode (the codec accepts the placeholder OIDs unconditionally; the IETF-final OIDs will be added as additional accepted values to preserve backward compatibility for early adopters).
- The inner bytes are deliberately the existing wire format including
the algorithm-id byte. That keeps
Import(rawBytes)andImport...Info(envelope)semantically identical once the envelope is unwrapped. - Tests cover: KEM v1 + X-Wing SPKI round-trips, KEM PKCS#8 round-trip
with semantic encap/decap, signature SPKI + PKCS#8 round-trips,
rejection of a wrong-OID SPKI (we use RSA's OID as a non-match
fixture), and direct
AsnReaderparse to confirm the emitted bytes are DER-valid.