Bug 1025230 - Allow import/export of JWK-formatted keys in WebCrypto r=bz,keeler
authorRichard Barnes <rbarnes@mozilla.com>
Sat, 19 Jul 2014 08:25:00 -0500
changeset 217094 b0d3e4c8385a27834a8a4f2288bb3a9115af46ff
parent 217093 6e75c02fedfcc1a3061e1ecb66ef325392bf1d36
child 217095 e6b5084690dcf70765bcb3ca036a051e92ace5c6
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, keeler
bugs1025230
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 1025230 - Allow import/export of JWK-formatted keys in WebCrypto r=bz,keeler
dom/base/SubtleCrypto.cpp
dom/base/SubtleCrypto.h
dom/crypto/AesKeyAlgorithm.cpp
dom/crypto/AesKeyAlgorithm.h
dom/crypto/CryptoBuffer.cpp
dom/crypto/CryptoBuffer.h
dom/crypto/CryptoKey.cpp
dom/crypto/CryptoKey.h
dom/crypto/HmacKeyAlgorithm.cpp
dom/crypto/HmacKeyAlgorithm.h
dom/crypto/KeyAlgorithm.cpp
dom/crypto/KeyAlgorithm.h
dom/crypto/RsaHashedKeyAlgorithm.cpp
dom/crypto/RsaHashedKeyAlgorithm.h
dom/crypto/WebCryptoCommon.h
dom/crypto/WebCryptoTask.cpp
dom/crypto/WebCryptoTask.h
dom/crypto/test/test-vectors.js
dom/crypto/test/tests.js
dom/webidl/SubtleCrypto.webidl
security/build/nss.def
security/manager/ssl/src/ScopedNSSTypes.h
--- a/dom/base/SubtleCrypto.cpp
+++ b/dom/base/SubtleCrypto.cpp
@@ -95,17 +95,17 @@ SubtleCrypto::Digest(JSContext* cx,
 {
   SUBTLECRYPTO_METHOD_BODY(Digest, aRv, cx, algorithm, data)
 }
 
 
 already_AddRefed<Promise>
 SubtleCrypto::ImportKey(JSContext* cx,
                         const nsAString& format,
-                        const KeyData& keyData,
+                        JS::Handle<JSObject*> keyData,
                         const ObjectOrString& algorithm,
                         bool extractable,
                         const Sequence<nsString>& keyUsages,
                         ErrorResult& aRv)
 {
   SUBTLECRYPTO_METHOD_BODY(ImportKey, aRv, cx, format, keyData, algorithm,
                            extractable, keyUsages)
 }
--- a/dom/base/SubtleCrypto.h
+++ b/dom/base/SubtleCrypto.h
@@ -15,17 +15,16 @@
 #include "js/TypeDecls.h"
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
 
 typedef ArrayBufferViewOrArrayBuffer CryptoOperationData;
-typedef ArrayBufferViewOrArrayBuffer KeyData;
 
 class SubtleCrypto MOZ_FINAL : public nsISupports,
                                public nsWrapperCache
 {
   ~SubtleCrypto() {}
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -68,17 +67,17 @@ public:
 
   already_AddRefed<Promise> Digest(JSContext* cx,
                                    const ObjectOrString& aAlgorithm,
                                    const CryptoOperationData& aData,
                                    ErrorResult& aRv);
 
   already_AddRefed<Promise> ImportKey(JSContext* cx,
                                       const nsAString& format,
-                                      const KeyData& keyData,
+                                      JS::Handle<JSObject*> keyData,
                                       const ObjectOrString& algorithm,
                                       bool extractable,
                                       const Sequence<nsString>& keyUsages,
                                       ErrorResult& aRv);
 
   already_AddRefed<Promise> ExportKey(const nsAString& format, CryptoKey& key,
                                       ErrorResult& aRv);
 
--- a/dom/crypto/AesKeyAlgorithm.cpp
+++ b/dom/crypto/AesKeyAlgorithm.cpp
@@ -12,16 +12,46 @@ namespace mozilla {
 namespace dom {
 
 JSObject*
 AesKeyAlgorithm::WrapObject(JSContext* aCx)
 {
   return AesKeyAlgorithmBinding::Wrap(aCx, this);
 }
 
+nsString
+AesKeyAlgorithm::ToJwkAlg() const
+{
+  if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC)) {
+    switch (mLength) {
+      case 128: return NS_LITERAL_STRING(JWK_ALG_A128CBC);
+      case 192: return NS_LITERAL_STRING(JWK_ALG_A192CBC);
+      case 256: return NS_LITERAL_STRING(JWK_ALG_A256CBC);
+    }
+  }
+
+  if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR)) {
+    switch (mLength) {
+      case 128: return NS_LITERAL_STRING(JWK_ALG_A128CTR);
+      case 192: return NS_LITERAL_STRING(JWK_ALG_A192CTR);
+      case 256: return NS_LITERAL_STRING(JWK_ALG_A256CTR);
+    }
+  }
+
+  if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+    switch (mLength) {
+      case 128: return NS_LITERAL_STRING(JWK_ALG_A128GCM);
+      case 192: return NS_LITERAL_STRING(JWK_ALG_A192GCM);
+      case 256: return NS_LITERAL_STRING(JWK_ALG_A256GCM);
+    }
+  }
+
+  return nsString();
+}
+
 bool
 AesKeyAlgorithm::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
 {
   return JS_WriteUint32Pair(aWriter, SCTAG_AESKEYALG, 0) &&
          JS_WriteUint32Pair(aWriter, mLength, 0) &&
          WriteString(aWriter, mName);
 }
 
