tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 7279cec1892bb643802f0bad6d992e25d147f85a
parent 19ca0b6bac25bf02474c8635b666ba9adb5e598a
Author: Anna Weine <anna.weine@mozilla.com>
Date:   Wed, 22 Oct 2025 16:28:11 +0000

Bug 1992446 - upgrade NSS to 18dcc6d128e0b6f2acc3276e81669ec5870b713e. r=nss-reviewers,jschanck UPGRADE_NSS_RELEASE

Differential Revision: https://phabricator.services.mozilla.com/D269623

Diffstat:
Msecurity/nss/gtests/pk11_gtest/pk11_import_unittest.cc | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asecurity/nss/gtests/pk11_gtest/pk11_import_vectors.h | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msecurity/nss/lib/certhigh/certvfy.c | 3+++
Msecurity/nss/lib/freebl/blapi.h | 7+++++++
Msecurity/nss/lib/freebl/ec.c | 18++++++++++++++++++
Msecurity/nss/lib/freebl/ecl/ecl.h | 4++++
Msecurity/nss/lib/freebl/ecl/ecp_secp256r1.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msecurity/nss/lib/freebl/ecl/ecp_secp384r1.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msecurity/nss/lib/freebl/ecl/ecp_secp521r1.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msecurity/nss/lib/freebl/ldvector.c | 3+++
Msecurity/nss/lib/freebl/loader.c | 9+++++++++
Msecurity/nss/lib/freebl/loader.h | 5++++-
Msecurity/nss/lib/softoken/pkcs11.c | 36+++++++++++++++++++++++++++---------
Msecurity/nss/moz.yaml | 4++--
14 files changed, 427 insertions(+), 12 deletions(-)

