From 58860abf23720b3bf029f8660de99a766a8b1192 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Sat, 19 Jul 2014 08:24:00 -0500 Subject: [PATCH] Bug 1034851 - Add wrapKey and unwrapKey methods to WebCrypto API r=bz --- dom/base/SubtleCrypto.cpp | 29 ++- dom/base/SubtleCrypto.h | 17 ++ dom/crypto/CryptoBuffer.cpp | 8 + dom/crypto/CryptoBuffer.h | 1 + dom/crypto/WebCryptoTask.cpp | 323 +++++++++++++++++++++++++++----- dom/crypto/WebCryptoTask.h | 46 +++-- dom/crypto/test/test-vectors.js | 8 + dom/crypto/test/tests.js | 155 +++++++++++++++ dom/webidl/SubtleCrypto.webidl | 15 ++ 9 files changed, 541 insertions(+), 61 deletions(-) diff --git a/dom/base/SubtleCrypto.cpp b/dom/base/SubtleCrypto.cpp index 721b9f824dd9..980dd732c312 100644 --- a/dom/base/SubtleCrypto.cpp +++ b/dom/base/SubtleCrypto.cpp @@ -42,7 +42,7 @@ SubtleCrypto::WrapObject(JSContext* aCx) if (aRv.Failed()) { \ return nullptr; \ } \ - nsRefPtr task = WebCryptoTask::Operation ## Task(__VA_ARGS__); \ + nsRefPtr task = WebCryptoTask::Create ## Operation ## Task(__VA_ARGS__); \ task->DispatchWithPromise(p); \ return p.forget(); @@ -148,5 +148,32 @@ SubtleCrypto::DeriveBits(JSContext* cx, SUBTLECRYPTO_METHOD_BODY(DeriveBits, aRv, cx, algorithm, baseKey, length) } +already_AddRefed +SubtleCrypto::WrapKey(JSContext* cx, + const nsAString& format, + CryptoKey& key, + CryptoKey& wrappingKey, + const ObjectOrString& wrapAlgorithm, + ErrorResult& aRv) +{ + SUBTLECRYPTO_METHOD_BODY(WrapKey, aRv, cx, format, key, wrappingKey, wrapAlgorithm) +} + +already_AddRefed +SubtleCrypto::UnwrapKey(JSContext* cx, + const nsAString& format, + const ArrayBufferViewOrArrayBuffer& wrappedKey, + CryptoKey& unwrappingKey, + const ObjectOrString& unwrapAlgorithm, + const ObjectOrString& unwrappedKeyAlgorithm, + bool extractable, + const Sequence& keyUsages, + ErrorResult& aRv) +{ + SUBTLECRYPTO_METHOD_BODY(UnwrapKey, aRv, cx, format, wrappedKey, unwrappingKey, + unwrapAlgorithm, unwrappedKeyAlgorithm, + extractable, keyUsages) +} + } // namespace dom } // namespace mozilla diff --git a/dom/base/SubtleCrypto.h b/dom/base/SubtleCrypto.h index 0254c0f945da..ff73bc5c5050 100644 --- a/dom/base/SubtleCrypto.h +++ b/dom/base/SubtleCrypto.h @@ -102,6 +102,23 @@ public: uint32_t length, ErrorResult& aRv); + already_AddRefed WrapKey(JSContext* cx, + const nsAString& format, + CryptoKey& key, + CryptoKey& wrappingKey, + const ObjectOrString& wrapAlgorithm, + ErrorResult& aRv); + + already_AddRefed UnwrapKey(JSContext* cx, + const nsAString& format, + const ArrayBufferViewOrArrayBuffer& wrappedKey, + CryptoKey& unwrappingKey, + const ObjectOrString& unwrapAlgorithm, + const ObjectOrString& unwrappedKeyAlgorithm, + bool extractable, + const Sequence& keyUsages, + ErrorResult& aRv); + private: nsCOMPtr mWindow; }; diff --git a/dom/crypto/CryptoBuffer.cpp b/dom/crypto/CryptoBuffer.cpp index 24d8dac16d7d..55c90cd4e3e5 100644 --- a/dom/crypto/CryptoBuffer.cpp +++ b/dom/crypto/CryptoBuffer.cpp @@ -10,6 +10,14 @@ namespace mozilla { namespace dom { +uint8_t* +CryptoBuffer::Assign(const CryptoBuffer& aData) +{ + // Same as in nsTArray_Impl::operator=, but return the value + // returned from ReplaceElementsAt to enable OOM detection + return ReplaceElementsAt(0, Length(), aData.Elements(), aData.Length()); +} + uint8_t* CryptoBuffer::Assign(const uint8_t* aData, uint32_t aLength) { diff --git a/dom/crypto/CryptoBuffer.h b/dom/crypto/CryptoBuffer.h index 5a763cc6ac19..fc4baca75aba 100644 --- a/dom/crypto/CryptoBuffer.h +++ b/dom/crypto/CryptoBuffer.h @@ -20,6 +20,7 @@ class OwningArrayBufferViewOrArrayBuffer; class CryptoBuffer : public FallibleTArray { public: + uint8_t* Assign(const CryptoBuffer& aData); uint8_t* Assign(const uint8_t* aData, uint32_t aLength); uint8_t* Assign(const SECItem* aItem); uint8_t* Assign(const ArrayBuffer& aData); diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp index a696630ae37a..38f03018906f 100644 --- a/dom/crypto/WebCryptoTask.cpp +++ b/dom/crypto/WebCryptoTask.cpp @@ -299,17 +299,48 @@ private: } }; -class AesTask : public ReturnArrayBufferViewTask +class DeferredData { public: + template + void SetData(const T& aData) { + mDataIsSet = mData.Assign(aData); + } + +protected: + DeferredData() + : mDataIsSet(false) + {} + + CryptoBuffer mData; + bool mDataIsSet; +}; + +class AesTask : public ReturnArrayBufferViewTask, + public DeferredData +{ +public: + AesTask(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, bool aEncrypt) + : mSymKey(aKey.GetSymKey()) + , mEncrypt(aEncrypt) + { + Init(aCx, aAlgorithm, aKey, aEncrypt); + } + AesTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aData, bool aEncrypt) : mSymKey(aKey.GetSymKey()) , mEncrypt(aEncrypt) { - ATTEMPT_BUFFER_INIT(mData, aData); + Init(aCx, aAlgorithm, aKey, aEncrypt); + SetData(aData); + } + void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, bool aEncrypt) + { nsString algName; mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName); if (NS_FAILED(mEarlyRv)) { @@ -398,7 +429,6 @@ private: CK_MECHANISM_TYPE mMechanism; CryptoBuffer mSymKey; CryptoBuffer mIv; // Initialization vector - CryptoBuffer mData; CryptoBuffer mAad; // Additional Authenticated Data uint8_t mTagLength; uint8_t mCounterLength; @@ -408,6 +438,10 @@ private: { nsresult rv; + if (!mDataIsSet) { + return NS_ERROR_DOM_OPERATION_ERR; + } + // Construct the parameters object depending on algorithm SECItem param; ScopedSECItem cbcParam; @@ -476,9 +510,19 @@ private: } }; -class RsaesPkcs1Task : public ReturnArrayBufferViewTask +class RsaesPkcs1Task : public ReturnArrayBufferViewTask, + public DeferredData { public: + RsaesPkcs1Task(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, bool aEncrypt) + : mPrivKey(aKey.GetPrivateKey()) + , mPubKey(aKey.GetPublicKey()) + , mEncrypt(aEncrypt) + { + Init(aCx, aAlgorithm, aKey, aEncrypt); + } + RsaesPkcs1Task(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aData, bool aEncrypt) @@ -486,9 +530,15 @@ public: , mPubKey(aKey.GetPublicKey()) , mEncrypt(aEncrypt) { - Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSAES_PKCS1); + Init(aCx, aAlgorithm, aKey, aEncrypt); + SetData(aData); + } - ATTEMPT_BUFFER_INIT(mData, aData); + void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, bool aEncrypt) + { + + Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSAES_PKCS1); if (mEncrypt) { if (!mPubKey) { @@ -496,14 +546,6 @@ public: return; } mStrength = SECKEY_PublicKeyStrength(mPubKey); - - // Verify that the data input is not too big - // (as required by PKCS#1 / RFC 3447, Section 7.2) - // http://tools.ietf.org/html/rfc3447#section-7.2 - if (mData.Length() > mStrength - 11) { - mEarlyRv = NS_ERROR_DOM_DATA_ERR; - return; - } } else { if (!mPrivKey) { mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; @@ -516,10 +558,25 @@ public: private: ScopedSECKEYPrivateKey mPrivKey; ScopedSECKEYPublicKey mPubKey; - CryptoBuffer mData; uint32_t mStrength; bool mEncrypt; + virtual nsresult BeforeCrypto() MOZ_OVERRIDE + { + if (!mDataIsSet) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + // Verify that the data input is not too big + // (as required by PKCS#1 / RFC 3447, Section 7.2) + // http://tools.ietf.org/html/rfc3447#section-7.2 + if (mEncrypt && mData.Length() > mStrength - 11) { + return NS_ERROR_DOM_DATA_ERR; + } + + return NS_OK; + } + virtual nsresult DoCrypto() MOZ_OVERRIDE { nsresult rv; @@ -549,9 +606,19 @@ private: } }; -class RsaOaepTask : public ReturnArrayBufferViewTask +class RsaOaepTask : public ReturnArrayBufferViewTask, + public DeferredData { public: + RsaOaepTask(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, bool aEncrypt) + : mPrivKey(aKey.GetPrivateKey()) + , mPubKey(aKey.GetPublicKey()) + , mEncrypt(aEncrypt) + { + Init(aCx, aAlgorithm, aKey, aEncrypt); + } + RsaOaepTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aData, bool aEncrypt) @@ -559,9 +626,14 @@ public: , mPubKey(aKey.GetPublicKey()) , mEncrypt(aEncrypt) { - Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_OAEP); + Init(aCx, aAlgorithm, aKey, aEncrypt); + SetData(aData); + } - ATTEMPT_BUFFER_INIT(mData, aData); + void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, bool aEncrypt) + { + Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_OAEP); if (mEncrypt) { if (!mPubKey) { @@ -620,7 +692,6 @@ private: ScopedSECKEYPrivateKey mPrivKey; ScopedSECKEYPublicKey mPubKey; CryptoBuffer mLabel; - CryptoBuffer mData; uint32_t mStrength; bool mEncrypt; @@ -628,6 +699,10 @@ private: { nsresult rv; + if (!mDataIsSet) { + return NS_ERROR_DOM_OPERATION_ERR; + } + // Ciphertext is an integer mod the modulus, so it will be // no longer than mStrength octets if (!mResult.SetLength(mStrength)) { @@ -895,10 +970,10 @@ private: } }; -class SimpleDigestTask : public ReturnArrayBufferViewTask +class DigestTask : public ReturnArrayBufferViewTask { public: - SimpleDigestTask(JSContext* aCx, + DigestTask(JSContext* aCx, const ObjectOrString& aAlgorithm, const CryptoOperationData& aData) { @@ -987,7 +1062,14 @@ public: } } + void SetKeyData(const CryptoBuffer& aKeyData) + { + // An OOM will just result in an error in BeforeCrypto + mKeyData = aKeyData; + } + protected: + CryptoBuffer mKeyData; nsRefPtr mKey; nsString mAlgName; @@ -1069,7 +1151,8 @@ public: if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) || mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) || mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) { - if (mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::DECRYPT)) { + if (mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::DECRYPT | + CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY)) { return NS_ERROR_DOM_DATA_ERR; } @@ -1102,14 +1185,7 @@ public: return NS_OK; } - void SetKeyData(const CryptoBuffer& aKeyData) - { - // An OOM will just result in an error in BeforeCrypto - mKeyData = aKeyData; - } - private: - CryptoBuffer mKeyData; nsString mHashName; }; @@ -1158,7 +1234,6 @@ public: } private: - CryptoBuffer mKeyData; nsString mFormat; nsString mHashName; uint32_t mModulusLength; @@ -1215,9 +1290,9 @@ private: if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1) || mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { if ((mKey->GetKeyType() == CryptoKey::PUBLIC && - mKey->HasUsageOtherThan(CryptoKey::ENCRYPT)) || + mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::WRAPKEY)) || (mKey->GetKeyType() == CryptoKey::PRIVATE && - mKey->HasUsageOtherThan(CryptoKey::DECRYPT))) { + mKey->HasUsageOtherThan(CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY))) { return NS_ERROR_DOM_DATA_ERR; } } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) { @@ -1252,10 +1327,10 @@ private: }; -class UnifiedExportKeyTask : public ReturnArrayBufferViewTask +class ExportKeyTask : public ReturnArrayBufferViewTask { public: - UnifiedExportKeyTask(const nsAString& aFormat, CryptoKey& aKey) + ExportKeyTask(const nsAString& aFormat, CryptoKey& aKey) : mFormat(aFormat) , mSymKey(aKey.GetSymKey()) , mPrivateKey(aKey.GetPrivateKey()) @@ -1779,11 +1854,69 @@ private: } }; +template +class WrapKeyTask : public ExportKeyTask +{ +public: + WrapKeyTask(JSContext* aCx, + const nsAString& aFormat, + CryptoKey& aKey, + CryptoKey& aWrappingKey, + const ObjectOrString& aWrapAlgorithm) + : ExportKeyTask(aFormat, aKey) + { + if (NS_FAILED(mEarlyRv)) { + return; + } + + mTask = new KeyEncryptTask(aCx, aWrapAlgorithm, aWrappingKey, true); + } + +private: + nsRefPtr mTask; + + virtual void Resolve() MOZ_OVERRIDE { + mTask->SetData(mResult); + mTask->DispatchWithPromise(mResultPromise); + } + + virtual void Cleanup() MOZ_OVERRIDE + { + mTask = nullptr; + } +}; + +template +class UnwrapKeyTask : public KeyEncryptTask +{ +public: + UnwrapKeyTask(JSContext* aCx, + const ArrayBufferViewOrArrayBuffer& aWrappedKey, + CryptoKey& aUnwrappingKey, + const ObjectOrString& aUnwrapAlgorithm, + ImportKeyTask* aTask) + : KeyEncryptTask(aCx, aUnwrapAlgorithm, aUnwrappingKey, aWrappedKey, false) + , mTask(aTask) + {} + +private: + nsRefPtr mTask; + + virtual void Resolve() MOZ_OVERRIDE { + mTask->SetKeyData(KeyEncryptTask::mResult); + mTask->DispatchWithPromise(KeyEncryptTask::mResultPromise); + } + + virtual void Cleanup() MOZ_OVERRIDE + { + mTask = nullptr; + } +}; // Task creation methods for WebCryptoTask WebCryptoTask* -WebCryptoTask::EncryptDecryptTask(JSContext* aCx, +WebCryptoTask::CreateEncryptDecryptTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aData, @@ -1819,7 +1952,7 @@ WebCryptoTask::EncryptDecryptTask(JSContext* aCx, } WebCryptoTask* -WebCryptoTask::SignVerifyTask(JSContext* aCx, +WebCryptoTask::CreateSignVerifyTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aSignature, @@ -1852,16 +1985,16 @@ WebCryptoTask::SignVerifyTask(JSContext* aCx, } WebCryptoTask* -WebCryptoTask::DigestTask(JSContext* aCx, +WebCryptoTask::CreateDigestTask(JSContext* aCx, const ObjectOrString& aAlgorithm, const CryptoOperationData& aData) { Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DIGEST); - return new SimpleDigestTask(aCx, aAlgorithm, aData); + return new DigestTask(aCx, aAlgorithm, aData); } WebCryptoTask* -WebCryptoTask::ImportKeyTask(JSContext* aCx, +WebCryptoTask::CreateImportKeyTask(JSContext* aCx, const nsAString& aFormat, const KeyData& aKeyData, const ObjectOrString& aAlgorithm, @@ -1895,7 +2028,7 @@ WebCryptoTask::ImportKeyTask(JSContext* aCx, } WebCryptoTask* -WebCryptoTask::ExportKeyTask(const nsAString& aFormat, +WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat, CryptoKey& aKey) { Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_EXPORTKEY); @@ -1903,12 +2036,12 @@ WebCryptoTask::ExportKeyTask(const nsAString& aFormat, if (aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) { return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR); } else { - return new UnifiedExportKeyTask(aFormat, aKey); + return new ExportKeyTask(aFormat, aKey); } } WebCryptoTask* -WebCryptoTask::GenerateKeyTask(JSContext* aCx, +WebCryptoTask::CreateGenerateKeyTask(JSContext* aCx, const ObjectOrString& aAlgorithm, bool aExtractable, const Sequence& aKeyUsages) @@ -1937,7 +2070,7 @@ WebCryptoTask::GenerateKeyTask(JSContext* aCx, } WebCryptoTask* -WebCryptoTask::DeriveKeyTask(JSContext* aCx, +WebCryptoTask::CreateDeriveKeyTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aBaseKey, const ObjectOrString& aDerivedKeyType, @@ -1961,7 +2094,7 @@ WebCryptoTask::DeriveKeyTask(JSContext* aCx, } WebCryptoTask* -WebCryptoTask::DeriveBitsTask(JSContext* aCx, +WebCryptoTask::CreateDeriveBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength) @@ -1981,6 +2114,108 @@ WebCryptoTask::DeriveBitsTask(JSContext* aCx, return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR); } +WebCryptoTask* +WebCryptoTask::CreateWrapKeyTask(JSContext* aCx, + const nsAString& aFormat, + CryptoKey& aKey, + CryptoKey& aWrappingKey, + const ObjectOrString& aWrapAlgorithm) +{ + Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_WRAPKEY); + + // Ensure key is usable for this operation + if (!aWrappingKey.HasUsage(CryptoKey::WRAPKEY)) { + return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR); + } + + nsString wrapAlgName; + nsresult rv = GetAlgorithmName(aCx, aWrapAlgorithm, wrapAlgName); + if (NS_FAILED(rv)) { + return new FailureTask(rv); + } + + if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) || + wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) || + wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) { + return new WrapKeyTask(aCx, aFormat, aKey, + aWrappingKey, aWrapAlgorithm); + } else if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) { + return new WrapKeyTask(aCx, aFormat, aKey, + aWrappingKey, aWrapAlgorithm); + } else if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { + return new WrapKeyTask(aCx, aFormat, aKey, + aWrappingKey, aWrapAlgorithm); + } + + return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR); +} + +WebCryptoTask* +WebCryptoTask::CreateUnwrapKeyTask(JSContext* aCx, + const nsAString& aFormat, + const ArrayBufferViewOrArrayBuffer& aWrappedKey, + CryptoKey& aUnwrappingKey, + const ObjectOrString& aUnwrapAlgorithm, + const ObjectOrString& aUnwrappedKeyAlgorithm, + bool aExtractable, + const Sequence& aKeyUsages) +{ + Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_UNWRAPKEY); + + // Ensure key is usable for this operation + if (!aUnwrappingKey.HasUsage(CryptoKey::UNWRAPKEY)) { + return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR); + } + + nsString keyAlgName; + nsresult rv = GetAlgorithmName(aCx, aUnwrappedKeyAlgorithm, keyAlgName); + if (NS_FAILED(rv)) { + return new FailureTask(rv); + } + + CryptoOperationData dummy; + nsRefPtr importTask; + if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) || + keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) || + keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) || + keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) { + importTask = new ImportSymmetricKeyTask(aCx, aFormat, dummy, + aUnwrappedKeyAlgorithm, + aExtractable, aKeyUsages); + } else if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSAES_PKCS1) || + keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) || + keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) { + importTask = new ImportRsaKeyTask(aCx, aFormat, dummy, + aUnwrappedKeyAlgorithm, + aExtractable, aKeyUsages); + } else { + return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + } + + nsString unwrapAlgName; + rv = GetAlgorithmName(aCx, aUnwrapAlgorithm, unwrapAlgName); + if (NS_FAILED(rv)) { + return new FailureTask(rv); + } + if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) || + unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) || + unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) { + return new UnwrapKeyTask(aCx, aWrappedKey, + aUnwrappingKey, aUnwrapAlgorithm, + importTask); + } else if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) { + return new UnwrapKeyTask(aCx, aWrappedKey, + aUnwrappingKey, aUnwrapAlgorithm, + importTask); + } else if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { + return new UnwrapKeyTask(aCx, aWrappedKey, + aUnwrappingKey, aUnwrapAlgorithm, + importTask); + } + + return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + +} } // namespace dom } // namespace mozilla diff --git a/dom/crypto/WebCryptoTask.h b/dom/crypto/WebCryptoTask.h index 276e99b04605..27cffa09e1ad 100644 --- a/dom/crypto/WebCryptoTask.h +++ b/dom/crypto/WebCryptoTask.h @@ -85,13 +85,13 @@ public: } protected: - static WebCryptoTask* EncryptDecryptTask(JSContext* aCx, + static WebCryptoTask* CreateEncryptDecryptTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aData, bool aEncrypt); - static WebCryptoTask* SignVerifyTask(JSContext* aCx, + static WebCryptoTask* CreateSignVerifyTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aSignature, @@ -99,69 +99,83 @@ protected: bool aSign); public: - static WebCryptoTask* EncryptTask(JSContext* aCx, + static WebCryptoTask* CreateEncryptTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aData) { - return EncryptDecryptTask(aCx, aAlgorithm, aKey, aData, true); + return CreateEncryptDecryptTask(aCx, aAlgorithm, aKey, aData, true); } - static WebCryptoTask* DecryptTask(JSContext* aCx, + static WebCryptoTask* CreateDecryptTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aData) { - return EncryptDecryptTask(aCx, aAlgorithm, aKey, aData, false); + return CreateEncryptDecryptTask(aCx, aAlgorithm, aKey, aData, false); } - static WebCryptoTask* SignTask(JSContext* aCx, + static WebCryptoTask* CreateSignTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aData) { CryptoOperationData dummy; dummy.SetAsArrayBuffer(aCx); - return SignVerifyTask(aCx, aAlgorithm, aKey, dummy, aData, true); + return CreateSignVerifyTask(aCx, aAlgorithm, aKey, dummy, aData, true); } - static WebCryptoTask* VerifyTask(JSContext* aCx, + static WebCryptoTask* CreateVerifyTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, const CryptoOperationData& aSignature, const CryptoOperationData& aData) { - return SignVerifyTask(aCx, aAlgorithm, aKey, aSignature, aData, false); + return CreateSignVerifyTask(aCx, aAlgorithm, aKey, aSignature, aData, false); } - static WebCryptoTask* DigestTask(JSContext* aCx, + static WebCryptoTask* CreateDigestTask(JSContext* aCx, const ObjectOrString& aAlgorithm, const CryptoOperationData& aData); - static WebCryptoTask* ImportKeyTask(JSContext* aCx, + static WebCryptoTask* CreateImportKeyTask(JSContext* aCx, const nsAString& aFormat, const KeyData& aKeyData, const ObjectOrString& aAlgorithm, bool aExtractable, const Sequence& aKeyUsages); - static WebCryptoTask* ExportKeyTask(const nsAString& aFormat, + static WebCryptoTask* CreateExportKeyTask(const nsAString& aFormat, CryptoKey& aKey); - static WebCryptoTask* GenerateKeyTask(JSContext* aCx, + static WebCryptoTask* CreateGenerateKeyTask(JSContext* aCx, const ObjectOrString& aAlgorithm, bool aExtractable, const Sequence& aKeyUsages); - static WebCryptoTask* DeriveKeyTask(JSContext* aCx, + static WebCryptoTask* CreateDeriveKeyTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aBaseKey, const ObjectOrString& aDerivedKeyType, bool extractable, const Sequence& aKeyUsages); - static WebCryptoTask* DeriveBitsTask(JSContext* aCx, + static WebCryptoTask* CreateDeriveBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength); + static WebCryptoTask* CreateWrapKeyTask(JSContext* aCx, + const nsAString& aFormat, + CryptoKey& aKey, + CryptoKey& aWrappingKey, + const ObjectOrString& aWrapAlgorithm); + static WebCryptoTask* CreateUnwrapKeyTask(JSContext* aCx, + const nsAString& aFormat, + const ArrayBufferViewOrArrayBuffer& aWrappedKey, + CryptoKey& aUnwrappingKey, + const ObjectOrString& aUnwrapAlgorithm, + const ObjectOrString& aUnwrappedKeyAlgorithm, + bool aExtractable, + const Sequence& aKeyUsages); + protected: nsRefPtr mResultPromise; nsresult mEarlyRv; diff --git a/dom/crypto/test/test-vectors.js b/dom/crypto/test/test-vectors.js index 99e418027997..dcfed7299711 100644 --- a/dom/crypto/test/test-vectors.js +++ b/dom/crypto/test/test-vectors.js @@ -355,6 +355,14 @@ tv = { ), }, + key_wrap_known_answer: { + key: util.hex2abv("0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"), + wrapping_key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + wrapping_iv: util.hex2abv("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"), + wrapped_key: util.hex2abv("9ed0283a9a2b7e4292ebc5135e6342cc" + + "8a7f65802a1f6fd41bd3251c4da0c138") + }, + // RFC 6070 pbkdf2_sha1: { password: new TextEncoder("utf-8").encode("passwordPASSWORDpassword"), diff --git a/dom/crypto/test/tests.js b/dom/crypto/test/tests.js index 89671b7c3a94..12bc90924ead 100644 --- a/dom/crypto/test/tests.js +++ b/dom/crypto/test/tests.js @@ -1274,3 +1274,158 @@ TestArray.addTest( } ); +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Key wrap known answer, using AES-GCM", + function () { + var that = this; + var alg = { + name: "AES-GCM", + iv: tv.key_wrap_known_answer.wrapping_iv, + tagLength: 128 + }; + var key, wrappingKey; + + function doImport(k) { + wrappingKey = k; + return crypto.subtle.importKey("raw", tv.key_wrap_known_answer.key, + alg, true, ['encrypt', 'decrypt']); + } + function doWrap(k) { + key = k; + return crypto.subtle.wrapKey("raw", key, wrappingKey, alg); + } + + crypto.subtle.importKey("raw", tv.key_wrap_known_answer.wrapping_key, + alg, false, ['wrapKey']) + .then(doImport, error(that)) + .then(doWrap, error(that)) + .then( + memcmp_complete(that, tv.key_wrap_known_answer.wrapped_key), + error(that) + ); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Key wrap failing on non-extractable key", + function () { + var that = this; + var alg = { + name: "AES-GCM", + iv: tv.key_wrap_known_answer.wrapping_iv, + tagLength: 128 + }; + var key, wrappingKey; + + function doImport(k) { + wrappingKey = k; + return crypto.subtle.importKey("raw", tv.key_wrap_known_answer.key, + alg, false, ['encrypt', 'decrypt']); + } + function doWrap(k) { + key = k; + return crypto.subtle.wrapKey("raw", key, wrappingKey, alg); + } + + crypto.subtle.importKey("raw", tv.key_wrap_known_answer.wrapping_key, + alg, false, ['wrapKey']) + .then(doImport, error(that)) + .then(doWrap, error(that)) + .then( + error(that), + complete(that) + ); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Key unwrap known answer, using AES-GCM", + function () { + var that = this; + var alg = { + name: "AES-GCM", + iv: tv.key_wrap_known_answer.wrapping_iv, + tagLength: 128 + }; + var key, wrappingKey; + + function doUnwrap(k) { + wrappingKey = k; + return crypto.subtle.unwrapKey( + "raw", tv.key_wrap_known_answer.wrapped_key, + wrappingKey, alg, + "AES-GCM", true, ['encrypt', 'decrypt'] + ); + } + function doExport(k) { + return crypto.subtle.exportKey("raw", k); + } + + crypto.subtle.importKey("raw", tv.key_wrap_known_answer.wrapping_key, + alg, false, ['unwrapKey']) + .then(doUnwrap, error(that)) + .then(doExport, error(that)) + .then( + memcmp_complete(that, tv.key_wrap_known_answer.key), + error(that) + ); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Key wrap/unwrap round-trip, using RSA-OAEP", + function () { + var that = this; + var oaep = { + name: "RSA-OAEP", + hash: "SHA-256" + }; + var gcm = { + name: "AES-GCM", + iv: tv.aes_gcm_enc.iv, + additionalData: tv.aes_gcm_enc.adata, + tagLength: 128 + }; + var unwrapKey; + + function doWrap(keys) { + var originalKey = keys[0]; + var wrapKey = keys[1]; + unwrapKey = keys[2]; + return crypto.subtle.wrapKey("raw", originalKey, wrapKey, oaep); + } + function doUnwrap(wrappedKey) { + return crypto.subtle.unwrapKey("raw", wrappedKey, unwrapKey, oaep, + gcm, false, ['encrypt']); + } + function doEncrypt(aesKey) { + return crypto.subtle.encrypt(gcm, aesKey, tv.aes_gcm_enc.data); + } + + // 1.Import: + // -> HMAC key + // -> OAEP wrap key (public) + // -> OAEP unwrap key (private) + // 2. Wrap the HMAC key + // 3. Unwrap it + // 4. Compute HMAC + // 5. Check HMAC value + Promise.all([ + crypto.subtle.importKey("raw", tv.aes_gcm_enc.key, gcm, true, ['encrypt']), + crypto.subtle.importKey("spki", tv.rsaoaep.spki, oaep, true, ['wrapKey']), + crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, oaep, false, ['unwrapKey']) + ]) + .then(doWrap, error(that)) + .then(doUnwrap, error(that)) + .then(doEncrypt, error(that)) + .then( + memcmp_complete(that, tv.aes_gcm_enc.result), + error(that) + ); + } +); + diff --git a/dom/webidl/SubtleCrypto.webidl b/dom/webidl/SubtleCrypto.webidl index f426bbf3f5f4..d67a935e4e75 100644 --- a/dom/webidl/SubtleCrypto.webidl +++ b/dom/webidl/SubtleCrypto.webidl @@ -176,5 +176,20 @@ interface SubtleCrypto { sequence keyUsages ); [Throws] Promise exportKey(KeyFormat format, CryptoKey key); + + [Throws] + Promise wrapKey(KeyFormat format, + CryptoKey key, + CryptoKey wrappingKey, + AlgorithmIdentifier wrapAlgorithm); + + [Throws] + Promise unwrapKey(KeyFormat format, + CryptoOperationData wrappedKey, + CryptoKey unwrappingKey, + AlgorithmIdentifier unwrapAlgorithm, + AlgorithmIdentifier unwrappedKeyAlgorithm, + boolean extractable, + sequence keyUsages ); };