Bug 1034851 - Add wrapKey and unwrapKey methods to WebCrypto API r=bz
authorRichard Barnes <rbarnes@mozilla.com>
Sat, 19 Jul 2014 08:24:00 -0500
changeset 217093 6e75c02fedfcc1a3061e1ecb66ef325392bf1d36
parent 217092 24a69de91baa8b56da4ee1b56f4f932a31534987
child 217094 b0d3e4c8385a27834a8a4f2288bb3a9115af46ff
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1034851
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1034851 - Add wrapKey and unwrapKey methods to WebCrypto API r=bz
dom/base/SubtleCrypto.cpp
dom/base/SubtleCrypto.h
dom/crypto/CryptoBuffer.cpp
dom/crypto/CryptoBuffer.h
dom/crypto/WebCryptoTask.cpp
dom/crypto/WebCryptoTask.h
dom/crypto/test/test-vectors.js
dom/crypto/test/tests.js
dom/webidl/SubtleCrypto.webidl
--- a/dom/base/SubtleCrypto.cpp
+++ b/dom/base/SubtleCrypto.cpp
@@ -37,17 +37,17 @@ SubtleCrypto::WrapObject(JSContext* aCx)
 
 #define SUBTLECRYPTO_METHOD_BODY(Operation, aRv, ...)                   \
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);        \
   MOZ_ASSERT(global);                                                   \
   nsRefPtr<Promise> p = Promise::Create(global, aRv);                   \
   if (aRv.Failed()) {                                                   \
     return nullptr;                                                     \
   }                                                                     \