diff --git a/security/nss/gtests/pk11_gtest/pk11_import_unittest.cc b/security/nss/gtests/pk11_gtest/pk11_import_unittest.cc @@ -15,6 +15,7 @@ #include "nss_scoped_ptrs.h" #include "gtest/gtest.h" #include "databuffer.h" +#include "pk11_import_vectors.h" #include "pk11_keygen.h" namespace nss_test { @@ -298,4 +299,121 @@ INSTANTIATE_TEST_SUITE_P(Pk11KeyImportTestEC, Pk11KeyImportTestEC, SEC_OID_SECG_EC_SECP521R1, SEC_OID_CURVE25519)); +struct Pkcs11CompressedECKeyTestParams { + DataBuffer compressedKey; + DataBuffer uncompressedKey; +}; + +class Pk11KeyImportTestECCompressed + : public Pk11KeyImportTestBase, + public ::testing::WithParamInterface<Pkcs11CompressedECKeyTestParams> { + public: + Pk11KeyImportTestECCompressed() = default; + virtual ~Pk11KeyImportTestECCompressed() = default; +}; + +// Importing a private key in PKCS#8 format with a point not on the curve will +// succeed. Using the contained public key however will fail when trying to +// import it before using it for any operation. +TEST_P(Pk11KeyImportTestECCompressed, CompressedPointTest) { + DataBuffer spki(GetParam().compressedKey); + SECItem spki_item = {siBuffer, toUcharPtr(spki.data()), + static_cast<unsigned int>(spki.len())}; + + ScopedCERTSubjectPublicKeyInfo cert_spki( + SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item)); + ASSERT_TRUE(cert_spki); + ScopedSECKEYPublicKey pub_key(SECKEY_ExtractPublicKey(cert_spki.get())); + ASSERT_TRUE(pub_key); + + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(slot); + + CK_OBJECT_HANDLE id = + PK11_ImportPublicKey(slot.get(), pub_key.get(), PR_FALSE); + ASSERT_NE(id, (unsigned int)CK_INVALID_HANDLE); + + StackSECItem publicDecoded; + SECStatus rv = PK11_ReadRawAttribute(PK11_TypePubKey, pub_key.get(), + CKA_EC_POINT, &publicDecoded); + ASSERT_EQ(rv, SECSuccess); + + ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + ASSERT_TRUE(arena); + + SECItem decodedItem; + rv = SEC_QuickDERDecodeItem(arena.get(), &decodedItem, + SEC_ASN1_GET(SEC_OctetStringTemplate), + &publicDecoded); + + ASSERT_EQ(rv, SECSuccess); + ASSERT_EQ(decodedItem.len, GetParam().uncompressedKey.len()); + ASSERT_EQ(0, PORT_Memcmp(decodedItem.data, GetParam().uncompressedKey.data(), + decodedItem.len)); +}; + +static const Pkcs11CompressedECKeyTestParams kCompressedVectors[] = { + {DataBuffer(kP256CompressedSpki, sizeof(kP256CompressedSpki)), + DataBuffer(kP256Uncompressed, sizeof(kP256Uncompressed))}, + {DataBuffer(kP384CompressedSpki, sizeof(kP384CompressedSpki)), + DataBuffer(kP384Uncompressed, sizeof(kP384Uncompressed))}, + {DataBuffer(kP521CompressedSpki, sizeof(kP521CompressedSpki)), + DataBuffer(kP521Uncompressed, sizeof(kP521Uncompressed))}}; + +INSTANTIATE_TEST_SUITE_P(Pk11KeyImportTestECCompressed, + Pk11KeyImportTestECCompressed, + ::testing::ValuesIn(kCompressedVectors)); + +// Importing a key with 0x7 sign value instead of 0x02/0x03 +TEST_F(Pk11KeyImportTestEC, CompressedPointWrongSign) { + DataBuffer spki(kP256CompressedSpkiWrongSign, + sizeof(kP256CompressedSpkiWrongSign)); + SECItem spki_item = {siBuffer, toUcharPtr(spki.data()), + static_cast<unsigned int>(spki.len())}; + + ScopedCERTSubjectPublicKeyInfo cert_spki( + SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item)); + ASSERT_TRUE(cert_spki); + ScopedSECKEYPublicKey pub_key(SECKEY_ExtractPublicKey(cert_spki.get())); + ASSERT_TRUE(pub_key); + + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(slot); + + CK_OBJECT_HANDLE id = + PK11_ImportPublicKey(slot.get(), pub_key.get(), PR_FALSE); + ASSERT_EQ(id, (unsigned int)CK_INVALID_HANDLE); +} + +TEST_F(Pk11KeyImportTestEC, CompressedPointWrongLen) { + DataBuffer spki(kP256CompressedSpkiWrongPointLength, + sizeof(kP256CompressedSpkiWrongPointLength)); + SECItem spki_item = {siBuffer, toUcharPtr(spki.data()), + static_cast<unsigned int>(spki.len())}; + + ScopedCERTSubjectPublicKeyInfo cert_spki( + SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item)); + ASSERT_FALSE(cert_spki); +} + +TEST_F(Pk11KeyImportTestEC, CompressedPointNotOnCurve) { + DataBuffer spki(kP256CompressedSpkiNotOnCurve, + sizeof(kP256CompressedSpkiNotOnCurve)); + SECItem spki_item = {siBuffer, toUcharPtr(spki.data()), + static_cast<unsigned int>(spki.len())}; + + ScopedCERTSubjectPublicKeyInfo cert_spki( + SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item)); + ASSERT_TRUE(cert_spki); + ScopedSECKEYPublicKey pub_key(SECKEY_ExtractPublicKey(cert_spki.get())); + ASSERT_TRUE(pub_key); + + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(slot); + + CK_OBJECT_HANDLE id = + PK11_ImportPublicKey(slot.get(), pub_key.get(), PR_FALSE); + ASSERT_NE(id, (unsigned int)CK_INVALID_HANDLE); +} + } // namespace nss_test diff --git a/security/nss/gtests/pk11_gtest/pk11_import_vectors.h b/security/nss/gtests/pk11_gtest/pk11_import_vectors.h @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The test +namespace nss_test { +const uint8_t kP256CompressedSpki[] = { + 0x30, 0x39, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x22, 0x00, 0x02, 0xd2, 0x10, 0xb0, 0xa6, 0xf9, 0xd9, 0xf0, 0x12, 0x86, + 0x80, 0x58, 0xb4, 0x3f, 0xa4, 0xf4, 0x71, 0x01, 0x85, 0x43, 0xbb, 0xa0, + 0x0c, 0x92, 0x50, 0xdf, 0x92, 0x57, 0xc2, 0xac, 0xae, 0x5d, 0xd1}; + +const uint8_t kP256Uncompressed[] = { + 0x04, 0xd2, 0x10, 0xb0, 0xa6, 0xf9, 0xd9, 0xf0, 0x12, 0x86, 0x80, + 0x58, 0xb4, 0x3f, 0xa4, 0xf4, 0x71, 0x01, 0x85, 0x43, 0xbb, 0xa0, + 0x0c, 0x92, 0x50, 0xdf, 0x92, 0x57, 0xc2, 0xac, 0xae, 0x5d, 0xd1, + 0xce, 0x03, 0x75, 0x52, 0xd4, 0x81, 0x45, 0x0c, 0xe3, 0x9b, 0x4d, + 0x10, 0x95, 0x70, 0x1b, 0x17, 0x5b, 0xfa, 0xb3, 0x4b, 0x8e, 0x6c, + 0x09, 0x9e, 0x18, 0xf1, 0xc1, 0x98, 0x35, 0x83, 0x61, 0xe8}; + +const uint8_t kP384CompressedSpki[] = { + 0x30, 0x46, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x32, 0x00, 0x02, + 0x21, 0x9c, 0x14, 0xd6, 0x66, 0x17, 0xb3, 0x6e, 0xc6, 0xd8, 0x85, 0x6b, + 0x38, 0x5b, 0x73, 0xa7, 0x4d, 0x34, 0x4f, 0xd8, 0xae, 0x75, 0xef, 0x04, + 0x64, 0x35, 0xdd, 0xa5, 0x4e, 0x3b, 0x44, 0xbd, 0x5f, 0xbd, 0xeb, 0xd1, + 0xd0, 0x8d, 0xd6, 0x9e, 0x2d, 0x7d, 0xc1, 0xdc, 0x21, 0x8c, 0xb4, 0x35}; + +const uint8_t kP384Uncompressed[] = { + 0x04, 0x21, 0x9c, 0x14, 0xd6, 0x66, 0x17, 0xb3, 0x6e, 0xc6, 0xd8, + 0x85, 0x6b, 0x38, 0x5b, 0x73, 0xa7, 0x4d, 0x34, 0x4f, 0xd8, 0xae, + 0x75, 0xef, 0x04, 0x64, 0x35, 0xdd, 0xa5, 0x4e, 0x3b, 0x44, 0xbd, + 0x5f, 0xbd, 0xeb, 0xd1, 0xd0, 0x8d, 0xd6, 0x9e, 0x2d, 0x7d, 0xc1, + 0xdc, 0x21, 0x8c, 0xb4, 0x35, 0xbd, 0x28, 0x13, 0x8c, 0xc7, 0x78, + 0x33, 0x7a, 0x84, 0x2f, 0x6b, 0xd6, 0x1b, 0x24, 0x0e, 0x74, 0x24, + 0x9f, 0x24, 0x66, 0x7c, 0x2a, 0x58, 0x10, 0xa7, 0x6b, 0xfc, 0x28, + 0xe0, 0x33, 0x5f, 0x88, 0xa6, 0x50, 0x1d, 0xec, 0x01, 0x97, 0x6d, + 0xa8, 0x5a, 0xfb, 0x00, 0x86, 0x9c, 0xb6, 0xac, 0xe8}; + +const uint8_t kP521CompressedSpki[] = { + 0x30, 0x58, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23, 0x03, 0x44, 0x00, 0x03, + 0x01, 0x56, 0xf4, 0x79, 0xf8, 0xdf, 0x1e, 0x20, 0xa7, 0xff, 0xc0, 0x4c, + 0xe4, 0x20, 0xc3, 0xe1, 0x54, 0xae, 0x25, 0x19, 0x96, 0xbe, 0xe4, 0x2f, + 0x03, 0x4b, 0x84, 0xd4, 0x1b, 0x74, 0x3f, 0x34, 0xe4, 0x5f, 0x31, 0x1b, + 0x81, 0x3a, 0x9c, 0xde, 0xc8, 0xcd, 0xa5, 0x9b, 0xbb, 0xbd, 0x31, 0xd4, + 0x60, 0xb3, 0x29, 0x25, 0x21, 0xe7, 0xc1, 0xb7, 0x22, 0xe5, 0x66, 0x7c, + 0x03, 0xdb, 0x2f, 0xae, 0x75, 0x3f}; + +const uint8_t kP521Uncompressed[] = { + 0x04, 0x01, 0x56, 0xf4, 0x79, 0xf8, 0xdf, 0x1e, 0x20, 0xa7, 0xff, 0xc0, + 0x4c, 0xe4, 0x20, 0xc3, 0xe1, 0x54, 0xae, 0x25, 0x19, 0x96, 0xbe, 0xe4, + 0x2f, 0x03, 0x4b, 0x84, 0xd4, 0x1b, 0x74, 0x3f, 0x34, 0xe4, 0x5f, 0x31, + 0x1b, 0x81, 0x3a, 0x9c, 0xde, 0xc8, 0xcd, 0xa5, 0x9b, 0xbb, 0xbd, 0x31, + 0xd4, 0x60, 0xb3, 0x29, 0x25, 0x21, 0xe7, 0xc1, 0xb7, 0x22, 0xe5, 0x66, + 0x7c, 0x03, 0xdb, 0x2f, 0xae, 0x75, 0x3f, 0x01, 0x50, 0x17, 0x36, 0xcf, + 0xe2, 0x47, 0x39, 0x43, 0x20, 0xd8, 0xe4, 0xaf, 0xc2, 0xfd, 0x39, 0xb5, + 0xa9, 0x33, 0x10, 0x61, 0xb8, 0x1e, 0x22, 0x41, 0x28, 0x2b, 0x9e, 0x17, + 0x89, 0x18, 0x22, 0xb5, 0xb7, 0x9e, 0x05, 0x2f, 0x45, 0x97, 0xb5, 0x96, + 0x43, 0xfd, 0x39, 0x37, 0x9c, 0x51, 0xbd, 0x51, 0x25, 0xc4, 0xf4, 0x8b, + 0xc3, 0xf0, 0x25, 0xce, 0x3c, 0xd3, 0x69, 0x53, 0x28, 0x6c, 0xcb, 0x38, + 0xfb}; + +const uint8_t kP256CompressedSpkiWrongSign[] = { + 0x30, 0x39, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x22, 0x00, 0x07, 0xd2, 0x10, 0xb0, 0xa6, 0xf9, 0xd9, 0xf0, 0x12, 0x86, + 0x80, 0x58, 0xb4, 0x3f, 0xa4, 0xf4, 0x71, 0x01, 0x85, 0x43, 0xbb, 0xa0, + 0x0c, 0x92, 0x50, 0xdf, 0x92, 0x57, 0xc2, 0xac, 0xae, 0x5d, 0xd1}; + +const uint8_t kP256CompressedSpkiWrongPointLength[] = { + 0x30, 0x39, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x22, 0x00, 0x02, 0xd2, 0x10, 0xb0, 0xa6, 0xf9, 0xd9, 0xf0, 0x12, 0x86, + 0x80, 0x58, 0xb4, 0x3f, 0xa4, 0xf4, 0x71, 0x01, 0x85, 0x43, 0xbb, 0xa0, + 0x0c, 0x92, 0x50, 0xdf, 0x92, 0x57, 0xc2, 0xac, 0xae, 0x5d}; + +const uint8_t kP256CompressedSpkiNotOnCurve[] = { + 0x30, 0x39, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x22, 0x00, 0x02, 0xd2, 0x10, 0xb0, 0xa6, 0xf9, 0xd9, 0xf0, 0x12, 0x86, + 0x80, 0x58, 0xb4, 0x3f, 0xa4, 0xf4, 0x71, 0x01, 0x85, 0x43, 0xbb, 0xa0, + 0x0c, 0x92, 0x50, 0xdf, 0x92, 0x57, 0xc2, 0xac, 0xae, 0x5d, 0xd2}; +} // namespace nss_test diff --git a/security/nss/lib/certhigh/certvfy.c b/security/nss/lib/certhigh/certvfy.c @@ -2158,6 +2158,7 @@ CERT_GetCertChainFromCert(CERTCertificate *cert, PRTime time, SECCertUsage usage chain = CERT_NewCertList(); if (NULL == chain) { + CERT_DestroyCertificate(cert); PORT_SetError(SEC_ERROR_NO_MEMORY); return NULL; } @@ -2165,6 +2166,7 @@ CERT_GetCertChainFromCert(CERTCertificate *cert, PRTime time, SECCertUsage usage while (cert != NULL && ++count <= CERT_MAX_CERT_CHAIN) { if (SECSuccess != CERT_AddCertToListTail(chain, cert)) { /* return partial chain */ + CERT_DestroyCertificate(cert); PORT_SetError(SEC_ERROR_NO_MEMORY); return chain; } @@ -2178,6 +2180,7 @@ CERT_GetCertChainFromCert(CERTCertificate *cert, PRTime time, SECCertUsage usage } /* return partial chain */ + CERT_DestroyCertificate(cert); PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); return chain; } diff --git a/security/nss/lib/freebl/blapi.h b/security/nss/lib/freebl/blapi.h @@ -1962,6 +1962,13 @@ SECStatus MLDSA_VerifyInit(MLDSAPublicKey *key, const SECItem *sgnCtx, SECStatus MLDSA_VerifyUpdate(MLDSAContext *ctx, const SECItem *data); SECStatus MLDSA_VerifyFinal(MLDSAContext *ctx, const SECItem *signature); +/* Decompress public key. + ** On input, publicCompressed == buffer containing compressed key + ** Output, publicRaw == decompressed public key. The function returns true iff the + ** decompressed key belongs to the appropriate curve. + */ +SECStatus EC_DecompressPublicKey(const SECItem *publicCompressed, const ECParams *params, SECItem *publicUncompressed); + SEC_END_PROTOS #endif /* _BLAPI_H_ */ diff --git a/security/nss/lib/freebl/ec.c b/security/nss/lib/freebl/ec.c @@ -710,3 +710,21 @@ EC_DerivePublicKey(const SECItem *privateKey, const ECParams *ecParams, SECItem return method->pt_mul(publicKey, (SECItem *)privateKey, NULL); } + +/* Supported only for P-256, P-384 and P-521*/ +SECStatus +EC_DecompressPublicKey(const SECItem *publicCompressed, const ECParams *ecParams, SECItem *publicUncompressed) +{ + /* I don't think that we need a special ECMethod extension for decompression. */ + switch (ecParams->name) { + case ECCurve_NIST_P256: + return ec_secp256r1_decompress(publicCompressed, publicUncompressed); + case ECCurve_NIST_P384: + return ec_secp384r1_decompress(publicCompressed, publicUncompressed); + case ECCurve_NIST_P521: + return ec_secp521r1_decompress(publicCompressed, publicUncompressed); + default: + PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE); + return SECFailure; + } +} diff --git a/security/nss/lib/freebl/ecl/ecl.h b/security/nss/lib/freebl/ecl/ecl.h @@ -26,6 +26,8 @@ SECStatus ec_secp256r1_sign_digest(ECPrivateKey *key, SECItem *signature, SECStatus ec_secp256r1_verify_digest(ECPublicKey *key, const SECItem *signature, const SECItem *digest); +SECStatus ec_secp256r1_decompress(const SECItem *publicCompressed, SECItem *publicRaw); + SECStatus ec_secp521r1_pt_mul(SECItem *X, SECItem *k, SECItem *P); SECStatus ec_secp521r1_pt_validate(const SECItem *px); SECStatus ec_secp521r1_scalar_validate(const SECItem *scalar); @@ -35,6 +37,7 @@ SECStatus ec_secp521r1_sign_digest(ECPrivateKey *key, SECItem *signature, const unsigned int kblen); SECStatus ec_secp521r1_verify_digest(ECPublicKey *key, const SECItem *signature, const SECItem *digest); +SECStatus ec_secp521r1_decompress(const SECItem *publicCompressed, SECItem *publicRaw); SECStatus ec_secp384r1_pt_mul(SECItem *X, SECItem *k, SECItem *P); SECStatus ec_secp384r1_pt_validate(const SECItem *px); @@ -45,5 +48,6 @@ SECStatus ec_secp384r1_sign_digest(ECPrivateKey *key, SECItem *signature, const unsigned int kblen); SECStatus ec_secp384r1_verify_digest(ECPublicKey *key, const SECItem *signature, const SECItem *digest); +SECStatus ec_secp384r1_decompress(const SECItem *publicCompressed, SECItem *publicRaw); #endif /* __ecl_h_ */ diff --git a/security/nss/lib/freebl/ecl/ecp_secp256r1.c b/security/nss/lib/freebl/ecl/ecp_secp256r1.c @@ -314,3 +314,52 @@ ec_secp256r1_verify_digest(ECPublicKey *key, const SECItem *signature, return res; } + +/* + Point decompression for P-256. + + publicCompressed must be 33 bytes (1 byte for a sign and 32 bytes for the x coordinate. + publicUncompressed must be 64 bytes (32 * 2). + The function returns SECSuccess if the decompression was success and the decompresse + point is a valid P-256 curve point. +*/ + +SECStatus +ec_secp256r1_decompress(const SECItem *publicCompressed, SECItem *publicUncompressed) +{ + if (!publicCompressed || !publicCompressed->data) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicCompressed->len != 33) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + + if (!publicUncompressed || !publicUncompressed->data) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicUncompressed->len != 65) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicCompressed->data[0] != EC_POINT_FORM_COMPRESSED_Y0 && + publicCompressed->data[0] != EC_POINT_FORM_COMPRESSED_Y1) { + PORT_SetError(SEC_ERROR_UNSUPPORTED_EC_POINT_FORM); + return SECFailure; + } + + bool b = Hacl_P256_compressed_to_raw(publicCompressed->data, publicUncompressed->data + 1); + + if (!b) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + + publicUncompressed->data[0] = EC_POINT_FORM_UNCOMPRESSED; + return SECSuccess; +} diff --git a/security/nss/lib/freebl/ecl/ecp_secp384r1.c b/security/nss/lib/freebl/ecl/ecp_secp384r1.c @@ -311,3 +311,52 @@ ec_secp384r1_verify_digest(ECPublicKey *key, const SECItem *signature, return res; } + +/* + Point decompression for P-384. + + publicCompressed must be 49 bytes (1 byte for a sign and 48 bytes for the x coordinate. + publicUncompressed must be 96 bytes (48 * 2). + The function returns SECSuccess if the decompression was success and the decompresse + point is a valid P-384 curve point. +*/ + +SECStatus +ec_secp384r1_decompress(const SECItem *publicCompressed, SECItem *publicUncompressed) +{ + if (!publicCompressed || !publicCompressed->data) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicCompressed->len != 49) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + + if (!publicUncompressed || !publicUncompressed->data) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicUncompressed->len != 97) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicCompressed->data[0] != EC_POINT_FORM_COMPRESSED_Y0 && + publicCompressed->data[0] != EC_POINT_FORM_COMPRESSED_Y1) { + PORT_SetError(SEC_ERROR_UNSUPPORTED_EC_POINT_FORM); + return SECFailure; + } + + bool b = Hacl_P384_compressed_to_raw(publicCompressed->data, publicUncompressed->data + 1); + + if (!b) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + + publicUncompressed->data[0] = EC_POINT_FORM_UNCOMPRESSED; + return SECSuccess; +} diff --git a/security/nss/lib/freebl/ecl/ecp_secp521r1.c b/security/nss/lib/freebl/ecl/ecp_secp521r1.c @@ -317,3 +317,52 @@ ec_secp521r1_verify_digest(ECPublicKey *key, const SECItem *signature, return res; } + +/* + Point decompression for P-521. + + publicCompressed must be 67 bytes (1 byte for a sign and 66 bytes for the x coordinate. + publicUncompressed must be 132 bytes (66 * 2). + The function returns SECSuccess if the decompression was success and the decompresse + point is a valid P-521 curve point. +*/ + +SECStatus +ec_secp521r1_decompress(const SECItem *publicCompressed, SECItem *publicUncompressed) +{ + if (!publicCompressed || !publicCompressed->data) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicCompressed->len != 67) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + + if (!publicUncompressed || !publicUncompressed->data) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicUncompressed->len != 133) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (publicCompressed->data[0] != EC_POINT_FORM_COMPRESSED_Y0 && + publicCompressed->data[0] != EC_POINT_FORM_COMPRESSED_Y1) { + PORT_SetError(SEC_ERROR_UNSUPPORTED_EC_POINT_FORM); + return SECFailure; + } + + bool b = Hacl_P521_compressed_to_raw(publicCompressed->data, publicUncompressed->data + 1); + + if (!b) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + + publicUncompressed->data[0] = EC_POINT_FORM_UNCOMPRESSED; + return SECSuccess; +} diff --git a/security/nss/lib/freebl/ldvector.c b/security/nss/lib/freebl/ldvector.c @@ -459,6 +459,9 @@ static const struct FREEBLVectorStr vector = { MLDSA_VerifyFinal, /* End of version 3.031 */ + EC_DecompressPublicKey, + /* End of version 3.032 */ + }; const FREEBLVector* diff --git a/security/nss/lib/freebl/loader.c b/security/nss/lib/freebl/loader.c @@ -2958,3 +2958,12 @@ MLDSA_VerifyFinal(MLDSAContext *ctx, const SECItem *signature) return SECFailure; return (vector->p_MLDSA_VerifyFinal)(ctx, signature); } + +/* ============== New for 3.0032 =============================== */ +SECStatus +EC_DecompressPublicKey(const SECItem *publicCompressed, const ECParams *params, SECItem *publicUncompressed) +{ + if (!vector && PR_SUCCESS != freebl_RunLoaderOnce()) + return SECFailure; + return (vector->p_EC_DecompressPublicKey)(publicCompressed, params, publicUncompressed); +} diff --git a/security/nss/lib/freebl/loader.h b/security/nss/lib/freebl/loader.h @@ -10,7 +10,7 @@ #include "blapi.h" -#define FREEBL_VERSION 0x0331 +#define FREEBL_VERSION 0x0332 struct FREEBLVectorStr { @@ -940,6 +940,9 @@ struct FREEBLVectorStr { SECStatus (*p_MLDSA_VerifyFinal)(MLDSAContext *ctx, const SECItem *signature); /* Version 3.031 came to here */ + SECStatus (*p_EC_DecompressPublicKey)(const SECItem *publicCompressed, const ECParams *params, SECItem *publicUncompressed); + /* Version 3.032 came to here */ + /* Add new function pointers at the end of this struct and bump * FREEBL_VERSION at the beginning of this file. */ }; diff --git a/security/nss/lib/softoken/pkcs11.c b/security/nss/lib/softoken/pkcs11.c @@ -1254,7 +1254,6 @@ sftk_handlePublicKeyObject(SFTKSession *session, SFTKObject *object, sizeof(CK_BBOOL)); if (crv != CKR_OK) return crv; - object->objectInfo = sftk_GetPubKey(object, key_type, &crv); if (object->objectInfo == NULL) { return crv; @@ -2186,7 +2185,6 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type, object, CKA_EC_POINT); if (crv == CKR_OK) { unsigned int keyLen = EC_GetPointSize(&pubKey->u.ec.ecParams); - /* special note: We can't just use the first byte to distinguish * between EC_POINT_FORM_UNCOMPRESSED and SEC_ASN1_OCTET_STRING. * Both are 0x04. */ @@ -2194,6 +2192,8 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type, /* Handle the non-DER encoded case. * Some curves are always pressumed to be non-DER. */ + + /* is the public key in uncompressed form? */ if (pubKey->u.ec.ecParams.type != ec_params_named || (pubKey->u.ec.publicValue.len == keyLen && pubKey->u.ec.publicValue.data[0] == EC_POINT_FORM_UNCOMPRESSED)) { @@ -2201,8 +2201,7 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type, } /* handle the encoded case */ - if ((pubKey->u.ec.publicValue.data[0] == SEC_ASN1_OCTET_STRING) && - pubKey->u.ec.publicValue.len > keyLen) { + if (pubKey->u.ec.publicValue.data[0] == SEC_ASN1_OCTET_STRING) { SECItem publicValue; SECStatus rv; @@ -2210,17 +2209,36 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type, SEC_ASN1_GET(SEC_OctetStringTemplate), &pubKey->u.ec.publicValue); /* nope, didn't decode correctly */ - if ((rv != SECSuccess) || (publicValue.len != keyLen)) { + if (rv != SECSuccess) { crv = CKR_ATTRIBUTE_VALUE_INVALID; break; } - /* we don't handle compressed points except in the case of ECCurve25519 */ - if (publicValue.data[0] != EC_POINT_FORM_UNCOMPRESSED) { + + if (publicValue.len == keyLen && publicValue.data[0] == EC_POINT_FORM_UNCOMPRESSED) { + /* Received uncompressed point */ + pubKey->u.ec.publicValue = publicValue; + break; + } + + /* Trying to decompress */ + SECItem publicDecompressed = { siBuffer, NULL, 0 }; + (void)SECITEM_AllocItem(arena, &publicDecompressed, keyLen); + if (EC_DecompressPublicKey(&publicValue, &pubKey->u.ec.ecParams, &publicDecompressed) == SECFailure) { crv = CKR_ATTRIBUTE_VALUE_INVALID; break; } - /* replace our previous with the decoded key */ - pubKey->u.ec.publicValue = publicValue; + + /* replace our previous public key with the decoded decompressed key */ + pubKey->u.ec.publicValue = publicDecompressed; + + SECItem publicDecompressedEncoded = { siBuffer, NULL, 0 }; + (void)SEC_ASN1EncodeItem(arena, &publicDecompressedEncoded, &publicDecompressed, + SEC_ASN1_GET(SEC_OctetStringTemplate)); + if (CKR_OK != sftk_forceAttribute(object, CKA_EC_POINT, sftk_item_expand(&publicDecompressedEncoded))) { + crv = CKR_ATTRIBUTE_VALUE_INVALID; + break; + } + break; } crv = CKR_ATTRIBUTE_VALUE_INVALID; diff --git a/security/nss/moz.yaml b/security/nss/moz.yaml @@ -9,8 +9,8 @@ origin: description: nss url: https://hg-edge.mozilla.org/projects/nss - release: f9041cc46f7495257b639e7e36fa8f2f0d50faa0 (2025-10-13T23:31:48Z). - revision: f9041cc46f7495257b639e7e36fa8f2f0d50faa0 + release: 18dcc6d128e0b6f2acc3276e81669ec5870b713e (2025-10-21T09:55:25Z). + revision: 18dcc6d128e0b6f2acc3276e81669ec5870b713e license: MPL-2.0 license-file: COPYING