--- a/dom/crypto/AesKeyAlgorithm.h
+++ b/dom/crypto/AesKeyAlgorithm.h
@@ -20,16 +20,18 @@ public:
     : BasicSymmetricKeyAlgorithm(aGlobal, aName, aLength)
   {}
 
   ~AesKeyAlgorithm()
   {}
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
+  virtual nsString ToJwkAlg() const MOZ_OVERRIDE;
+
   virtual bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const MOZ_OVERRIDE;
   static KeyAlgorithm* Create(nsIGlobalObject* aGlobal,
                               JSStructuredCloneReader* aReader);
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/crypto/CryptoBuffer.cpp
+++ b/dom/crypto/CryptoBuffer.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CryptoBuffer.h"
+#include "mozilla/Base64.h"
 #include "mozilla/dom/UnionTypes.h"
 
 namespace mozilla {
 namespace dom {
 
 uint8_t*
 CryptoBuffer::Assign(const CryptoBuffer& aData)
 {
@@ -70,18 +71,84 @@ CryptoBuffer::Assign(const OwningArrayBu
   }
 
   // If your union is uninitialized, something's wrong
   MOZ_ASSERT(false);
   SetLength(0);
   return nullptr;
 }
 
+// Helpers to encode/decode JWK's special flavor of Base64
+// * No whitespace
+// * No padding
+// * URL-safe character set
+nsresult
+CryptoBuffer::FromJwkBase64(const nsString& aBase64)
+{
+  NS_ConvertUTF16toUTF8 temp(aBase64);
+  temp.StripWhitespace();
+
+  // Re-add padding
+  if (temp.Length() % 4 == 3) {
+    temp.AppendLiteral("=");
+  } else if (temp.Length() % 4 == 2) {
+    temp.AppendLiteral("==");
+  } if (temp.Length() % 4 == 1) {
+    return NS_ERROR_FAILURE; // bad Base64
+  }
+
+  // Translate from URL-safe character set to normal
+  temp.ReplaceChar('-', '+');
+  temp.ReplaceChar('_', '/');
+
+  // Perform the actual base64 decode
+  nsCString binaryData;
+  nsresult rv = Base64Decode(temp, binaryData);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!Assign((const uint8_t*) binaryData.BeginReading(),
+              binaryData.Length())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CryptoBuffer::ToJwkBase64(nsString& aBase64)
+{
+  // Shortcut for the empty octet string
+  if (Length() == 0) {
+    aBase64.Truncate();
+    return NS_OK;
+  }
+
+  // Perform the actual base64 encode
+  nsCString base64;
+  nsDependentCSubstring binaryData((const char*) Elements(),
+                                   (const char*) (Elements() + Length()));
+  nsresult rv = Base64Encode(binaryData, base64);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Strip padding
+  base64.Trim("=");
+
+  // Translate to the URL-safe charset
+  base64.ReplaceChar('+', '-');
+  base64.ReplaceChar('/', '_');
+  if (base64.FindCharInSet("+/", 0) != kNotFound) {
+    return NS_ERROR_FAILURE;
+  }
+
+  CopyASCIItoUTF16(base64, aBase64);
+  return NS_OK;
+}
+
 SECItem*
-CryptoBuffer::ToSECItem()
+CryptoBuffer::ToSECItem() const
 {
   uint8_t* data = (uint8_t*) moz_malloc(Length());
   if (!data) {
     return nullptr;
   }
 
   SECItem* item = new SECItem();
   item->type = siBuffer;
--- a/dom/crypto/CryptoBuffer.h
+++ b/dom/crypto/CryptoBuffer.h
@@ -32,18 +32,19 @@ public:
            JSObject* UnwrapArray(JSObject*),
            void GetLengthAndData(JSObject*, uint32_t*, T**)>
   uint8_t* Assign(const TypedArray_base<T, UnwrapArray, GetLengthAndData>& aArray)
   {
     aArray.ComputeLengthAndData();
     return Assign(aArray.Data(), aArray.Length());
   }
 
-
-  SECItem* ToSECItem();
+  nsresult FromJwkBase64(const nsString& aBase64);
+  nsresult ToJwkBase64(nsString& aBase64);
+  SECItem* ToSECItem() const;
 
   bool GetBigIntValue(unsigned long& aRetVal);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_CryptoBuffer_h
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -347,16 +347,216 @@ CryptoKey::PublicKeyToSpki(SECKEYPublicK
   if (!spkiItem.get()) {
     return NS_ERROR_DOM_INVALID_ACCESS_ERR;
   }
 
   aRetVal.Assign(spkiItem.get());
   return NS_OK;
 }
 
+SECKEYPrivateKey*
+CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
+                             const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  if (!aJwk.mKty.WasPassed() || !aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_RSA)) {
+    return nullptr;
+  }
+
+  // Verify that all of the required parameters are present
+  CryptoBuffer n, e, d, p, q, dp, dq, qi;
+  if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
+      !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) ||
+      !aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) ||
+      !aJwk.mP.WasPassed() || NS_FAILED(p.FromJwkBase64(aJwk.mP.Value())) ||
+      !aJwk.mQ.WasPassed() || NS_FAILED(q.FromJwkBase64(aJwk.mQ.Value())) ||
+      !aJwk.mDp.WasPassed() || NS_FAILED(dp.FromJwkBase64(aJwk.mDp.Value())) ||
+      !aJwk.mDq.WasPassed() || NS_FAILED(dq.FromJwkBase64(aJwk.mDq.Value())) ||
+      !aJwk.mQi.WasPassed() || NS_FAILED(qi.FromJwkBase64(aJwk.mQi.Value()))) {
+    return nullptr;
+  }
+
+  // Compute the ID for this key
+  // This is generated with a SHA-1 hash, so unlikely to collide
+  ScopedSECItem nItem(n.ToSECItem());
+  ScopedSECItem objID(PK11_MakeIDFromPubKey(nItem.get()));
+  if (!nItem.get() || !objID.get()) {
+    return nullptr;
+  }
+
+  // Populate template from parameters
+  CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
+  CK_KEY_TYPE rsaValue = CKK_RSA;
+  CK_BBOOL falseValue = CK_FALSE;
+  CK_ATTRIBUTE keyTemplate[14] = {
+    { CKA_CLASS,            &privateKeyValue,      sizeof(privateKeyValue) },
+    { CKA_KEY_TYPE,         &rsaValue,             sizeof(rsaValue) },
+    { CKA_TOKEN,            &falseValue,           sizeof(falseValue) },
+    { CKA_SENSITIVE,        &falseValue,           sizeof(falseValue) },
+    { CKA_PRIVATE,          &falseValue,           sizeof(falseValue) },
+    { CKA_ID,               objID->data,           objID->len },
+    { CKA_MODULUS,          (void*) n.Elements(),  n.Length() },
+    { CKA_PUBLIC_EXPONENT,  (void*) e.Elements(),  e.Length() },
+    { CKA_PRIVATE_EXPONENT, (void*) d.Elements(),  d.Length() },
+    { CKA_PRIME_1,          (void*) p.Elements(),  p.Length() },
+    { CKA_PRIME_2,          (void*) q.Elements(),  q.Length() },
+    { CKA_EXPONENT_1,       (void*) dp.Elements(), dp.Length() },
+    { CKA_EXPONENT_2,       (void*) dq.Elements(), dq.Length() },
+    { CKA_COEFFICIENT,      (void*) qi.Elements(), qi.Length() },
+  };
+
+
+  // Create a generic object with the contents of the key
+  ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+  if (!slot.get()) {
+    return nullptr;
+  }
+
+  ScopedPK11GenericObject obj(PK11_CreateGenericObject(slot.get(),
+                                                       keyTemplate,
+                                                       PR_ARRAY_SIZE(keyTemplate),
+                                                       PR_FALSE));
+  if (!obj.get()) {
+    return nullptr;
+  }
+
+  // Have NSS translate the object to a private key by inspection
+  // and make a copy we can own
+  ScopedSECKEYPrivateKey privKey(PK11_FindKeyByKeyID(slot.get(), objID.get(),
+                                                     nullptr));
+  if (!privKey.get()) {
+    return nullptr;
+  }
+  return SECKEY_CopyPrivateKey(privKey.get());
+}
+
+bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
+                            CK_ATTRIBUTE_TYPE aAttribute,
+                            Optional<nsString>& aDst)
+{
+  ScopedSECItem item(new SECItem());
+  if (PK11_ReadRawAttribute(PK11_TypePrivKey, aKey, aAttribute, item)
+        != SECSuccess) {
+    return false;
+  }
+
+  CryptoBuffer buffer;
+  if (!buffer.Assign(item)) {
+    return false;
+  }
+
+  if (NS_FAILED(buffer.ToJwkBase64(aDst.Value()))) {
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
+                           JsonWebKey& aRetVal,
+                           const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  switch (aPrivKey->keyType) {
+    case rsaKey: {
+      aRetVal.mN.Construct();
+      aRetVal.mE.Construct();
+      aRetVal.mD.Construct();
+      aRetVal.mP.Construct();
+      aRetVal.mQ.Construct();
+      aRetVal.mDp.Construct();
+      aRetVal.mDq.Construct();
+      aRetVal.mQi.Construct();
+
+      if (!ReadAndEncodeAttribute(aPrivKey, CKA_MODULUS, aRetVal.mN) ||
+          !ReadAndEncodeAttribute(aPrivKey, CKA_PUBLIC_EXPONENT, aRetVal.mE) ||
+          !ReadAndEncodeAttribute(aPrivKey, CKA_PRIVATE_EXPONENT, aRetVal.mD) ||
+          !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_1, aRetVal.mP) ||
+          !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_2, aRetVal.mQ) ||
+          !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_1, aRetVal.mDp) ||
+          !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_2, aRetVal.mDq) ||
+          !ReadAndEncodeAttribute(aPrivKey, CKA_COEFFICIENT, aRetVal.mQi)) {
+        return NS_ERROR_DOM_OPERATION_ERR;
+      }
+
+      aRetVal.mKty.Construct(NS_LITERAL_STRING(JWK_TYPE_RSA));
+      return NS_OK;
+    }
+    case ecKey: // TODO: Bug 1034855
+    default:
+      return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+  }
+}
+
+SECKEYPublicKey*
+CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk,
+                            const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  if (!aJwk.mKty.WasPassed() || !aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_RSA)) {
+    return nullptr;
+  }
+
+  // Verify that all of the required parameters are present
+  CryptoBuffer n, e;
+  if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
+      !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value()))) {
+    return nullptr;
+  }
+
+  // Transcode to a DER RSAPublicKey structure
+  struct RSAPublicKeyData {
+    SECItem n;
+    SECItem e;
+  };
+  const RSAPublicKeyData input = {
+    { siUnsignedInteger, n.Elements(), (unsigned int) n.Length() },
+    { siUnsignedInteger, e.Elements(), (unsigned int) e.Length() }
+  };
+  const SEC_ASN1Template rsaPublicKeyTemplate[] = {
+    {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(RSAPublicKeyData)},
+    {SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, n),},
+    {SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, e),},
+    {0,}
+  };
+
+  ScopedSECItem pkDer(SEC_ASN1EncodeItem(nullptr, nullptr, &input,
+                                         rsaPublicKeyTemplate));
+  if (!pkDer.get()) {
+    return nullptr;
+  }
+
+  return SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA);
+}
+
+nsresult
+CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
+                          JsonWebKey& aRetVal,
+                          const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  switch (aPubKey->keyType) {
+    case rsaKey: {
+      CryptoBuffer n, e;
+      aRetVal.mN.Construct();
+      aRetVal.mE.Construct();
+
+      if (!n.Assign(&aPubKey->u.rsa.modulus) ||
+          !e.Assign(&aPubKey->u.rsa.publicExponent) ||
+          NS_FAILED(n.ToJwkBase64(aRetVal.mN.Value())) ||
+          NS_FAILED(e.ToJwkBase64(aRetVal.mE.Value()))) {
+        return NS_ERROR_DOM_OPERATION_ERR;
+      }
+
+      aRetVal.mKty.Construct(NS_LITERAL_STRING(JWK_TYPE_RSA));
+      return NS_OK;
+    }
+    case ecKey: // TODO
+    default:
+      return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+  }
+}
+
 bool
 CryptoKey::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return false;
   }
 
--- a/dom/crypto/CryptoKey.h
+++ b/dom/crypto/CryptoKey.h
@@ -48,16 +48,18 @@ In the order of a hex value for a uint32
 
 Thus, internally, a key has the following fields:
 * uint32_t - flags for extractable, usage, type
 * KeyAlgorithm - the algorithm (which must serialize/deserialize itself)
 * The actual keys (which the CryptoKey must serialize)
 
 */
 
