Summary
SetAsymKeyDer and DecodeAsymKey in wolfcrypt/src/asn.c treat the optional publicKey field of OneAsymmetricKey (RFC 5958 §2) as raw bytes instead of as a BIT STRING. This causes:
- Write side: the encoded DER omits the BIT STRING unused-bits prefix byte, producing non-conformant output.
- Read side: when parsing DER from conformant implementations (BoringSSL, OpenSSL, ring), the unused-bits byte is not stripped, causing
wc_ed25519_import_private_key to receive 33 bytes instead of 32 and reject the key.
This affects Ed25519, Ed448, X25519, and X448 — all key types that go through SetAsymKeyDer/DecodeAsymKey.
RFC 5958 §2 definition
OneAsymmetricKey ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] Attributes OPTIONAL,
...,
[[2: publicKey [1] PublicKey OPTIONAL ]],
...
}
PublicKey ::= BIT STRING
The publicKey field is [1] IMPLICIT BIT STRING. Per X.690, IMPLICIT tagging replaces the tag but preserves the content encoding. BIT STRING content is defined as an unused-bits count byte followed by the bit string value (X.690 §8.6). So the correct DER encoding for a 32-byte Ed25519 public key is:
81 21 00 <32 bytes>
^^ ^^ unused-bits byte (always 00 for byte-aligned keys)
| ^^ length = 33 (1 unused-bits byte + 32 key bytes)
| tag = context-specific, primitive, tag number 1
What wolfSSL produces (incorrect)
SetAsymKeyDer (line ~38719, non-template path) writes:
idx += SetHeader(ASN_CONTEXT_SPECIFIC | ASN_ASYMKEY_PUBKEY,
pubKeyLen, output + idx, 0);
XMEMCPY(output + idx, pubKey, pubKeyLen);
Where pubKeyLen is ED25519_PUB_KEY_SIZE (32). This produces:
The unused-bits byte 00 is missing. This is not valid BIT STRING content per X.690 §8.6.
What conformant implementations produce
BoringSSL, OpenSSL, and ring all produce the correct encoding:
Read-side failure
When DecodeAsymKey parses DER produced by BoringSSL/OpenSSL/ring, it extracts the raw content of the [1] field (33 bytes: 00 + 32-byte key) and passes it as-is to wc_ed25519_import_private_key:
ret = wc_ed25519_import_private_key(privKey, privKeyLen,
pubKey, pubKeyLen, key);
With pubKeyLen = 33, this fails because Ed25519 public keys are exactly 32 bytes. The result is that wolfSSL cannot import Ed25519 PKCS#8 v2 (OneAsymmetricKey) keys produced by other implementations.
Suggested fix
Write side (SetAsymKeyDer): insert a 0x00 unused-bits byte before the public key data:
if (pubKey) {
idx += SetHeader(ASN_CONTEXT_SPECIFIC | ASN_ASYMKEY_PUBKEY,
1 + pubKeyLen, output + idx, 0); /* +1 for unused-bits byte */
output[idx++] = 0x00; /* BIT STRING unused-bits */
XMEMCPY(output + idx, pubKey, pubKeyLen);
idx += pubKeyLen;
}
Read side (DecodeAsymKey): after extracting the [1] field content, strip the leading unused-bits byte before returning pubKey/pubKeyLen.
The same fix applies to the ASN template code path.
Reproduction
/* Encode an Ed25519 key with public key, then try to decode the
* same format that BoringSSL produces */
ed25519_key key;
wc_ed25519_init(&key);
wc_ed25519_make_key(&rng, 32, &key);
/* wolfSSL encodes: 81 20 <32 bytes> (wrong) */
byte der[256];
int len = wc_Ed25519KeyToDer(&key, der, sizeof(der));
/* BoringSSL-format DER with: 81 21 00 <32 bytes> (correct per RFC) */
/* wc_Ed25519PrivateKeyDecode fails on this with error -173 */
Affected versions
Tested against current master branch (commit at HEAD of https://github.com/wolfSSL/wolfssl).
References
Summary
SetAsymKeyDerandDecodeAsymKeyinwolfcrypt/src/asn.ctreat the optionalpublicKeyfield of OneAsymmetricKey (RFC 5958 §2) as raw bytes instead of as a BIT STRING. This causes:wc_ed25519_import_private_keyto receive 33 bytes instead of 32 and reject the key.This affects Ed25519, Ed448, X25519, and X448 — all key types that go through
SetAsymKeyDer/DecodeAsymKey.RFC 5958 §2 definition
The
publicKeyfield is[1] IMPLICIT BIT STRING. Per X.690, IMPLICIT tagging replaces the tag but preserves the content encoding. BIT STRING content is defined as an unused-bits count byte followed by the bit string value (X.690 §8.6). So the correct DER encoding for a 32-byte Ed25519 public key is:What wolfSSL produces (incorrect)
SetAsymKeyDer(line ~38719, non-template path) writes:Where
pubKeyLenisED25519_PUB_KEY_SIZE(32). This produces:The unused-bits byte
00is missing. This is not valid BIT STRING content per X.690 §8.6.What conformant implementations produce
BoringSSL, OpenSSL, and ring all produce the correct encoding:
Read-side failure
When
DecodeAsymKeyparses DER produced by BoringSSL/OpenSSL/ring, it extracts the raw content of the[1]field (33 bytes:00+ 32-byte key) and passes it as-is towc_ed25519_import_private_key:With
pubKeyLen = 33, this fails because Ed25519 public keys are exactly 32 bytes. The result is that wolfSSL cannot import Ed25519 PKCS#8 v2 (OneAsymmetricKey) keys produced by other implementations.Suggested fix
Write side (
SetAsymKeyDer): insert a0x00unused-bits byte before the public key data:Read side (
DecodeAsymKey): after extracting the[1]field content, strip the leading unused-bits byte before returningpubKey/pubKeyLen.The same fix applies to the ASN template code path.
Reproduction
Affected versions
Tested against current
masterbranch (commit at HEAD of https://github.com/wolfSSL/wolfssl).References