Bug 1034855 - Implement JWK import/export for ECDH r=rbarnes,keeler

This commit is contained in:
Tim Taubert 2014-07-29 11:10:07 +02:00
parent 1b1b69b829
commit 30036cea6f
4 changed files with 660 additions and 74 deletions

View File

@ -352,57 +352,35 @@ CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
return NS_OK;
}
SECKEYPrivateKey*
CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
SECItem*
CreateECPointForCoordinates(const CryptoBuffer& aX,
const CryptoBuffer& aY,
PLArenaPool* aArena)
{
if (!aJwk.mKty.WasPassed() || !aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_RSA)) {
// Check that both points have the same length.
if (aX.Length() != aY.Length()) {
return nullptr;
}
// Verify that all of the required parameters are present
CryptoBuffer n, e, d, p, q, dp, dq, qi;
if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
!aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) ||
!aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) ||
!aJwk.mP.WasPassed() || NS_FAILED(p.FromJwkBase64(aJwk.mP.Value())) ||
!aJwk.mQ.WasPassed() || NS_FAILED(q.FromJwkBase64(aJwk.mQ.Value())) ||
!aJwk.mDp.WasPassed() || NS_FAILED(dp.FromJwkBase64(aJwk.mDp.Value())) ||
!aJwk.mDq.WasPassed() || NS_FAILED(dq.FromJwkBase64(aJwk.mDq.Value())) ||
!aJwk.mQi.WasPassed() || NS_FAILED(qi.FromJwkBase64(aJwk.mQi.Value()))) {
// Create point.
SECItem* point = ::SECITEM_AllocItem(aArena, nullptr, aX.Length() + aY.Length() + 1);
if (!point) {
return nullptr;
}
// Compute the ID for this key
// This is generated with a SHA-1 hash, so unlikely to collide
ScopedSECItem nItem(n.ToSECItem());
ScopedSECItem objID(PK11_MakeIDFromPubKey(nItem.get()));
if (!nItem.get() || !objID.get()) {
return nullptr;
}
// Populate template from parameters
CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
CK_KEY_TYPE rsaValue = CKK_RSA;
CK_BBOOL falseValue = CK_FALSE;
CK_ATTRIBUTE keyTemplate[14] = {
{ CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
{ CKA_KEY_TYPE, &rsaValue, sizeof(rsaValue) },
{ CKA_TOKEN, &falseValue, sizeof(falseValue) },
{ CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
{ CKA_PRIVATE, &falseValue, sizeof(falseValue) },
{ CKA_ID, objID->data, objID->len },
{ CKA_MODULUS, (void*) n.Elements(), n.Length() },
{ CKA_PUBLIC_EXPONENT, (void*) e.Elements(), e.Length() },
{ CKA_PRIVATE_EXPONENT, (void*) d.Elements(), d.Length() },
{ CKA_PRIME_1, (void*) p.Elements(), p.Length() },
{ CKA_PRIME_2, (void*) q.Elements(), q.Length() },
{ CKA_EXPONENT_1, (void*) dp.Elements(), dp.Length() },
{ CKA_EXPONENT_2, (void*) dq.Elements(), dq.Length() },
{ CKA_COEFFICIENT, (void*) qi.Elements(), qi.Length() },
};
// Set point data.
point->data[0] = EC_POINT_FORM_UNCOMPRESSED;
memcpy(point->data + 1, aX.Elements(), aX.Length());
memcpy(point->data + 1 + aX.Length(), aY.Elements(), aY.Length());
return point;
}
SECKEYPrivateKey*
PrivateKeyFromPrivateKeyTemplate(SECItem* aObjID,
CK_ATTRIBUTE* aTemplate,
CK_ULONG aTemplateSize)
{
// Create a generic object with the contents of the key
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot.get()) {
@ -410,8 +388,8 @@ CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
}
ScopedPK11GenericObject obj(PK11_CreateGenericObject(slot.get(),
keyTemplate,
PR_ARRAY_SIZE(keyTemplate),
aTemplate,
aTemplateSize,
PR_FALSE));
if (!obj.get()) {
return nullptr;
@ -419,14 +397,134 @@ CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
// Have NSS translate the object to a private key by inspection
// and make a copy we can own
ScopedSECKEYPrivateKey privKey(PK11_FindKeyByKeyID(slot.get(), objID.get(),
ScopedSECKEYPrivateKey privKey(PK11_FindKeyByKeyID(slot.get(), aObjID,
nullptr));
if (!privKey.get()) {
return nullptr;
}
return SECKEY_CopyPrivateKey(privKey.get());
}
SECKEYPrivateKey*
CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
if (!aJwk.mKty.WasPassed()) {
return nullptr;
}
CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
CK_BBOOL falseValue = CK_FALSE;
if (aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_EC)) {
// Verify that all of the required parameters are present
CryptoBuffer x, y, d;
if (!aJwk.mCrv.WasPassed() ||
!aJwk.mX.WasPassed() || NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) ||
!aJwk.mY.WasPassed() || NS_FAILED(y.FromJwkBase64(aJwk.mY.Value())) ||
!aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value()))) {
return nullptr;
}
nsString namedCurve;
if (!NormalizeNamedCurveValue(aJwk.mCrv.Value(), namedCurve)) {
return nullptr;
}
ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
if (!arena) {
return nullptr;
}
// Create parameters.
SECItem* params = CreateECParamsForCurve(namedCurve, arena.get());
if (!params) {
return nullptr;
}
SECItem* ecPoint = CreateECPointForCoordinates(x, y, arena.get());
if (!ecPoint) {
return nullptr;
}
// Compute the ID for this key
// This is generated with a SHA-1 hash, so unlikely to collide
ScopedSECItem objID(PK11_MakeIDFromPubKey(ecPoint));
if (!objID.get()) {
return nullptr;
}
// Populate template from parameters
CK_KEY_TYPE ecValue = CKK_EC;
CK_ATTRIBUTE keyTemplate[9] = {
{ CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
{ CKA_KEY_TYPE, &ecValue, sizeof(ecValue) },
{ CKA_TOKEN, &falseValue, sizeof(falseValue) },
{ CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
{ CKA_PRIVATE, &falseValue, sizeof(falseValue) },
{ CKA_ID, objID->data, objID->len },
{ CKA_EC_PARAMS, params->data, params->len },
{ CKA_EC_POINT, ecPoint->data, ecPoint->len },
{ CKA_VALUE, (void*) d.Elements(), d.Length() },
};
return PrivateKeyFromPrivateKeyTemplate(objID, keyTemplate,
PR_ARRAY_SIZE(keyTemplate));
}
if (aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_RSA)) {
// Verify that all of the required parameters are present
CryptoBuffer n, e, d, p, q, dp, dq, qi;
if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
!aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) ||
!aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) ||
!aJwk.mP.WasPassed() || NS_FAILED(p.FromJwkBase64(aJwk.mP.Value())) ||
!aJwk.mQ.WasPassed() || NS_FAILED(q.FromJwkBase64(aJwk.mQ.Value())) ||
!aJwk.mDp.WasPassed() || NS_FAILED(dp.FromJwkBase64(aJwk.mDp.Value())) ||
!aJwk.mDq.WasPassed() || NS_FAILED(dq.FromJwkBase64(aJwk.mDq.Value())) ||
!aJwk.mQi.WasPassed() || NS_FAILED(qi.FromJwkBase64(aJwk.mQi.Value()))) {
return nullptr;
}
// Compute the ID for this key
// This is generated with a SHA-1 hash, so unlikely to collide
ScopedSECItem nItem(n.ToSECItem());
if (!nItem.get()) {
return nullptr;
}
ScopedSECItem objID(PK11_MakeIDFromPubKey(nItem.get()));
if (!objID.get()) {
return nullptr;
}
// Populate template from parameters
CK_KEY_TYPE rsaValue = CKK_RSA;
CK_ATTRIBUTE keyTemplate[14] = {
{ CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
{ CKA_KEY_TYPE, &rsaValue, sizeof(rsaValue) },
{ CKA_TOKEN, &falseValue, sizeof(falseValue) },
{ CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
{ CKA_PRIVATE, &falseValue, sizeof(falseValue) },
{ CKA_ID, objID->data, objID->len },
{ CKA_MODULUS, (void*) n.Elements(), n.Length() },
{ CKA_PUBLIC_EXPONENT, (void*) e.Elements(), e.Length() },
{ CKA_PRIVATE_EXPONENT, (void*) d.Elements(), d.Length() },
{ CKA_PRIME_1, (void*) p.Elements(), p.Length() },
{ CKA_PRIME_2, (void*) q.Elements(), q.Length() },
{ CKA_EXPONENT_1, (void*) dp.Elements(), dp.Length() },
{ CKA_EXPONENT_2, (void*) dq.Elements(), dq.Length() },
{ CKA_COEFFICIENT, (void*) qi.Elements(), qi.Length() },
};
return PrivateKeyFromPrivateKeyTemplate(objID, keyTemplate,
PR_ARRAY_SIZE(keyTemplate));
}
return nullptr;
}
bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
CK_ATTRIBUTE_TYPE aAttribute,
Optional<nsString>& aDst)
@ -449,6 +547,71 @@ bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
return true;
}
bool
ECKeyToJwk(const PK11ObjectType aKeyType, void* aKey, const SECItem* aEcParams,
const SECItem* aPublicValue, JsonWebKey& aRetVal)
{
aRetVal.mX.Construct();
aRetVal.mY.Construct();
// Check that the given EC parameters are valid.
if (!CheckEncodedECParameters(aEcParams)) {
return false;
}
// Construct the OID tag.
SECItem oid = { siBuffer, nullptr, 0 };
oid.len = aEcParams->data[1];
oid.data = aEcParams->data + 2;
uint32_t flen;
switch (SECOID_FindOIDTag(&oid)) {
case SEC_OID_SECG_EC_SECP256R1:
flen = 32; // bytes
aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P256));
break;
case SEC_OID_SECG_EC_SECP384R1:
flen = 48; // bytes
aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P384));
break;
case SEC_OID_SECG_EC_SECP521R1:
flen = 66; // bytes
aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P521));
break;
default:
return false;
}
// No support for compressed points.
if (aPublicValue->data[0] != EC_POINT_FORM_UNCOMPRESSED) {
return false;
}
// Check length of uncompressed point coordinates.
if (aPublicValue->len != (2 * flen + 1)) {
return false;
}
ScopedSECItem ecPointX(::SECITEM_AllocItem(nullptr, nullptr, flen));
ScopedSECItem ecPointY(::SECITEM_AllocItem(nullptr, nullptr, flen));
if (!ecPointX || !ecPointY) {
return false;
}
// Extract point data.
memcpy(ecPointX->data, aPublicValue->data + 1, flen);
memcpy(ecPointY->data, aPublicValue->data + 1 + flen, flen);
CryptoBuffer x, y;
if (!x.Assign(ecPointX) || NS_FAILED(x.ToJwkBase64(aRetVal.mX.Value())) ||
!y.Assign(ecPointY) || NS_FAILED(y.ToJwkBase64(aRetVal.mY.Value()))) {
return false;
}
aRetVal.mKty.Construct(NS_LITERAL_STRING(JWK_TYPE_EC));
return true;
}
nsresult
CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
JsonWebKey& aRetVal,
@ -479,7 +642,36 @@ CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
aRetVal.mKty.Construct(NS_LITERAL_STRING(JWK_TYPE_RSA));
return NS_OK;
}
case ecKey: // TODO: Bug 1034855
case ecKey: {
// Read EC params.
ScopedSECItem params(::SECITEM_AllocItem(nullptr, nullptr, 0));
SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey,
CKA_EC_PARAMS, params);
if (rv != SECSuccess) {
return NS_ERROR_DOM_OPERATION_ERR;
}
// Read public point Q.
ScopedSECItem ecPoint(::SECITEM_AllocItem(nullptr, nullptr, 0));
rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, CKA_EC_POINT,
ecPoint);
if (rv != SECSuccess) {
return NS_ERROR_DOM_OPERATION_ERR;
}
if (!ECKeyToJwk(PK11_TypePrivKey, aPrivKey, params, ecPoint, aRetVal)) {
return NS_ERROR_DOM_OPERATION_ERR;
}
aRetVal.mD.Construct();
// Read private value.
if (!ReadAndEncodeAttribute(aPrivKey, CKA_VALUE, aRetVal.mD)) {
return NS_ERROR_DOM_OPERATION_ERR;
}
return NS_OK;
}
default:
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
@ -489,40 +681,89 @@ SECKEYPublicKey*
CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
if (!aJwk.mKty.WasPassed() || !aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_RSA)) {
if (!aJwk.mKty.WasPassed()) {
return nullptr;
}
// Verify that all of the required parameters are present
CryptoBuffer n, e;
if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
!aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value()))) {
return nullptr;
if (aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_RSA)) {
// Verify that all of the required parameters are present
CryptoBuffer n, e;
if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
!aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value()))) {
return nullptr;
}
// Transcode to a DER RSAPublicKey structure
struct RSAPublicKeyData {
SECItem n;
SECItem e;
};
const RSAPublicKeyData input = {
{ siUnsignedInteger, n.Elements(), (unsigned int) n.Length() },
{ siUnsignedInteger, e.Elements(), (unsigned int) e.Length() }
};
const SEC_ASN1Template rsaPublicKeyTemplate[] = {
{SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(RSAPublicKeyData)},
{SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, n),},
{SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, e),},
{0,}
};
ScopedSECItem pkDer(SEC_ASN1EncodeItem(nullptr, nullptr, &input,
rsaPublicKeyTemplate));
if (!pkDer.get()) {
return nullptr;
}
return SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA);
}
// Transcode to a DER RSAPublicKey structure
struct RSAPublicKeyData {
SECItem n;
SECItem e;
};
const RSAPublicKeyData input = {
{ siUnsignedInteger, n.Elements(), (unsigned int) n.Length() },
{ siUnsignedInteger, e.Elements(), (unsigned int) e.Length() }
};
const SEC_ASN1Template rsaPublicKeyTemplate[] = {
{SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(RSAPublicKeyData)},
{SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, n),},
{SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, e),},
{0,}
};
if (aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_EC)) {
// Verify that all of the required parameters are present
CryptoBuffer x, y;
if (!aJwk.mCrv.WasPassed() ||
!aJwk.mX.WasPassed() || NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) ||
!aJwk.mY.WasPassed() || NS_FAILED(y.FromJwkBase64(aJwk.mY.Value()))) {
return nullptr;
}
ScopedSECItem pkDer(SEC_ASN1EncodeItem(nullptr, nullptr, &input,
rsaPublicKeyTemplate));
if (!pkDer.get()) {
return nullptr;
ScopedSECKEYPublicKey key(PORT_ZNew(SECKEYPublicKey));
if (!key) {
return nullptr;
}
key->keyType = ecKey;
key->pkcs11Slot = nullptr;
key->pkcs11ID = CK_INVALID_HANDLE;
nsString namedCurve;
if (!NormalizeNamedCurveValue(aJwk.mCrv.Value(), namedCurve)) {
return nullptr;
}
ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
if (!arena) {
return nullptr;
}
// Create parameters.
SECItem* params = CreateECParamsForCurve(namedCurve, arena.get());
if (!params) {
return nullptr;
}
key->u.ec.DEREncodedParams = *params;
// Create point.
SECItem* point = CreateECPointForCoordinates(x, y, arena.get());
if (!point) {
return nullptr;
}
key->u.ec.publicValue = *point;
return SECKEY_CopyPublicKey(key);
}
return SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA);
return nullptr;
}
nsresult
@ -546,7 +787,12 @@ CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
aRetVal.mKty.Construct(NS_LITERAL_STRING(JWK_TYPE_RSA));
return NS_OK;
}
case ecKey: // TODO
case ecKey:
if (!ECKeyToJwk(PK11_TypePubKey, aPubKey, &aPubKey->u.ec.DEREncodedParams,
&aPubKey->u.ec.publicValue, aRetVal)) {
return NS_ERROR_DOM_OPERATION_ERR;
}
return NS_OK;
default:
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}