+struct JsonWebKey;
+
 class CryptoKey MOZ_FINAL : public nsISupports,
                             public nsWrapperCache,
                             public nsNSSShutDownObject
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CryptoKey)
 
@@ -144,16 +146,28 @@ public:
                                     const nsNSSShutDownPreventionLock& /*proofOfLock*/);
 
   static SECKEYPublicKey* PublicKeyFromSpki(CryptoBuffer& aKeyData,
                                             const nsNSSShutDownPreventionLock& /*proofOfLock*/);
   static nsresult PublicKeyToSpki(SECKEYPublicKey* aPrivKey,
                                   CryptoBuffer& aRetVal,
                                   const nsNSSShutDownPreventionLock& /*proofOfLock*/);
 
+  static SECKEYPrivateKey* PrivateKeyFromJwk(const JsonWebKey& aJwk,
+                                             const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+  static nsresult PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
+                                  JsonWebKey& aRetVal,
+                                  const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
+  static SECKEYPublicKey* PublicKeyFromJwk(const JsonWebKey& aKeyData,
+                                           const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+  static nsresult PublicKeyToJwk(SECKEYPublicKey* aPrivKey,
+                                 JsonWebKey& aRetVal,
+                                 const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
   // Structured clone methods use these to clone keys
   bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
   bool ReadStructuredClone(JSStructuredCloneReader* aReader);
 
 private:
   ~CryptoKey();
 
   nsRefPtr<nsIGlobalObject> mGlobal;
--- a/dom/crypto/HmacKeyAlgorithm.cpp
+++ b/dom/crypto/HmacKeyAlgorithm.cpp
@@ -17,16 +17,28 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END_INHERITING(KeyAlgorithm)
 
 JSObject*
 HmacKeyAlgorithm::WrapObject(JSContext* aCx)
 {
   return HmacKeyAlgorithmBinding::Wrap(aCx, this);
 }
 
+nsString
+HmacKeyAlgorithm::ToJwkAlg() const
+{
+  switch (mMechanism) {
+    case CKM_SHA_1_HMAC:  return NS_LITERAL_STRING(JWK_ALG_HS1);
+    case CKM_SHA256_HMAC: return NS_LITERAL_STRING(JWK_ALG_HS256);
+    case CKM_SHA384_HMAC: return NS_LITERAL_STRING(JWK_ALG_HS384);
+    case CKM_SHA512_HMAC: return NS_LITERAL_STRING(JWK_ALG_HS512);
+  }
+  return nsString();
+}
+
 bool
 HmacKeyAlgorithm::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
 {
   nsString hashName;
   mHash->GetName(hashName);
   return JS_WriteUint32Pair(aWriter, SCTAG_HMACKEYALG, 0) &&
          JS_WriteUint32Pair(aWriter, mLength, 0) &&
          WriteString(aWriter, hashName) &&
--- a/dom/crypto/HmacKeyAlgorithm.h
+++ b/dom/crypto/HmacKeyAlgorithm.h
@@ -47,16 +47,18 @@ public:
     return mHash;
   }
 
   uint32_t Length() const
   {
     return mLength;
   }
 
+  virtual nsString ToJwkAlg() const MOZ_OVERRIDE;
+
   virtual bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const MOZ_OVERRIDE;
   static KeyAlgorithm* Create(nsIGlobalObject* aGlobal,
                               JSStructuredCloneReader* aReader);
 
 protected:
   ~HmacKeyAlgorithm()
   {}
 
--- a/dom/crypto/KeyAlgorithm.cpp
+++ b/dom/crypto/KeyAlgorithm.cpp
@@ -42,16 +42,22 @@ KeyAlgorithm::~KeyAlgorithm()
 }
 
 JSObject*
 KeyAlgorithm::WrapObject(JSContext* aCx)
 {
   return KeyAlgorithmBinding::Wrap(aCx, this);
 }
 
+nsString
+KeyAlgorithm::ToJwkAlg() const
+{
+  return nsString();
+}
+
 void
 KeyAlgorithm::GetName(nsString& aRetVal) const
 {
   aRetVal.Assign(mName);
 }
 
 bool
 KeyAlgorithm::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
--- a/dom/crypto/KeyAlgorithm.h
+++ b/dom/crypto/KeyAlgorithm.h
@@ -47,16 +47,18 @@ public:
   {
     return mGlobal;
   }
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   void GetName(nsString& aRetVal) const;
 
+  virtual nsString ToJwkAlg() const;
+
   // Structured clone support methods
   virtual bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
   static KeyAlgorithm* Create(nsIGlobalObject* aGlobal,
                               JSStructuredCloneReader* aReader);
 
   // Helper method to look up NSS methods
   // Sub-classes should assign mMechanism on constructor
   CK_MECHANISM_TYPE Mechanism() const {
--- a/dom/crypto/RsaHashedKeyAlgorithm.cpp
+++ b/dom/crypto/RsaHashedKeyAlgorithm.cpp
@@ -18,16 +18,40 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END_INHERITING(RsaKeyAlgorithm)
 
 JSObject*
 RsaHashedKeyAlgorithm::WrapObject(JSContext* aCx)
 {
   return RsaHashedKeyAlgorithmBinding::Wrap(aCx, this);
 }
 
+nsString
+RsaHashedKeyAlgorithm::ToJwkAlg() const
+{
+  if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+    switch (mHash->Mechanism()) {
+      case CKM_SHA_1:  return NS_LITERAL_STRING(JWK_ALG_RS1);
+      case CKM_SHA256: return NS_LITERAL_STRING(JWK_ALG_RS256);
+      case CKM_SHA384: return NS_LITERAL_STRING(JWK_ALG_RS384);
+      case CKM_SHA512: return NS_LITERAL_STRING(JWK_ALG_RS512);
+    }
+  }
+
+  if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+    switch(mHash->Mechanism()) {
+      case CKM_SHA_1:  return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP);
+      case CKM_SHA256: return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_256);
+      case CKM_SHA384: return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_256);
+      case CKM_SHA512: return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_512);
+    }
+  }
+
+  return nsString();
+}
+
 bool
 RsaHashedKeyAlgorithm::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
 {
   nsString hashName;
   mHash->GetName(hashName);
   return JS_WriteUint32Pair(aWriter, SCTAG_RSAHASHEDKEYALG, 0) &&
          JS_WriteUint32Pair(aWriter, mModulusLength, 0) &&
          WriteBuffer(aWriter, mPublicExponent) &&
--- a/dom/crypto/RsaHashedKeyAlgorithm.h
+++ b/dom/crypto/RsaHashedKeyAlgorithm.h
@@ -25,16 +25,18 @@ public:
                         const CryptoBuffer& aPublicExponent,
                         const nsString& aHashName)
     : RsaKeyAlgorithm(aGlobal, aName, aModulusLength, aPublicExponent)
     , mHash(new KeyAlgorithm(aGlobal, aHashName))
   {}
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
+  virtual nsString ToJwkAlg() const MOZ_OVERRIDE;
+
   KeyAlgorithm* Hash() const
   {
     return mHash;
   }
 
   virtual bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const MOZ_OVERRIDE;
   static KeyAlgorithm* Create(nsIGlobalObject* aGlobal,
                               JSStructuredCloneReader* aReader);
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -48,20 +48,41 @@
 #define WEBCRYPTO_KEY_USAGE_UNWRAPKEY   "unwrapKey"
 
 // JWK key types
 #define JWK_TYPE_SYMMETRIC          "oct"
 #define JWK_TYPE_RSA                "RSA"
 #define JWK_TYPE_EC                 "EC"
 
 // JWK algorithms
-#define JWK_ALG_RS1                 "RS1"
+#define JWK_ALG_A128CBC             "A128CBC"  // CBC
+#define JWK_ALG_A192CBC             "A192CBC"
+#define JWK_ALG_A256CBC             "A256CBC"
+#define JWK_ALG_A128CTR             "A128CTR"  // CTR
+#define JWK_ALG_A192CTR             "A192CTR"
+#define JWK_ALG_A256CTR             "A256CTR"
+#define JWK_ALG_A128GCM             "A128GCM"  // GCM
+#define JWK_ALG_A192GCM             "A192GCM"
+#define JWK_ALG_A256GCM             "A256GCM"
+#define JWK_ALG_HS1                 "HS1"      // HMAC
+#define JWK_ALG_HS256               "HS256"
+#define JWK_ALG_HS384               "HS384"
+#define JWK_ALG_HS512               "HS512"
+#define JWK_ALG_RS1                 "RS1"      // RSASSA-PKCS1
 #define JWK_ALG_RS256               "RS256"
 #define JWK_ALG_RS384               "RS384"
 #define JWK_ALG_RS512               "RS512"