-  nsRefPtr<WebCryptoTask> task = WebCryptoTask::Operation ## Task(__VA_ARGS__); \
+  nsRefPtr<WebCryptoTask> task = WebCryptoTask::Create ## Operation ## Task(__VA_ARGS__); \
   task->DispatchWithPromise(p); \
   return p.forget();
 
 already_AddRefed<Promise>
 SubtleCrypto::Encrypt(JSContext* cx,
                       const ObjectOrString& algorithm,
                       CryptoKey& key,
                       const CryptoOperationData& data,
@@ -143,10 +143,37 @@ SubtleCrypto::DeriveBits(JSContext* cx,
                          const ObjectOrString& algorithm,
                          CryptoKey& baseKey,
                          uint32_t length,
                          ErrorResult& aRv)
 {
   SUBTLECRYPTO_METHOD_BODY(DeriveBits, aRv, cx, algorithm, baseKey, length)
 }
 
+already_AddRefed<Promise>
+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<Promise>
+SubtleCrypto::UnwrapKey(JSContext* cx,
+                        const nsAString& format,
+                        const ArrayBufferViewOrArrayBuffer& wrappedKey,
+                        CryptoKey& unwrappingKey,
+                        const ObjectOrString& unwrapAlgorithm,
+                        const ObjectOrString& unwrappedKeyAlgorithm,
+                        bool extractable,
+                        const Sequence<nsString>& keyUsages,
+                        ErrorResult& aRv)
+{
+  SUBTLECRYPTO_METHOD_BODY(UnwrapKey, aRv, cx, format, wrappedKey, unwrappingKey,
+                           unwrapAlgorithm, unwrappedKeyAlgorithm,
+                           extractable, keyUsages)
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/SubtleCrypto.h
+++ b/dom/base/SubtleCrypto.h
@@ -97,16 +97,33 @@ public:
                                       ErrorResult& aRv);
 
   already_AddRefed<Promise> DeriveBits(JSContext* cx,
                                        const ObjectOrString& algorithm,
                                        CryptoKey& baseKey,
                                        uint32_t length,
                                        ErrorResult& aRv);
 
+  already_AddRefed<Promise> WrapKey(JSContext* cx,
+                                    const nsAString& format,
+                                    CryptoKey& key,
+                                    CryptoKey& wrappingKey,
+                                    const ObjectOrString& wrapAlgorithm,
+                                    ErrorResult& aRv);
+
+  already_AddRefed<Promise> UnwrapKey(JSContext* cx,
+                                      const nsAString& format,
+                                      const ArrayBufferViewOrArrayBuffer& wrappedKey,
+                                      CryptoKey& unwrappingKey,
+                                      const ObjectOrString& unwrapAlgorithm,
+                                      const ObjectOrString& unwrappedKeyAlgorithm,
+                                      bool extractable,
+                                      const Sequence<nsString>& keyUsages,
+                                      ErrorResult& aRv);
+
 private:
   nsCOMPtr<nsPIDOMWindow> mWindow;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_SubtleCrypto_h
--- a/dom/crypto/CryptoBuffer.cpp
+++ b/dom/crypto/CryptoBuffer.cpp
@@ -6,16 +6,24 @@
 
 #include "CryptoBuffer.h"
 #include "mozilla/dom/UnionTypes.h"
 
 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)
 {
   return ReplaceElementsAt(0, Length(), aData, aLength);
 }
 
 uint8_t*
 CryptoBuffer::Assign(const SECItem* aItem)
 {
--- a/dom/crypto/CryptoBuffer.h
+++ b/dom/crypto/CryptoBuffer.h
@@ -15,16 +15,17 @@ namespace mozilla {
 namespace dom {
 
 class ArrayBufferViewOrArrayBuffer;
 class OwningArrayBufferViewOrArrayBuffer;
 
 class CryptoBuffer : public FallibleTArray<uint8_t>
 {
 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);
   uint8_t* Assign(const ArrayBufferView& aData);
   uint8_t* Assign(const ArrayBufferViewOrArrayBuffer& aData);
   uint8_t* Assign(const OwningArrayBufferViewOrArrayBuffer& aData);
 
   template<typename T,
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -294,27 +294,58 @@ private:
   // Returns mResult as an ArrayBufferView, or an error
   virtual void Resolve() MOZ_OVERRIDE
   {
     TypedArrayCreator<Uint8Array> ret(mResult);
     mResultPromise->MaybeResolve(ret);
   }
 };
 
-class AesTask : public ReturnArrayBufferViewTask
+class DeferredData
 {
 public:
+  template<class T>
+  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)) {
       return;
     }
 
     // Check that we got a reasonable key
     if ((mSymKey.Length() != 16) &&
@@ -393,26 +424,29 @@ public:
     }
     Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
   }
 
 private:
   CK_MECHANISM_TYPE mMechanism;
   CryptoBuffer mSymKey;
   CryptoBuffer mIv;   // Initialization vector
-  CryptoBuffer mData;
   CryptoBuffer mAad;  // Additional Authenticated Data
   uint8_t mTagLength;
   uint8_t mCounterLength;
   bool mEncrypt;
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
     nsresult rv;
 
+    if (!mDataIsSet) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
     // Construct the parameters object depending on algorithm
     SECItem param;
     ScopedSECItem cbcParam;
     CK_AES_CTR_PARAMS ctrParams;
     CK_GCM_PARAMS gcmParams;
     switch (mMechanism) {
       case CKM_AES_CBC_PAD:
         ATTEMPT_BUFFER_TO_SECITEM(cbcParam, mIv);
@@ -471,60 +505,83 @@ private:
     }
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
 
     mResult.SetLength(outLen);
     return rv;
   }
 };
 
-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)
     : mPrivKey(aKey.GetPrivateKey())
     , 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) {
         mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
         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;
         return;
       }
       mStrength = PK11_GetPrivateModulusLen(mPrivKey);
     }
   }
 
 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;
 
     // Ciphertext is an integer mod the modulus, so it will be
     // no longer than mStrength octets
     if (!mResult.SetLength(mStrength)) {
       return NS_ERROR_DOM_UNKNOWN_ERR;
@@ -544,29 +601,44 @@ private:
       mResult.SetLength(outLen);
     }
 
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
     return NS_OK;
   }
 };
 
