Skip to content

Commit f739dac

Browse files
committed
Bug 2047310 - add PK11_CreatePrivateKeyFromTemplate. r=nss-reviewers,keeler
Differential Revision: https://phabricator.services.mozilla.com/D306572
1 parent 32d4df3 commit f739dac

7 files changed

Lines changed: 289 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
1 Added function:
3+
4+
'function SECKEYPrivateKey* PK11_CreatePrivateKeyFromTemplate(PK11SlotInfo*, const CK_ATTRIBUTE*, unsigned int, void*)' {PK11_CreatePrivateKeyFromTemplate@@NSS_3.126}
5+

gtests/pk11_gtest/manifest.mn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ CPPSRCS = \
1919
pk11_aeskeywrappad_unittest.cc \
2020
pk11_cbc_unittest.cc \
2121
pk11_chacha20poly1305_unittest.cc \
22+
pk11_create_priv_key_from_template_unittest.cc \
2223
pk11_curve25519_unittest.cc \
2324
pk11_der_private_key_import_unittest.cc \
2425
pk11_des_unittest.cc \
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2+
/* vim: set ts=2 et sw=2 tw=80: */
3+
/* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
5+
* You can obtain one at http://mozilla.org/MPL/2.0/. */
6+
7+
#include <vector>
8+
9+
#include "keyhi.h"
10+
#include "nss.h"
11+
#include "pk11pub.h"
12+
#include "prerror.h"
13+
#include "secerr.h"
14+
#include "secitem.h"
15+
16+
#include "gtest/gtest.h"
17+
#include "nss_scoped_ptrs.h"
18+
#include "pk11_keygen.h"
19+
20+
namespace nss_test {
21+
22+
// Tests for PK11_CreatePrivateKeyFromTemplate, which builds a SECKEYPrivateKey
23+
// directly from a fully-specified PKCS #11 private-key template.
24+
25+
class CreatePrivKeyFromTemplateTest : public ::testing::Test {
26+
protected:
27+
// Read a raw PKCS #11 attribute off a private key. The key must be
28+
// non-sensitive for secret attributes (e.g. CKA_VALUE) to be readable.
29+
std::vector<uint8_t> ReadAttr(SECKEYPrivateKey* key, CK_ATTRIBUTE_TYPE type) {
30+
SECItem item = {siBuffer, nullptr, 0};
31+
EXPECT_EQ(SECSuccess,
32+
PK11_ReadRawAttribute(PK11_TypePrivKey, key, type, &item))
33+
<< "failed to read attribute " << type;
34+
std::vector<uint8_t> out(item.data, item.data + item.len);
35+
SECITEM_FreeItem(&item, PR_FALSE);
36+
return out;
37+
}
38+
39+
// Generate an extractable (non-sensitive, session) key pair so the raw
40+
// attributes that make up a template can be read back out.
41+
void GenerateExtractable(CK_MECHANISM_TYPE mech, SECOidTag curve,
42+
ScopedSECKEYPrivateKey* priv,
43+
ScopedSECKEYPublicKey* pub) {
44+
Pkcs11KeyPairGenerator generator(mech, curve);
45+
generator.GenerateKey(priv, pub, /*sensitive=*/false);
46+
ASSERT_TRUE(*priv);
47+
ASSERT_TRUE(*pub);
48+
}
49+
50+
// Create a key from a fully-specified template tagged with a unique 'id'
51+
// (CKA_ID), then check the same properties for every key type: the object is
52+
// created in 'slot' (findable by its CKA_ID), it signs data that the original
53+
// public key verifies, and freeing the key destroys the underlying object.
54+
void VerifyRoundTrip(PK11SlotInfo* slot, CK_ATTRIBUTE* templ,
55+
unsigned int count, SECItem* id, SECKEYPublicKey* pub,
56+
CK_MECHANISM_TYPE mech, KeyType expected_type,
57+
unsigned int hash_len) {
58+
ScopedSECKEYPrivateKey key(
59+
PK11_CreatePrivateKeyFromTemplate(slot, templ, count, nullptr));
60+
ASSERT_TRUE(key) << PORT_ErrorToName(PORT_GetError());
61+
EXPECT_EQ(expected_type, SECKEY_GetPrivateKeyType(key.get()));
62+
63+
// The object was created in the slot.
64+
ScopedSECKEYPrivateKey found(PK11_FindKeyByKeyID(slot, id, nullptr));
65+
EXPECT_TRUE(found);
66+
// PK11_FindKeyByKeyID returns a non-owning wrapper; dropping it frees the
67+
// wrapper but leaves the underlying object in place.
68+
found.reset();
69+
70+
// Sign with the imported key and verify with the original public key. This
71+
// confirms the private value was imported correctly and the key is usable.
72+
std::vector<uint8_t> hash_buf(hash_len, 0);
73+
SECItem hash = {siBuffer, hash_buf.data(), hash_len};
74+
int sig_len = PK11_SignatureLen(key.get());
75+
ASSERT_GT(sig_len, 0);
76+
std::vector<uint8_t> sig_buf(sig_len);
77+
SECItem sig = {siBuffer, sig_buf.data(), (unsigned int)sig_len};
78+
ASSERT_EQ(SECSuccess,
79+
PK11_SignWithMechanism(key.get(), mech, nullptr, &sig, &hash));
80+
EXPECT_EQ(SECSuccess, PK11_VerifyWithMechanism(pub, mech, nullptr, &sig,
81+
&hash, nullptr));
82+
83+
// The returned key owns its PKCS #11 object: destroying the key destroys
84+
// the object, so it can no longer be found by its ID.
85+
key.reset();
86+
ScopedSECKEYPrivateKey gone(PK11_FindKeyByKeyID(slot, id, nullptr));
87+
EXPECT_FALSE(gone);
88+
}
89+
};
90+
91+
// Build an EC P-256 private-key template from raw key material, create a key
92+
// from it, use it, and confirm it owns (and cleans up) its PKCS #11 object.
93+
TEST_F(CreatePrivKeyFromTemplateTest, EcRoundTrip) {
94+
ScopedSECKEYPrivateKey gen_priv;
95+
ScopedSECKEYPublicKey gen_pub;
96+
GenerateExtractable(CKM_EC_KEY_PAIR_GEN, SEC_OID_SECG_EC_SECP256R1, &gen_priv,
97+
&gen_pub);
98+
99+
std::vector<uint8_t> ec_params = ReadAttr(gen_priv.get(), CKA_EC_PARAMS);
100+
std::vector<uint8_t> value = ReadAttr(gen_priv.get(), CKA_VALUE);
101+
// The public point lives on the public key, not the private key, just as
102+
// WebCrypto's AddPublicKeyData reads it from aPublicKey->u.ec.publicValue.
103+
SECItem& point = gen_pub->u.ec.publicValue;
104+
105+
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
106+
ASSERT_TRUE(slot);
107+
108+
// A unique CKA_ID lets us confirm via PK11_FindKeyByKeyID that the object is
109+
// first created and later destroyed.
110+
uint8_t id_buf[20];
111+
ASSERT_EQ(SECSuccess, PK11_GenerateRandom(id_buf, sizeof(id_buf)));
112+
SECItem id = {siBuffer, id_buf, sizeof(id_buf)};
113+
114+
CK_OBJECT_CLASS classValue = CKO_PRIVATE_KEY;
115+
CK_KEY_TYPE keyTypeValue = CKK_EC;
116+
CK_BBOOL falseValue = CK_FALSE;
117+
CK_ATTRIBUTE templ[] = {
118+
{CKA_CLASS, &classValue, sizeof(classValue)},
119+
{CKA_KEY_TYPE, &keyTypeValue, sizeof(keyTypeValue)},
120+
{CKA_TOKEN, &falseValue, sizeof(falseValue)},
121+
{CKA_SENSITIVE, &falseValue, sizeof(falseValue)},
122+
{CKA_PRIVATE, &falseValue, sizeof(falseValue)},
123+
{CKA_ID, id_buf, sizeof(id_buf)},
124+
{CKA_EC_PARAMS, ec_params.data(), (CK_ULONG)ec_params.size()},
125+
{CKA_EC_POINT, point.data, point.len},
126+
{CKA_VALUE, value.data(), (CK_ULONG)value.size()},
127+
};
128+
129+
VerifyRoundTrip(slot.get(), templ, PR_ARRAY_SIZE(templ), &id, gen_pub.get(),
130+
CKM_ECDSA, ecKey, /*hash_len=*/32);
131+
}
132+
133+
// Same round trip for an RSA key, exercising the multi-attribute CRT template.
134+
TEST_F(CreatePrivKeyFromTemplateTest, RsaRoundTrip) {
135+
ScopedSECKEYPrivateKey gen_priv;
136+
ScopedSECKEYPublicKey gen_pub;
137+
GenerateExtractable(CKM_RSA_PKCS_KEY_PAIR_GEN, SEC_OID_UNKNOWN, &gen_priv,
138+
&gen_pub);
139+
140+
std::vector<uint8_t> modulus = ReadAttr(gen_priv.get(), CKA_MODULUS);
141+
std::vector<uint8_t> pub_exp = ReadAttr(gen_priv.get(), CKA_PUBLIC_EXPONENT);
142+
std::vector<uint8_t> priv_exp =
143+
ReadAttr(gen_priv.get(), CKA_PRIVATE_EXPONENT);
144+
std::vector<uint8_t> prime1 = ReadAttr(gen_priv.get(), CKA_PRIME_1);
145+
std::vector<uint8_t> prime2 = ReadAttr(gen_priv.get(), CKA_PRIME_2);
146+
std::vector<uint8_t> exp1 = ReadAttr(gen_priv.get(), CKA_EXPONENT_1);
147+
std::vector<uint8_t> exp2 = ReadAttr(gen_priv.get(), CKA_EXPONENT_2);
148+
std::vector<uint8_t> coeff = ReadAttr(gen_priv.get(), CKA_COEFFICIENT);
149+
150+
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
151+
ASSERT_TRUE(slot);
152+
153+
// A unique CKA_ID lets us confirm via PK11_FindKeyByKeyID that the object is
154+
// first created and later destroyed.
155+
uint8_t id_buf[20];
156+
ASSERT_EQ(SECSuccess, PK11_GenerateRandom(id_buf, sizeof(id_buf)));
157+
SECItem id = {siBuffer, id_buf, sizeof(id_buf)};
158+
159+
CK_OBJECT_CLASS classValue = CKO_PRIVATE_KEY;
160+
CK_KEY_TYPE keyTypeValue = CKK_RSA;
161+
CK_BBOOL falseValue = CK_FALSE;
162+
CK_ATTRIBUTE templ[] = {
163+
{CKA_CLASS, &classValue, sizeof(classValue)},
164+
{CKA_KEY_TYPE, &keyTypeValue, sizeof(keyTypeValue)},
165+
{CKA_TOKEN, &falseValue, sizeof(falseValue)},
166+
{CKA_SENSITIVE, &falseValue, sizeof(falseValue)},
167+
{CKA_PRIVATE, &falseValue, sizeof(falseValue)},
168+
{CKA_ID, id_buf, sizeof(id_buf)},
169+
{CKA_MODULUS, modulus.data(), (CK_ULONG)modulus.size()},
170+
{CKA_PUBLIC_EXPONENT, pub_exp.data(), (CK_ULONG)pub_exp.size()},
171+
{CKA_PRIVATE_EXPONENT, priv_exp.data(), (CK_ULONG)priv_exp.size()},
172+
{CKA_PRIME_1, prime1.data(), (CK_ULONG)prime1.size()},
173+
{CKA_PRIME_2, prime2.data(), (CK_ULONG)prime2.size()},
174+
{CKA_EXPONENT_1, exp1.data(), (CK_ULONG)exp1.size()},
175+
{CKA_EXPONENT_2, exp2.data(), (CK_ULONG)exp2.size()},
176+
{CKA_COEFFICIENT, coeff.data(), (CK_ULONG)coeff.size()},
177+
};
178+
179+
VerifyRoundTrip(slot.get(), templ, PR_ARRAY_SIZE(templ), &id, gen_pub.get(),
180+
CKM_RSA_PKCS, rsaKey, /*hash_len=*/20);
181+
}
182+
183+
// NULL slot, NULL template, and a zero count are rejected up front.
184+
TEST_F(CreatePrivKeyFromTemplateTest, InvalidArgs) {
185+
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
186+
ASSERT_TRUE(slot);
187+
188+
CK_OBJECT_CLASS classValue = CKO_PRIVATE_KEY;
189+
CK_ATTRIBUTE templ[] = {{CKA_CLASS, &classValue, sizeof(classValue)}};
190+
191+
EXPECT_EQ(nullptr,
192+
PK11_CreatePrivateKeyFromTemplate(nullptr, templ, 1, nullptr));
193+
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
194+
195+
EXPECT_EQ(nullptr,
196+
PK11_CreatePrivateKeyFromTemplate(slot.get(), nullptr, 1, nullptr));
197+
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
198+
199+
EXPECT_EQ(nullptr,
200+
PK11_CreatePrivateKeyFromTemplate(slot.get(), templ, 0, nullptr));
201+
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
202+
}
203+
204+
// A template that lacks the required key material is rejected by the token; the
205+
// function returns NULL rather than a half-formed key.
206+
TEST_F(CreatePrivKeyFromTemplateTest, IncompleteTemplate) {
207+
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
208+
ASSERT_TRUE(slot);
209+
210+
CK_OBJECT_CLASS classValue = CKO_PRIVATE_KEY;
211+
CK_KEY_TYPE keyTypeValue = CKK_EC;
212+
CK_BBOOL falseValue = CK_FALSE;
213+
// Missing CKA_EC_PARAMS / CKA_EC_POINT / CKA_VALUE.
214+
CK_ATTRIBUTE templ[] = {
215+
{CKA_CLASS, &classValue, sizeof(classValue)},
216+
{CKA_KEY_TYPE, &keyTypeValue, sizeof(keyTypeValue)},
217+
{CKA_TOKEN, &falseValue, sizeof(falseValue)},
218+
};
219+
220+
EXPECT_EQ(nullptr, PK11_CreatePrivateKeyFromTemplate(
221+
slot.get(), templ, PR_ARRAY_SIZE(templ), nullptr));
222+
}
223+
224+
} // namespace nss_test

gtests/pk11_gtest/pk11_gtest.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
'pk11_cbc_unittest.cc',
2222
'pk11_chacha20poly1305_unittest.cc',
2323
'pk11_cipherop_unittest.cc',
24+
'pk11_create_priv_key_from_template_unittest.cc',
2425
'pk11_curve25519_unittest.cc',
2526
'pk11_der_private_key_import_unittest.cc',
2627
'pk11_des_unittest.cc',

lib/nss/nss.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,3 +1314,9 @@ PK11_UnwrapPrivKeyByKeyType;
13141314
;+ local:
13151315
;+ *;
13161316
;+};
1317+
;+NSS_3.126 { # NSS 3.126 release
1318+
;+ global:
1319+
PK11_CreatePrivateKeyFromTemplate;
1320+
;+ local:
1321+
;+ *;
1322+
;+};

lib/pk11wrap/pk11akey.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* This file contains functions to manage asymetric keys, (public and
66
* private keys).
77
*/
8+
#include <limits.h>
89
#include <stddef.h>
910

1011
#include "seccomon.h"
@@ -2840,6 +2841,49 @@ PK11_FindKeyByKeyID(PK11SlotInfo *slot, SECItem *keyID, void *wincx)
28402841
return privKey;
28412842
}
28422843

2844+
/*
2845+
* Create a SECKEYPrivateKey directly from a PKCS #11 private key template.
2846+
*
2847+
* The template must fully describe a private key object (at minimum CKA_CLASS =
2848+
* CKO_PRIVATE_KEY, CKA_KEY_TYPE, and the key material). The object is created
2849+
* as a session (non-token) object on 'slot'. The returned SECKEYPrivateKey owns
2850+
* the underlying PKCS #11 object and destroys it when the key is freed with
2851+
* SECKEY_DestroyPrivateKey.
2852+
*/
2853+
SECKEYPrivateKey *
2854+
PK11_CreatePrivateKeyFromTemplate(PK11SlotInfo *slot,
2855+
const CK_ATTRIBUTE *theTemplate,
2856+
unsigned int count, void *wincx)
2857+
{
2858+
CK_OBJECT_HANDLE keyHandle;
2859+
SECKEYPrivateKey *privKey;
2860+
SECStatus rv;
2861+
2862+
/* PK11_CreateNewObject takes the count as an int, so guard against a
2863+
* value that would overflow when narrowed. */
2864+
if (slot == NULL || theTemplate == NULL || count == 0 || count > INT_MAX) {
2865+
PORT_SetError(SEC_ERROR_INVALID_ARGS);
2866+
return NULL;
2867+
}
2868+
2869+
rv = PK11_CreateNewObject(slot, CK_INVALID_HANDLE, theTemplate, count,
2870+
PR_FALSE, &keyHandle);
2871+
if (rv != SECSuccess) {
2872+
return NULL;
2873+
}
2874+
2875+
/* Pass isOwner = PR_TRUE so the returned key owns the session object we
2876+
* just created and destroys it on cleanup. */
2877+
privKey = pk11_MakePrivKey(slot, nullKey, PR_TRUE, keyHandle, wincx);
2878+
if (privKey == NULL) {
2879+
/* Don't leak the object we created if wrapping it failed. */
2880+
(void)PK11_DestroyObject(slot, keyHandle);
2881+
return NULL;
2882+
}
2883+
2884+
return privKey;
2885+
}
2886+
28432887
/*
28442888
* Generate a CKA_ID from the relevant public key data. The CKA_ID is generated
28452889
* from the pubKeyData by SHA1_Hashing it to produce a smaller CKA_ID (to make

lib/pk11wrap/pk11pub.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,14 @@ SECKEYPrivateKey *PK11_FindPrivateKeyFromCert(PK11SlotInfo *slot,
562562
SECKEYPrivateKey *PK11_FindKeyByAnyCert(CERTCertificate *cert, void *wincx);
563563
SECKEYPrivateKey *PK11_FindKeyByKeyID(PK11SlotInfo *slot, SECItem *keyID,
564564
void *wincx);
565+
/*
566+
* Create a SECKEYPrivateKey directly from a fully-specified PKCS #11 private
567+
* key template (CKA_CLASS = CKO_PRIVATE_KEY, CKA_KEY_TYPE, and the key
568+
* material). The key is created as a session object on 'slot'.
569+
*/
570+
SECKEYPrivateKey *PK11_CreatePrivateKeyFromTemplate(
571+
PK11SlotInfo *slot, const CK_ATTRIBUTE *theTemplate, unsigned int count,
572+
void *wincx);
565573
int PK11_GetPrivateModulusLen(SECKEYPrivateKey *key);
566574

567575
SECStatus PK11_Decrypt(PK11SymKey *symkey,

0 commit comments

Comments
 (0)