+#define JWK_ALG_RSA_OAEP            "RSA-OAEP" // RSA-OAEP
+#define JWK_ALG_RSA_OAEP_256        "RSA-OAEP-256"
+#define JWK_ALG_RSA_OAEP_384        "RSA-OAEP-384"
+#define JWK_ALG_RSA_OAEP_512        "RSA-OAEP-512"
+
+// JWK usages
+#define JWK_USE_ENC                 "enc"
+#define JWK_USE_SIG                 "sig"
 
 // Define an unknown mechanism type
 #define UNKNOWN_CK_MECHANISM        CKM_VENDOR_DEFINED+1
 
 namespace mozilla {
 namespace dom {
 
 // Helper functions for structured cloning
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -4,29 +4,30 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "pk11pub.h"
 #include "cryptohi.h"
 #include "secerr.h"
 #include "ScopedNSSTypes.h"
 
-#include "mozilla/dom/WebCryptoTask.h"
-#include "mozilla/dom/TypedArray.h"
+#include "jsapi.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/AesKeyAlgorithm.h"
+#include "mozilla/dom/CryptoBuffer.h"
 #include "mozilla/dom/CryptoKey.h"
-#include "mozilla/dom/KeyAlgorithm.h"
 #include "mozilla/dom/CryptoKeyPair.h"
-#include "mozilla/dom/AesKeyAlgorithm.h"
 #include "mozilla/dom/HmacKeyAlgorithm.h"
-#include "mozilla/dom/RsaKeyAlgorithm.h"
+#include "mozilla/dom/KeyAlgorithm.h"
 #include "mozilla/dom/RsaHashedKeyAlgorithm.h"
-#include "mozilla/dom/CryptoBuffer.h"
+#include "mozilla/dom/RsaKeyAlgorithm.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/TypedArray.h"
 #include "mozilla/dom/WebCryptoCommon.h"
-
-#include "mozilla/Telemetry.h"
+#include "mozilla/dom/WebCryptoTask.h"
 
 namespace mozilla {
 namespace dom {
 
 // Pre-defined identifiers for telemetry histograms
 
 enum TelemetryMethod {
   TM_ENCRYPT      = 0,
@@ -215,16 +216,38 @@ GetKeySizeForAlgorithm(JSContext* aCx, c
 
     aLength = length;
     return NS_OK;
   }
 
   return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 }
 
+// Helper function to clone data from an ArrayBuffer or ArrayBufferView object
+inline bool
+CloneData(JSContext* aCx, CryptoBuffer& aDst, JS::Handle<JSObject*> aSrc)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Try ArrayBuffer
+  RootedTypedArray<ArrayBuffer> ab(aCx);
+  if (ab.Init(aSrc)) {
+    return !!aDst.Assign(ab);
+  }
+
+  // Try ArrayBufferView
+  RootedTypedArray<ArrayBufferView> abv(aCx);
+  if (abv.Init(aSrc)) {
+    return !!aDst.Assign(abv);
+  }
+
+  return false;
+}
+
+
 // Implementation of WebCryptoTask methods
 
 void
 WebCryptoTask::FailWithError(nsresult aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, false);
 
@@ -1027,21 +1050,24 @@ private:
 
     return rv;
   }
 };
 
 class ImportKeyTask : public WebCryptoTask
 {
 public:
-  ImportKeyTask(JSContext* aCx,
-      const nsAString& aFormat, const KeyData& aKeyData,
+  void Init(JSContext* aCx,
+      const nsAString& aFormat,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
+    mFormat = aFormat;
+    mDataIsSet = false;
+
     // Get the current global object from the context
     nsIGlobalObject *global = xpc::GetNativeForGlobal(JS::CurrentGlobalOrNull(aCx));
     if (!global) {
       mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
       return;
     }
 
     // This stuff pretty much always happens, so we'll do it here
@@ -1057,25 +1083,98 @@ public:
 
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName);
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_DATA_ERR;
       return;
     }
   }
 
+  static bool JwkCompatible(const JsonWebKey& aJwk, const CryptoKey* aKey)
+  {
+    // Check 'ext'
+    if (aKey->Extractable() &&
+        aJwk.mExt.WasPassed() && !aJwk.mExt.Value()) {
+      return false;
+    }
+
+    // Check 'alg'
+    if (aJwk.mAlg.WasPassed() &&
+        aJwk.mAlg.Value() != aKey->Algorithm()->ToJwkAlg()) {
+      return false;
+    }
+
+    // Check 'key_ops'
+    if (aJwk.mKey_ops.WasPassed()) {
+      nsTArray<nsString> usages;
+      aKey->GetUsages(usages);
+      for (size_t i = 0; i < usages.Length(); ++i) {
+        if (!aJwk.mKey_ops.Value().Contains(usages[i])) {
+          return false;
+        }
+      }
+    }
+
+    // Individual algorithms may still have to check 'use'
+    return true;
+  }
+
+  void SetKeyData(JSContext* aCx, JS::Handle<JSObject*> aKeyData) {
+    // First try to treat as ArrayBuffer/ABV,
+    // and if that fails, try to initialize a JWK
+    if (CloneData(aCx, mKeyData, aKeyData)) {
+      mDataIsJwk = false;
+
+      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+        SetJwkFromKeyData();
+      }
+    } else {
+      JS::RootedValue value(aCx, JS::ObjectValue(*aKeyData));
+      if (!mJwk.Init(aCx, value)) {
+        return;
+      }
+      mDataIsJwk = true;
+    }
+  }
+
   void SetKeyData(const CryptoBuffer& aKeyData)
   {
-    // An OOM will just result in an error in BeforeCrypto
     mKeyData = aKeyData;
+    mDataIsJwk = false;
+
+    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+      SetJwkFromKeyData();
+    }
+  }
+
+  void SetJwkFromKeyData()
+  {
+    nsDependentCSubstring utf8((const char*) mKeyData.Elements(),
+                               (const char*) (mKeyData.Elements() +
+                                              mKeyData.Length()));
+    if (!IsUTF8(utf8)) {
+      mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+      return;
+    }
+
+    nsString json = NS_ConvertUTF8toUTF16(utf8);
+    if (!mJwk.Init(json)) {
+      mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+      return;
+    }
+    mDataIsJwk = true;
   }
 
 protected:
+  nsString mFormat;
+  nsRefPtr<CryptoKey> mKey;
   CryptoBuffer mKeyData;
-  nsRefPtr<CryptoKey> mKey;
+  bool mDataIsSet;
+  bool mDataIsJwk;
+  JsonWebKey mJwk;
   nsString mAlgName;
 
 private:
   virtual void Resolve() MOZ_OVERRIDE
   {
     mResultPromise->MaybeResolve(mKey);
   }
 
@@ -1085,43 +1184,43 @@ private:
   }
 };
 
 
 class ImportSymmetricKeyTask : public ImportKeyTask
 {
 public:
   ImportSymmetricKeyTask(JSContext* aCx,
-      const nsAString& aFormat, const KeyData& aKeyData,
+      const nsAString& aFormat,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
-    : ImportKeyTask(aCx, aFormat, aKeyData, aAlgorithm, aExtractable, aKeyUsages)
   {
+    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+  }
+
+  ImportSymmetricKeyTask(JSContext* aCx,
+      const nsAString& aFormat, const JS::Handle<JSObject*> aKeyData,
+      const ObjectOrString& aAlgorithm, bool aExtractable,
+      const Sequence<nsString>& aKeyUsages)
+  {
+    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
-    // Import the key data
-    if (aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
-      if (aKeyData.IsArrayBufferView()) {
-        mKeyData.Assign(aKeyData.GetAsArrayBufferView());
-      } else if (aKeyData.IsArrayBuffer()) {
-        mKeyData.Assign(aKeyData.GetAsArrayBuffer());
-      }
-      // We would normally fail here if the key data is not an ArrayBuffer or
-      // an ArrayBufferView but let's wait for BeforeCrypto() to be called in
-      // case PBKDF2's deriveKey() operation passed dummy key data. When that
-      // happens DerivePbkdfKeyTask is responsible for calling SetKeyData()
-      // itself before this task is actually run.
-    } else if (aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
-      mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
-      return;
-    } else {
-      // Invalid key format
-      mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+    SetKeyData(aCx, aKeyData);
+  }
+
+  void Init(JSContext* aCx,
+      const nsAString& aFormat,
+      const ObjectOrString& aAlgorithm, bool aExtractable,
+      const Sequence<nsString>& aKeyUsages)
+  {
+    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     // If this is an HMAC key, import the hash name
     if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
       RootedDictionary<HmacImportParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv) || !params.mHash.WasPassed()) {
@@ -1133,16 +1232,31 @@ public:
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
     }
   }
 
   virtual nsresult BeforeCrypto() MOZ_OVERRIDE
   {
+    nsresult rv;
+
+    // If we're doing a JWK import, import the key data
+    if (mDataIsJwk) {
+      if (!mJwk.mK.WasPassed()) {
+        return NS_ERROR_DOM_DATA_ERR;
+      }
+
+      // Import the key material
+      rv = mKeyData.FromJwkBase64(mJwk.mK.Value());
+      if (NS_FAILED(rv)) {
+        return NS_ERROR_DOM_DATA_ERR;
+      }
+    }
+
     // Check that we have valid key data.
     if (mKeyData.Length() == 0) {
       return NS_ERROR_DOM_DATA_ERR;
     }
 
     // Construct an appropriate KeyAlorithm,
     // and verify that usages are appropriate
     nsRefPtr<KeyAlgorithm> algorithm;
@@ -1155,68 +1269,99 @@ public:
                                   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);
+
+      if (mDataIsJwk && mJwk.mUse.WasPassed() &&
+          !mJwk.mUse.Value().EqualsLiteral(JWK_USE_ENC)) {
+        return NS_ERROR_DOM_DATA_ERR;
+      }
     } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
       if (mKey->HasUsageOtherThan(CryptoKey::DERIVEKEY)) {
         return NS_ERROR_DOM_DATA_ERR;
       }
       algorithm = new BasicSymmetricKeyAlgorithm(global, mAlgName, length);
