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

This commit is contained in:
Tim Taubert 2014-08-04 09:39:12 +02:00
parent 454efc0d1e
commit d3f757e062
5 changed files with 168 additions and 12 deletions

View File

@ -322,6 +322,23 @@ CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData,
return nullptr;
}
// Check for id-ecDH. Per the WebCrypto spec we must support it but NSS
// does unfortunately not know about it. Let's change the algorithm to
// id-ecPublicKey to make NSS happy.
if (SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH, &spki->algorithm.algorithm)) {
// Retrieve OID data for id-ecPublicKey (1.2.840.10045.2.1).
SECOidData* oidData = SECOID_FindOIDByTag(SEC_OID_ANSIX962_EC_PUBLIC_KEY);
if (!oidData) {
return nullptr;
}
SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
&oidData->oid);
if (rv != SECSuccess) {
return nullptr;
}
}
return SECKEY_ExtractPublicKey(spki.get());
}
@ -343,11 +360,25 @@ CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
CryptoBuffer& aRetVal,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
ScopedSECItem spkiItem(PK11_DEREncodePublicKey(aPubKey));
if (!spkiItem.get()) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
ScopedCERTSubjectPublicKeyInfo spki(SECKEY_CreateSubjectPublicKeyInfo(aPubKey));
if (!spki) {
return NS_ERROR_DOM_OPERATION_ERR;
}
// Per WebCrypto spec we must export ECDH SPKIs with the algorithm OID
// id-ecDH (1.3.132.112). NSS doesn't know about that OID and there is
// no way to specify the algorithm to use when exporting a public key.
if (aPubKey->keyType == ecKey) {
SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
&SEC_OID_DATA_EC_DH);
if (rv != SECSuccess) {
return NS_ERROR_DOM_OPERATION_ERR;
}
}
const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate);
ScopedSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki, tpl));
aRetVal.Assign(spkiItem.get());
return NS_OK;
}

View File

@ -92,6 +92,11 @@
// Define an unknown mechanism type
#define UNKNOWN_CK_MECHANISM CKM_VENDOR_DEFINED+1
// python security/pkix/tools/DottedOIDToCode.py id-ecDH 1.3.132.112
static const uint8_t id_ecDH[] = { 0x2b, 0x81, 0x04, 0x70 };
const SECItem SEC_OID_DATA_EC_DH = { siBuffer, (unsigned char*)id_ecDH,
PR_ARRAY_SIZE(id_ecDH) };
namespace mozilla {
namespace dom {

View File

@ -258,6 +258,26 @@ GetKeySizeForAlgorithm(JSContext* aCx, const ObjectOrString& aAlgorithm,
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
inline bool
MapOIDTagToNamedCurve(SECOidTag aOIDTag, nsString& aResult)
{
switch (aOIDTag) {
case SEC_OID_SECG_EC_SECP256R1:
aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P256);
break;
case SEC_OID_SECG_EC_SECP384R1:
aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P384);
break;
case SEC_OID_SECG_EC_SECP521R1:
aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P521);
break;
default:
return false;
}
return true;
}
// Helper function to clone data from an ArrayBuffer or ArrayBufferView object
inline bool
CloneData(JSContext* aCx, CryptoBuffer& aDst, JS::Handle<JSObject*> aSrc)
@ -1682,16 +1702,12 @@ private:
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()) {
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) && mJwk.mD.WasPassed()) {
// Private key import
privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
if (!privKey) {
@ -1700,19 +1716,47 @@ private:
mKey->SetPrivateKey(privKey.get());
mKey->SetType(CryptoKey::PRIVATE);
} else {
} else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) ||
(mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
!mJwk.mD.WasPassed())) {
// Public key import
pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
} else {
pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
}
if (!pubKey) {
return NS_ERROR_DOM_DATA_ERR;
}
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
if (!CheckEncodedECParameters(&pubKey->u.ec.DEREncodedParams)) {
return NS_ERROR_DOM_OPERATION_ERR;
}
// Construct the OID tag.
SECItem oid = { siBuffer, nullptr, 0 };
oid.len = pubKey->u.ec.DEREncodedParams.data[1];
oid.data = pubKey->u.ec.DEREncodedParams.data + 2;
// Find a matching and supported named curve.
if (!MapOIDTagToNamedCurve(SECOID_FindOIDTag(&oid), mNamedCurve)) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
}
mKey->SetPublicKey(pubKey.get());
mKey->SetType(CryptoKey::PUBLIC);
} else {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
if (!NormalizeNamedCurveValue(mJwk.mCrv.Value(), mNamedCurve)) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
// Extract 'crv' parameter from JWKs.
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
if (!NormalizeNamedCurveValue(mJwk.mCrv.Value(), mNamedCurve)) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
}
return NS_OK;

View File

@ -472,6 +472,20 @@ tv = {
y: "is9pWAaneK4RdxmdLfsq5IwizDmUS2w8OGS99sKm3ek"
},
// vector with algorithm = id-ecDH
spki: util.hex2abv(
"3056301006042b81047006082a8648ce3d030107034200045ce7b86e3b326604" +
"03e63712ef0998deae1027faec3c1be9f76f934dfeb58e98f4cf075b39405dd1" +
"f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
),
// vector with algorithm = id-ecPublicKey
spki_id_ecpk: util.hex2abv(
"3059301306072a8648ce3d020106082a8648ce3d030107034200045ce7b86e3b" +
"32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e98f4cf075b39" +
"405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
),
secret: util.hex2abv(
"35669cd5c244ba6c1ea89b8802c3d1db815cd769979072e6556eb98548c65f7d"
)

View File

@ -2191,3 +2191,65 @@ TestArray.addTest(
.then(complete(that), error(that));
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"SPKI import/export of public ECDH keys (P-256)",
function () {
var that = this;
var alg = { name: "ECDH" };
var keys = ["spki", "spki_id_ecpk"];
function doImport(key) {
return crypto.subtle.importKey("spki", tv.ecdh_p256[key], alg, true, ["deriveBits"]);
}
function doExport(x) {
return crypto.subtle.exportKey("spki", x);
}
function nextKey() {
var key = keys.shift();
var imported = doImport(key);
var derived = imported.then(doExport);
return derived.then(function (x) {
if (!util.memcmp(x, tv.ecdh_p256.spki)) {
throw "exported key is invalid";
}
if (keys.length) {
return nextKey();
}
});
}
nextKey().then(complete(that), error(that));
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"SPKI/JWK import ECDH keys (P-256) and derive a known secret",
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("spki", tv.ecdh_p256.spki, alg, false, ["deriveBits"])
.then(setPub),
crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"])
.then(setPriv)
]).then(doDerive)
.then(memcmp_complete(that, tv.ecdh_p256.secret), error(that));
}
);