diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp index 052e92583827..460434c1c86f 100644 --- a/dom/crypto/WebCryptoTask.cpp +++ b/dom/crypto/WebCryptoTask.cpp @@ -2451,9 +2451,110 @@ private: virtual nsresult DoCrypto() MOZ_OVERRIDE { + // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the + // derived symmetric key and don't matter because we ignore them anyway. ScopedPK11SymKey symKey(PK11_PubDeriveWithKDF( mPrivKey, mPubKey, PR_FALSE, nullptr, nullptr, CKM_ECDH1_DERIVE, - CKM_CONCATENATE_DATA_AND_BASE, CKA_DERIVE, 0, CKD_NULL, nullptr, nullptr)); + CKM_SHA512_HMAC, CKA_SIGN, 0, CKD_NULL, nullptr, nullptr)); + + if (!symKey.get()) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey)); + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + // This doesn't leak, because the SECItem* returned by PK11_GetKeyData + // just refers to a buffer managed by symKey. The assignment copies the + // data, so mResult manages one copy, while symKey manages another. + ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey)); + + if (mLength > mResult.Length()) { + return NS_ERROR_DOM_DATA_ERR; + } + + if (!mResult.SetLength(mLength)) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + return NS_OK; + } +}; + +class DeriveDhBitsTask : public ReturnArrayBufferViewTask +{ +public: + DeriveDhBitsTask(JSContext* aCx, + const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength) + : mLength(aLength), + mPrivKey(aKey.GetPrivateKey()) + { + Init(aCx, aAlgorithm, aKey); + } + + DeriveDhBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm) + : mPrivKey(aKey.GetPrivateKey()) + { + mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, mLength); + if (NS_SUCCEEDED(mEarlyRv)) { + Init(aCx, aAlgorithm, aKey); + } + } + + void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey) + { + CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_DH); + + // Check that we have a private key. + if (!mPrivKey) { + mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; + return; + } + + mLength = mLength >> 3; // bits to bytes + + // Retrieve the peer's public key. + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } + + CryptoKey* publicKey = params.mPublic; + mPubKey = publicKey->GetPublicKey(); + if (!mPubKey) { + mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; + return; + } + + KeyAlgorithmProxy alg1 = publicKey->Algorithm(); + CHECK_KEY_ALGORITHM(alg1, WEBCRYPTO_ALG_DH); + + // Both keys must use the same prime and generator. + KeyAlgorithmProxy alg2 = aKey.Algorithm(); + if (alg1.mDh.mPrime != alg2.mDh.mPrime || + alg1.mDh.mGenerator != alg2.mDh.mGenerator) { + mEarlyRv = NS_ERROR_DOM_DATA_ERR; + return; + } + } + +private: + size_t mLength; + ScopedSECKEYPrivateKey mPrivKey; + ScopedSECKEYPublicKey mPubKey; + + virtual nsresult DoCrypto() MOZ_OVERRIDE + { + // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the + // derived symmetric key and don't matter because we ignore them anyway. + ScopedPK11SymKey symKey(PK11_PubDeriveWithKDF( + mPrivKey, mPubKey, PR_FALSE, nullptr, nullptr, CKM_DH_PKCS_DERIVE, + CKM_SHA512_HMAC, CKA_SIGN, 0, CKD_NULL, nullptr, nullptr)); if (!symKey.get()) { return NS_ERROR_DOM_OPERATION_ERR; @@ -2874,6 +2975,10 @@ WebCryptoTask::CreateDeriveBitsTask(JSContext* aCx, return new DeriveEcdhBitsTask(aCx, aAlgorithm, aKey, aLength); } + if (algName.EqualsASCII(WEBCRYPTO_ALG_DH)) { + return new DeriveDhBitsTask(aCx, aAlgorithm, aKey, aLength); + } + return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR); } diff --git a/dom/crypto/test/test-vectors.js b/dom/crypto/test/test-vectors.js index 46fe7632df50..80de1c8b86f3 100644 --- a/dom/crypto/test/test-vectors.js +++ b/dom/crypto/test/test-vectors.js @@ -665,6 +665,13 @@ tv = { "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a63a3620ffffffffffffffff" + ), + + prime2: util.hex2abv( + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff" ) }, } diff --git a/dom/crypto/test/test_WebCrypto_DH.html b/dom/crypto/test/test_WebCrypto_DH.html index 488a0266deaa..8d54701ff2de 100644 --- a/dom/crypto/test/test_WebCrypto_DH.html +++ b/dom/crypto/test/test_WebCrypto_DH.html @@ -55,6 +55,128 @@ TestArray.addTest( ); } ); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Derive bits from a DH key", + function() { + var that = this; + var alg = { + name: "DH", + prime: tv.dh.prime, + generator: new Uint8Array([0x02]) + }; + + function doDerive(x) { + var alg = { + name: "DH", + public: x.publicKey + }; + return crypto.subtle.deriveBits(alg, x.privateKey, 128); + } + + crypto.subtle.generateKey(alg, false, ["deriveBits"]) + .then(doDerive, error(that)) + .then(complete(that, function (x) { + return x.byteLength == 16; + }), error(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Test that DH deriveBits() fails when the public key is not a DH key", + function() { + var that = this; + var pubKey, privKey; + function setPub(x) { pubKey = x.publicKey; } + function setPriv(x) { privKey = x.privateKey; } + + function doGenerateDH() { + var alg = { + name: "DH", + prime: tv.dh.prime, + generator: new Uint8Array([0x02]) + }; + return crypto.subtle.generateKey(alg, false, ["deriveBits"]); + } + + function doGenerateRSA() { + var alg = { + name: "RSA-OAEP", + hash: "SHA-256", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]) + }; + return crypto.subtle.generateKey(alg, false, ["encrypt"]) + } + + function doDerive() { + var alg = {name: "DH", public: pubKey}; + return crypto.subtle.deriveBits(alg, privKey, 128); + } + + doGenerateDH() + .then(setPriv, error(that)) + .then(doGenerateRSA, error(that)) + .then(setPub, error(that)) + .then(doDerive, error(that)) + .then(error(that), complete(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Test that DH deriveBits() fails when the given keys' primes or bases don't match", + function() { + var that = this; + var pubKey, privKey; + function setPub(x) { pubKey = x.publicKey; } + function setPriv(x) { privKey = x.privateKey; } + + function doGenerateDH() { + var alg = { + name: "DH", + prime: tv.dh.prime, + generator: new Uint8Array([0x02]) + }; + return crypto.subtle.generateKey(alg, false, ["deriveBits"]); + } + + function doGenerateDH2() { + var alg = { + name: "DH", + prime: tv.dh.prime2, + generator: new Uint8Array([0x02]) + }; + return crypto.subtle.generateKey(alg, false, ["deriveBits"]); + } + + function doGenerateDH3() { + var alg = { + name: "DH", + prime: tv.dh.prime, + generator: new Uint8Array([0x03]) + }; + return crypto.subtle.generateKey(alg, false, ["deriveBits"]); + } + + function doDerive() { + var alg = {name: "DH", public: pubKey}; + return crypto.subtle.deriveBits(alg, privKey, 128); + } + + doGenerateDH() + .then(setPriv, error(that)) + .then(doGenerateDH2, error(that)) + .then(setPub, error(that)) + .then(doDerive, error(that)) + .then(error(that), doGenerateDH3) + .then(setPub, error(that)) + .then(doDerive, error(that)) + .then(error(that), complete(that)); + } +); /*]]>*/ diff --git a/dom/webidl/SubtleCrypto.webidl b/dom/webidl/SubtleCrypto.webidl index 09bd3ac97ba3..629234d140d9 100644 --- a/dom/webidl/SubtleCrypto.webidl +++ b/dom/webidl/SubtleCrypto.webidl @@ -87,6 +87,10 @@ dictionary EcdhKeyDeriveParams : Algorithm { required CryptoKey public; }; +dictionary DhKeyDeriveParams : Algorithm { + required CryptoKey public; +}; + dictionary EcdsaParams : Algorithm { required AlgorithmIdentifier hash; };