+
+      if (mDataIsJwk && mJwk.mUse.WasPassed()) {
+        // There is not a 'use' value consistent with PBKDF
+        return NS_ERROR_DOM_DATA_ERR;
+      };
     } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
       if (mKey->HasUsageOtherThan(CryptoKey::SIGN | CryptoKey::VERIFY)) {
         return NS_ERROR_DOM_DATA_ERR;
       }
 
       algorithm = new HmacKeyAlgorithm(global, mAlgName, length, mHashName);
       if (algorithm->Mechanism() == UNKNOWN_CK_MECHANISM) {
         return NS_ERROR_DOM_SYNTAX_ERR;
       }
+
+      if (mDataIsJwk && mJwk.mUse.WasPassed() &&
+          !mJwk.mUse.Value().EqualsLiteral(JWK_USE_SIG)) {
+        return NS_ERROR_DOM_DATA_ERR;
+      }
     } else {
       return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
     }
 
     mKey->SetAlgorithm(algorithm);
     mKey->SetSymKey(mKeyData);
     mKey->SetType(CryptoKey::SECRET);
     mEarlyComplete = true;
     return NS_OK;
   }
 
+  nsresult AfterCrypto() MOZ_OVERRIDE
+  {
+    if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+      return NS_ERROR_DOM_DATA_ERR;
+    }
+    return NS_OK;
+  }
+
 private:
   nsString mHashName;
 };
 
 class ImportRsaKeyTask : public ImportKeyTask
 {
 public:
   ImportRsaKeyTask(JSContext* aCx,
-      const nsAString& aFormat, const KeyData& aKeyData,
+      const nsAString& aFormat,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
-    : ImportKeyTask(aCx, aFormat, aKeyData, aAlgorithm, aExtractable, aKeyUsages)
   {
+    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+  }
+
+  ImportRsaKeyTask(JSContext* aCx,
+      const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
+      const ObjectOrString& aAlgorithm, bool aExtractable,
+      const Sequence<nsString>& aKeyUsages)
+  {
+    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
-    mFormat = aFormat;
+    SetKeyData(aCx, aKeyData);
+  }
 
-    // Import the key data
-    if (aKeyData.IsArrayBufferView()) {
-      mKeyData.Assign(aKeyData.GetAsArrayBufferView());
-    } else if (aKeyData.IsArrayBuffer()) {
-      mKeyData.Assign(aKeyData.GetAsArrayBuffer());
-    } else {
-      // TODO This will need to be changed for JWK (Bug 1005220)
-      mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+  void Init(JSContext* aCx,
+      const nsAString& aFormat,
+      const ObjectOrString& aAlgorithm, bool aExtractable,
+      const Sequence<nsString>& aKeyUsages)
+  {
+    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     // If this is RSA with a hash, cache the hash name
     if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
         mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
       RootedDictionary<RsaHashedImportParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
@@ -1229,68 +1374,78 @@ public:
       if (NS_FAILED(mEarlyRv)) {
         mEarlyRv = NS_ERROR_DOM_DATA_ERR;
         return;
       }
     }
   }
 
 private:
-  nsString mFormat;
   nsString mHashName;
   uint32_t mModulusLength;
   CryptoBuffer mPublicExponent;
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
     nsNSSShutDownPreventionLock locker;
 
     // Import the key data itself
     ScopedSECKEYPublicKey pubKey;
-    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
-      ScopedSECKEYPrivateKey privKey(CryptoKey::PrivateKeyFromPkcs8(mKeyData, locker));
-      if (!privKey.get()) {
+    ScopedSECKEYPrivateKey privKey;
+    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) ||
+        (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+         !mJwk.mD.WasPassed())) {
+      // Public key import
+      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+        pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+      } else {
+        pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
+      }
+
+      if (!pubKey) {
+        return NS_ERROR_DOM_DATA_ERR;
+      }
+
+      mKey->SetPublicKey(pubKey.get());
+      mKey->SetType(CryptoKey::PUBLIC);
+    } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) ||
+        (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+         mJwk.mD.WasPassed())) {
+      // Private key import
+      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
+        privKey = CryptoKey::PrivateKeyFromPkcs8(mKeyData, locker);
+      } else {
+        privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
+      }
+
+      if (!privKey) {
         return NS_ERROR_DOM_DATA_ERR;
       }
 
       mKey->SetPrivateKey(privKey.get());
       mKey->SetType(CryptoKey::PRIVATE);
       pubKey = SECKEY_ConvertToPublicKey(privKey.get());
       if (!pubKey) {
         return NS_ERROR_DOM_UNKNOWN_ERR;
       }
-    } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
-      pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
-      if (!pubKey.get()) {
-        return NS_ERROR_DOM_DATA_ERR;
-      }
-
-      if (pubKey->keyType != rsaKey) {
-        return NS_ERROR_DOM_DATA_ERR;
-      }
-
-      mKey->SetPublicKey(pubKey.get());
-      mKey->SetType(CryptoKey::PUBLIC);
-    } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
-      return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
     } else {
       // Invalid key format
       return NS_ERROR_DOM_SYNTAX_ERR;
     }
 
     // Extract relevant information from the public key
     mModulusLength = 8 * pubKey->u.rsa.modulus.len;
     mPublicExponent.Assign(&pubKey->u.rsa.publicExponent);
 
     return NS_OK;
   }
 
   virtual nsresult AfterCrypto() MOZ_OVERRIDE
   {
-    // Construct an appropriate KeyAlgorithm
+    // Check permissions for the requested operation
     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 | CryptoKey::WRAPKEY)) ||
           (mKey->GetKeyType() == CryptoKey::PRIVATE &&
            mKey->HasUsageOtherThan(CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY))) {
         return NS_ERROR_DOM_DATA_ERR;
@@ -1317,43 +1472,58 @@ private:
       }
 
       if (algorithm->Hash()->Mechanism() == UNKNOWN_CK_MECHANISM) {
         return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
       }
       mKey->SetAlgorithm(algorithm);
     }
 
+    if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+      return NS_ERROR_DOM_DATA_ERR;
+    }
+
     return NS_OK;
   }
 };
 
-
-class ExportKeyTask : public ReturnArrayBufferViewTask
+class ExportKeyTask : public WebCryptoTask
 {
 public:
   ExportKeyTask(const nsAString& aFormat, CryptoKey& aKey)
     : mFormat(aFormat)
     , mSymKey(aKey.GetSymKey())
     , mPrivateKey(aKey.GetPrivateKey())
     , mPublicKey(aKey.GetPublicKey())
+    , mKeyType(aKey.GetKeyType())
+    , mExtractable(aKey.Extractable())
+    , mAlg(aKey.Algorithm()->ToJwkAlg())
   {
     if (!aKey.Extractable()) {
       mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
       return;
     }
+
+    aKey.GetUsages(mKeyUsages);
   }
 
 
-private:
+protected:
   nsString mFormat;
   CryptoBuffer mSymKey;
   ScopedSECKEYPrivateKey mPrivateKey;
   ScopedSECKEYPublicKey mPublicKey;
+  CryptoKey::KeyType mKeyType;
+  bool mExtractable;
+  nsString mAlg;
+  nsTArray<nsString> mKeyUsages;
+  CryptoBuffer mResult;
+  JsonWebKey mJwk;
 
+private:
   virtual void ReleaseNSSResources() MOZ_OVERRIDE
   {
     mPrivateKey.dispose();
     mPublicKey.dispose();
   }
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
@@ -1380,21 +1550,72 @@ private:
       }
     } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
       if (!mPublicKey) {
         return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
       }
 
       return CryptoKey::PublicKeyToSpki(mPublicKey.get(), mResult, locker);
     } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