View File

@ -1654,6 +1654,89 @@ private:
}
};
class ImportEcKeyTask : public ImportKeyTask
{
public:
ImportEcKeyTask(JSContext* aCx, const nsAString& aFormat,
const ObjectOrString& aAlgorithm, bool aExtractable,
const Sequence<nsString>& aKeyUsages)
{
ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
}
ImportEcKeyTask(JSContext* aCx, const nsAString& aFormat,
JS::Handle<JSObject*> aKeyData,
const ObjectOrString& aAlgorithm, bool aExtractable,
const Sequence<nsString>& aKeyUsages)
{
ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
if (NS_FAILED(mEarlyRv)) {
return;
}
SetKeyData(aCx, aKeyData);
}
private:
nsString mNamedCurve;
virtual nsresult DoCrypto() MOZ_OVERRIDE
{
if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
// Import the key data itself
ScopedSECKEYPublicKey pubKey;
ScopedSECKEYPrivateKey privKey;
nsNSSShutDownPreventionLock locker;
if (mJwk.mD.WasPassed()) {
// Private key import
privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
if (!privKey) {
return NS_ERROR_DOM_DATA_ERR;
}
mKey->SetPrivateKey(privKey.get());
mKey->SetType(CryptoKey::PRIVATE);
} else {
// Public key import
pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
if (!pubKey) {
return NS_ERROR_DOM_DATA_ERR;
}
mKey->SetPublicKey(pubKey.get());
mKey->SetType(CryptoKey::PUBLIC);
}
if (!NormalizeNamedCurveValue(mJwk.mCrv.Value(), mNamedCurve)) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
return NS_OK;
}
virtual nsresult AfterCrypto() MOZ_OVERRIDE
{
// Check permissions for the requested operation
if (mKey->GetKeyType() == CryptoKey::PRIVATE &&
mKey->HasUsageOtherThan(CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY)) {
return NS_ERROR_DOM_DATA_ERR;
}
nsIGlobalObject* global = mKey->GetParentObject();
mKey->SetAlgorithm(new EcKeyAlgorithm(global, mAlgName, mNamedCurve));
if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
return NS_ERROR_DOM_DATA_ERR;
}
return NS_OK;
}
};
class ExportKeyTask : public WebCryptoTask
{
public:
@ -2589,6 +2672,9 @@ WebCryptoTask::CreateImportKeyTask(JSContext* aCx,
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
return new ImportRsaKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
aExtractable, aKeyUsages);
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
return new ImportEcKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
aExtractable, aKeyUsages);
} else {
return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
}

