Bug 998803 - Add support for RSA encryption and signing to WebCrypto API. r=bz,dkeeler

This commit is contained in:
Richard Barnes 2014-05-23 15:29:00 +02:00
parent b3f539f7af
commit 8d8df0c940
4 changed files with 494 additions and 0 deletions

View File

@ -6,6 +6,7 @@
#include "pk11pub.h"
#include "cryptohi.h"
#include "secerr.h"
#include "ScopedNSSTypes.h"
#include "mozilla/dom/WebCryptoTask.h"
@ -289,6 +290,76 @@ private:
}
};
class RsaesPkcs1Task : public ReturnArrayBufferViewTask
{
public:
RsaesPkcs1Task(JSContext* aCx, const ObjectOrString& aAlgorithm,
mozilla::dom::Key& aKey, const CryptoOperationData& aData,
bool aEncrypt)
: mPrivKey(aKey.GetPrivateKey())
, mPubKey(aKey.GetPublicKey())
, mEncrypt(aEncrypt)
{
ATTEMPT_BUFFER_INIT(mData, aData);
if (mEncrypt) {
if (!mPubKey) {
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
return;
}
mStrength = SECKEY_PublicKeyStrength(mPubKey);
// Verify that the data input is not too big
// (as required by PKCS#1 / RFC 3447)
if (mData.Length() > mStrength - 11) {
mEarlyRv = NS_ERROR_DOM_DATA_ERR;
return;
}
} else {
if (!mPrivKey) {
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
return;
}
mStrength = PK11_GetPrivateModulusLen(mPrivKey);
}
}
private:
ScopedSECKEYPrivateKey mPrivKey;
ScopedSECKEYPublicKey mPubKey;
CryptoBuffer mData;
uint32_t mStrength;
bool mEncrypt;
virtual nsresult DoCrypto() MOZ_OVERRIDE
{
nsresult rv;
// Ciphertext is an integer mod the modulus, so it will be
// no longer than mStrength octets
if (!mResult.SetLength(mStrength)) {
return NS_ERROR_DOM_UNKNOWN_ERR;
}
if (mEncrypt) {
rv = MapSECStatus(PK11_PubEncryptPKCS1(
mPubKey.get(), mResult.Elements(),
mData.Elements(), mData.Length(),
nullptr));
} else {
uint32_t outLen;
rv = MapSECStatus(PK11_PrivDecryptPKCS1(
mPrivKey.get(), mResult.Elements(),
&outLen, mResult.Length(),
mData.Elements(), mData.Length()));
mResult.SetLength(outLen);
}
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
return NS_OK;
}
};
class HmacTask : public WebCryptoTask
{
public:
@ -379,6 +450,122 @@ private:
}
};
class RsassaPkcs1Task : public WebCryptoTask
{
public:
RsassaPkcs1Task(JSContext* aCx, const ObjectOrString& aAlgorithm,
mozilla::dom::Key& aKey,
const CryptoOperationData& aSignature,
const CryptoOperationData& aData,
bool aSign)
: mOidTag(SEC_OID_UNKNOWN)
, mPrivKey(aKey.GetPrivateKey())
, mPubKey(aKey.GetPublicKey())
, mSign(aSign)
, mVerified(false)
{
ATTEMPT_BUFFER_INIT(mData, aData);
if (!aSign) {
ATTEMPT_BUFFER_INIT(mSignature, aSignature);
}
// Look up the SECOidTag based on the KeyAlgorithm
// static_cast is safe because we only get here if the algorithm name
// is RSASSA-PKCS1-v1_5, and that only happens if we've constructed
// an RsaHashedKeyAlgorithm
nsRefPtr<RsaHashedKeyAlgorithm> rsaAlg = static_cast<RsaHashedKeyAlgorithm*>(aKey.Algorithm());
nsRefPtr<KeyAlgorithm> hashAlg = rsaAlg->Hash();
switch (hashAlg->Mechanism()) {
case CKM_SHA_1:
mOidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; break;
case CKM_SHA224:
mOidTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION; break;
case CKM_SHA256:
mOidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; break;
case CKM_SHA384:
mOidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; break;
case CKM_SHA512:
mOidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; break;
default: {
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
return;
}
}
// Check that we have the appropriate key
if ((mSign && !mPrivKey) || (!mSign && !mPubKey)) {
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
return;
}
}
private:
SECOidTag mOidTag;
ScopedSECKEYPrivateKey mPrivKey;
ScopedSECKEYPublicKey mPubKey;
CryptoBuffer mSignature;
CryptoBuffer mData;
bool mSign;
bool mVerified;
virtual nsresult DoCrypto() MOZ_OVERRIDE
{
nsresult rv;
if (mSign) {
ScopedSECItem signature((SECItem*) PORT_Alloc(sizeof(SECItem)));
ScopedSGNContext ctx(SGN_NewContext(mOidTag, mPrivKey));
if (!ctx) {
return NS_ERROR_DOM_OPERATION_ERR;
}
rv = MapSECStatus(SGN_Begin(ctx));
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
rv = MapSECStatus(SGN_Update(ctx, mData.Elements(), mData.Length()));
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
rv = MapSECStatus(SGN_End(ctx, signature));
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
mSignature.Assign(signature);
} else {
ScopedSECItem signature(mSignature.ToSECItem());
ScopedVFYContext ctx(VFY_CreateContext(mPubKey, signature,
mOidTag, nullptr));
if (!ctx) {
int err = PORT_GetError();
if (err == SEC_ERROR_BAD_SIGNATURE) {
mVerified = false;
return NS_OK;
}
return NS_ERROR_DOM_OPERATION_ERR;
}
rv = MapSECStatus(VFY_Begin(ctx));
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
rv = MapSECStatus(VFY_Update(ctx, mData.Elements(), mData.Length()));
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
rv = MapSECStatus(VFY_End(ctx));
mVerified = NS_SUCCEEDED(rv);
}
return NS_OK;
}
virtual void Resolve() MOZ_OVERRIDE
{
if (mSign) {
TypedArrayCreator<Uint8Array> ret(mSignature);
mResultPromise->MaybeResolve(ret);
} else {
mResultPromise->MaybeResolve(mVerified);
}
}
};
class SimpleDigestTask : public ReturnArrayBufferViewTask
{
public:
@ -798,6 +985,8 @@ WebCryptoTask::EncryptDecryptTask(JSContext* aCx,
algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
return new AesTask(aCx, aAlgorithm, aKey, aData, aEncrypt);
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
return new RsaesPkcs1Task(aCx, aAlgorithm, aKey, aData, aEncrypt);
}
return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
@ -825,6 +1014,8 @@ WebCryptoTask::SignVerifyTask(JSContext* aCx,
if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
return new HmacTask(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
return new RsassaPkcs1Task(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
}
return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);

View File

@ -202,4 +202,112 @@ tv = {
sig_fail: util.hex2abv("000000001ee0b67f0d8a26aacbf5b77f" +
"8e0bc6213728c5140546040f0ee37f54"),
},
// RSA test vectors, Example 1.3
// <ftp://ftp.rsa.com/pub/rsalabs/tmp/pkcs1v15crypt-vectors.txt>
rsaes: {
pkcs8: util.hex2abv(
"30820276020100300d06092a864886f70d0101010500048202603082025c0201" +
"0002818100a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8ae" +
"4811a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0cbc64a742c6c630" +
"f533c8cc72f62ae833c40bf25842e984bb78bdbf97c0107d55bdb662f5c4e0fa" +
"b9845cb5148ef7392dd3aaff93ae1e6b667bb3d4247616d4f5ba10d4cfd226de" +
"88d39f16fb020301000102818053339cfdb79fc8466a655c7316aca85c55fd8f" +
"6dd898fdaf119517ef4f52e8fd8e258df93fee180fa0e4ab29693cd83b152a55" +
"3d4ac4d1812b8b9fa5af0e7f55fe7304df41570926f3311f15c4d65a732c4831" +
"16ee3d3d2d0af3549ad9bf7cbfb78ad884f84d5beb04724dc7369b31def37d0c" +
"f539e9cfcdd3de653729ead5d1024100cc8853d1d54da630fac004f471f281c7" +
"b8982d8224a490edbeb33d3e3d5cc93c4765703d1dd791642f1f116a0dd852be" +
"2419b2af72bfe9a030e860b0288b5d77024100d32737e7267ffe1341b2d5c0d1" +
"50a81b586fb3132bed2f8d5262864a9cb9f30af38be448598d413a172efb802c" +
"21acf1c11c520c2f26a471dcad212eac7ca39d02410095297b0f95a2fa67d007" +
"07d609dfd4fc05c89dafc2ef6d6ea55bec771ea333734d9251e79082ecda866e" +
"fef13c459e1a631386b7e354c899f5f112ca85d7158302400e12bf1718e9cef5" +
"599ba1c3882fe8046a90874eefce8f2ccc20e4f2741fb0a33a3848aec9c9305f" +
"becbd2d76819967d4671acc6431e4037968db37878e695c102407fbf3360826f" +
"c125dceac9bf8d38b6cad8ccc8b4f867bfea02dcbf5df008258ee9902ea07e28" +
"7770df660328c81e906184b6aa2239868775204d098fc846c669"
),
spki: util.hex2abv(
"30819f300d06092a864886f70d010101050003818d0030818902818100a8b3b2" +
"84af8eb50b387034a860f146c4919f318763cd6c5598c8ae4811a1e0abc4c7e0" +
"b082d693a5e7fced675cf4668512772c0cbc64a742c6c630f533c8cc72f62ae8" +
"33c40bf25842e984bb78bdbf97c0107d55bdb662f5c4e0fab9845cb5148ef739" +
"2dd3aaff93ae1e6b667bb3d4247616d4f5ba10d4cfd226de88d39f16fb020301" +
"0001"
),
data: util.hex2abv(
"d94ae0832e6445ce42331cb06d531a82b1db4baad30f746dc916df24d4e3c245" +
"1fff59a6423eb0e1d02d4fe646cf699dfd818c6e97b051"
),
result: util.hex2abv(
"709c7d2d4598c96065b6588da2f89fa87f062d7241ef6595898f637ada57eae9" +
"0173f0fb4bf6a91ebd96506907c853dacf208494be94d313a04185d474a90741" +
"2effc3e024d07e4d09aa245fbcb130219bfa5de02d4f7e2ec9e62e8ad32dee5f" +
"f4d8e4cfecbc5033a1c2c61c5233ae16192a481d0075bfc7ce028212cd27bebe"
),
},
// RSA test vectors, Example 1.3 (sig256 generated with PyCrypto)
// <ftp://ftp.rsa.com/pub/rsalabs/tmp/pkcs1v15sign-vectors.txt>
rsassa: {
pkcs8: util.hex2abv(
"30820275020100300d06092a864886f70d01010105000482025f3082025b0201" +
"0002818100a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1" +
"e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ce" +
"abfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e" +
"6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb22" +
"49bd9a2137020301000102818033a5042a90b27d4f5451ca9bbbd0b44771a101" +
"af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca" +
"0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574" +
"501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c06" +
"22ad79c6dcee883547c6a3b325024100e7e8942720a877517273a356053ea2a1" +
"bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1" +
"535bd9b3cc34160b3b6dcd3eda8e6443024100b69dca1cf7d4d7ec81e75b90fc" +
"ca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542" +
"cd20dc723e6963364a1f9425452b269a6799fd024028fa13938655be1f8a159c" +
"baca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8d" +
"d3ede2448328f385d81b30e8e43b2fffa02786197902401a8b38f398fa712049" +
"898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455e" +
"aeb6e1678255827580a8e4e8e14151d1510a82a3f2e729024027156aba4126d2" +
"4a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a" +
"2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d"
),
spki: util.hex2abv(
"30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
"0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
"56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
"08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
"d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
"0001"
),
data: util.hex2abv(
"a4b159941761c40c6a82f2b80d1b94f5aa2654fd17e12d588864679b54cd04ef" +
"8bd03012be8dc37f4b83af7963faff0dfa225477437c48017ff2be8191cf3955" +
"fc07356eab3f322f7f620e21d254e5db4324279fe067e0910e2e81ca2cab31c7" +
"45e67a54058eb50d993cdb9ed0b4d029c06d21a94ca661c3ce27fae1d6cb20f4" +
"564d66ce4767583d0e5f060215b59017be85ea848939127bd8c9c4d47b51056c" +
"031cf336f17c9980f3b8f5b9b6878e8b797aa43b882684333e17893fe9caa6aa" +
"299f7ed1a18ee2c54864b7b2b99b72618fb02574d139ef50f019c9eef4169713" +
"38e7d470"
),
sig1: util.hex2abv(
"0b1f2e5180e5c7b4b5e672929f664c4896e50c35134b6de4d5a934252a3a245f" +
"f48340920e1034b7d5a5b524eb0e1cf12befef49b27b732d2c19e1c43217d6e1" +
"417381111a1d36de6375cf455b3c9812639dbc27600c751994fb61799ecf7da6" +
"bcf51540afd0174db4033188556675b1d763360af46feeca5b60f882829ee7b2"
),
sig256: util.hex2abv(
"558af496a9900ec497a51723a0bf1be167a3fdd0e40c95764575bcc93d35d415" +
"94aef08cd8d339272387339fe5faa5635a1c4ad6c9b622f8c38edce6b26d9b76" +
"e3fec5b567e5b996624c4aeef74191c4349e5ac9e29b848c54bcfa538fec58d5" +
"9368253f0ff9a7ba0637918dd16b2c95f8c73ad7484482ba4387655f2f7d4b00"
),
sig_fail: util.hex2abv(
"8000000080e5c7b4b5e672929f664c4896e50c35134b6de4d5a934252a3a245f" +
"f48340920e1034b7d5a5b524eb0e1cf12befef49b27b732d2c19e1c43217d6e1" +
"417381111a1d36de6375cf455b3c9812639dbc27600c751994fb61799ecf7da6" +
"bcf51540afd0174db4033188556675b1d763360af46feeca5b60f882829ee7b2"
),
},
}

View File

@ -590,3 +590,181 @@ TestArray.addTest(
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"RSAES-PKCS#1 encrypt/decrypt round-trip",
function () {
var that = this;
var privKey, pubKey;
var alg = {name:"RSAES-PKCS1-v1_5"};
var privKey, pubKey, data, ct, pt;
function setPriv(x) { privKey = x; }
function setPub(x) { pubKey = x; }
function doEncrypt() {
return crypto.subtle.encrypt(alg.name, pubKey, tv.rsaes.data);
}
function doDecrypt(x) {
return crypto.subtle.decrypt(alg.name, privKey, x);
}
function fail() { error(that); }
Promise.all([
crypto.subtle.importKey("pkcs8", tv.rsaes.pkcs8, alg, false, ['decrypt'])
.then(setPriv, error(that)),
crypto.subtle.importKey("spki", tv.rsaes.spki, alg, false, ['encrypt'])
.then(setPub, error(that))
]).then(doEncrypt, error(that))
.then(doDecrypt, error(that))
.then(
memcmp_complete(that, tv.rsaes.data),
error(that)
);
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"RSAES-PKCS#1 decryption known answer",
function () {
var that = this;
var alg = {name:"RSAES-PKCS1-v1_5"};
function doDecrypt(x) {
return crypto.subtle.decrypt(alg.name, x, tv.rsaes.result);
}
function fail() { error(that); }
crypto.subtle.importKey("pkcs8", tv.rsaes.pkcs8, alg, false, ['decrypt'])
.then( doDecrypt, fail )
.then( memcmp_complete(that, tv.rsaes.data), fail );
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"RSASSA/SHA-1 signature",
function () {
var that = this;
var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
function doSign(x) {
console.log("sign");
console.log(x);
return crypto.subtle.sign(alg.name, x, tv.rsassa.data);
}
function fail() { console.log("fail"); error(that); }
crypto.subtle.importKey("pkcs8", tv.rsassa.pkcs8, alg, false, ['sign'])
.then( doSign, fail )
.then( memcmp_complete(that, tv.rsassa.sig1), fail );
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"RSASSA verification (SHA-1)",
function () {
var that = this;
var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
function doVerify(x) {
return crypto.subtle.verify(alg.name, x, tv.rsassa.sig1, tv.rsassa.data);
}
function fail(x) { error(that); }
crypto.subtle.importKey("spki", tv.rsassa.spki, alg, false, ['verify'])
.then( doVerify, fail )
.then(
complete(that, function(x) { return x; }),
fail
);
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"RSASSA verification (SHA-1), failing verification",
function () {
var that = this;
var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
function doVerify(x) {
return crypto.subtle.verify(alg.name, x, tv.rsassa.sig_fail, tv.rsassa.data);
}
function fail(x) { error(that); }
crypto.subtle.importKey("spki", tv.rsassa.spki, alg, false, ['verify'])
.then( doVerify, fail )
.then(
complete(that, function(x) { return !x; }),
fail
);
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"RSASSA/SHA-256 signature",
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("pkcs8", tv.rsassa.pkcs8, alg, false, ['sign'])
.then( doSign, fail )
.then( memcmp_complete(that, tv.rsassa.sig256), fail );
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"RSASSA verification (SHA-256)",
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("spki", tv.rsassa.spki, alg, false, ['verify'])
.then( doVerify, fail )
.then(
complete(that, function(x) { return x; }),
fail
);
}
);
// -----------------------------------------------------------------------------
TestArray.addTest(
"RSASSA verification (SHA-256), failing verification",
function () {
var that = this;
var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
var use = ['sign', 'verify'];
function doVerify(x) {
console.log("verifying")
return crypto.subtle.verify(alg.name, x, tv.rsassa.sig_fail, tv.rsassa.data);
}
function fail(x) { console.log("failing"); error(that)(x); }
console.log("running")
crypto.subtle.importKey("spki", tv.rsassa.spki, alg, false, ['verify'])
.then( doVerify, fail )
.then(
complete(that, function(x) { return !x; }),
fail
);
}
);

View File

@ -16,6 +16,7 @@
#include "cert.h"
#include "cms.h"
#include "keyhi.h"
#include "cryptohi.h"
#include "pk11pub.h"
#include "sechash.h"
#include "secpkcs7.h"
@ -120,11 +121,27 @@ PK11_DestroyContext_true(PK11Context * ctx) {
PK11_DestroyContext(ctx, true);
}
inline void
SGN_DestroyContext_true(SGNContext* ctx) {
SGN_DestroyContext(ctx, true);
}
inline void
VFY_DestroyContext_true(VFYContext * ctx) {
VFY_DestroyContext(ctx, true);
}
} // namespace mozilla::psm
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11Context,
PK11Context,
mozilla::psm::PK11_DestroyContext_true)
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSGNContext,
SGNContext,
mozilla::psm::SGN_DestroyContext_true)
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedVFYContext,
VFYContext,
mozilla::psm::VFY_DestroyContext_true)
/** A more convenient way of dealing with digests calculated into
* stack-allocated buffers. NSS must be initialized on the main thread before