-      return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+      if (mKeyType == CryptoKey::SECRET) {
+        nsString k;
+        nsresult rv = mSymKey.ToJwkBase64(k);
+        if (NS_FAILED(rv)) {
+          return NS_ERROR_DOM_OPERATION_ERR;
+        }
+        mJwk.mK.Construct(k);
+        mJwk.mKty.Construct(NS_LITERAL_STRING(JWK_TYPE_SYMMETRIC));
+      } else if (mKeyType == CryptoKey::PUBLIC) {
+        if (!mPublicKey) {
+          return NS_ERROR_DOM_UNKNOWN_ERR;
+        }
+
+        nsresult rv = CryptoKey::PublicKeyToJwk(mPublicKey, mJwk, locker);
+        if (NS_FAILED(rv)) {
+          return NS_ERROR_DOM_OPERATION_ERR;
+        }
+      } else if (mKeyType == CryptoKey::PRIVATE) {
+        if (!mPrivateKey) {
+          return NS_ERROR_DOM_UNKNOWN_ERR;
+        }
+
+        nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey, mJwk, locker);
+        if (NS_FAILED(rv)) {
+          return NS_ERROR_DOM_OPERATION_ERR;
+        }
+      }
+
+      if (!mAlg.IsEmpty()) {
+        mJwk.mAlg.Construct(mAlg);
+      }
+
+      mJwk.mExt.Construct(mExtractable);
+
+      if (!mKeyUsages.IsEmpty()) {
+        mJwk.mKey_ops.Construct();
+        mJwk.mKey_ops.Value().AppendElements(mKeyUsages);
+      }
+
+      return NS_OK;
     }
 
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
+
+  // Returns mResult as an ArrayBufferView or JWK, as appropriate
+  virtual void Resolve() MOZ_OVERRIDE
+  {
+    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+      mResultPromise->MaybeResolve(mJwk);
+      return;
+    }
+
+    TypedArrayCreator<Uint8Array> ret(mResult);
+    mResultPromise->MaybeResolve(ret);
+  }
 };
 
 class GenerateSymmetricKeyTask : public WebCryptoTask
 {
 public:
   GenerateSymmetricKeyTask(JSContext* aCx,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
@@ -1827,20 +2048,18 @@ public:
                      const ObjectOrString& aDerivedKeyType, bool aExtractable,
                      const Sequence<nsString>& aKeyUsages)
     : DerivePbkdfBitsTask(aCx, aAlgorithm, aBaseKey, aDerivedKeyType)
   {
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
-    CryptoOperationData dummy;
     NS_NAMED_LITERAL_STRING(format, WEBCRYPTO_KEY_FORMAT_RAW);
-
-    mTask = new ImportSymmetricKeyTask(aCx, format, dummy, aDerivedKeyType,
+    mTask = new ImportSymmetricKeyTask(aCx, format, aDerivedKeyType,
                                        aExtractable, aKeyUsages);
   }
 
 protected:
   nsRefPtr<ImportSymmetricKeyTask> mTask;
 
 private:
   virtual void Resolve() MOZ_OVERRIDE {
@@ -1849,16 +2068,25 @@ private:
   }
 
   virtual void Cleanup() MOZ_OVERRIDE
   {
     mTask = nullptr;
   }
 };
 
+static bool
+JSONCreator(const jschar* aBuf, uint32_t aLen, void* aData)
+{
+  nsAString* result = static_cast<nsAString*>(aData);
+  result->Append(static_cast<const char16_t*>(aBuf),
+                 static_cast<uint32_t>(aLen));
+  return true;
+}
+
 template<class KeyEncryptTask>
 class WrapKeyTask : public ExportKeyTask
 {
 public:
   WrapKeyTask(JSContext* aCx,
               const nsAString& aFormat,
               CryptoKey& aKey,
               CryptoKey& aWrappingKey,
@@ -1870,16 +2098,50 @@ public:
     }
 
     mTask = new KeyEncryptTask(aCx, aWrapAlgorithm, aWrappingKey, true);
   }
 
 private:
   nsRefPtr<KeyEncryptTask> mTask;
 
+  static bool StringifyJWK(const JsonWebKey& aJwk, nsAString& aRetVal)
+  {
+    // XXX: This should move into DictionaryBase and Codegen.py,
+    //      in the same way as ParseJSON is split out. (Bug 1038399)
+    // We use AutoSafeJSContext even though the exact compartment
+    // doesn't matter (since we're making an XPCOM string)
+    MOZ_ASSERT(NS_IsMainThread());
+    AutoSafeJSContext cx;
+    JS::Rooted<JS::Value> obj(cx);
+    bool ok = ToJSValue(cx, aJwk, &obj);
+    if (!ok) {
+      JS_ClearPendingException(cx);
+      return false;
+    }
+
+    return JS_Stringify(cx, &obj, JS::NullPtr(), JS::NullHandleValue,
+                        JSONCreator, &aRetVal);
+  }
+
+  virtual nsresult AfterCrypto() MOZ_OVERRIDE {
+    // If wrapping JWK, stringify the JSON
+    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+      nsAutoString json;
+      if (!StringifyJWK(mJwk, json)) {
+        return NS_ERROR_DOM_OPERATION_ERR;
+      }
+
+      NS_ConvertUTF16toUTF8 utf8(json);
+      mResult.Assign((const uint8_t*) utf8.BeginReading(), utf8.Length());
+    }
+
+    return NS_OK;
+  }
+
   virtual void Resolve() MOZ_OVERRIDE {
     mTask->SetData(mResult);
     mTask->DispatchWithPromise(mResultPromise);
   }
 
   virtual void Cleanup() MOZ_OVERRIDE
   {
     mTask = nullptr;
@@ -1991,17 +2253,17 @@ WebCryptoTask::CreateDigestTask(JSContex
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DIGEST);
   return new DigestTask(aCx, aAlgorithm, aData);
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateImportKeyTask(JSContext* aCx,
                              const nsAString& aFormat,
-                             const KeyData& aKeyData,
+                             JS::Handle<JSObject*> aKeyData,
                              const ObjectOrString& aAlgorithm,
                              bool aExtractable,
                              const Sequence<nsString>& aKeyUsages)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_IMPORTKEY);
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_IMPORT, aExtractable);
 
   nsString algName;
@@ -2028,21 +2290,17 @@ WebCryptoTask::CreateImportKeyTask(JSCon
 }
 
 WebCryptoTask*
 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 ExportKeyTask(aFormat, aKey);