View File

@ -452,5 +452,114 @@ tv = {
"c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c09742de989547288" +
"0416021442c6ee70beb7465928a1efe692d2281b8f7b53d6"
)
}
},
// KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [EC]
// <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
ecdh_p256: {
jwk_pub: {
kty: "EC",
crv: "P-256",
x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
},
jwk_priv: {
kty: "EC",
crv: "P-256",
d: "qq_LEzeJpR00KM5DQvL2MNtJcbi0KcGVcoPIHNnwm2A",
x: "FNwJHA-FwnSx5tKXFV_iLN408gbKUHRV06WnQlzTdN4",
y: "is9pWAaneK4RdxmdLfsq5IwizDmUS2w8OGS99sKm3ek"
},
secret: util.hex2abv(
"35669cd5c244ba6c1ea89b8802c3d1db815cd769979072e6556eb98548c65f7d"
)
},
// KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [ED]
// <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
ecdh_p384: {
jwk_pub: {
kty: "EC",
crv: "P-384",
x: "YoV6fhCph4kyt7sUkqiZOtbRs0rF6etPqlnrn1nzSB95NElaw4uTK7Pn2nlFFqqH",
y: "bf3tRz6icq3-W6hhmoqDTBKjdOQUJ5xHr5kX4X-h5MZk_P_nCrG3IUVl1SAbhWDw"
},
jwk_priv: {
kty: "EC",
crv: "P-384",
d: "RT8f0pRw4CL1Tgk4rwuNnNbFoQBNTTBkr7WVLLm4fDA3boYZpNB_t-rbMVLx0CRp",
x: "_XwhXRnOzEfCsWIRCz3QLClaDkigQFvXmqYNdh/7vJdADykPbfGi1VgAu3XJdXoD",
y: "S1P_FBCXYGE-5VPvTCRnFT7bPIPmUPV9qKTM24TQFYEUgIDfzCLsyGCWK-rhP6jU"
},
secret: util.hex2abv(
"a3d28aa18f905a48a5f166b4ddbf5f6b499e43858ccdd80b869946aba2c5d461" +
"db6a1e5b1137687801878ff0f8d9a7b3"
)
},
// KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [EE]
// <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
ecdh_p521: {
jwk_pub: {
kty: "EC",
crv: "P-521",
x: "AeCLgRZ-BPqfhq4jt409-E26VHW5l29q74cHbIbQiS_-Gcqdo-087jHdPXUksGpr" +
"Nyp_RcTZd94t3peXzQziQIqo",
y: "AZIAp8QVnU9hBOkLScv0dy540uGtBWHkWj4DGh-Exh4iWZ0E-YBS8-HVx2eB-nfG" +
"AGEy4-BzfpFFlfidOS1Tg77J"
},
jwk_priv: {
kty: "EC",
crv: "P-521",
d: "ABtsfkDGFarQU4kb7e2gPszGCTT8GLDaaJbFQenFZce3qp_dh0qZarXHKBZ-BVic" +
"NeIW5Sk661UoNfwykSvmh77S",
x: "AcD_6Eb4A-8QdUM70c6F0WthN1kvV4fohS8QHbod6B4y1ZDU54mQuCR-3IBjcV1c" +
"oh18uxbyUn5szMuCgjZUiD0y",
y: "AU3WKJffztkhAQetBXaLvUSIHa87HMn8vZFB04lWipH-SrsrAu_4N-6iam0OD4EJ" +
"0kOMH8iEh7yuivaKsFRzm2-m"
},
secret: util.hex2abv(
"00561eb17d856552c21b8cbe7d3d60d1ea0db738b77d4050fa2dbd0773edc395" +
"09854d9e30e843964ed3fd303339e338f31289120a38f94e9dc9ff7d4b3ea8f2" +
"5e01"
)
},
// Some test vectors that we should fail to import.
ecdh_p256_negative: {
// The given curve doesn't exist / isn't supported.
jwk_bad_crv: {
kty: "EC",
crv: "P-123",
x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
},
// The crv parameter is missing.
jwk_missing_crv: {
kty: "EC",
x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
},
// The X coordinate is missing.
jwk_missing_x: {
kty: "EC",
crv: "P-256",
y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
},
// The Y coordinate is missing.
jwk_missing_y: {
kty: "EC",
crv: "P-256",
x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
}
},
}

