Skip to content

Import and export EC private keys from NSS - #297

Open
LinuxJedi wants to merge 2 commits into
wolfSSL:masterfrom
LinuxJedi:nss-ec-key-import
Open

LinuxJedi wants to merge 2 commits into
wolfSSL:masterfrom
LinuxJedi:nss-ec-key-import

Conversation

@LinuxJedi

@LinuxJedi LinuxJedi commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

EC private keys could not be imported through NSS. pk12util and Firefox failed on every EC PKCS#12 file, PK11_ImportDERPrivateKeyInfo failed for EC keys (Firefox WebCrypto importKey('pkcs8'), for example), and EC PKCS#12 export failed. RSA was not affected. This was found by running NSS's pk11_gtests against wolfPKCS11; our CI only runs ssl_gtests, and the pk12util workflows only use RSA keys.

There were three causes:

  • NSS creates the private key with the raw X9.63 point (04||X||Y) in CKA_EC_POINT (pk11pk12.c). EcSetPoint only took a DER OCTET STRING, so the 0x04 was read as the tag, the first byte of X as the length, and the key was refused with CKR_ATTRIBUTE_VALUE_INVALID.
  • NSS sends an empty CKA_EC_POINT when the PKCS#8 has no public value, which was refused as well.
  • The default (encrypted) PKCS#12 form imports through C_UnwrapKey and exports through C_WrapKey, and neither took EC private keys (CKR_TEMPLATE_INCONSISTENT and CKR_KEY_NOT_WRAPPABLE).

PKCS#11 only gives EC private keys CKA_EC_PARAMS and CKA_VALUE, but softokn accepts CKA_EC_POINT on them in either form and NSS depends on that.

Change.

  • A private key's CKA_EC_POINT now takes the raw point as well as DER. They are told apart by length, as softokn does: a value of 2 * field size + 1 bytes starting with 0x04 is raw. The point must still be on the curve. An empty CKA_EC_POINT on a private key is treated as not given.
  • Public keys still take DER only, as object_template_codes_test asserts.
  • EC private keys are wrapped and unwrapped as PKCS#8, as RSA keys already are (WP11_Ec_SerializeKeyPTPKC8, WP11_Ec_ParsePrivKey).
    • An unwrapped key gets the same scalar range and point checks as C_CreateObject.
    • A CKA_EC_PARAMS in the template naming another curve returns CKR_TEMPLATE_INCONSISTENT.
    • Data that is not an EC private key returns CKR_WRAPPED_KEY_INVALID.
    • A key with no private scalar in software, such as one generated on a TPM, returns CKR_KEY_NOT_WRAPPABLE.
  • NSS builds now return the raw point when CKA_EC_POINT is read from a private key. PK11_ExportPrivKeyInfo copies the attribute into the PKCS#8 as is, so the DER form made a malformed key on unencrypted export, the path WebCrypto exportKey('pkcs8') uses. softokn returns the value NSS stored, which is the raw point. Other builds still return DER.

object_template_codes_test expected CKR_TEMPLATE_INCONSISTENT for an EC private key unwrap, because EC unwrap was not supported. Its data is not a wrapped key, so it now gets CKR_WRAPPED_KEY_INVALID, or CKR_MECHANISM_INVALID in builds without AES-CBC. Builds without ECC still refuse the key type with CKR_TEMPLATE_INCONSISTENT.

Testing.

  • New ec_private_key_import_test (34 checks). It covers:

    • raw, DER and empty points on private keys, and the point read back;
    • an off-curve raw point, and a raw point on a public key;
    • a wrap and unwrap round trip with and without the public point, which checks the PKCS#8 and signs with the unwrapped key;
    • a curve mismatch and invalid wrapped data.

    It fails on master and passes here.

  • make check passes in the default build (macOS) and the --enable-nss build (Ubuntu 24.04, Docker), and in the --disable-aescbc and --disable-ecc --disable-hkdf --disable-aescbc --disable-concatenate builds. The no-store build compiles with -Werror.

  • The new test, object_template_codes_test and pkcs11test are clean under ASan and UBSan in both builds.

  • Against NSS 3.112 with the osp patches, built as nss.yml does:

    • ssl_gtests are unchanged: 4556 run, 0 failed.
    • pk12util imports encrypted and plain-keyBag P-256, P-384 and P-521 PKCS#12 files, exports them, and re-imports the exports into a fresh database. OpenSSL confirms the exported keys match the originals, and CMS signatures made with the re-imported keys verify in OpenSSL.
    • pk11_gtests: 16 more tests pass and none newly fail.

