зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1025230 - Allow import/export of JWK-formatted keys in WebCrypto r=bz,keeler
This commit is contained in:
Родитель
58860abf23
Коммит
e10fdbd715
|
@ -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());
|
||||
SetKeyData(aCx, aKeyData);
|
||||
}
|
||||
// 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;
|
||||
|
||||
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;
|
||||
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)) {
|
||||
ScopedSECKEYPrivateKey privKey(CryptoKey::PrivateKeyFromPkcs8(mKeyData, locker));
|
||||
if (!privKey.get()) {
|
||||
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,12 +2295,8 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
WebCryptoTask*
|
||||
WebCryptoTask::CreateGenerateKeyTask(JSContext* aCx,
|
||||
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче