Add Web Crypto API tests and Java tests using test vectors (#35)
* Add test vectors * Add Java tests using test vectors * Add Web Crypto API tests.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities for hash operations
|
||||
*/
|
||||
goog.provide('wycheproof.webcryptoapi.HashUtil');
|
||||
|
||||
|
||||
// Hash function names
|
||||
wycheproof.webcryptoapi.HashUtil.SHA256 = 'SHA-256';
|
||||
wycheproof.webcryptoapi.HashUtil.SHA384 = 'SHA-384';
|
||||
wycheproof.webcryptoapi.HashUtil.SHA512 = 'SHA-512';
|
||||
wycheproof.webcryptoapi.HashUtil.SHA1 = 'SHA-1';
|
||||
|
||||
|
||||
/**
|
||||
* Calculates a hash of the given message using the given hash algorithm.
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
* @param {!ArrayBuffer} msg The message to be hashed
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
wycheproof.webcryptoapi.HashUtil.digest = function(hashAlg, msg) {
|
||||
return window.crypto.subtle.digest(hashAlg, msg);
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Some utilities for testing RSA on Web Crypto APIs
|
||||
*/
|
||||
goog.provide('wycheproof.webcryptoapi.RsaUtil');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('wycheproof.webcryptoapi.HashUtil');
|
||||
|
||||
var HashUtil = wycheproof.webcryptoapi.HashUtil;
|
||||
|
||||
// algorithm names
|
||||
wycheproof.webcryptoapi.RsaUtil.RSASSA_PKCS1 = 'RSASSA-PKCS1-v1_5';
|
||||
wycheproof.webcryptoapi.RsaUtil.RSA_OAEP = 'RSA-OAEP';
|
||||
|
||||
// public exponents
|
||||
wycheproof.webcryptoapi.RsaUtil.E_65537 = new Uint8Array([0x01, 0x00, 0x01]);
|
||||
wycheproof.webcryptoapi.RsaUtil.E_3 = new Uint8Array([0x03]);
|
||||
|
||||
/**
|
||||
* Imports a RSA public key.
|
||||
* @param {!string} e RSA public exponent in base64url format
|
||||
* @param {!string} n RSA modulus in base64url format
|
||||
* @param {!string} schemeName The usage scheme.
|
||||
* Supported values are "RSASSA-PKCS1-v1_5", "RSA-PSS".
|
||||
* @param {!string} hashAlg The hash algorithm used for the scheme.
|
||||
* Supported values are "SHA-1", "SHA-256", "SHA-384", and "SHA-512".
|
||||
* @param {!Array<string>} usages An Array indicating what can be done with the key.
|
||||
*
|
||||
* @return {!Promise} A Promise object containing the public key
|
||||
*/
|
||||
wycheproof.webcryptoapi.RsaUtil.importPublicKey =
|
||||
function(e, n, schemeName, hashAlg, usages) {
|
||||
return window.crypto.subtle.importKey(
|
||||
'jwk', {
|
||||
kty: 'RSA',
|
||||
e: e,
|
||||
n: n,
|
||||
ext: true,
|
||||
}, {
|
||||
name: schemeName,
|
||||
hash: {name: hashAlg},
|
||||
},
|
||||
false,
|
||||
usages
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies a RSA signature using the given public key.
|
||||
* @param {!CryptoKey} pk The public key object
|
||||
* @param {!ArrayBuffer} msg
|
||||
* The message that was signed by the corresponding private key, in
|
||||
* @param {!ArrayBuffer} sig
|
||||
* The signature to be verified
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
* @param {!string} schemeName The signature scheme
|
||||
*
|
||||
* @return {!Promise} A Promise object containing the verification result.
|
||||
*/
|
||||
wycheproof.webcryptoapi.RsaUtil.verify = function(pk, msg, sig, hashAlg, schemeName) {
|
||||
return window.crypto.subtle.verify(
|
||||
{
|
||||
name: schemeName,
|
||||
hash: hashAlg
|
||||
},
|
||||
pk,
|
||||
sig,
|
||||
msg
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a new RSA key pair.
|
||||
* @param {!string} schemeName The algorithm scheme
|
||||
* @param {number} keySize The key size in bits
|
||||
* @param {!ArrayBuffer} e The public exponent
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
* @param {!Array<string>} usages The usages of the key
|
||||
*
|
||||
* @return {!Promise} A promise containing the new key pair.
|
||||
*/
|
||||
wycheproof.webcryptoapi.RsaUtil.generateKey
|
||||
= function(schemeName, keySize, e, hashAlg, usages) {
|
||||
return window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: schemeName,
|
||||
modulusLength: keySize,
|
||||
publicExponent: e,
|
||||
hash: {name: hashAlg},
|
||||
},
|
||||
true,
|
||||
usages
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypts the given ciphertext.
|
||||
* @param {!string} schemeName The algorithm scheme
|
||||
* @param {!CryptoKey} sk The private key that will be used for decryption
|
||||
* @param {!string} ct The ciphertext to be decrypted
|
||||
*
|
||||
* @return {!Promise} A promise containing the decrypted text.
|
||||
*/
|
||||
wycheproof.webcryptoapi.RsaUtil.decrypt = function(schemeName, sk, ct) {
|
||||
return window.crypto.subtle.decrypt({name: schemeName}, sk, ct);
|
||||
};
|
||||
|
||||
/**
|
||||
* A class containing RSA signature test case's parameters
|
||||
* @param {!number} id
|
||||
* @param {!string} e RSA public exponent in base64url format
|
||||
* @param {!string} n RSA modulus in base64url format
|
||||
* @param {!string} hashAlg The hash algorithm used for the scheme.
|
||||
* @param {!string} scheme The usage scheme.
|
||||
* @param {!ArrayBuffer} msg The message to be verified
|
||||
* @param {!ArrayBuffer} sig The signature to be verified
|
||||
* @param {!string} result The test result
|
||||
*/
|
||||
wycheproof.webcryptoapi.RsaUtil.RsaSignatureTestCase
|
||||
= function(id, e, n, hashAlg, scheme, msg, sig, result) {
|
||||
this.id = id;
|
||||
this.e = e;
|
||||
this.n = n;
|
||||
this.hashAlg = hashAlg;
|
||||
this.scheme = scheme;
|
||||
this.msg = msg;
|
||||
this.sig = sig;
|
||||
this.result = result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests RSA signature verification.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
wycheproof.webcryptoapi.RsaUtil.testVerification = function() {
|
||||
var tc = this;
|
||||
var promise = new Promise(function(resolve, reject){
|
||||
RsaUtil.importPublicKey(tc.e, tc.n, tc.scheme, tc.hashAlg, ['verify'])
|
||||
.then(function(pk){
|
||||
wycheproof.webcryptoapi.RsaUtil.verify(pk, tc.msg, tc.sig,
|
||||
tc.hashAlg, tc.scheme).then(function(isValid){
|
||||
if ((tc.result == 'valid' && !isValid) ||
|
||||
(tc.result == 'invalid' && isValid)) {
|
||||
reject('Failed on test case ' + tc.id);
|
||||
}
|
||||
resolve();
|
||||
}).catch(function(err){
|
||||
// don't expect any exception in signature verification
|
||||
reject('Unexpected exception on test case ' + tc.id + ": " + err);
|
||||
});
|
||||
}).catch(function(err){
|
||||
reject('Failed to import public key in test case ' + tc.id + ': ' + err);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
// This file was autogenerated by depswriter.py.
|
||||
// Please do not edit.
|
||||
goog.addDependency('../../../../javascript/BigInteger.js', ['wycheproof.BigInteger', 'wycheproof.BigPrimeInteger'], ['e2e.BigNum', 'e2e.BigPrimeNum', 'goog.crypt', 'goog.crypt.base64', 'goog.testing.asserts', 'wycheproof.TestUtil'], false);
|
||||
goog.addDependency('../../../../javascript/EcUtil.js', ['wycheproof.EcUtil'], ['wycheproof.BigInteger', 'wycheproof.BigPrimeInteger'], false);
|
||||
goog.addDependency('../../../../javascript/TestUtil.js', ['wycheproof.TestUtil'], ['goog.crypt', 'goog.crypt.base64', 'goog.json'], false);
|
||||
goog.addDependency('../../../../javascript/webcryptoapi/HashUtil.js', ['wycheproof.webcryptoapi.HashUtil'], [], false);
|
||||
goog.addDependency('../../../../javascript/webcryptoapi/RsaUtil.js', ['wycheproof.webcryptoapi.RsaUtil'], ['goog.testing.asserts', 'wycheproof.webcryptoapi.HashUtil'], false);
|
||||
goog.addDependency('../../../../javascript/webcryptoapi/WebCryptoTest.js', ['wycheproof.webcryptoapi'], ['goog.testing.TestCase', 'goog.userAgent.product'], false);
|
||||
goog.addDependency('../../../../javascript/webcryptoapi/testcases/AesGcmTest.js', ['wycheproof.webcryptoapi.AES-GCM'], ['goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent.product', 'wycheproof.BigInteger', 'wycheproof.TestUtil'], false);
|
||||
goog.addDependency('../../../../javascript/webcryptoapi/testcases/EcdhTest.js', ['wycheproof.webcryptoapi.ECDH'], ['goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit', 'wycheproof.BigInteger', 'wycheproof.TestUtil', 'wycheproof.webcryptoapi.RsaUtil'], false);
|
||||
goog.addDependency('../../../../javascript/webcryptoapi/testcases/EcdsaTest.js', ['wycheproof.webcryptoapi.ECDSA'], ['e2e.ecc.Ecdsa', 'goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit', 'wycheproof.BigInteger', 'wycheproof.EcUtil', 'wycheproof.TestUtil', 'wycheproof.webcryptoapi.HashUtil'], false);
|
||||
goog.addDependency('../../../../javascript/webcryptoapi/testcases/RsaOaedTest.js', ['wycheproof.webcryptoapi.RSA-OAED'], ['e2e.random', 'goog.testing.asserts', 'goog.testing.jsunit', 'wycheproof.TestUtil', 'wycheproof.webcryptoapi.RsaUtil'], false);
|
||||
goog.addDependency('../../../../javascript/webcryptoapi/testcases/RsaSsaPkcs1Test.js', ['wycheproof.webcryptoapi.RSASSA-PKCS1-V1_5'], ['goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit', 'wycheproof.BigInteger', 'wycheproof.TestUtil', 'wycheproof.webcryptoapi.HashUtil', 'wycheproof.webcryptoapi.RsaUtil'], false);
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- Copyright 2017 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// -->
|
||||
<!DOCTYPE html>
|
||||
<title>Unit Tests of Web Crypto API</title>
|
||||
<script src="../../external/com_google_javascript_closure_library/closure/goog/base.js"></script>
|
||||
<script src="../../../bazel-bin/E2EDeps.js"></script>
|
||||
|
||||
<!-- This file is generated with depswriter:
|
||||
https://developers.google.com/closure/library/docs/depswriter
|
||||
It needs to be updated whenever there are changes. -->
|
||||
<script src="WebCryptoDeps.js"></script>
|
||||
|
||||
<script>
|
||||
goog.require('wycheproof.webcryptoapi');
|
||||
goog.require('wycheproof.webcryptoapi.AES-GCM');
|
||||
goog.require('wycheproof.webcryptoapi.ECDSA');
|
||||
goog.require('wycheproof.webcryptoapi.ECDH');
|
||||
goog.require('wycheproof.webcryptoapi.RSA-OAED');
|
||||
goog.require('wycheproof.webcryptoapi.RSASSA-PKCS1-V1_5');
|
||||
</script>
|
||||
<script>
|
||||
function setUpPage(){
|
||||
wycheproof.webcryptoapi.setupAllTests();
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* @fileoverview Tests for Web Crypto API
|
||||
*/
|
||||
goog.provide('wycheproof.webcryptoapi');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.userAgent.product');
|
||||
|
||||
const TEST_TIMEOUT = 120*1000;
|
||||
|
||||
// A dictionary containing the parameters that are supported by the current browser
|
||||
var SUPPORTED;
|
||||
// Parameters that are supported by Microsoft Edge
|
||||
const EDGE_SUPPORTED = {
|
||||
'aesgcm-key-size': [128, 192, 256],
|
||||
'ecdsa-curve': ['P-256', 'P-384', 'P-521'],
|
||||
'hash': ['SHA-256', 'SHA-384', 'SHA-512']
|
||||
};
|
||||
// Parameters that are supported by Chrome
|
||||
const CHROME_SUPPORTED = {
|
||||
'aesgcm-key-size': [128, 256],
|
||||
'ecdsa-curve': ['P-256', 'P-384', 'P-521'],
|
||||
'hash': ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']
|
||||
};
|
||||
// Parameters that are supported by Firefox
|
||||
const FIREFOX_SUPPORTED = {
|
||||
'aesgcm-key-size': [128, 192, 256],
|
||||
'ecdsa-curve': ['P-256', 'P-384', 'P-521'],
|
||||
'hash': ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']
|
||||
};
|
||||
|
||||
// Test names
|
||||
const TEST_AES_GCM_VECTORS = 'testAesGcmVectors';
|
||||
const TEST_ECDSA_VECTORS = 'testEcdsaVectors';
|
||||
const TEST_ECDSA_BIAS = 'testEcdsaBiasAll';
|
||||
const TEST_RSASSAPKCS1_VECTORS = 'testRsaSsaPkcs1Vectors';
|
||||
const TEST_RSA_ENCRYPT = 'testRsaEncryptionException';
|
||||
const TEST_ECDH_VECTORS = 'testEcdhVectors';
|
||||
|
||||
// Tests to run
|
||||
var TESTS_ALL = {};
|
||||
TESTS_ALL[TEST_AES_GCM_VECTORS] = true;
|
||||
TESTS_ALL[TEST_ECDSA_VECTORS] = true;
|
||||
TESTS_ALL[TEST_ECDSA_BIAS] = true;
|
||||
TESTS_ALL[TEST_RSASSAPKCS1_VECTORS] = true;
|
||||
TESTS_ALL[TEST_RSA_ENCRYPT] = true;
|
||||
TESTS_ALL[TEST_ECDH_VECTORS] = true;
|
||||
|
||||
// Tests to run on Chrome
|
||||
var TESTS_TO_RUN_CHROME = goog.cloneObject(TESTS_ALL);
|
||||
|
||||
// Tests to run on Firefox
|
||||
var TESTS_TO_RUN_FIREFOX = goog.cloneObject(TESTS_ALL);
|
||||
|
||||
// Tests to run on MS Edge.
|
||||
// MS Edge doesn't support ECDSA and ECDH
|
||||
var TESTS_TO_RUN_EDGE = goog.cloneObject(TESTS_ALL);
|
||||
TESTS_TO_RUN_EDGE[TEST_ECDSA_VECTORS] = false;
|
||||
TESTS_TO_RUN_EDGE[TEST_ECDSA_BIAS] = false;
|
||||
TESTS_TO_RUN_EDGE[TEST_ECDH_VECTORS] = false;
|
||||
|
||||
/**
|
||||
* Runs all tests.
|
||||
*/
|
||||
wycheproof.webcryptoapi.setupAllTests = function() {
|
||||
var testCase = goog.testing.TestCase.getActiveTestCase();
|
||||
testCase.promiseTimeout = TEST_TIMEOUT;
|
||||
if (goog.userAgent.product.CHROME) {
|
||||
testCase.setTestsToRun(TESTS_TO_RUN_CHROME);
|
||||
SUPPORTED = CHROME_SUPPORTED;
|
||||
} else if (goog.userAgent.product.FIREFOX) {
|
||||
testCase.setTestsToRun(TESTS_TO_RUN_FIREFOX);
|
||||
SUPPORTED = FIREFOX_SUPPORTED;
|
||||
} else if (goog.userAgent.product.EDGE) {
|
||||
testCase.setTestsToRun(TESTS_TO_RUN_EDGE);
|
||||
SUPPORTED = EDGE_SUPPORTED;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for AES-GCM implementations of Web Crypto API.
|
||||
*/
|
||||
goog.provide('wycheproof.webcryptoapi.AES-GCM');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.userAgent.product');
|
||||
goog.require('wycheproof.BigInteger');
|
||||
goog.require('wycheproof.TestUtil');
|
||||
|
||||
var TestUtil = wycheproof.TestUtil;
|
||||
var BigInteger = wycheproof.BigInteger;
|
||||
|
||||
// Test vector file
|
||||
var AES_GCM_VECTOR_FILE = '../../testvectors/aes_gcm_test.json';
|
||||
|
||||
|
||||
/**
|
||||
* A class containing a AES-GCM test case's parameters.
|
||||
* @param {!number} id Identifier of the test case
|
||||
* @param {!string} k The key in Base64URL format
|
||||
* @param {!number} keySize The key's size in bits
|
||||
* @param {!string} tag The expected tag of the encryption in hex format
|
||||
* @param {!number} tagSize The tag size in bits
|
||||
* @param {!ArrayBuffer} iv The initialization vector
|
||||
* @param {!number} ivSize The iv's size in bits
|
||||
* @param {!ArrayBuffer} aad The additional authentication data
|
||||
* @param {!ArrayBuffer} msg The message to be encrypted
|
||||
* @param {!string} ct The expected ciphertext of the encryption in hex format
|
||||
* @param {!string} result The expected result of the test
|
||||
*/
|
||||
var AesGcmTestCase = function(id, k, keySize, tag, tagSize, iv, ivSize,
|
||||
aad, msg, ct, result) {
|
||||
this.id = id;
|
||||
this.k = k;
|
||||
this.keySize = keySize;
|
||||
this.tag = tag;
|
||||
this.tagSize = tagSize;
|
||||
this.iv = iv;
|
||||
this.ivSize = ivSize;
|
||||
this.aad = aad;
|
||||
this.msg = msg;
|
||||
this.ct = ct;
|
||||
this.result = result;
|
||||
};
|
||||
|
||||
|
||||
/** AES-GCM wrapper */
|
||||
var AesGcm = function() {};
|
||||
|
||||
/**
|
||||
* Returns the AES-GCM algorithm name that is accepted by WebCrypto APIs.
|
||||
* @param {!number} keySize The key's size
|
||||
*
|
||||
* @return {!string} The algorithm's name
|
||||
*/
|
||||
AesGcm.getAlgName = function(keySize) {
|
||||
return 'A' + keySize + 'GCM';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Imports a AES-GCM key.
|
||||
* @param {!string} k The key in Base64URL format
|
||||
* @param {!number} keySize The key's size in bits
|
||||
*
|
||||
* @return {!Promise} A Promise containing the imported key
|
||||
*/
|
||||
AesGcm.importKey = function(k, keySize) {
|
||||
return window.crypto.subtle.importKey(
|
||||
"jwk",
|
||||
{
|
||||
kty: 'oct',
|
||||
k: k,
|
||||
alg: AesGcm.getAlgName(keySize),
|
||||
ext: true,
|
||||
}, {
|
||||
name: 'AES-GCM',
|
||||
},
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs AES-GCM encryption.
|
||||
* @param {!CryptoKey} key The key that is used for encryption
|
||||
* @param {!ArrayBuffer} aad The additional authentication data
|
||||
* @param {!ArrayBuffer} msg The message to be encrypted
|
||||
* @param {!ArrayBuffer} iv The initialization vector
|
||||
* @param {number} tagSize The tag size in bits
|
||||
*
|
||||
* @return {!Promise} A Promise containing the ciphetext
|
||||
*/
|
||||
AesGcm.encrypt = function(key, aad, msg, iv, tagSize) {
|
||||
var alg = {
|
||||
name: 'AES-GCM',
|
||||
iv: iv,
|
||||
tagLength: tagSize
|
||||
};
|
||||
if (aad.byteLength > 0) {
|
||||
alg.additionalData = aad;
|
||||
}
|
||||
return window.crypto.subtle.encrypt(alg, key, msg);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests AES-GCM encryption.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
AesGcm.testEncrypt = function() {
|
||||
var tc = this;
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
AesGcm.importKey(tc.k, tc.keySize).then(function(key){
|
||||
AesGcm.encrypt(key, tc.aad, tc.msg, tc.iv, tc.tagSize)
|
||||
.then(function(ct){
|
||||
// Fail if the iv is empty and the encryption still succeeds
|
||||
if (tc.ivSize == 0) {
|
||||
reject('Failed on test case ' + tc.id + ': 0-length iv should not be accepted');
|
||||
} else {
|
||||
var obtainedCt = TestUtil.arrayBufferToHex(ct);
|
||||
var expectedCt = tc.ct+tc.tag;
|
||||
if ((tc.result == 'valid' && obtainedCt != expectedCt) ||
|
||||
(tc.result == 'invalid' && obtainedCt == expectedCt)) {
|
||||
reject('Failed on test case ' + tc.id);
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}).catch(function(err){
|
||||
// don't expect any exception during encryption
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests AES-GCM encryption implementation with test vectors from files
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
function testAesGcmVectors() {
|
||||
var tv = TestUtil.readJsonTestVectorsFromFile(AES_GCM_VECTOR_FILE);
|
||||
var testCase = new goog.testing.TestCase();
|
||||
testCase.promiseTimeout = 2*1000;
|
||||
|
||||
for (var i = 0; i < tv['testGroups'].length; i++) {
|
||||
var tg = tv['testGroups'][i];
|
||||
var keySizeBit = parseInt(tg['keySize']);
|
||||
var tagSizeBit = parseInt(tg['tagSize']);
|
||||
var ivSizeBit = parseInt(tg['ivSize']);
|
||||
// Only run the tests if the key size is supported
|
||||
if (SUPPORTED['aesgcm-key-size'].indexOf(keySizeBit) == -1) {
|
||||
continue;
|
||||
}
|
||||
// MS Edge only supports IVs of size 96
|
||||
if (goog.userAgent.product.EDGE && ivSizeBit != 96) {
|
||||
continue;
|
||||
}
|
||||
for (var j = 0; j < tg['tests'].length; j++){
|
||||
var tc = tg['tests'][j];
|
||||
// MS Edge doesn't accept 0-length message
|
||||
if (goog.userAgent.product.EDGE && tc['msg'].length == 0) {
|
||||
continue;
|
||||
}
|
||||
var k = BigInteger.fromHex(tc['key']).toBase64Url(keySizeBit/8);
|
||||
var aad = TestUtil.hexToArrayBuffer(tc['aad']);
|
||||
var msg = TestUtil.hexToArrayBuffer(tc['msg']);
|
||||
var iv;
|
||||
if (ivSizeBit == 0) {
|
||||
iv = new Uint8Array([]).buffer;
|
||||
} else {
|
||||
iv = BigInteger.fromHex(tc['iv']).toArrayBuffer(ivSizeBit/8);
|
||||
}
|
||||
var ct = tc['ct'];
|
||||
var tag = tc['tag'];
|
||||
var result = tc['result'];
|
||||
var tcId = tc['tcId'];
|
||||
var test = new AesGcmTestCase(tcId, k, keySizeBit, tag, tagSizeBit,
|
||||
iv, ivSizeBit, aad, msg, ct, result);
|
||||
testCase.addNewTest('Test ' + tcId, AesGcm.testEncrypt, test);
|
||||
}
|
||||
}
|
||||
|
||||
return testCase.runTestsReturningPromise().then(TestUtil.checkTestCaseResult);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for ECDH implementations of Web Crypto API.
|
||||
*/
|
||||
goog.provide('wycheproof.webcryptoapi.ECDH');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('wycheproof.BigInteger');
|
||||
goog.require('wycheproof.TestUtil');
|
||||
goog.require('wycheproof.webcryptoapi.RsaUtil');
|
||||
|
||||
var TestUtil = wycheproof.TestUtil;
|
||||
var RsaUtil = wycheproof.webcryptoapi.RsaUtil;
|
||||
var BigInteger = wycheproof.BigInteger;
|
||||
|
||||
// Test vector files
|
||||
var ECDH_VECTOR_FILE = '../../testvectors/ecdh_webcrypto_test.json';
|
||||
|
||||
/** ECDH wrapper */
|
||||
var Ecdh = function() {};
|
||||
|
||||
/**
|
||||
* Generates an ECDH key pair using the given curve name.
|
||||
* @param {!string} curveName The curve name
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdh.generateKey = function(curveName) {
|
||||
return window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curveName,
|
||||
},
|
||||
true,
|
||||
['deriveKey', 'deriveBits']
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Imports a ECDH key from the given key data.
|
||||
* @param {!string} curveName The curve name
|
||||
* @param {!JSONObject} keyData The key data in JWK format
|
||||
* @param {!Array<string>} usages The usages of the key
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdh.importKey = function(curveName, keyData, usages) {
|
||||
return window.crypto.subtle.importKey(
|
||||
"jwk",
|
||||
keyData,
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curveName,
|
||||
},
|
||||
true,
|
||||
usages
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Exports the given ECDSA key as a JSON object.
|
||||
* @param {!CryptoKey} key The ECDH key to be exported
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdh.exportKey = function(key) {
|
||||
return window.crypto.subtle.exportKey("jwk", key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Derives bits from a public key and a private key.
|
||||
* @param {!CryptoKey} pubKey The public key
|
||||
* @param {!CryptoKey} privKey The private key
|
||||
* @param {number} bitLen The number of bits to be derived
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdh.deriveBits = function(pubKey, privKey, bitLen) {
|
||||
return window.crypto.subtle.deriveBits(
|
||||
{
|
||||
name: 'ECDH',
|
||||
public: pubKey,
|
||||
},
|
||||
privKey,
|
||||
bitLen
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests ECDH key derivation. The test case's parameters are passed
|
||||
* to 'this' variable of the function.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdh.testKeyDerivation = function() {
|
||||
tc = this;
|
||||
var sk, pk;
|
||||
var promise = new Promise(function(resolve, reject){
|
||||
Ecdh.importKey(tc.curveName, tc.privKeyData, ['deriveBits']).then(function(key){
|
||||
sk = key;
|
||||
Ecdh.importKey(tc.curveName, tc.pubKeyData, []).then(function(key){
|
||||
pk = key;
|
||||
Ecdh.deriveBits(pk, sk, tc.sharedKeyLen).then(function(sharedKey){
|
||||
if (tc.result == 'invalid') {
|
||||
reject('Failed on test case ' + tc.id);
|
||||
}
|
||||
var hexSharedKey = TestUtil.arrayBufferToHex(sharedKey);
|
||||
if (hexSharedKey != tc.sharedKey) {
|
||||
reject('Failed on test case ' + tc.id);
|
||||
}
|
||||
resolve();
|
||||
}).catch(function(err) {
|
||||
// don't expect any exception in signature verification
|
||||
reject('Unexpected exception on test case ' + tc.id + ": " + err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
reject('Failed to import public key: ' + err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
reject('Failed to import private key: ' + err);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parameters of a ECDH key derivation test.
|
||||
* @param {!number} id Test case's id
|
||||
* @param {!string} curveName The name of the elliptic curve
|
||||
* @param {!JSONObject} privKeyData The private key's data in JWK format
|
||||
* @param {!JSONObject} pubKeyData The public key's data in JWK format
|
||||
* @param {!string} sharedKey The expected shared key
|
||||
* @param {!string} result The expected result of the test case
|
||||
*/
|
||||
var EcdhTestCase = function(id, curveName, privKeyData, pubKeyData, sharedKey, result) {
|
||||
this.id = id;
|
||||
this.curveName = curveName;
|
||||
this.privKeyData = privKeyData;
|
||||
this.pubKeyData = pubKeyData;
|
||||
this.sharedKey = sharedKey;
|
||||
this.sharedKeyLen = sharedKey.length/2*8;
|
||||
this.result = result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests ECDH key derivation with a number of test vectors.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
function testEcdhVectors() {
|
||||
var tv = TestUtil.readJsonTestVectorsFromFile(ECDH_VECTOR_FILE);
|
||||
var testCase = new goog.testing.TestCase();
|
||||
for (var i = 0; i < tv['testGroups'].length; i++) {
|
||||
tg = tv['testGroups'][i];
|
||||
for (var j = 0; j < tg['tests'].length; j++) {
|
||||
tc = tg['tests'][j];
|
||||
var test = new EcdhTestCase(tc['tcId'], tc['curve'], tc['private'], tc['public'],
|
||||
tc['shared'], tc['result']);
|
||||
testCase.addNewTest(tc['tcId'], Ecdh.testKeyDerivation, test);
|
||||
}
|
||||
}
|
||||
return testCase.runTestsReturningPromise().then(TestUtil.checkTestCaseResult);
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for ECDSA implementations of Web Crypto API.
|
||||
*/
|
||||
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;
|
||||
|
||||
// Test vector files
|
||||
var ECDSA_VECTOR_FILE = '../../testvectors/ecdsa_webcrypto_test.json';
|
||||
|
||||
/** ECDSA wrapper */
|
||||
var Ecdsa = function() {};
|
||||
|
||||
/**
|
||||
* Verifies the given signature using the given ECDSA public key.
|
||||
* @param {!CryptoKey} pk The ECDSA public key
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
* @param {!ArrayBuffer} msg The message to be verified
|
||||
* @param {!ArrayBuffer} sig The signature to be verified
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdsa.verify = function(pk, hashAlg, msg, sig) {
|
||||
return crypto.subtle.verify(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
hash: {name: hashAlg}
|
||||
},
|
||||
pk,
|
||||
sig,
|
||||
msg
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Signs a message using the given ECDSA private key and hash algorithm.
|
||||
* @param {!CryptoKey} sk The ECDSA private key
|
||||
* @param {!ArrayBuffer} msg The message to be signed
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdsa.sign = function(sk, msg, hashAlg) {
|
||||
return crypto.subtle.sign(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
hash: {name: hashAlg},
|
||||
},
|
||||
sk,
|
||||
msg
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Imports a ECDSA public key.
|
||||
* @param {!JSONObject} keyData The key data in JWK format
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
* @param {!Array<string>} usages The usages of the key
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdsa.importPublicKey = function(keyData, hashAlg, usages) {
|
||||
return crypto.subtle.importKey(
|
||||
'jwk',
|
||||
keyData,
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: keyData['crv'],
|
||||
hash: {name: hashAlg}
|
||||
},
|
||||
true,
|
||||
usages
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Exports the given ECDSA key as a JSON object.
|
||||
* @param {!CryptoKey} key The ECDSA key to be exported
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdsa.exportKey = function(key) {
|
||||
return crypto.subtle.exportKey('jwk', key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates an ECDSA key pair using the given hash algorithm and curve name.
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
* @param {!string} curveName The curve name
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdsa.generateKey = function(hashAlg, curveName) {
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: curveName,
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests ECDSA signature verification. The test case's parameters are passed
|
||||
* to 'this' variable of the function.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
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){
|
||||
// don't expect any exception in signature verification
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parameters of a ECDSA signature verification test.
|
||||
* @param {!number} id Test case's id
|
||||
* @param {!JSONObject} keyData The key data in JWK format
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
* @param {!ArrayBuffer} msg The message that was signed
|
||||
* @param {!ArrayBuffer} sig The signature to be verified
|
||||
* @param {!string} result The expected result of the test case
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests ECDSA signature implementation with a number of test vectors.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
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['message']);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts 'r' and 's' values from the given signature. The function assumes
|
||||
* that the given signature is the concatenation of 'r' and 's'.
|
||||
* @param {!ArrayBuffer} sig The signature
|
||||
*
|
||||
* @return {!Array<wycheproof.BigInteger>}
|
||||
*/
|
||||
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];
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the random nonce that was used to calculate the given signature.
|
||||
* @param {!wycheproof.BigInteger} h The digest of the message
|
||||
* @param {!wycheproof.BigInteger} r The 'r' value of the signature
|
||||
* @param {!wycheproof.BigInteger} s The 's' value of the signature
|
||||
* @param {!wycheproof.BigInteger} d The private component of the ECDSA key
|
||||
* @param {!NistCurveSpec} curveSpec The specifications of the used curve
|
||||
*
|
||||
* @return {!wycheproof.BigInteger}
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the given nonce was actually used during the signing process
|
||||
* of the given signature.
|
||||
* @param {!ArrayBuffer} msg The message that was signed
|
||||
* @param {!wycheproof.BigInteger} r The 'r' value of the signature
|
||||
* @param {!wycheproof.BigInteger} s The 's' value of the signature
|
||||
* @param {!wycheproof.BigInteger} d The private component of the ECDSA key
|
||||
* @param {!wycheproof.BigInteger} k The nonce that needs to be checked
|
||||
* @param {!string} curveName The curve name
|
||||
*/
|
||||
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));
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests whether there is bias in the nonce generation during
|
||||
* ECDSA signing process. The test case's parameters are passed
|
||||
* to 'this' variable of the function. The test is based on the fact that
|
||||
* if we throw a fair coin nTests times then the probability that
|
||||
* either heads or tails appears less than minCount is less than 2^{-32}.
|
||||
* Therefore the test below is not expected to fail unless the generation
|
||||
* of the one time keys is indeed biased.
|
||||
*
|
||||
* NOTE: This test only works correctly if the length of the output of the
|
||||
* hash function is equal to the bit length of the curve order.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
Ecdsa.testBias = function() {
|
||||
var tc = this;
|
||||
var nDone = 0;
|
||||
var countLsb = 0;
|
||||
var countMsb = 0;
|
||||
var promise = new Promise(function(resolve, reject){
|
||||
for (var i = 0; i < tc.nTests; i++) {
|
||||
Ecdsa.generateKey(tc.hashAlg, tc.curveName).then(function(key){
|
||||
Ecdsa.exportKey(key.privateKey).then(function(keyData){
|
||||
Ecdsa.sign(key.privateKey, tc.msg, tc.hashAlg).then(function(sig){
|
||||
HashUtil.digest(tc.hashAlg, tc.msg).then(function(digest){
|
||||
var curveSpec = EcUtil.getCurveSpec(tc.curveName);
|
||||
var h = new BigInteger(new Uint8Array(digest));
|
||||
// private key value
|
||||
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);
|
||||
// Uncomment this line to check correctness of nonce calculation
|
||||
// Ecdsa.checkNonceCorrectness(tc.msg, r, s, d, k, tc.curveName);
|
||||
var halfN = curveSpec.n.shiftRight(1);
|
||||
if (k.isBitSet(0)) countLsb += 1;
|
||||
if (k.compare(halfN) == 1) countMsb += 1;
|
||||
|
||||
nDone += 1;
|
||||
if (nDone == tc.nTests) {
|
||||
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);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}).catch(function(err){
|
||||
reject('Failed to sign: ' + err);
|
||||
});
|
||||
}).catch(function(err){
|
||||
reject('Failed to export private key: ' + err);
|
||||
});
|
||||
}).catch(function(err){
|
||||
reject('Failed to generate key: ' + err);
|
||||
});
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parameters of a ECDSA bias test.
|
||||
* @param {!string} hashAlg The hash algorithm
|
||||
* @param {!string} curveName The curve name
|
||||
* @param {!ArrayBuffer} msg The message that was signed
|
||||
* @param {!number} nTests The number of key to be generated
|
||||
* @param {!number} minCount
|
||||
* The expected number of times that a bit of the nonce should be 1 or 0
|
||||
*/
|
||||
var EcdsaBiasTestCase = function(hashAlg, curveName, msg, nTests, minCount) {
|
||||
this.msg = msg;
|
||||
this.hashAlg = hashAlg;
|
||||
this.curveName = curveName;
|
||||
this.nTests = nTests;
|
||||
this.minCount = minCount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests whether there is bias in the nonce generation during
|
||||
* ECDSA signing process. It considers three curves: secp256r1, secp384r1,
|
||||
* and secp521r1.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
function testEcdsaBiasAll() {
|
||||
var testCase = new goog.testing.TestCase();
|
||||
testCase.promiseTimeout = 120*1000;
|
||||
var msg = TestUtil.hexToArrayBuffer('48656c6c6f'); // msg = 'Hello'
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for RSA-OAEP encryption implementations of Web Crypto API.
|
||||
*/
|
||||
goog.provide('wycheproof.webcryptoapi.RSA-OAED');
|
||||
goog.require('e2e.random');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('wycheproof.TestUtil');
|
||||
goog.require('wycheproof.webcryptoapi.RsaUtil');
|
||||
|
||||
var RsaUtil = wycheproof.webcryptoapi.RsaUtil;
|
||||
var TestUtil = wycheproof.TestUtil;
|
||||
|
||||
/**
|
||||
* Tries decrypting random messages with a given algorithm. Counts the number of distinct error
|
||||
* messages and expects this number to be 1.
|
||||
*
|
||||
* <p><b>References:</b>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Bleichenbacher, "Chosen ciphertext attacks against protocols based on the RSA encryption
|
||||
* standard PKCS# 1" Crypto 98
|
||||
* <li>Manger, "A chosen ciphertext attack on RSA optimal asymmetric encryption padding (OAEP)
|
||||
* as standardized in PKCS# 1 v2.0", Crypto 2001 This paper shows that OAEP is susceptible
|
||||
* to a chosen ciphertext attack if error messages distinguish between different failure
|
||||
* condidtions.
|
||||
* <li>Bardou, Focardi, Kawamoto, Simionato, Steel, Tsay "Efficient Padding Oracle Attacks on
|
||||
* Cryptographic Hardware", Crypto 2012 The paper shows that small differences on what
|
||||
* information an attacker recieves can make a big difference on the number of chosen
|
||||
* message necessary for an attack.
|
||||
* <li>Smart, "Errors matter: Breaking RSA-based PIN encryption with thirty ciphertext validity
|
||||
* queries" RSA conference, 2010 This paper shows that padding oracle attacks can be
|
||||
* successful with even a small number of queries.
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Some recent bugs:</b> CVE-2012-5081: Java JSSE provider leaked information through
|
||||
* exceptions and timing. Both the PKCS #1 padding and the OAEP padding were broken:
|
||||
* http://www-brs.ub.ruhr-uni-bochum.de/netahtml/HSS/Diss/MeyerChristopher/diss.pdf
|
||||
*
|
||||
* <p><b>What this test does not (yet) cover:</b>
|
||||
*
|
||||
* <ul>
|
||||
* <li> A previous version of one of the provider leaked the block type. (when was this fixed?)
|
||||
* <li> Some attacks require a large number of ciphertexts to be detected if random ciphertexts
|
||||
* are used. Such problems require specifically crafted ciphertexts to run in a unit test.
|
||||
* E.g. "Attacking RSA-based Sessions in SSL/TLS" by V. Klima, O. Pokorny, and T. Rosa:
|
||||
* https://eprint.iacr.org/2003/052/
|
||||
* <li> Timing leakages because of differences in parsing the padding (e.g. CVE-2015-7827) Such
|
||||
* differences are too small to be reliably detectable in unit tests.
|
||||
* </ul>
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
function testRsaEncryptionException() {
|
||||
var promise = new Promise(function(resolve, reject){
|
||||
var scheme = RsaUtil.RSA_OAEP;
|
||||
var keySize = 1024;
|
||||
var e = RsaUtil.E_65537;
|
||||
var hashAlg = 'SHA-256';
|
||||
var usages = ['encrypt', 'decrypt'];
|
||||
var ctLen = keySize/8;
|
||||
var nTest = 1000;
|
||||
var nDone = 0;
|
||||
var exceptions = new Set();
|
||||
RsaUtil.generateKey(scheme, keySize, e, hashAlg, usages).then(function(key) {
|
||||
for (var i = 0; i < nTest; i++) {
|
||||
var ct = new Uint8Array(e2e.random.getRandomBytes(ctLen)).buffer;
|
||||
RsaUtil.decrypt(scheme, key.privateKey, ct).then(function(pt){
|
||||
reject('RSA-OAEP decryption should not succeed on random bytes');
|
||||
}).catch(function(err){
|
||||
exceptions.add(err.name + ': ' + err.message);
|
||||
nDone += 1;
|
||||
if (nDone == nTest) {
|
||||
if (exceptions.size > 1) {
|
||||
var msg = 'Exceptions leak information about the padding for ' + scheme + '\n';
|
||||
exceptions.forEach(function(e) {
|
||||
msg += e;
|
||||
});
|
||||
reject(msg);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch(function(err){
|
||||
reject('Failed to generate key: ' + err);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for RSASSA-PKCS1-v1_5 signature implementations of Web Crypto APIs.
|
||||
*/
|
||||
goog.provide('wycheproof.webcryptoapi.RSASSA-PKCS1-V1_5');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('wycheproof.BigInteger');
|
||||
goog.require('wycheproof.TestUtil');
|
||||
goog.require('wycheproof.webcryptoapi.HashUtil');
|
||||
goog.require('wycheproof.webcryptoapi.RsaUtil');
|
||||
|
||||
var TestUtil = wycheproof.TestUtil;
|
||||
var RsaUtil = wycheproof.webcryptoapi.RsaUtil;
|
||||
var HashUtil = wycheproof.webcryptoapi.HashUtil;
|
||||
var BigInteger = wycheproof.BigInteger;
|
||||
// Test vector file
|
||||
var RSASSA_PKCS1_SIGNATURE_VECTOR_FILE
|
||||
= '../../testvectors/rsa_signature_test.json';
|
||||
|
||||
/**
|
||||
* Tests RSASSA-PKCS1-v1_5 signature implementation with a number of vectors.
|
||||
*
|
||||
* @return {!Promise}
|
||||
*/
|
||||
function testRsaSsaPkcs1Vectors() {
|
||||
var tv = TestUtil.readJsonTestVectorsFromFile(
|
||||
RSASSA_PKCS1_SIGNATURE_VECTOR_FILE);
|
||||
var testCase = new goog.testing.TestCase();
|
||||
|
||||
for (var i = 0; i < tv['testGroups'].length; i++) {
|
||||
var tg = tv['testGroups'][i];
|
||||
var e = BigInteger.fromHex(tg['e']).toBase64Url();
|
||||
var n = BigInteger.fromHex(tg['n']).toBase64Url();
|
||||
var hashAlg = tg['sha'];
|
||||
if (SUPPORTED['hash'].indexOf(hashAlg) == -1) {
|
||||
continue;
|
||||
}
|
||||
for (var j = 0; j < tg['tests'].length; j++) {
|
||||
var tc = tg['tests'][j];
|
||||
var msg = BigInteger.fromHex(tc['message']).toArrayBuffer();
|
||||
var sig = TestUtil.hexToArrayBuffer(tc['sig']);
|
||||
var result = tc['result'];
|
||||
var tcId = tc['tcId'];
|
||||
|
||||
// Creates new Test Case object
|
||||
var test = new RsaUtil.RsaSignatureTestCase(tcId, e, n, hashAlg,
|
||||
RsaUtil.RSASSA_PKCS1, msg, sig, result);
|
||||
testCase.addNewTest('Test case ' + tcId, RsaUtil.testVerification, test);
|
||||
}
|
||||
}
|
||||
|
||||
return testCase.runTestsReturningPromise().then(TestUtil.checkTestCaseResult);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user