-  }
+  return new ExportKeyTask(aFormat, aKey);
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateGenerateKeyTask(JSContext* aCx,
                                const ObjectOrString& aAlgorithm,
                                bool aExtractable,
                                const Sequence<nsString>& aKeyUsages)
 {
@@ -2174,23 +2432,23 @@ WebCryptoTask::CreateUnwrapKeyTask(JSCon
   }
 
   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,
+    importTask = new ImportSymmetricKeyTask(aCx, aFormat,
                                             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,
+    importTask = new ImportRsaKeyTask(aCx, aFormat,
                                       aUnwrappedKeyAlgorithm,
                                       aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 
   nsString unwrapAlgName;
   rv = GetAlgorithmName(aCx, aUnwrapAlgorithm, unwrapAlgName);
--- a/dom/crypto/WebCryptoTask.h
+++ b/dom/crypto/WebCryptoTask.h
@@ -135,17 +135,17 @@ public:
   }
 
   static WebCryptoTask* CreateDigestTask(JSContext* aCx,
                           const ObjectOrString& aAlgorithm,
                           const CryptoOperationData& aData);
 
   static WebCryptoTask* CreateImportKeyTask(JSContext* aCx,
                           const nsAString& aFormat,
-                          const KeyData& aKeyData,
+                          JS::Handle<JSObject*> aKeyData,
                           const ObjectOrString& aAlgorithm,
                           bool aExtractable,
                           const Sequence<nsString>& aKeyUsages);
   static WebCryptoTask* CreateExportKeyTask(const nsAString& aFormat,
                           CryptoKey& aKey);
   static WebCryptoTask* CreateGenerateKeyTask(JSContext* aCx,
                           const ObjectOrString& aAlgorithm,
                           bool aExtractable,
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -98,16 +98,20 @@ tv = {
                          "4540A42BDE6D7836D59A5CEAAEF3105325B2072F"),
   },
 
   // Test case #18 from McGrew and Viega
   // <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf>
   aes_gcm_enc: {
     key: util.hex2abv("feffe9928665731c6d6a8f9467308308" +
                       "feffe9928665731c6d6a8f9467308308"),
+    key_jwk: {
+      kty: "oct",
+      k: "_v_pkoZlcxxtao-UZzCDCP7_6ZKGZXMcbWqPlGcwgwg"
+    },
     iv: util.hex2abv("9313225df88406e555909c5aff5269aa" +
                      "6a7a9538534f7da1e4c303d2a318a728" +
                      "c3c0c95156809539fcf0e2429a6b5254" +
                      "16aedbf5a0de6a57a637b39b"),
     adata: util.hex2abv("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
     data: util.hex2abv("d9313225f88406e5a55909c5aff5269a" +
                        "86a7a9531534f7da2e4c303d8a318a72" +
                        "1c3c0c95956809532fcf0e2449a6b525" +
@@ -268,24 +272,51 @@ tv = {
       "cd20dc723e6963364a1f9425452b269a6799fd024028fa13938655be1f8a159c" +
       "baca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8d" +
       "d3ede2448328f385d81b30e8e43b2fffa02786197902401a8b38f398fa712049" +
       "898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455e" +
       "aeb6e1678255827580a8e4e8e14151d1510a82a3f2e729024027156aba4126d2" +
       "4a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a" +
       "2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d"
     ),
+    jwk_priv: {
+      kty: "RSA",
+      n:  "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
+          "oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
+          "_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
+      e:  "AQAB",
+      d:  "M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-kuiUpySsPFaMj5eFOtB8A" +
+          "mbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQGlMmgxCcKr" +
+          "rKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU",
+      p:  "5-iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh31W" +
+          "hU1vZs8w0Fgs7bc0-2o5kQw",
+      q:  "tp3KHPfU1-yB51uQ_MqHSrzeEj_ScAGAqpBHm25I3o1n7ST58Z2FuidYdPVCz" +
+          "SDccj5pYzZKH5QlRSsmmmeZ_Q",
+      dp: "KPoTk4ZVvh-KFZy6ylpy6hkMMAieGc0nSlVvNsT24Z9VSzTAd3kEJ7vdjdPt4" +
+          "kSDKPOF2Bsw6OQ7L_-gJ4YZeQ",
+      dq: "Gos485j6cSBJiY1_t57gp3ZoeRKZzfoJ78DlB6yyHtdDAe9b_Ui-RV6utuFng" +
+          "lWCdYCo5OjhQVHRUQqCo_LnKQ",
+      qi: "JxVqukEm0kqB86Uoy_sn9WiG-ECp9uhuF6RLlP6TGVhLjiL93h5aLjvYqluo2" +
+          "FhBlOshkKz4MrhH8To9JKefTQ"
+    },
     spki: util.hex2abv(
       "30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
       "0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
       "56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
       "08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
       "d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
       "0001"
     ),
+    jwk_pub: {
+      kty: "RSA",
+      n:  "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
+          "oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
+          "_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
+      e:  "AQAB",
+    },
     data: util.hex2abv(
       "a4b159941761c40c6a82f2b80d1b94f5aa2654fd17e12d588864679b54cd04ef" +
       "8bd03012be8dc37f4b83af7963faff0dfa225477437c48017ff2be8191cf3955" +
       "fc07356eab3f322f7f620e21d254e5db4324279fe067e0910e2e81ca2cab31c7" +
       "45e67a54058eb50d993cdb9ed0b4d029c06d21a94ca661c3ce27fae1d6cb20f4" +
       "564d66ce4767583d0e5f060215b59017be85ea848939127bd8c9c4d47b51056c" +
       "031cf336f17c9980f3b8f5b9b6878e8b797aa43b882684333e17893fe9caa6aa" +
       "299f7ed1a18ee2c54864b7b2b99b72618fb02574d139ef50f019c9eef4169713" +
--- a/dom/crypto/test/tests.js
+++ b/dom/crypto/test/tests.js
@@ -1,21 +1,42 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function exists(x) {
   return (x !== undefined);
 }
 
+function hasFields(object, fields) {
+  return fields
+          .map(x => exists(object[x]))
+          .reduce((x,y) => (x && y));
+}
+
 function hasKeyFields(x) {
-  return exists(x.algorithm) &&
-         exists(x.extractable) &&
-         exists(x.type) &&
-         exists(x.usages);
+  return hasFields(x, ["algorithm", "extractable", "type", "usages"]);
+}
+
+function hasBaseJwkFields(x) {
+  return hasFields(x, ["kty", "alg", "ext", "key_ops"]);
+}
+
+function shallowArrayEquals(x, y) {
+  if (x.length != y.length) {
+    return false;
+  }
+
+  for (i in x) {
+    if (x[i] != y[i]) {
+      return false;
+    }
+  }
+
+  return true;
 }
 
 function error(test) {
   return function(x) {
     console.log("ERROR :: " + x);
     test.complete(false);
     throw x;
   }
@@ -87,16 +108,17 @@ TestArray.addTest(
 TestArray.addTest(
   "Import / export round-trip with 'raw'",
   function() {
     var that = this;
     var alg = "AES-GCM";
 
     function doExport(x) {
       if (!hasKeyFields(x)) {
+        window.result = x;
         throw "Invalid key; missing field(s)";
       } else if ((x.algorithm.name != alg) ||
         (x.algorithm.length != 8 * tv.raw.length) ||
         (x.type != "secret") ||
         (!x.extractable) ||
         (x.usages.length != 1) ||
         (x.usages[0] != 'encrypt')){
         throw "Invalid key: incorrect key data";
@@ -1424,8 +1446,275 @@ TestArray.addTest(
       .then(doEncrypt, error(that))
       .then(
         memcmp_complete(that, tv.aes_gcm_enc.result),
         error(that)
       );
   }
 );
 
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import and use of an AES-GCM key",
+  function () {
+    var that = this;
+
+    function doEncrypt(x) {
+      return crypto.subtle.encrypt(
+        {
+          name: "AES-GCM",
+          iv: tv.aes_gcm_enc.iv,
+          additionalData: tv.aes_gcm_enc.adata,
+          tagLength: 128
+        },
+        x, tv.aes_gcm_enc.data);
+    }
+
+    crypto.subtle.importKey("jwk", tv.aes_gcm_enc.key_jwk, "AES-GCM", false, ['encrypt'])
+      .then(doEncrypt)
+      .then(
+        memcmp_complete(that, tv.aes_gcm_enc.result),
+        error(that)
+      );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import and use of an RSASSA-PKCS1-v1_5 private key",
+  function () {
+    var that = this;
+    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+
+    function doSign(x) {
+      return crypto.subtle.sign(alg.name, x, tv.rsassa.data);
+    }
+    function fail(x) { console.log(x); error(that); }
+
+    crypto.subtle.importKey("jwk", tv.rsassa.jwk_priv, alg, false, ['sign'])
+      .then( doSign, fail )
+      .then( memcmp_complete(that, tv.rsassa.sig256), fail );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import and use of an RSASSA-PKCS1-v1_5 public key",
+  function () {
+    var that = this;
+    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+
+    function doVerify(x) {
+      return crypto.subtle.verify(alg.name, x, tv.rsassa.sig256, tv.rsassa.data);
+    }
+    function fail(x) { error(that); }
+
+    crypto.subtle.importKey("jwk", tv.rsassa.jwk_pub, alg, false, ['verify'])
+      .then( doVerify, fail )
+      .then(
+        complete(that, function(x) { return x; }),
+        fail
+      );
+  });
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import failure on incomplete RSA private key (missing 'qi')",
+  function () {
+    var that = this;
+    var alg = { name: "RSA-OAEP", hash: "SHA-256" };
+    var jwk = {
+      kty: "RSA",
+      n: tv.rsassa.jwk_priv.n,
+      e: tv.rsassa.jwk_priv.e,
+      d: tv.rsassa.jwk_priv.d,
+      p: tv.rsassa.jwk_priv.p,
+      q: tv.rsassa.jwk_priv.q,
+      dp: tv.rsassa.jwk_priv.dp,
+      dq: tv.rsassa.jwk_priv.dq,
+    };
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
+      .then( error(that), complete(that) );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import failure on algorithm mismatch",
+  function () {
+    var that = this;
+    var alg = "AES-GCM";
+    var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", alg: "A256GCM" };
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
+      .then( error(that), complete(that) );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import failure on usages mismatch",
+  function () {
+    var that = this;
+    var alg = "AES-GCM";
+    var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", key_ops: ['encrypt'] };
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
+      .then( error(that), complete(that) );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import failure on extractable mismatch",
+  function () {
+    var that = this;
+    var alg = "AES-GCM";
+    var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", ext: false };
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt'])
+      .then( error(that), complete(that) );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK export of a symmetric key",
+  function () {
+    var that = this;
+    var alg = "AES-GCM";
+    var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ" };
+
+    function doExport(k) {
+      return crypto.subtle.exportKey("jwk", k);
+    }
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
+      .then(doExport)
+      .then(
+        complete(that, function(x) {
+          return hasBaseJwkFields(x) &&
+                 hasFields(x, ['k']) &&
+                 x.kty == 'oct' &&
+                 x.alg == 'A128GCM' &&
+                 x.ext &&
+                 shallowArrayEquals(x.key_ops, ['encrypt','decrypt']) &&
+                 x.k == jwk.k
+        }),
+        error(that)
+      );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK export of an RSA private key",
+  function () {
+    var that = this;
+    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+    var jwk = tv.rsassa.jwk_priv;
+
+    function doExport(k) {
+      return crypto.subtle.exportKey("jwk", k);
+    }
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ['sign'])
+      .then(doExport)
+      .then(
+        complete(that, function(x) {
+          window.jwk_priv = x;
+          console.log(JSON.stringify(x));
+          return hasBaseJwkFields(x) &&
+                 hasFields(x, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']) &&
+                 x.kty == 'RSA' &&
+                 x.alg == 'RS256' &&
+                 x.ext &&
+                 shallowArrayEquals(x.key_ops, ['sign']) &&
+                 x.n  == jwk.n  &&
+                 x.e  == jwk.e  &&
+                 x.d  == jwk.d  &&
+                 x.p  == jwk.p  &&
+                 x.q  == jwk.q  &&
+                 x.dp == jwk.dp &&
+                 x.dq == jwk.dq &&
+                 x.qi == jwk.qi;
+          }),
+        error(that)
+      );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK export of an RSA public key",
+  function () {
+    var that = this;
+    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+    var jwk = tv.rsassa.jwk_pub;
+
+    function doExport(k) {
+      return crypto.subtle.exportKey("jwk", k);
+    }
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ['verify'])
+      .then(doExport)
+      .then(
+        complete(that, function(x) {
+          window.jwk_pub = x;
+          return hasBaseJwkFields(x) &&
+                 hasFields(x, ['n', 'e']) &&
+                 x.kty == 'RSA' &&
+                 x.alg == 'RS256' &&
+                 x.ext &&
+                 shallowArrayEquals(x.key_ops, ['verify']) &&
+                 x.n  == jwk.n  &&
+                 x.e  == jwk.e;
+          }),
+        error(that)
+      );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK wrap/unwrap round-trip, with AES-GCM",
+  function () {
+    var that = this;
+    var genAlg = { name: "HMAC", hash: "SHA-384", length: 512 };
+    var wrapAlg = { name: "AES-GCM", iv: tv.aes_gcm_enc.iv };
+    var wrapKey, originalKey, originalKeyJwk;
+
+    function doExport(k) {
+      return crypto.subtle.exportKey("jwk", k);
+    }
+    function doWrap() {
+      return crypto.subtle.wrapKey("jwk", originalKey, wrapKey, wrapAlg);
+    }
+    function doUnwrap(wrappedKey) {
+      return crypto.subtle.unwrapKey("jwk", wrappedKey, wrapKey, wrapAlg,
+                                     { name: "HMAC", hash: "SHA-384"},
+                                     true, ['sign', 'verify']);
+    }
+
+    function temperr(x) { return function(y) { console.log("error in "+x); console.log(y); } }
+
+    Promise.all([
+      crypto.subtle.importKey("jwk", tv.aes_gcm_enc.key_jwk,
+                              "AES-GCM", false, ['wrapKey','unwrapKey'])
+        .then(function(x) { console.log("wrapKey"); wrapKey = x; }),
+      crypto.subtle.generateKey(genAlg, true, ['sign', 'verify'])
+        .then(function(x) { console.log("originalKey"); originalKey = x; return x; })
+        .then(doExport)
+        .then(function(x) { originalKeyJwk = x; })
+    ])
+      .then(doWrap, temperr("initial phase"))
+      .then(doUnwrap, temperr("wrap"))
+      .then(doExport, temperr("unwrap"))
+      .then(
+        complete(that, function(x) {
+          return exists(x.k) && x.k == originalKeyJwk.k;
+        }),
+        error(that)
+      );
+  }
+);
+
--- a/dom/webidl/SubtleCrypto.webidl
+++ b/dom/webidl/SubtleCrypto.webidl
@@ -104,16 +104,53 @@ dictionary DhKeyGenParams : Algorithm {
   BigInteger generator;
 };
 
 typedef DOMString NamedCurve;
 dictionary EcKeyGenParams : Algorithm {
   NamedCurve namedCurve;
 };
 
+
+/***** JWK *****/
+
+dictionary RsaOtherPrimesInfo {
+  // The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms
+  DOMString r;
+  DOMString d;
+  DOMString t;
+};
+
+dictionary JsonWebKey {
+  // The following fields are defined in Section 3.1 of JSON Web Key
+  DOMString kty;
+  DOMString use;
+  sequence<DOMString> key_ops;
+  DOMString alg;
+
+  // The following fields are defined in JSON Web Key Parameters Registration
+  boolean ext;
+
+  // The following fields are defined in Section 6 of JSON Web Algorithms
+  DOMString crv;
+  DOMString x;
+  DOMString y;
+  DOMString d;
+  DOMString n;
+  DOMString e;
+  DOMString p;
+  DOMString q;
+  DOMString dp;
+  DOMString dq;
+  DOMString qi;
+  sequence<RsaOtherPrimesInfo> oth;
+  DOMString k;
+};
+
+
 /***** The Main API *****/
 
 [Pref="dom.webcrypto.enabled"]
 interface CryptoKey {
   readonly attribute KeyType type;
   readonly attribute boolean extractable;
   readonly attribute KeyAlgorithm algorithm;
   [Cached, Constant, Frozen] readonly attribute sequence<KeyUsage> usages;
@@ -122,17 +159,16 @@ interface CryptoKey {
 [Pref="dom.webcrypto.enabled"]
 interface CryptoKeyPair {
   readonly attribute CryptoKey publicKey;
   readonly attribute CryptoKey privateKey;
 };
 
 typedef DOMString KeyFormat;
 typedef (ArrayBufferView or ArrayBuffer) CryptoOperationData;
-typedef (ArrayBufferView or ArrayBuffer) KeyData;
 typedef (object or DOMString) AlgorithmIdentifier;
 
 [Pref="dom.webcrypto.enabled"]
 interface SubtleCrypto {
   [Throws]
   Promise encrypt(AlgorithmIdentifier algorithm,
                   CryptoKey key,
                   CryptoOperationData data);
@@ -165,17 +201,17 @@ interface SubtleCrypto {
                     sequence<KeyUsage> keyUsages );
   [Throws]
   Promise deriveBits(AlgorithmIdentifier algorithm,
                      CryptoKey baseKey,
                      unsigned long length);
 
   [Throws]
   Promise importKey(KeyFormat format,
-                    KeyData keyData,
+                    object keyData,
                     AlgorithmIdentifier algorithm,
                     boolean extractable,
                     sequence<KeyUsage> keyUsages );
   [Throws]
   Promise exportKey(KeyFormat format, CryptoKey key);
 
   [Throws]
   Promise wrapKey(KeyFormat format,
--- a/security/build/nss.def
+++ b/security/build/nss.def
@@ -289,40 +289,43 @@ NSSUTIL_Quote
 PK11_AlgtagToMechanism
 PK11_Authenticate
 PK11_ChangePW
 PK11_CheckUserPassword
 PK11_CipherOp
 PK11_ConfigurePKCS11
 PK11_CreateContextBySymKey
 PK11_CreateDigestContext
+PK11_CreateGenericObject
 PK11_CreateMergeLog
 PK11_CreatePBEV2AlgorithmID
 PK11_Decrypt
 PK11_DeleteTokenCertAndKey
 PK11_DeleteTokenPrivateKey
 PK11_DeleteTokenPublicKey
 PK11_DEREncodePublicKey
 PK11_Derive
 PK11_DeriveWithTemplate
 PK11_DestroyContext
+PK11_DestroyGenericObject
 PK11_DestroyMergeLog
 PK11_DestroyTokenObject
 PK11_DigestBegin
 PK11_DigestFinal
 PK11_DigestOp
 PK11_DoesMechanism
 PK11_Encrypt
 PK11_ExportDERPrivateKeyInfo
 PK11_ExtractKeyValue
 PK11_FindCertFromNickname
 PK11_FindCertsFromEmailAddress
 PK11_FindCertsFromNickname
 PK11_FindKeyByAnyCert
 PK11_FindKeyByDERCert
+PK11_FindKeyByKeyID
 PK11_FindSlotByName
 PK11_FindSlotsByNames
 PK11_FreeSlot
 PK11_FreeSlotList
 PK11_FreeSlotListElement
 PK11_FreeSymKey
 PK11_GenerateKeyPair
 PK11_GenerateKeyPairWithFlags
@@ -381,16 +384,17 @@ PK11_KeyGen
 PK11_KeyGenWithTemplate
 PK11_ListCerts
 PK11_ListCertsInSlot
 PK11_ListPrivateKeysInSlot
 PK11_ListPrivKeysInSlot
 PK11_LoadPrivKey
 PK11_Logout
 PK11_LogoutAll
+PK11_MakeIDFromPubKey
 PK11_MechanismToAlgtag
 PK11_MergeTokens
 PK11_NeedLogin
 PK11_NeedUserInit
 PK11_ParamFromIV
 PK11_PBEKeyGen
 PK11_PQG_DestroyParams
 PK11_PQG_DestroyVerify
@@ -504,16 +508,17 @@ SECKEY_DecodeDERSubjectPublicKeyInfo
 SECKEY_DestroyPrivateKey
 SECKEY_DestroyPrivateKeyList
 SECKEY_DestroyPublicKey
 SECKEY_DestroySubjectPublicKeyInfo
 SECKEY_ECParamsToBasePointOrderLen
 SECKEY_ECParamsToKeySize
 SECKEY_EncodeDERSubjectPublicKeyInfo
 SECKEY_ExtractPublicKey
+SECKEY_ImportDERPublicKey
 SECKEY_PublicKeyStrength
 SECKEY_PublicKeyStrengthInBits
 SECKEY_RSAPSSParamsTemplate DATA
 SECMIME_DecryptionAllowed
 SECMOD_AddNewModule
 SECMOD_AddNewModuleEx
 SECMOD_CancelWait
 SECMOD_CanDeleteInternalModule
--- a/security/manager/ssl/src/ScopedNSSTypes.h
+++ b/security/manager/ssl/src/ScopedNSSTypes.h
@@ -222,16 +222,19 @@ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLAT
                                           PK11SlotInfo,
                                           PK11_FreeSlot)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11SlotList,
                                           PK11SlotList,
                                           PK11_FreeSlotList)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11SymKey,
                                           PK11SymKey,
                                           PK11_FreeSymKey)
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11GenericObject,
+                                          PK11GenericObject,
+                                          PK11_DestroyGenericObject)
 
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSEC_PKCS7ContentInfo,
                                           SEC_PKCS7ContentInfo,
                                           SEC_PKCS7DestroyContentInfo)
 
 namespace internal {
 
 inline void