-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)
     : mPrivKey(aKey.GetPrivateKey())
     , 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) {
         mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
         return;
       }
       mStrength = SECKEY_PublicKeyStrength(mPubKey);
     } else {
@@ -615,24 +687,27 @@ public:
   }
 
 private:
   CK_MECHANISM_TYPE mHashMechanism;
   CK_MECHANISM_TYPE mMgfMechanism;
   ScopedSECKEYPrivateKey mPrivKey;
   ScopedSECKEYPublicKey mPubKey;
   CryptoBuffer mLabel;
-  CryptoBuffer mData;
   uint32_t mStrength;
   bool mEncrypt;
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
     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)) {
       return NS_ERROR_DOM_UNKNOWN_ERR;
     }
 
     CK_RSA_PKCS_OAEP_PARAMS oaepParams;
     oaepParams.source = CKZ_DATA_SPECIFIED;
@@ -890,20 +965,20 @@ private:
       TypedArrayCreator<Uint8Array> ret(mSignature);
       mResultPromise->MaybeResolve(ret);
     } else {
       mResultPromise->MaybeResolve(mVerified);
     }
   }
 };
 
-class SimpleDigestTask : public ReturnArrayBufferViewTask
+class DigestTask : public ReturnArrayBufferViewTask
 {
 public:
-  SimpleDigestTask(JSContext* aCx,
+  DigestTask(JSContext* aCx,
                    const ObjectOrString& aAlgorithm,
                    const CryptoOperationData& aData)
   {
     ATTEMPT_BUFFER_INIT(mData, aData);
 
     nsString algName;
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
     if (NS_FAILED(mEarlyRv)) {
@@ -982,17 +1057,24 @@ public:
 
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName);
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_DATA_ERR;
       return;
     }
   }
 
+  void SetKeyData(const CryptoBuffer& aKeyData)
+  {
+    // An OOM will just result in an error in BeforeCrypto
+    mKeyData = aKeyData;
+  }
+
 protected:
+  CryptoBuffer mKeyData;
   nsRefPtr<CryptoKey> mKey;
   nsString mAlgName;
 
 private:
   virtual void Resolve() MOZ_OVERRIDE
   {
     mResultPromise->MaybeResolve(mKey);
   }
@@ -1064,17 +1146,18 @@ public:
     // Construct an appropriate KeyAlorithm,
     // and verify that usages are appropriate
     nsRefPtr<KeyAlgorithm> algorithm;
     nsIGlobalObject* global = mKey->GetParentObject();
     uint32_t length = 8 * mKeyData.Length(); // bytes to bits
     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;
       }
 
       if ( (length != 128) && (length != 192) && (length != 256) ) {
         return NS_ERROR_DOM_DATA_ERR;
       }
       algorithm = new AesKeyAlgorithm(global, mAlgName, length);
     } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
@@ -1097,24 +1180,17 @@ public:
 
     mKey->SetAlgorithm(algorithm);
     mKey->SetSymKey(mKeyData);
     mKey->SetType(CryptoKey::SECRET);
     mEarlyComplete = true;
     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;
 };
 
 class ImportRsaKeyTask : public ImportKeyTask
 {
 public:
   ImportRsaKeyTask(JSContext* aCx,
       const nsAString& aFormat, const KeyData& aKeyData,
@@ -1153,17 +1229,16 @@ public:
       if (NS_FAILED(mEarlyRv)) {
         mEarlyRv = NS_ERROR_DOM_DATA_ERR;
         return;
       }
     }
   }
 
 private:
-  CryptoBuffer mKeyData;
   nsString mFormat;
   nsString mHashName;
   uint32_t mModulusLength;
   CryptoBuffer mPublicExponent;
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
     nsNSSShutDownPreventionLock locker;
@@ -1210,19 +1285,19 @@ private:
 
   virtual nsresult AfterCrypto() MOZ_OVERRIDE
   {
     // Construct an appropriate KeyAlgorithm
     nsIGlobalObject* global = mKey->GetParentObject();
     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)) {
       if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
            mKey->HasUsageOtherThan(CryptoKey::VERIFY)) ||
           (mKey->GetKeyType() == CryptoKey::PRIVATE &&
            mKey->HasUsageOtherThan(CryptoKey::SIGN))) {
         return NS_ERROR_DOM_DATA_ERR;
@@ -1247,20 +1322,20 @@ private:
       mKey->SetAlgorithm(algorithm);
     }
 
     return NS_OK;
   }
 };
 
 
