mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1034855 - Implement JWK import/export for ECDH r=rbarnes,keeler
This commit is contained in:
parent
3ad9fd0393
commit
a0fd9a526a
@ -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;
|
||||
ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
||||
if (!arena) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SECKEYPublicKey* key = PORT_ArenaZNew(arena, 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user