diff -up ./nss/gtests/pk11_gtest/pk11_import_unittest.cc.pub-priv-mechs ./nss/gtests/pk11_gtest/pk11_import_unittest.cc --- ./nss/gtests/pk11_gtest/pk11_import_unittest.cc.pub-priv-mechs 2019-05-10 14:14:18.000000000 -0700 +++ ./nss/gtests/pk11_gtest/pk11_import_unittest.cc 2019-06-05 12:01:13.728544204 -0700 @@ -78,17 +78,40 @@ class Pk11KeyImportTestBase : public ::t CK_MECHANISM_TYPE mech_; private: + SECItem GetPublicComponent(ScopedSECKEYPublicKey& pub_key) { + SECItem null = { siBuffer, NULL, 0}; + switch(SECKEY_GetPublicKeyType(pub_key.get())) { + case rsaKey: + case rsaPssKey: + case rsaOaepKey: + return pub_key->u.rsa.modulus; + case keaKey: + return pub_key->u.kea.publicValue; + case dsaKey: + return pub_key->u.dsa.publicValue; + case dhKey: + return pub_key->u.dh.publicValue; + case ecKey: + return pub_key->u.ec.publicValue; + case fortezzaKey: /* depricated */ + case nullKey: + /* didn't use default here so we can catch new key types at compile time */ + break; + } + return null; + } void CheckForPublicKey(const ScopedSECKEYPrivateKey& priv_key, const SECItem* expected_public) { // Verify the public key exists. StackSECItem priv_id; + KeyType type = SECKEY_GetPrivateKeyType(priv_key.get()); SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, priv_key.get(), CKA_ID, &priv_id); ASSERT_EQ(SECSuccess, rv) << "Couldn't read CKA_ID from private key: " << PORT_ErrorToName(PORT_GetError()); CK_ATTRIBUTE_TYPE value_type = CKA_VALUE; - switch (SECKEY_GetPrivateKeyType(priv_key.get())) { + switch (type) { case rsaKey: value_type = CKA_MODULUS; break; @@ -106,6 +129,8 @@ class Pk11KeyImportTestBase : public ::t FAIL() << "unknown key type"; } + // Scan public key objects until we find one with the same CKA_ID as + // priv_key std::unique_ptr objs( PK11_FindGenericObjects(slot_.get(), CKO_PUBLIC_KEY)); ASSERT_NE(nullptr, objs); @@ -128,20 +153,46 @@ class Pk11KeyImportTestBase : public ::t ASSERT_EQ(1U, token.len); ASSERT_NE(0, token.data[0]); - StackSECItem value; - rv = PK11_ReadRawAttribute(PK11_TypeGeneric, obj, value_type, &value); + StackSECItem raw_value; + SECItem decoded_value; + rv = PK11_ReadRawAttribute(PK11_TypeGeneric, obj, value_type, &raw_value); ASSERT_EQ(SECSuccess, rv); + SECItem value = raw_value; + // Decode the EC_POINT and check the output against expected. // CKA_EC_POINT isn't stable, see Bug 1520649. + ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + ASSERT_TRUE(arena); if (value_type == CKA_EC_POINT) { - continue; - } + // If this fails due to the noted inconsistency, we may need to + // check the whole raw_value, or remove a leading UNCOMPRESSED_POINT tag + rv = SEC_QuickDERDecodeItem(arena.get(), &decoded_value, + SEC_ASN1_GET(SEC_OctetStringTemplate), + &raw_value); + ASSERT_EQ(SECSuccess, rv); + value = decoded_value; + } ASSERT_TRUE(SECITEM_ItemsAreEqual(expected_public, &value)) << "expected: " << DataBuffer(expected_public->data, expected_public->len) << std::endl << "actual: " << DataBuffer(value.data, value.len) << std::endl; + + // Finally, convert the private to public and ensure it matches. + ScopedSECKEYPublicKey pub_key( + SECKEY_ConvertToPublicKey(priv_key.get())); + ASSERT_TRUE(pub_key); + SECItem converted_public = GetPublicComponent(pub_key); + ASSERT_TRUE(converted_public.len != 0); + + ASSERT_TRUE(SECITEM_ItemsAreEqual(expected_public, &converted_public)) + << "expected: " + << DataBuffer(expected_public->data, expected_public->len) + << std::endl + << "actual: " + << DataBuffer(converted_public.data, converted_public.len) + << std::endl; } } diff -up ./nss/lib/cryptohi/seckey.c.pub-priv-mechs ./nss/lib/cryptohi/seckey.c --- ./nss/lib/cryptohi/seckey.c.pub-priv-mechs 2019-05-10 14:14:18.000000000 -0700 +++ ./nss/lib/cryptohi/seckey.c 2019-06-05 12:01:13.729544204 -0700 @@ -1206,6 +1206,37 @@ SECKEY_CopyPublicKey(const SECKEYPublicK return NULL; } +/* + * Use the private key to find a public key handle. The handle will be on + * the same slot as the private key. + */ +static CK_OBJECT_HANDLE +seckey_FindPublicKeyHandle(SECKEYPrivateKey *privk, SECKEYPublicKey *pubk) +{ + CK_OBJECT_HANDLE keyID; + + /* this helper function is only used below. If we want to make this more + * general, we would need to free up any already cached handles if the + * slot doesn't match up with the private key slot */ + PORT_Assert(pubk->pkcs11ID == CK_INVALID_HANDLE); + + /* first look for a matching public key */ + keyID = PK11_MatchItem(privk->pkcs11Slot, privk->pkcs11ID, CKO_PUBLIC_KEY); + if (keyID != CK_INVALID_HANDLE) { + return keyID; + } + + /* none found, create a temp one, make the pubk the owner */ + pubk->pkcs11ID = PK11_DerivePubKeyFromPrivKey(privk); + if (pubk->pkcs11ID == CK_INVALID_HANDLE) { + /* end of the road. Token doesn't have matching public key, nor can + * token regenerate a new public key from and existing private key. */ + return CK_INVALID_HANDLE; + } + pubk->pkcs11Slot = PK11_ReferenceSlot(privk->pkcs11Slot); + return pubk->pkcs11ID; +} + SECKEYPublicKey * SECKEY_ConvertToPublicKey(SECKEYPrivateKey *privk) { @@ -1213,6 +1244,8 @@ SECKEY_ConvertToPublicKey(SECKEYPrivateK PLArenaPool *arena; CERTCertificate *cert; SECStatus rv; + CK_OBJECT_HANDLE pubKeyHandle; + SECItem decodedPoint; /* * First try to look up the cert. @@ -1243,11 +1276,47 @@ SECKEY_ConvertToPublicKey(SECKEYPrivateK switch (privk->keyType) { case nullKey: - case dhKey: - case dsaKey: /* Nothing to query, if the cert isn't there, we're done -- no way * to get the public key */ break; + case dsaKey: + pubKeyHandle = seckey_FindPublicKeyHandle(privk, pubk); + if (pubKeyHandle == CK_INVALID_HANDLE) + break; + rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, + CKA_BASE, arena, &pubk->u.dsa.params.base); + if (rv != SECSuccess) + break; + rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, + CKA_PRIME, arena, &pubk->u.dsa.params.prime); + if (rv != SECSuccess) + break; + rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, + CKA_SUBPRIME, arena, &pubk->u.dsa.params.subPrime); + if (rv != SECSuccess) + break; + rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, + CKA_VALUE, arena, &pubk->u.dsa.publicValue); + if (rv != SECSuccess) + break; + return pubk; + case dhKey: + pubKeyHandle = seckey_FindPublicKeyHandle(privk, pubk); + if (pubKeyHandle == CK_INVALID_HANDLE) + break; + rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, + CKA_BASE, arena, &pubk->u.dh.base); + if (rv != SECSuccess) + break; + rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, + CKA_PRIME, arena, &pubk->u.dh.prime); + if (rv != SECSuccess) + break; + rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, + CKA_VALUE, arena, &pubk->u.dh.publicValue); + if (rv != SECSuccess) + break; + return pubk; case rsaKey: rv = PK11_ReadAttribute(privk->pkcs11Slot, privk->pkcs11ID, CKA_MODULUS, arena, &pubk->u.rsa.modulus); @@ -1258,7 +1327,6 @@ SECKEY_ConvertToPublicKey(SECKEYPrivateK if (rv != SECSuccess) break; return pubk; - break; case ecKey: rv = PK11_ReadAttribute(privk->pkcs11Slot, privk->pkcs11ID, CKA_EC_PARAMS, arena, &pubk->u.ec.DEREncodedParams); @@ -1268,7 +1336,23 @@ SECKEY_ConvertToPublicKey(SECKEYPrivateK rv = PK11_ReadAttribute(privk->pkcs11Slot, privk->pkcs11ID, CKA_EC_POINT, arena, &pubk->u.ec.publicValue); if (rv != SECSuccess || pubk->u.ec.publicValue.len == 0) { - break; + pubKeyHandle = seckey_FindPublicKeyHandle(privk, pubk); + if (pubKeyHandle == CK_INVALID_HANDLE) + break; + rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, + CKA_EC_POINT, arena, &pubk->u.ec.publicValue); + if (rv != SECSuccess) + break; + } + /* ec.publicValue should be decoded, PKCS #11 defines CKA_EC_POINT + * as encoded, but it's not always. try do decoded it and if it + * succeeds store the decoded value */ + rv = SEC_QuickDERDecodeItem(arena, &decodedPoint, + SEC_ASN1_GET(SEC_OctetStringTemplate), &pubk->u.ec.publicValue); + if (rv == SECSuccess) { + /* both values are in the public key arena, so it's safe to + * overwrite the old value */ + pubk->u.ec.publicValue = decodedPoint; } pubk->u.ec.encoding = ECPoint_Undefined; return pubk; @@ -1276,7 +1360,9 @@ SECKEY_ConvertToPublicKey(SECKEYPrivateK break; } - PORT_FreeArena(arena, PR_FALSE); + /* must use Destroy public key here, because some paths create temporary + * PKCS #11 objects which need to be freed */ + SECKEY_DestroyPublicKey(pubk); return NULL; } diff -up ./nss/lib/pk11wrap/pk11priv.h.pub-priv-mechs ./nss/lib/pk11wrap/pk11priv.h --- ./nss/lib/pk11wrap/pk11priv.h.pub-priv-mechs 2019-05-10 14:14:18.000000000 -0700 +++ ./nss/lib/pk11wrap/pk11priv.h 2019-06-05 12:01:13.729544204 -0700 @@ -111,6 +111,7 @@ CK_OBJECT_HANDLE PK11_FindObjectForCert( PK11SymKey *pk11_CopyToSlot(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, CK_ATTRIBUTE_TYPE operation, PK11SymKey *symKey); unsigned int pk11_GetPredefinedKeyLength(CK_KEY_TYPE keyType); +CK_OBJECT_HANDLE PK11_DerivePubKeyFromPrivKey(SECKEYPrivateKey *privKey); /********************************************************************** * Certs diff -up ./nss/lib/pk11wrap/pk11skey.c.pub-priv-mechs ./nss/lib/pk11wrap/pk11skey.c --- ./nss/lib/pk11wrap/pk11skey.c.pub-priv-mechs 2019-05-10 14:14:18.000000000 -0700 +++ ./nss/lib/pk11wrap/pk11skey.c 2019-06-05 12:01:13.730544203 -0700 @@ -1840,6 +1840,35 @@ loser: } /* + * This regenerate a public key from a private key. This function is currently + * NSS private. If we want to make it public, we need to add and optional + * template or at least flags (a.la. PK11_DeriveWithFlags). + */ +CK_OBJECT_HANDLE +PK11_DerivePubKeyFromPrivKey(SECKEYPrivateKey *privKey) +{ + PK11SlotInfo *slot = privKey->pkcs11Slot; + CK_MECHANISM mechanism; + CK_OBJECT_HANDLE objectID = CK_INVALID_HANDLE; + CK_RV crv; + + mechanism.mechanism = CKM_NSS_PUB_FROM_PRIV; + mechanism.pParameter = NULL; + mechanism.ulParameterLen = 0; + + PK11_EnterSlotMonitor(slot); + crv = PK11_GETTAB(slot)->C_DeriveKey(slot->session, &mechanism, + privKey->pkcs11ID, NULL, 0, + &objectID); + PK11_ExitSlotMonitor(slot); + if (crv != CKR_OK) { + PORT_SetError(PK11_MapError(crv)); + return CK_INVALID_HANDLE; + } + return objectID; +} + +/* * This Generates a wrapping key based on a privateKey, publicKey, and two * random numbers. For Mail usage RandomB should be NULL. In the Sender's * case RandomA is generate, outherwize it is passed.