-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())
     , mPublicKey(aKey.GetPublicKey())
   {
     if (!aKey.Extractable()) {
       mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
       return;
@@ -1774,21 +1849,79 @@ private:
   }
 
   virtual void Cleanup() MOZ_OVERRIDE
   {
     mTask = nullptr;
   }
 };
 
+template<class KeyEncryptTask>
+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<KeyEncryptTask> mTask;
+
+  virtual void Resolve() MOZ_OVERRIDE {
+    mTask->SetData(mResult);
+    mTask->DispatchWithPromise(mResultPromise);
+  }
+
+  virtual void Cleanup() MOZ_OVERRIDE
+  {
+    mTask = nullptr;
+  }
+};
+
+template<class KeyEncryptTask>
+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<ImportKeyTask> 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,
                                   bool aEncrypt)
 {
   TelemetryMethod method = (aEncrypt)? TM_ENCRYPT : TM_DECRYPT;
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, method);
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_ENC, aKey.Extractable());
@@ -1814,17 +1947,17 @@ WebCryptoTask::EncryptDecryptTask(JSCont
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
     return new RsaOaepTask(aCx, aAlgorithm, aKey, aData, aEncrypt);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
-WebCryptoTask::SignVerifyTask(JSContext* aCx,
+WebCryptoTask::CreateSignVerifyTask(JSContext* aCx,
                               const ObjectOrString& aAlgorithm,
                               CryptoKey& aKey,
                               const CryptoOperationData& aSignature,
                               const CryptoOperationData& aData,
                               bool aSign)
 {
   TelemetryMethod method = (aSign)? TM_SIGN : TM_VERIFY;
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, method);
@@ -1847,26 +1980,26 @@ WebCryptoTask::SignVerifyTask(JSContext*
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
     return new RsassaPkcs1Task(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 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,
                              bool aExtractable,
                              const Sequence<nsString>& aKeyUsages)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_IMPORTKEY);
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_IMPORT, aExtractable);
@@ -1890,30 +2023,30 @@ WebCryptoTask::ImportKeyTask(JSContext* 
     return new ImportRsaKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                 aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
-WebCryptoTask::ExportKeyTask(const nsAString& aFormat,
+WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
                              CryptoKey& aKey)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_EXPORTKEY);
 
   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<nsString>& aKeyUsages)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_GENERATEKEY);
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_GENERATE, aExtractable);
 
   nsString algName;
