diff --git a/config/external/nss/nss.symbols b/config/external/nss/nss.symbols index 0a6b015e0ae..850765ccd4b 100644 --- a/config/external/nss/nss.symbols +++ b/config/external/nss/nss.symbols @@ -405,6 +405,7 @@ PK11_LoadPrivKey PK11_Logout PK11_LogoutAll PK11_MakeIDFromPubKey +PK11_MapSignKeyType PK11_MechanismToAlgtag PK11_MergeTokens PK11_NeedLogin @@ -429,11 +430,13 @@ PK11_SetPasswordFunc PK11_SetSlotPWValues PK11_Sign PK11_SignatureLen +PK11_SignWithMechanism PK11_UnwrapPrivKey PK11_UnwrapSymKey PK11_UpdateSlotAttribute PK11_UserDisableSlot PK11_UserEnableSlot +PK11_VerifyWithMechanism PK11_WrapPrivKey PK11_WrapSymKey PORT_Alloc @@ -631,9 +634,11 @@ SEC_StringToOID SEC_UTF8StringTemplate @DATA@ SEC_UTF8StringTemplate_Util @DATA@ SGN_Begin +SGN_CreateDigestInfo SGN_CreateDigestInfo_Util SGN_DecodeDigestInfo SGN_DestroyContext +SGN_DestroyDigestInfo SGN_DestroyDigestInfo_Util SGN_End SGN_NewContext diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp index a42dfca4375..d771781fbfe 100644 --- a/dom/crypto/WebCryptoTask.cpp +++ b/dom/crypto/WebCryptoTask.cpp @@ -20,6 +20,20 @@ #include "mozilla/dom/WebCryptoTask.h" #include "mozilla/dom/WebCryptoThreadPool.h" +// Template taken from security/nss/lib/util/templates.c +// This (or SGN_EncodeDigestInfo) would ideally be exported +// by NSS and until that happens we have to keep our own copy. +const SEC_ASN1Template SGN_DigestInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(SGNDigestInfo) }, + { SEC_ASN1_INLINE, + offsetof(SGNDigestInfo,digestAlgorithm), + SEC_ASN1_GET(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, + offsetof(SGNDigestInfo,digest) }, + { 0, } +}; + namespace mozilla { namespace dom { @@ -265,6 +279,41 @@ MapOIDTagToNamedCurve(SECOidTag aOIDTag, nsString& aResult) return true; } +inline SECOidTag +MapHashAlgorithmNameToOID(const nsString& aName) +{ + SECOidTag hashOID(SEC_OID_UNKNOWN); + + if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) { + hashOID = SEC_OID_SHA1; + } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) { + hashOID = SEC_OID_SHA256; + } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) { + hashOID = SEC_OID_SHA384; + } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) { + hashOID = SEC_OID_SHA512; + } + + return hashOID; +} + +inline CK_MECHANISM_TYPE +MapHashAlgorithmNameToMgfMechanism(const nsString& aName) { + CK_MECHANISM_TYPE mech(UNKNOWN_CK_MECHANISM); + + if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) { + mech = CKG_MGF1_SHA1; + } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) { + mech = CKG_MGF1_SHA256; + } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) { + mech = CKG_MGF1_SHA384; + } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) { + mech = CKG_MGF1_SHA512; + } + + return mech; +} + // Helper function to clone data from an ArrayBuffer or ArrayBufferView object inline bool CloneData(JSContext* aCx, CryptoBuffer& aDst, JS::Handle aSrc) @@ -839,21 +888,15 @@ public: } // Otherwise mLabel remains the empty octet string, as intended - // Look up the MGF based on the KeyAlgorithm. - mHashMechanism = KeyAlgorithmProxy::GetMechanism(aKey.Algorithm().mRsa.mHash); + KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash; + mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg); + mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlg.mName); - switch (mHashMechanism) { - case CKM_SHA_1: - mMgfMechanism = CKG_MGF1_SHA1; break; - case CKM_SHA256: - mMgfMechanism = CKG_MGF1_SHA256; break; - case CKM_SHA384: - mMgfMechanism = CKG_MGF1_SHA384; break; - case CKM_SHA512: - mMgfMechanism = CKG_MGF1_SHA512; break; - default: - mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; - return; + // Check we found appropriate mechanisms. + if (mHashMechanism == UNKNOWN_CK_MECHANISM || + mMgfMechanism == UNKNOWN_CK_MECHANISM) { + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + return; } } @@ -1038,11 +1081,14 @@ public: const CryptoOperationData& aData, bool aSign) : mOidTag(SEC_OID_UNKNOWN) + , mHashMechanism(UNKNOWN_CK_MECHANISM) + , mMgfMechanism(UNKNOWN_CK_MECHANISM) , mPrivKey(aKey.GetPrivateKey()) , mPubKey(aKey.GetPublicKey()) + , mSaltLength(0) , mSign(aSign) , mVerified(false) - , mEcdsa(false) + , mAlgorithm(Algorithm::UNKNOWN) { ATTEMPT_BUFFER_INIT(mData, aData); if (!aSign) { @@ -1050,34 +1096,44 @@ public: } nsString algName; + nsString hashAlgName; mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName); if (NS_FAILED(mEarlyRv)) { return; } - // Look up the SECOidTag if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) { - mEcdsa = false; + mAlgorithm = Algorithm::RSA_PKCS1; Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1); CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSASSA_PKCS1); + hashAlgName = aKey.Algorithm().mRsa.mHash.mName; + } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) { + mAlgorithm = Algorithm::RSA_PSS; + Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_PSS); + CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSA_PSS); - // For RSA, the hash name comes from the key algorithm - nsString hashName = aKey.Algorithm().mRsa.mHash.mName; - switch (MapAlgorithmNameToMechanism(hashName)) { - case CKM_SHA_1: - mOidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; break; - case CKM_SHA256: - mOidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; break; - case CKM_SHA384: - mOidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; break; - case CKM_SHA512: - mOidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; break; - default: - mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; - return; + KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash; + hashAlgName = hashAlg.mName; + mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg); + mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlgName); + + // Check we found appropriate mechanisms. + if (mHashMechanism == UNKNOWN_CK_MECHANISM || + mMgfMechanism == UNKNOWN_CK_MECHANISM) { + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + return; } + + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } + + mSaltLength = params.mSaltLength; } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { - mEcdsa = true; + mAlgorithm = Algorithm::ECDSA; Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDSA); CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDSA); @@ -1089,38 +1145,27 @@ public: return; } - nsString hashName; - mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName); + mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashAlgName); if (NS_FAILED(mEarlyRv)) { mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; return; } - - CK_MECHANISM_TYPE hashMechanism = MapAlgorithmNameToMechanism(hashName); - if (hashMechanism == UNKNOWN_CK_MECHANISM) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } - - switch (hashMechanism) { - case CKM_SHA_1: - mOidTag = SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE; break; - case CKM_SHA256: - mOidTag = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; break; - case CKM_SHA384: - mOidTag = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE; break; - case CKM_SHA512: - mOidTag = SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE; break; - default: - mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; - return; - } } else { // This shouldn't happen; CreateSignVerifyTask shouldn't create // one of these unless it's for the above algorithms. MOZ_ASSERT(false); } + // Must have a valid algorithm by now. + MOZ_ASSERT(mAlgorithm != Algorithm::UNKNOWN); + + // Determine hash algorithm to use. + mOidTag = MapHashAlgorithmNameToOID(hashAlgName); + if (mOidTag == SEC_OID_UNKNOWN) { + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + return; + } + // Check that we have the appropriate key if ((mSign && !mPrivKey) || (!mSign && !mPubKey)) { mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; @@ -1130,63 +1175,88 @@ public: private: SECOidTag mOidTag; + CK_MECHANISM_TYPE mHashMechanism; + CK_MECHANISM_TYPE mMgfMechanism; ScopedSECKEYPrivateKey mPrivKey; ScopedSECKEYPublicKey mPubKey; CryptoBuffer mSignature; CryptoBuffer mData; + uint32_t mSaltLength; bool mSign; bool mVerified; - bool mEcdsa; + + // The signature algorithm to use. + enum class Algorithm: uint8_t {ECDSA, RSA_PKCS1, RSA_PSS, UNKNOWN}; + Algorithm mAlgorithm; virtual nsresult DoCrypto() override { - nsresult rv; - if (mSign) { - ScopedSECItem signature(::SECITEM_AllocItem(nullptr, nullptr, 0)); - if (!signature.get()) { + SECStatus rv; + ScopedSECItem hash(::SECITEM_AllocItem(nullptr, nullptr, + HASH_ResultLenByOidTag(mOidTag))); + if (!hash) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + // Compute digest over given data. + rv = PK11_HashBuf(mOidTag, hash->data, mData.Elements(), mData.Length()); + NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR); + + // Wrap hash in a digest info template (RSA-PKCS1 only). + if (mAlgorithm == Algorithm::RSA_PKCS1) { + ScopedSGNDigestInfo di(SGN_CreateDigestInfo(mOidTag, hash->data, hash->len)); + if (!di) { return NS_ERROR_DOM_OPERATION_ERR; } - rv = MapSECStatus(SEC_SignData(signature, mData.Elements(), - mData.Length(), mPrivKey, mOidTag)); - - if (mEcdsa) { - // DER-decode the signature - int signatureLength = PK11_SignatureLen(mPrivKey); - ScopedSECItem rawSignature(DSAU_DecodeDerSigToLen(signature.get(), - signatureLength)); - if (!rawSignature.get()) { - return NS_ERROR_DOM_OPERATION_ERR; - } - - ATTEMPT_BUFFER_ASSIGN(mSignature, rawSignature); - } else { - ATTEMPT_BUFFER_ASSIGN(mSignature, signature); + // Reuse |hash|. + SECITEM_FreeItem(hash, false); + if (!SEC_ASN1EncodeItem(nullptr, hash, di, SGN_DigestInfoTemplate)) { + return NS_ERROR_DOM_OPERATION_ERR; } + } + SECItem* params = nullptr; + CK_MECHANISM_TYPE mech = PK11_MapSignKeyType((mSign ? mPrivKey->keyType : + mPubKey->keyType)); + + CK_RSA_PKCS_PSS_PARAMS rsaPssParams; + SECItem rsaPssParamsItem = { siBuffer, }; + + // Set up parameters for RSA-PSS. + if (mAlgorithm == Algorithm::RSA_PSS) { + rsaPssParams.hashAlg = mHashMechanism; + rsaPssParams.mgf = mMgfMechanism; + rsaPssParams.sLen = mSaltLength; + + rsaPssParamsItem.data = (unsigned char*)&rsaPssParams; + rsaPssParamsItem.len = sizeof(rsaPssParams); + params = &rsaPssParamsItem; + + mech = CKM_RSA_PKCS_PSS; + } + + // Allocate SECItem to hold the signature. + uint32_t len = mSign ? PK11_SignatureLen(mPrivKey) : 0; + ScopedSECItem sig(::SECITEM_AllocItem(nullptr, nullptr, len)); + if (!sig) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + if (mSign) { + // Sign the hash. + rv = PK11_SignWithMechanism(mPrivKey, mech, params, sig, hash); + NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR); + ATTEMPT_BUFFER_ASSIGN(mSignature, sig); } else { - ScopedSECItem signature(::SECITEM_AllocItem(nullptr, nullptr, 0)); - if (!signature.get()) { - return NS_ERROR_DOM_UNKNOWN_ERR; + // Copy the given signature to the SECItem. + if (!mSignature.ToSECItem(nullptr, sig)) { + return NS_ERROR_DOM_OPERATION_ERR; } - if (mEcdsa) { - // DER-encode the signature - ScopedSECItem rawSignature(::SECITEM_AllocItem(nullptr, nullptr, 0)); - if (!rawSignature || !mSignature.ToSECItem(nullptr, rawSignature)) { - return NS_ERROR_DOM_UNKNOWN_ERR; - } - - rv = MapSECStatus(DSAU_EncodeDerSigWithLen(signature, rawSignature, - rawSignature->len)); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); - } else if (!mSignature.ToSECItem(nullptr, signature)) { - return NS_ERROR_DOM_UNKNOWN_ERR; - } - - rv = MapSECStatus(VFY_VerifyData(mData.Elements(), mData.Length(), - mPubKey, signature, mOidTag, nullptr)); - mVerified = NS_SUCCEEDED(rv); + // Verify the signature. + rv = PK11_VerifyWithMechanism(mPubKey, mech, params, sig, hash, nullptr); + mVerified = NS_SUCCEEDED(MapSECStatus(rv)); } return NS_OK; @@ -1221,22 +1291,19 @@ public: TelemetryAlgorithm telemetryAlg; if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) { - mOidTag = SEC_OID_SHA1; telemetryAlg = TA_SHA_1; } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) { - mOidTag = SEC_OID_SHA256; telemetryAlg = TA_SHA_224; } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) { - mOidTag = SEC_OID_SHA384; telemetryAlg = TA_SHA_256; } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) { - mOidTag = SEC_OID_SHA512; telemetryAlg = TA_SHA_384; } else { mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; return; } Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg); + mOidTag = MapHashAlgorithmNameToOID(algName); } private: @@ -3195,6 +3262,7 @@ WebCryptoTask::CreateSignVerifyTask(JSContext* aCx, if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) { return new HmacTask(aCx, aAlgorithm, aKey, aSignature, aData, aSign); } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || + algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) || algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { return new AsymmetricSignVerifyTask(aCx, aAlgorithm, aKey, aSignature, aData, aSign); diff --git a/dom/crypto/test/test_WebCrypto_RSA_PSS.html b/dom/crypto/test/test_WebCrypto_RSA_PSS.html index d4835cd432a..6e0fa05ce2e 100644 --- a/dom/crypto/test/test_WebCrypto_RSA_PSS.html +++ b/dom/crypto/test/test_WebCrypto_RSA_PSS.html @@ -41,6 +41,38 @@ TestArray.addTest( .then(complete(that), error(that)); } ); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "RSA-PSS key generation and sign/verify round-trip (SHA-256, 2048-bit)", + function () { + var that = this; + var alg = { + name: "RSA-PSS", + hash: "SHA-256", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]) + }; + + var privKey, pubKey; + var data = crypto.getRandomValues(new Uint8Array(128)); + function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; } + function doSign() { + var alg = {name: "RSA-PSS", saltLength: 32}; + return crypto.subtle.sign(alg, privKey, data); + } + function doVerify(x) { + var alg = {name: "RSA-PSS", saltLength: 32}; + return crypto.subtle.verify(alg, pubKey, x, data); + } + + crypto.subtle.generateKey(alg, false, ["sign", "verify"]) + .then(setKey, error(that)) + .then(doSign, error(that)) + .then(doVerify, error(that)) + .then(complete(that, x => x), error(that)) + } +); /*]]>*/ diff --git a/dom/webidl/SubtleCrypto.webidl b/dom/webidl/SubtleCrypto.webidl index a0bf40a4885..ebc4e84a410 100644 --- a/dom/webidl/SubtleCrypto.webidl +++ b/dom/webidl/SubtleCrypto.webidl @@ -66,6 +66,10 @@ dictionary RsaOaepParams : Algorithm { BufferSource label; }; +dictionary RsaPssParams : Algorithm { + [EnforceRange] required unsigned long saltLength; +}; + dictionary DhKeyGenParams : Algorithm { required BigInteger prime; required BigInteger generator; diff --git a/security/manager/ssl/ScopedNSSTypes.h b/security/manager/ssl/ScopedNSSTypes.h index 08c63458f77..938806b6ac2 100644 --- a/security/manager/ssl/ScopedNSSTypes.h +++ b/security/manager/ssl/ScopedNSSTypes.h @@ -131,6 +131,9 @@ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11Context, MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSGNContext, SGNContext, mozilla::psm::SGN_DestroyContext_true) +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSGNDigestInfo, + SGNDigestInfo, + SGN_DestroyDigestInfo) MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedVFYContext, VFYContext, mozilla::psm::VFY_DestroyContext_true)