Not changed. Some pk11_gtest EC tests still fail:

  • Vectors whose public point is all zero, off the curve, or one byte short (the P-521 PKCS#8). softokn does not check the point on a private key; wolfPKCS11 still refuses an invalid one.
  • Vectors with a zero-padded private scalar that expect it back unchanged. wolfPKCS11 returns the minimal integer.
  • CKM_NSS_PUB_FROM_PRIV, which is not supported.
  • Curve25519 keys.

No EC private key could be imported through NSS, so pk12util and
Firefox failed on every EC PKCS#12 file and PK11_ImportDERPrivateKeyInfo
failed for EC keys. EC PKCS#12 export failed too.

- NSS creates the private key with the raw X9.63 point (04||X||Y) in
  CKA_EC_POINT (pk11pk12.c). EcSetPoint only took a DER OCTET STRING, so
  the 0x04 was read as the tag, the first byte of X as the length, and
  the key was refused with CKR_ATTRIBUTE_VALUE_INVALID.
- NSS sends an empty CKA_EC_POINT when the PKCS#8 has no public value,
  which was refused as well.
- The default encrypted PKCS#12 form imports through C_UnwrapKey and
  exports through C_WrapKey, which did not take EC private keys
  (CKR_TEMPLATE_INCONSISTENT and CKR_KEY_NOT_WRAPPABLE).

A private key's CKA_EC_POINT now takes the raw point as well as DER.
The two are told apart by length, as softokn does: a value of
2 * field size + 1 bytes starting with 0x04 is raw. The point must
still be on the curve. An empty CKA_EC_POINT on a private key is
treated as not given. Public keys still take DER only.

EC private keys are wrapped and unwrapped as PKCS#8, as RSA keys
already are. An unwrapped key gets the same scalar range and point
checks as C_CreateObject. A CKA_EC_PARAMS in the template naming
another curve returns CKR_TEMPLATE_INCONSISTENT, and data that is not
an EC private key returns CKR_WRAPPED_KEY_INVALID. A key with no
private scalar in software, such as one generated on a TPM, returns
CKR_KEY_NOT_WRAPPABLE.

NSS builds now return the raw point when CKA_EC_POINT is read from a
private key. PK11_ExportPrivKeyInfo copies the attribute into the
PKCS#8 as is, so the DER form made a malformed key on unencrypted
export, the path Firefox WebCrypto exportKey('pkcs8') uses. softokn
returns the value NSS stored, which is the raw point. Other builds
still return DER.

object_template_codes_test expected CKR_TEMPLATE_INCONSISTENT for an
EC private key unwrap. Its data is not a wrapped key, so it now gets
CKR_WRAPPED_KEY_INVALID.

The new ec_private_key_import_test covers raw, DER and empty points on
private keys, the point read back, an off-curve raw point, a raw point
on a public key, a wrap and unwrap round trip with and without the
public point, a curve mismatch and invalid wrapped data.
Copilot AI balanced review requested due to automatic review settings November 10, 2026 18:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@LinuxJedi

Copy link
Copy Markdown
Member Author

@claude review

CHECK_WRAPPABLE took EC private keys in every build. Without ECC,
C_UnwrapKey then went on to decrypt and failed on the mechanism or the
key type, instead of refusing the key type with
CKR_TEMPLATE_INCONSISTENT as it did before. EC private keys are now only
wrappable when HAVE_ECC is defined.

object_template_codes_test now expects CKR_WRAPPED_KEY_INVALID for its
EC unwrap only with ECC and AES-CBC, CKR_MECHANISM_INVALID with ECC but
no AES-CBC, and CKR_TEMPLATE_INCONSISTENT without ECC.

ec_private_key_import_test defined two constants that only the wrap
tests use outside the TEST_EC_WRAP guard, which broke the
--disable-aescbc build with -Werror.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I reviewed this PR and didn't find any bugs. It adds new paths for exporting and importing private key material (C_WrapKey/C_UnwrapKey for EC) and changes how CKA_EC_POINT is parsed, so a human look would still be worthwhile.

What was reviewed:

  • The raw-vs-DER split in EcSetPoint: raw form is accepted only on private keys, only when the length is exactly 2*size+1 and the first byte is 0x04, and it still goes through the on-curve check.
  • WP11_Ec_ParsePrivKey: the curve from the template is saved before the key is reset, then the curve-mismatch, scalar-range and point checks run, and the key is freed on error. AddECPrivateKeyObject cleans up on failure.
  • WP11_Ec_SerializeKeyPTPKC8: it returns the length first and then fills the buffer, holds the read lock, and returns BAD_STATE_E for keys with no scalar. Also checked that the empty-CKA_EC_POINT skip in SetAttributeValue applies only to EC private keys.
Extended reasoning...

The change touches src/crypto.c and src/internal.c in wolfPKCS11, about 300 lines of library code plus a 539-line new test. It makes EC private keys wrappable and unwrappable as plain PKCS#8, and it accepts and returns a raw X9.63 point on private keys. That is security-sensitive: it handles private key material and decides when a key may leave the token. No bugs were found, but because this is crypto key import/export code, a human maintainer should confirm the design choice and the behaviour across the many feature-flag builds (FIPS, TPM, NSS).

This review covers commit 85dbd5e, which is no longer the latest commit on this pull request; later commits are not covered by it.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #297

Scan targets checked: wolfpkcs11-src, wolfpkcs11-bugs
Not reviewed (no scan target covers them): tests/include.am
Coverage: 5 of 5 in-scope changed file(s) opened by the reviewer

Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Reported findings require changes before merge.

Review tier: Max

Comment thread tests/ec_private_key_import_test.c Resolved
Comment thread tests/object_template_codes_test.c Resolved

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #297

Scan targets checked: wolfpkcs11-src, wolfpkcs11-bugs
Not reviewed (no scan target covers them): tests/include.am
Coverage: 2 of 3 in-scope changed file(s) opened by the reviewer; not opened: tests/ec_private_key_import_test.c

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

Review tier: Lite

@wolfSSL-Fenrir-bot
wolfSSL-Fenrir-bot dismissed their stale review November 11, 2026 06:34

Fenrir's latest completed scan found no issues; clearing the prior automated change request.

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.

4 participants