@@ -1932,17 +2065,17 @@ WebCryptoTask::GenerateKeyTask(JSContext
              algName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) {
     return new GenerateAsymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
-WebCryptoTask::DeriveKeyTask(JSContext* aCx,
+WebCryptoTask::CreateDeriveKeyTask(JSContext* aCx,
                              const ObjectOrString& aAlgorithm,
                              CryptoKey& aBaseKey,
                              const ObjectOrString& aDerivedKeyType,
                              bool aExtractable,
                              const Sequence<nsString>& aKeyUsages)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEKEY);
 
@@ -1956,17 +2089,17 @@ WebCryptoTask::DeriveKeyTask(JSContext* 
     return new DerivePbkdfKeyTask(aCx, aAlgorithm, aBaseKey, aDerivedKeyType,
                                   aExtractable, aKeyUsages);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
-WebCryptoTask::DeriveBitsTask(JSContext* aCx,
+WebCryptoTask::CreateDeriveBitsTask(JSContext* aCx,
                               const ObjectOrString& aAlgorithm,
                               CryptoKey& aKey,
                               uint32_t aLength)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEBITS);
 
   nsString algName;
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
@@ -1976,11 +2109,113 @@ WebCryptoTask::DeriveBitsTask(JSContext*
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
     return new DerivePbkdfBitsTask(aCx, aAlgorithm, aKey, aLength);
   }
 
   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<AesTask>(aCx, aFormat, aKey,
+                                    aWrappingKey, aWrapAlgorithm);
+  } else if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
+    return new WrapKeyTask<RsaesPkcs1Task>(aCx, aFormat, aKey,
+                                           aWrappingKey, aWrapAlgorithm);
+  } else if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+    return new WrapKeyTask<RsaOaepTask>(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<nsString>& 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<ImportKeyTask> 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<AesTask>(aCx, aWrappedKey,
+                                      aUnwrappingKey, aUnwrapAlgorithm,
+                                      importTask);
+  } else if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
+    return new UnwrapKeyTask<RsaesPkcs1Task>(aCx, aWrappedKey,
+                                      aUnwrappingKey, aUnwrapAlgorithm,
+                                      importTask);
+  } else if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+    return new UnwrapKeyTask<RsaOaepTask>(aCx, aWrappedKey,
+                                      aUnwrappingKey, aUnwrapAlgorithm,
+                                      importTask);
+  }
+
+  return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+
+}
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/crypto/WebCryptoTask.h
+++ b/dom/crypto/WebCryptoTask.h
@@ -80,93 +80,107 @@ public:
       return;
     }
 
      mEarlyRv = Dispatch("SubtleCrypto");
      MAYBE_EARLY_FAIL(mEarlyRv)
   }
 
 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,
                           const CryptoOperationData& aData,
                           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<nsString>& 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<nsString>& aKeyUsages);
 
-  static WebCryptoTask* DeriveKeyTask(JSContext* aCx,
+  static WebCryptoTask* CreateDeriveKeyTask(JSContext* aCx,
                           const ObjectOrString& aAlgorithm,
                           CryptoKey& aBaseKey,
                           const ObjectOrString& aDerivedKeyType,
                           bool extractable,
                           const Sequence<nsString>& 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<nsString>& aKeyUsages);
+
 protected:
   nsRefPtr<Promise> mResultPromise;
   nsresult mEarlyRv;
   bool mEarlyComplete;
 
   WebCryptoTask()
     : mEarlyRv(NS_OK)
     , mEarlyComplete(false)
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -350,16 +350,24 @@ tv = {
     result: util.hex2abv(
       "354fe67b4a126d5d35fe36c777791a3f7ba13def484e2d3908aff722fad468fb" +
       "21696de95d0be911c2d3174f8afcc201035f7b6d8e69402de5451618c21a535f" +
       "a9d7bfc5b8dd9fc243f8cf927db31322d6e881eaa91a996170e657a05a266426" +
       "d98c88003f8477c1227094a0d9fa1e8c4024309ce1ecccb5210035d47ac72e8a"
     ),
   },
 
+  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 <http://tools.ietf.org/html/rfc6070>
   pbkdf2_sha1: {
     password: new TextEncoder("utf-8").encode("passwordPASSWORDpassword"),
     salt: new TextEncoder("utf-8").encode("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
     iterations: 4096,
     length: 25 * 8,
 
     derived: util.hex2abv(
--- a/dom/crypto/test/tests.js
+++ b/dom/crypto/test/tests.js
@@ -1269,8 +1269,163 @@ TestArray.addTest(
     var that = this;
     var alg = {name: "RSA-OAEP", hash: "SHA-123"};
 
     crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, alg, false, ['decrypt'])
       .then(error(that), complete(that));
   }
 );
 
+// -----------------------------------------------------------------------------
+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)
+      );
+  }
+);
+
--- a/dom/webidl/SubtleCrypto.webidl
+++ b/dom/webidl/SubtleCrypto.webidl
@@ -171,10 +171,25 @@ interface SubtleCrypto {
   [Throws]
   Promise importKey(KeyFormat format,
                     KeyData keyData,
                     AlgorithmIdentifier algorithm,
                     boolean extractable,
                     sequence<KeyUsage> 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<KeyUsage> keyUsages );
 };