goog.provide('wycheproof.webcryptoapi.ECDSA');
goog.require('e2e.ecc.Ecdsa');
goog.require('goog.testing.TestCase');
goog.require('goog.testing.asserts');
goog.require('goog.testing.jsunit');
goog.require('wycheproof.BigInteger');
goog.require('wycheproof.EcUtil');
goog.require('wycheproof.TestUtil');
goog.require('wycheproof.webcryptoapi.HashUtil');
var TestUtil = wycheproof.TestUtil;
var EcUtil = wycheproof.EcUtil;
var BigInteger = wycheproof.BigInteger;
var HashUtil = wycheproof.webcryptoapi.HashUtil;
var ECDSA_VECTOR_FILE = '../../testvectors/ecdsa_webcrypto_test.json';
var Ecdsa = function() {};
Ecdsa.verify = function(pk, hashAlg, msg, sig) {
return crypto.subtle.verify(
{name: 'ECDSA', hash: {name: hashAlg}}, pk, sig, msg);
};
Ecdsa.sign = function(sk, msg, hashAlg) {
return crypto.subtle.sign(
{
name: 'ECDSA',
hash: {name: hashAlg},
},
sk, msg);
};
Ecdsa.importPublicKey = function(keyData, hashAlg, usages) {
return crypto.subtle.importKey(
'jwk', keyData,
{name: 'ECDSA', namedCurve: keyData['crv'], hash: {name: hashAlg}}, true,
usages);
};
Ecdsa.exportKey = function(key) {
return crypto.subtle.exportKey('jwk', key);
};
Ecdsa.generateKey = function(hashAlg, curveName) {
return crypto.subtle.generateKey(
{
name: 'ECDSA',
namedCurve: curveName,
},
true, ['sign', 'verify']);
};
Ecdsa.testVerify = function() {
tc = this;
var promise = new Promise((resolve, reject) => {
Ecdsa.importPublicKey(tc.keyData, tc.hashAlg, ['verify'])
.then(function(pk) {
Ecdsa.verify(pk, tc.hashAlg, tc.msg, tc.sig)
.then(function(isValid) {
if ((tc.result == 'valid' && !isValid) ||
(tc.result == 'invalid' && isValid)) {
reject('Failed in test case ' + tc.id);
}
resolve();
})
.catch(function(err) {
reject(
'Unexpected exception on test case ' + tc.id + ': ' + err);
});
})
.catch(function(err) {
reject('Failed to import key in test case ' + tc.id + ': ' + err);
});
});
return promise;
};
var EcdsaVerifyTestCase = function(id, keyData, hashAlg, msg, sig, result) {
this.id = id;
this.keyData = keyData;
this.msg = msg;
this.sig = sig;
this.result = result;
this.hashAlg = hashAlg;
};
function testEcdsaVectors() {
var tv = TestUtil.readJsonTestVectorsFromFile(ECDSA_VECTOR_FILE);
var testCase = new goog.testing.TestCase();
for (var i = 0; i < tv['testGroups'].length; i++) {
var tg = tv['testGroups'][i];
var keyData = tg['jwk'];
var curveName = keyData['crv'];
var hashAlg = tg['sha'];
if (SUPPORTED['ecdsa-curve'].indexOf(curveName) == -1 ||
SUPPORTED['hash'].indexOf(hashAlg) == -1) {
continue;
}
for (var j = 0; j < tg['tests'].length; j++) {
var tc = tg['tests'][j];
var tcId = tc['tcId'];
var result = tc['result'];
var msg = TestUtil.hexToArrayBuffer(tc['msg']);
var sig = TestUtil.hexToArrayBuffer(tc['sig']);
var test =
new EcdsaVerifyTestCase(tcId, keyData, hashAlg, msg, sig, result);
testCase.addNewTest('Test ' + tcId, Ecdsa.testVerify, test);
}
}
return testCase.runTestsReturningPromise().then(TestUtil.checkTestCaseResult);
}
Ecdsa.extractSig = function(sig) {
var bytes = new Uint8Array(sig);
var byteLen = bytes.length;
var rBytes = bytes.subarray(0, byteLen / 2);
var r = new BigInteger(rBytes);
var sBytes = bytes.subarray(byteLen / 2, byteLen);
var s = new BigInteger(sBytes);
return [r, s];
};
Ecdsa.extractNonce = function(h, r, s, d, curveSpec) {
var n = curveSpec.n;
var k = d.multiply(r).add(h).multiply(n.modInverse(s)).mod(n);
return k;
};
Ecdsa.checkNonceCorrectness = function(msg, r, s, d, k, curveName) {
var e2eCurveMap = {
'P-256': 'P_256',
'P-384': 'P_384',
'P-521': 'P_521',
};
var e2eCurveName = e2eCurveMap[curveName];
var key = new e2e.ecc.Ecdsa(e2eCurveName, {privKey: d.toByteArray()});
var msgBytes = new Uint8Array(msg);
var calSig = key.signForTestingOnly(msgBytes, k);
var calR = new BigInteger(calSig['r']);
var calS = new BigInteger(calSig['s']);
assertTrue(
'Nonce calculation was incorrect', r.isEqual(calR) && s.isEqual(calS));
};
Ecdsa.testBias = function() {
var tc = this;
var countLsb = 0;
var countMsb = 0;
return Ecdsa.generateKey(tc.hashAlg, tc.curveName)
.then(function(key) {
return Ecdsa.exportKey(key.privateKey)
.then(function(keyData) {
var promises = [];
for (var i = 0; i < tc.nTests; i++) {
promises.push(
Ecdsa.sign(key.privateKey, tc.msg, tc.hashAlg)
.then(function(sig) {
return HashUtil.digest(tc.hashAlg, tc.msg)
.then(function(digest) {
var curveSpec =
EcUtil.getCurveSpec(tc.curveName);
var h = new BigInteger(new Uint8Array(digest));
var d = BigInteger.fromHex(
TestUtil.base64UrlToHex(keyData['d']));
var r, s;
[r, s] = Ecdsa.extractSig(sig);
var k =
Ecdsa.extractNonce(h, r, s, d, curveSpec);
var halfN = curveSpec.n.shiftRight(1);
if (k.isBitSet(0)) countLsb += 1;
if (k.compare(halfN) == 1) countMsb += 1;
});
}));
}
return Promise.all(promises).then(function() {
if (countLsb < tc.minCount ||
countLsb > tc.nTests - tc.minCount) {
reject(
'Bias detected in the LSB of k' +
', hash: ' + tc.hashAlg + ', curve: ' + tc.curveName +
', countLSB: ' + countLsb + ', countMSB: ' + countMsb);
}
if (countMsb < tc.minCount ||
countMsb > tc.nTests - tc.minCount) {
reject(
'Bias detected in the MSB of k' +
', hash: ' + tc.hashAlg + ', curve: ' + tc.curveName +
', countLSB: ' + countLsb + ', countMSB: ' + countMsb);
}
});
})
.catch(function(err) {
throw new Error('Failed to export private key: ' + err);
});
})
.catch(function(err) {
throw new Error('Failed to generate key: ' + err);
});
};
var EcdsaBiasTestCase = function(hashAlg, curveName, msg, nTests, minCount) {
this.msg = msg;
this.hashAlg = hashAlg;
this.curveName = curveName;
this.nTests = nTests;
this.minCount = minCount;
};
function testEcdsaBiasAll() {
var testCase = new goog.testing.TestCase();
testCase.promiseTimeout = 120 * 1000;
var msg = TestUtil.hexToArrayBuffer('48656c6c6f'); var nTests = 1024;
var minCount = 410;
var biasTest256 =
new EcdsaBiasTestCase('SHA-256', 'P-256', msg, nTests, minCount);
testCase.addNewTest('bias256', Ecdsa.testBias, biasTest256);
var biasTest384 =
new EcdsaBiasTestCase('SHA-384', 'P-384', msg, nTests, minCount);
testCase.addNewTest('bias384', Ecdsa.testBias, biasTest384);
var biasTest521 =
new EcdsaBiasTestCase('SHA-512', 'P-521', msg, nTests, minCount);
testCase.addNewTest('bias521', Ecdsa.testBias, biasTest521);
return testCase.runTestsReturningPromise().then(
wycheproof.TestUtil.checkTestCaseResult);
}