View File

@ -1998,3 +1998,148 @@ TestArray.addTest(
.then(error(that), complete(that));
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"JWK import an ECDH public and private key and derive bits (P-256)",
function () {
var that = this;
var alg = { name: "ECDH" };
var pubKey, privKey;
function setPub(x) { pubKey = x; }
function setPriv(x) { privKey = x; }
function doDerive() {
var alg = { name: "ECDH", public: pubKey };
return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p256.secret.byteLength * 8);
}
Promise.all([
crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"])
.then(setPriv, error(that)),
crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, false, ["deriveBits"])
.then(setPub, error(that))
]).then(doDerive, error(that))
.then(memcmp_complete(that, tv.ecdh_p256.secret), error(that));
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"JWK import an ECDH public and private key and derive bits (P-384)",
function () {
var that = this;
var alg = { name: "ECDH" };
var pubKey, privKey;
function setPub(x) { pubKey = x; }
function setPriv(x) { privKey = x; }
function doDerive() {
var alg = { name: "ECDH", public: pubKey };
return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p384.secret.byteLength * 8);
}
Promise.all([
crypto.subtle.importKey("jwk", tv.ecdh_p384.jwk_priv, alg, false, ["deriveBits"])
.then(setPriv, error(that)),
crypto.subtle.importKey("jwk", tv.ecdh_p384.jwk_pub, alg, false, ["deriveBits"])
.then(setPub, error(that))
]).then(doDerive, error(that))
.then(memcmp_complete(that, tv.ecdh_p384.secret), error(that));
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"JWK import an ECDH public and private key and derive bits (P-521)",
function () {
var that = this;
var alg = { name: "ECDH" };
var pubKey, privKey;
function setPub(x) { pubKey = x; }
function setPriv(x) { privKey = x; }
function doDerive() {
var alg = { name: "ECDH", public: pubKey };
return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p521.secret.byteLength * 8);
}
Promise.all([
crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_priv, alg, false, ["deriveBits"])
.then(setPriv, error(that)),
crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_pub, alg, false, ["deriveBits"])
.then(setPub, error(that))
]).then(doDerive, error(that))
.then(memcmp_complete(that, tv.ecdh_p521.secret), error(that));
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"JWK import/export roundtrip with ECDH (P-256)",
function () {
var that = this;
var alg = { name: "ECDH" };
var pubKey, privKey;
function setPub(x) { pubKey = x; }
function setPriv(x) { privKey = x; }
function doExportPub() {
return crypto.subtle.exportKey("jwk", pubKey);
}
function doExportPriv() {
return crypto.subtle.exportKey("jwk", privKey);
}
Promise.all([
crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, true, ["deriveBits"])
.then(setPriv, error(that)),
crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, true, ["deriveBits"])
.then(setPub, error(that))
]).then(doExportPub, error(that))
.then(function (x) {
var tp = tv.ecdh_p256.jwk_pub;
if ((tp.kty != x.kty) &&
(tp.crv != x.crv) &&
(tp.x != x.x) &&
(tp.y != x.y)) {
throw "exported public key doesn't match";
}
}, error(that))
.then(doExportPriv, error(that))
.then(complete(that, function (x) {
var tp = tv.ecdh_p256.jwk_priv;
return (tp.kty == x.kty) &&
(tp.crv == x.crv) &&
(tp.d == x.d) &&
(tp.x == x.x) &&
(tp.y == x.y);
}), error(that));
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"Test that importing bad JWKs fails",
function () {
var that = this;
var alg = { name: "ECDH" };
var tvs = tv.ecdh_p256_negative;
function doTryImport(jwk) {
return function () {
return crypto.subtle.importKey("jwk", jwk, alg, false, ["deriveBits"]);
}
}
doTryImport(tvs.jwk_bad_crv)()
.then(error(that), doTryImport(tvs.jwk_missing_crv))
.then(error(that), doTryImport(tvs.jwk_missing_x))
.then(error(that), doTryImport(tvs.jwk_missing_y))
.then(error(that), complete(that));
}
);