Bug 1025230 - Allow import/export of JWK-formatted keys in WebCrypto r=bz,keeler

This commit is contained in:
Richard Barnes 2014-07-19 08:25:00 -05:00
Родитель 58860abf23
Коммит e10fdbd715
22 изменённых файлов: 1099 добавлений и 95 удалений

Просмотреть файл

@ -100,7 +100,7 @@ SubtleCrypto::Digest(JSContext* cx,
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,

Просмотреть файл

@ -20,7 +20,6 @@ namespace dom {
class Promise;
typedef ArrayBufferViewOrArrayBuffer CryptoOperationData;
typedef ArrayBufferViewOrArrayBuffer KeyData;
class SubtleCrypto MOZ_FINAL : public nsISupports,
public nsWrapperCache
@ -73,7 +72,7 @@ public:
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,

Просмотреть файл

@ -17,6 +17,36 @@ 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
{

Просмотреть файл

@ -25,6 +25,8 @@ public:
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);

Просмотреть файл

@ -5,6 +5,7 @@
* 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 {
@ -75,8 +76,74 @@ CryptoBuffer::Assign(const OwningArrayBufferViewOrArrayBuffer& aData)
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) {

Просмотреть файл

@ -37,8 +37,9 @@ public:
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);
};

Просмотреть файл

@ -352,6 +352,206 @@ CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
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
{

Просмотреть файл

@ -53,6 +53,8 @@ Thus, internally, a key has the following fields:
*/
struct JsonWebKey;
class CryptoKey MOZ_FINAL : public nsISupports,
public nsWrapperCache,
public nsNSSShutDownObject
@ -149,6 +151,18 @@ public:
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);

Просмотреть файл

@ -22,6 +22,18 @@ 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
{

Просмотреть файл

@ -52,6 +52,8 @@ public:
return mLength;
}
virtual nsString ToJwkAlg() const MOZ_OVERRIDE;
virtual bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const MOZ_OVERRIDE;
static KeyAlgorithm* Create(nsIGlobalObject* aGlobal,
JSStructuredCloneReader* aReader);

Просмотреть файл

@ -47,6 +47,12 @@ KeyAlgorithm::WrapObject(JSContext* aCx)
return KeyAlgorithmBinding::Wrap(aCx, this);
}
nsString
KeyAlgorithm::ToJwkAlg() const
{
return nsString();
}
void
KeyAlgorithm::GetName(nsString& aRetVal) const
{

Просмотреть файл

@ -52,6 +52,8 @@ public:
void GetName(nsString& aRetVal) const;
virtual nsString ToJwkAlg() const;
// Structured clone support methods
virtual bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
static KeyAlgorithm* Create(nsIGlobalObject* aGlobal,

Просмотреть файл

@ -23,6 +23,30 @@ 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
{

Просмотреть файл

@ -30,6 +30,8 @@ public:
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
virtual nsString ToJwkAlg() const MOZ_OVERRIDE;
KeyAlgorithm* Hash() const
{
return mHash;

Просмотреть файл

@ -53,10 +53,31 @@
#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

Просмотреть файл

@ -9,19 +9,20 @@
#include "secerr.h"
#include "ScopedNSSTypes.h"
#include "mozilla/dom/WebCryptoTask.h"
#include "mozilla/dom/TypedArray.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/RsaHashedKeyAlgorithm.h"
#include "mozilla/dom/CryptoBuffer.h"
#include "mozilla/dom/WebCryptoCommon.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/CryptoKeyPair.h"
#include "mozilla/dom/HmacKeyAlgorithm.h"
#include "mozilla/dom/KeyAlgorithm.h"
#include "mozilla/dom/RsaHashedKeyAlgorithm.h"
#include "mozilla/dom/RsaKeyAlgorithm.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/WebCryptoCommon.h"
#include "mozilla/dom/WebCryptoTask.h"
namespace mozilla {
namespace dom {
@ -220,6 +221,28 @@ GetKeySizeForAlgorithm(JSContext* aCx, const ObjectOrString& aAlgorithm,
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
@ -1032,11 +1055,14 @@ private:
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) {
@ -1062,15 +1088,88 @@ public:
}
}
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:
CryptoBuffer mKeyData;
nsString mFormat;
nsRefPtr<CryptoKey> mKey;
CryptoBuffer mKeyData;
bool mDataIsSet;
bool mDataIsJwk;
JsonWebKey mJwk;
nsString mAlgName;
private:
@ -1090,33 +1189,33 @@ 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;
}
@ -1138,6 +1237,21 @@ public:
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;
@ -1160,11 +1274,21 @@ public:
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;
@ -1174,6 +1298,11 @@ public:
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;
}
@ -1185,6 +1314,14 @@ public:
return NS_OK;
}
nsresult AfterCrypto() MOZ_OVERRIDE
{
if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
return NS_ERROR_DOM_DATA_ERR;
}
return NS_OK;
}
private:
nsString mHashName;
};
@ -1193,25 +1330,33 @@ 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;
}
@ -1234,7 +1379,6 @@ public:
}
private:
nsString mFormat;
nsString mHashName;
uint32_t mModulusLength;
CryptoBuffer mPublicExponent;
@ -1245,9 +1389,34 @@ private:
// 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;
}
@ -1257,20 +1426,6 @@ private:
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;
@ -1285,7 +1440,7 @@ private:
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)) {
@ -1322,12 +1477,15 @@ private:
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)
@ -1335,20 +1493,32 @@ public:
, 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();
@ -1385,11 +1555,62 @@ private:
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
@ -1832,10 +2053,8 @@ public:
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);
}
@ -1854,6 +2073,15 @@ private:
}
};
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
{
@ -1875,6 +2103,40 @@ public:
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);
@ -1996,7 +2258,7 @@ WebCryptoTask::CreateDigestTask(JSContext* aCx,
WebCryptoTask*
WebCryptoTask::CreateImportKeyTask(JSContext* aCx,
const nsAString& aFormat,
const KeyData& aKeyData,
JS::Handle<JSObject*> aKeyData,
const ObjectOrString& aAlgorithm,
bool aExtractable,
const Sequence<nsString>& aKeyUsages)
@ -2033,11 +2295,7 @@ WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
{
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*
@ -2179,13 +2437,13 @@ WebCryptoTask::CreateUnwrapKeyTask(JSContext* aCx,
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 {

Просмотреть файл

@ -140,7 +140,7 @@ public:
static WebCryptoTask* CreateImportKeyTask(JSContext* aCx,
const nsAString& aFormat,
const KeyData& aKeyData,
JS::Handle<JSObject*> aKeyData,
const ObjectOrString& aAlgorithm,
bool aExtractable,
const Sequence<nsString>& aKeyUsages);

Просмотреть файл

@ -103,6 +103,10 @@ tv = {
aes_gcm_enc: {
key: util.hex2abv("feffe9928665731c6d6a8f9467308308" +
"feffe9928665731c6d6a8f9467308308"),
key_jwk: {
kty: "oct",
k: "_v_pkoZlcxxtao-UZzCDCP7_6ZKGZXMcbWqPlGcwgwg"
},
iv: util.hex2abv("9313225df88406e555909c5aff5269aa" +
"6a7a9538534f7da1e4c303d2a318a728" +
"c3c0c95156809539fcf0e2429a6b5254" +
@ -273,6 +277,26 @@ tv = {
"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" +
@ -281,6 +305,13 @@ tv = {
"d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
"0001"
),
jwk_pub: {
kty: "RSA",
n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
"oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
"_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
e: "AQAB",
},
data: util.hex2abv(
"a4b159941761c40c6a82f2b80d1b94f5aa2654fd17e12d588864679b54cd04ef" +
"8bd03012be8dc37f4b83af7963faff0dfa225477437c48017ff2be8191cf3955" +

Просмотреть файл

@ -6,11 +6,32 @@ 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) {
@ -92,6 +113,7 @@ TestArray.addTest(
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) ||
@ -1429,3 +1451,270 @@ TestArray.addTest(
}
);
// -----------------------------------------------------------------------------
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)
);
}
);

Просмотреть файл

@ -109,6 +109,43 @@ 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"]
@ -127,7 +164,6 @@ interface CryptoKeyPair {
typedef DOMString KeyFormat;
typedef (ArrayBufferView or ArrayBuffer) CryptoOperationData;
typedef (ArrayBufferView or ArrayBuffer) KeyData;
typedef (object or DOMString) AlgorithmIdentifier;
[Pref="dom.webcrypto.enabled"]
@ -170,7 +206,7 @@ interface SubtleCrypto {
[Throws]
Promise importKey(KeyFormat format,
KeyData keyData,
object keyData,
AlgorithmIdentifier algorithm,
boolean extractable,
sequence<KeyUsage> keyUsages );

Просмотреть файл

@ -294,6 +294,7 @@ PK11_CipherOp
PK11_ConfigurePKCS11
PK11_CreateContextBySymKey
PK11_CreateDigestContext
PK11_CreateGenericObject
PK11_CreateMergeLog
PK11_CreatePBEV2AlgorithmID
PK11_Decrypt
@ -304,6 +305,7 @@ PK11_DEREncodePublicKey
PK11_Derive
PK11_DeriveWithTemplate
PK11_DestroyContext
PK11_DestroyGenericObject
PK11_DestroyMergeLog
PK11_DestroyTokenObject
PK11_DigestBegin
@ -318,6 +320,7 @@ PK11_FindCertsFromEmailAddress
PK11_FindCertsFromNickname
PK11_FindKeyByAnyCert
PK11_FindKeyByDERCert
PK11_FindKeyByKeyID
PK11_FindSlotByName
PK11_FindSlotsByNames
PK11_FreeSlot
@ -386,6 +389,7 @@ PK11_ListPrivKeysInSlot
PK11_LoadPrivKey
PK11_Logout
PK11_LogoutAll
PK11_MakeIDFromPubKey
PK11_MechanismToAlgtag
PK11_MergeTokens
PK11_NeedLogin
@ -509,6 +513,7 @@ SECKEY_ECParamsToBasePointOrderLen
SECKEY_ECParamsToKeySize
SECKEY_EncodeDERSubjectPublicKeyInfo
SECKEY_ExtractPublicKey
SECKEY_ImportDERPublicKey
SECKEY_PublicKeyStrength
SECKEY_PublicKeyStrengthInBits
SECKEY_RSAPSSParamsTemplate DATA

Просмотреть файл

@ -227,6 +227,9 @@ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11SlotList,
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,