Skip to content

SetAsymKeyDer/DecodeAsymKey missing BIT STRING unused-bits byte for OneAsymmetricKey publicKey field #10019

@MarkAtwood

Description

@MarkAtwood

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:

  1. Write side: the encoded DER omits the BIT STRING unused-bits prefix byte, producing non-conformant output.
  2. 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:

81 20 <32 bytes>

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:

81 21 00 <32 bytes>

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions