blob: 4f0d15aa1a69c4ce8482355e3dfb97351ca245e9 [file] [log] [blame]
// 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.
//
////////////////////////////////////////////////////////////////////////////////
goog.module('tink.subtle.EciesHkdfKemRecipientTest');
goog.setTestOnly('tink.subtle.EciesHkdfKemRecipientTest');
const Bytes = goog.require('tink.subtle.Bytes');
const EciesHkdfKemRecipient = goog.require('tink.subtle.EciesHkdfKemRecipient');
const EciesHkdfKemSender = goog.require('tink.subtle.EciesHkdfKemSender');
const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
const Random = goog.require('tink.subtle.Random');
const TestCase = goog.require('goog.testing.TestCase');
const testSuite = goog.require('goog.testing.testSuite');
const userAgent = goog.require('goog.userAgent');
testSuite({
shouldRunTests() {
// https://msdn.microsoft.com/en-us/library/mt801195(v=vs.85).aspx
return !userAgent.EDGE; // b/120286783
},
setUp() {
// Use a generous promise timeout for running continuously.
TestCase.getActiveTestCase().promiseTimeout = 1000 * 1000; // 1000s
},
tearDown() {
// Reset the promise timeout to default value.
TestCase.getActiveTestCase().promiseTimeout = 1000; // 1s
},
async testEncapDecap() {
const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
const privateKey = await EllipticCurves.exportCryptoKey(keyPair.privateKey);
const sender = await EciesHkdfKemSender.newInstance(publicKey);
const recipient = await EciesHkdfKemRecipient.newInstance(privateKey);
for (let i = 1; i < 20; i++) {
const keySizeInBytes = i;
const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
const hkdfHash = 'SHA-256';
const hkdfInfo = Random.randBytes(i);
const hkdfSalt = Random.randBytes(i);
const kemKeyToken = await sender.encapsulate(
keySizeInBytes, pointFormat, hkdfHash, hkdfInfo, hkdfSalt);
const key = await recipient.decapsulate(
kemKeyToken['token'], keySizeInBytes, pointFormat, hkdfHash, hkdfInfo,
hkdfSalt);
assertEquals(keySizeInBytes, kemKeyToken['key'].length);
assertEquals(Bytes.toHex(key), Bytes.toHex(kemKeyToken['key']));
}
},
async testDecap_nonIntegerKeySize() {
const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
const privateKey = await EllipticCurves.exportCryptoKey(keyPair.privateKey);
const sender = await EciesHkdfKemSender.newInstance(publicKey);
const recipient = await EciesHkdfKemRecipient.newInstance(privateKey);
const keySizeInBytes = 16;
const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
const hkdfHash = 'SHA-256';
const hkdfInfo = Random.randBytes(16);
const hkdfSalt = Random.randBytes(16);
const kemKeyToken = await sender.encapsulate(
keySizeInBytes, pointFormat, hkdfHash, hkdfInfo, hkdfSalt);
try {
await recipient.decapsulate(
kemKeyToken['token'], NaN, pointFormat, hkdfHash, hkdfInfo, hkdfSalt);
fail('An exception should be thrown.');
} catch (e) {
assertEquals('CustomError: size must be an integer', e.toString());
}
try {
await recipient.decapsulate(
kemKeyToken['token'], undefined, pointFormat, hkdfHash, hkdfInfo,
hkdfSalt);
fail('An exception should be thrown.');
} catch (e) {
assertEquals('CustomError: size must be an integer', e.toString());
}
try {
await recipient.decapsulate(
kemKeyToken['token'], 1.8, pointFormat, hkdfHash, hkdfInfo, hkdfSalt);
fail('An exception should be thrown.');
} catch (e) {
assertEquals('CustomError: size must be an integer', e.toString());
}
},
async testNewInstance_invalidParameters() {
// Test newInstance without key.
try {
await EciesHkdfKemRecipient.newInstance(null);
fail('An exception should be thrown.');
} catch (e) {
}
// Test newInstance with public key instead private key.
const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
try {
await EciesHkdfKemRecipient.newInstance(publicKey);
fail('An exception should be thrown.');
} catch (e) {
}
// Test newInstance with CryptoKey instead of JSON key.
try {
await EciesHkdfKemRecipient.newInstance(keyPair.publicKey);
fail('An exception should be thrown.');
} catch (e) {
}
},
async testNewInstance_invalidPrivateKey() {
for (let testVector of TEST_VECTORS) {
const ellipticCurveString = EllipticCurves.curveToString(testVector.crv);
const privateJwk = EllipticCurves.pointDecode(
ellipticCurveString, testVector.pointFormat,
Bytes.fromHex(testVector.privateKeyPoint));
privateJwk['d'] = Bytes.toBase64(
Bytes.fromHex(testVector.privateKeyValue), /* opt_webSafe = */ true);
// Change the x value such that the key si no more valid. Recipient should
// either throw an exception or ignore the x value and compute the same
// output value.
const xLength = EllipticCurves.fieldSizeInBytes(testVector.crv);
privateJwk['x'] =
Bytes.toBase64(new Uint8Array(xLength), /* opt_webSafe = */ true);
let output;
try {
const recipient = await EciesHkdfKemRecipient.newInstance(privateJwk);
const hkdfInfo = Bytes.fromHex(testVector.hkdfInfo);
const salt = Bytes.fromHex(testVector.salt);
output = await recipient.decapsulate(
Bytes.fromHex(testVector.token), testVector.outputLength,
testVector.pointFormat, testVector.hashType, hkdfInfo, salt);
} catch (e) {
// Everything works properly if exception was thrown.
return;
}
// If there was no exception, the output should be still correct (x value
// should be ignored during the computation).
assertEquals(testVector.expectedOutput, Bytes.toHex(output));
}
},
async testConstructor_invalidParameters() {
// Test constructor without key.
try {
new EciesHkdfKemRecipient(null);
fail('An exception should be thrown.');
} catch (e) {
assertEquals(
'CustomError: Private key has to be non-null.', e.toString());
}
// Test public key instead of private key.
const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
try {
new EciesHkdfKemRecipient(keyPair.publicKey);
fail('An exception should be thrown.');
} catch (e) {
assertEquals(
'CustomError: Expected crypto key of type: private.', e.toString());
}
// Test that JSON key cannot be used instead of CryptoKey.
const privateKey = await EllipticCurves.exportCryptoKey(keyPair.privateKey);
try {
new EciesHkdfKemRecipient(privateKey);
fail('An exception should be thrown.');
} catch (e) {
}
},
async testEncapDecap_differentParams() {
const curveTypes = Object.keys(EllipticCurves.CurveType);
const hashTypes = ['SHA-1', 'SHA-256', 'SHA-512'];
for (let curve of curveTypes) {
const curveString =
EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
for (let hashType of hashTypes) {
const keyPair =
await EllipticCurves.generateKeyPair('ECDH', curveString);
const keySizeInBytes = 32;
const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
const hkdfInfo = Random.randBytes(8);
const hkdfSalt = Random.randBytes(16);
const publicKey =
await EllipticCurves.exportCryptoKey(keyPair.publicKey);
const sender = await EciesHkdfKemSender.newInstance(publicKey);
const kemKeyToken = await sender.encapsulate(
keySizeInBytes, pointFormat, hashType, hkdfInfo, hkdfSalt);
const privateKey =
await EllipticCurves.exportCryptoKey(keyPair.privateKey);
const recipient = await EciesHkdfKemRecipient.newInstance(privateKey);
const key = await recipient.decapsulate(
kemKeyToken['token'], keySizeInBytes, pointFormat, hashType,
hkdfInfo, hkdfSalt);
assertEquals(keySizeInBytes, kemKeyToken['key'].length);
assertEquals(Bytes.toHex(key), Bytes.toHex(kemKeyToken['key']));
}
}
},
async testEncapDecap_modifiedToken() {
const curveTypes = Object.keys(EllipticCurves.CurveType);
const hashTypes = ['SHA-1', 'SHA-256', 'SHA-512'];
for (let crvId of curveTypes) {
const curve = EllipticCurves.CurveType[crvId];
const curveString = EllipticCurves.curveToString(curve);
for (let hashType of hashTypes) {
const keyPair =
await EllipticCurves.generateKeyPair('ECDH', curveString);
const privateKey =
await EllipticCurves.exportCryptoKey(keyPair.privateKey);
const recipient = await EciesHkdfKemRecipient.newInstance(privateKey);
const keySizeInBytes = 32;
const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
const hkdfInfo = Random.randBytes(8);
const hkdfSalt = Random.randBytes(16);
// Create invalid token (EC point), while preserving the 0x04 prefix
// byte.
const token = Random.randBytes(
EllipticCurves.encodingSizeInBytes(curve, pointFormat));
token[0] = 0x04;
try {
await recipient.decapsulate(
token, keySizeInBytes, pointFormat, hashType, hkdfInfo, hkdfSalt);
fail('Should throw an exception');
} catch (e) {
}
}
}
},
async testDecapsulate_testVectorsGeneratedByJava() {
for (let testVector of TEST_VECTORS) {
const ellipticCurveString = EllipticCurves.curveToString(testVector.crv);
const privateJwk = EllipticCurves.pointDecode(
ellipticCurveString, testVector.pointFormat,
Bytes.fromHex(testVector.privateKeyPoint));
privateJwk['d'] = Bytes.toBase64(
Bytes.fromHex(testVector.privateKeyValue), /* opt_webSafe = */ true);
const recipient = await EciesHkdfKemRecipient.newInstance(privateJwk);
const hkdfInfo = Bytes.fromHex(testVector.hkdfInfo);
const salt = Bytes.fromHex(testVector.salt);
const output = await recipient.decapsulate(
Bytes.fromHex(testVector.token), testVector.outputLength,
testVector.pointFormat, testVector.hashType, hkdfInfo, salt);
assertEquals(testVector.expectedOutput, Bytes.toHex(output));
}
},
});
class TestVector {
/**
* @param {!EllipticCurves.CurveType} crv
* @param {string} hashType
* @param {!EllipticCurves.PointFormatType} pointFormat
* @param {string} token
* @param {string} privateKeyPoint
* @param {string} privateKeyValue
* @param {string} salt
* @param {string} hkdfInfo
* @param {number} outputLength
* @param {string} expectedOutput
*/
constructor(
crv, hashType, pointFormat, token, privateKeyPoint, privateKeyValue, salt,
hkdfInfo, outputLength, expectedOutput) {
/** @const {!EllipticCurves.CurveType} */
this.crv = crv;
/** @const {string} */
this.hashType = hashType;
/** @const {!EllipticCurves.PointFormatType} */
this.pointFormat = pointFormat;
/** @const {string} */
this.token = token;
/** @const {string} */
this.privateKeyPoint = privateKeyPoint;
/** @const {string} */
this.privateKeyValue = privateKeyValue;
/** @const {string} */
this.salt = salt;
/** @const {string} */
this.hkdfInfo = hkdfInfo;
/** @const {number} */
this.outputLength = outputLength;
/** @const {string} */
this.expectedOutput = expectedOutput;
}
}
// Test vectors generated by Java version of Tink.
//
// Token (i.e. sender public key) and privateKeyPoint values are in UNCOMPRESSED
// EcPoint encoding (i.e. it has prefix '04' followed by x and y values).
/** {!Array<!TestVector>} */
const TEST_VECTORS = [
new TestVector(
EllipticCurves.CurveType.P256, 'SHA-256',
EllipticCurves.PointFormatType.UNCOMPRESSED,
/* token = */ '04' +
'5cdd8e426d11970a610f0e5f9b27f247a421c477b379f2ff3fd3bac50dfff9ff' +
'7cada79ab1de9ce4aeaff45fcd2628d1b6d7ecac99d4c26409d4ab8a362c8e7a',
/* privateKeyPoint = */ '04' +
'4adf0fff84b995bb97af250128a3d779c86ba3cd7e5c0fa2c10895d0b995aaee' +
'cdced57616ebb04c808f191c2bf3848c495dcfddcdd1bb73d8ea7a15c642af05',
/* privateKeyValue = */
'da73e10f7d81483daa63438b982c879706bcf8fef8c7c4d3071c3ef2367714f3',
/* salt = */ 'abcdef',
/* hkdfInfo = */ 'aaaaaaaaaaaaaaaa',
/* outputLength = */ 32,
/* expectedOutput = */
'aeeee35a14967310798f037e2f126e2e326369115eb9e2d1a34d9c6761f60511'),
new TestVector(
EllipticCurves.CurveType.P384, 'SHA-1',
EllipticCurves.PointFormatType.UNCOMPRESSED,
/* token = */ '04' +
'75bc8a2e6cf80ce2e0a1cd60ab3d68e4d357b58ff69f0de14b7ec13c58a79750496e07db3f933167148d80730b96f000' +
'9389967de410535ca3e103e7ce73dae9525f934589a6cd1fca37e61411985788dcedc71b35ef63b7365e391f6e2a945f',
/* privateKeyPoint = */ '04' +
'5f81886c4202897355b1da79348d53abd9e9119a7de6f5f10dfe751f7ca9c807035c029bac59499337c4af185fe61728' +
'f132bfb234365a9c61e1e56c11acca3bee6621961c7c38eb9dcbd39b332fd35006876dccdb206a7b2d43cf70589c3356',
/* privateKeyValue = */
'544b5f32731d6277fa71e756f0b2d6840f62e6b744a8b8cdf91f8cf29e6d8562f6237369721f756ab044711e0d42c53c',
/* salt = */ 'ababcdcd',
/* hkdfInfo = */ '100000000000000001',
/* outputLength = */ 32,
/* expectedOutput = */
'7a25c525eabaa0d994c27f7661a208b5ea25c2a778198237de6e4f235cd64a33'),
new TestVector(
EllipticCurves.CurveType.P521, 'SHA-512',
EllipticCurves.PointFormatType.UNCOMPRESSED,
/* token = */ '04' +
'0075192f8decddf7a0371b2c859aad738cc5424fa70e74b560070ed8309ae8a6064b06f9aaad8020ac8620e62a6c1196efa44180d325a36a54945743b9382bd49bc1' +
'000dfa1e30b228e975998b7afeaaf30235ec505960e58bf3269b69fffcbce9f15fc1441fab2ed97f554ae4bde8b956efb2372c5b330cb1aa0ab81b99e792acd7f5a8',
/* privateKeyPoint = */ '04' +
'00e57037a96bcbca532ef2f75646d825304ea716bbc9c4bf953455074347158f4818122c76e26a4cf94b39f451b7f5960b9cda43d49999ddc401c1be7f082052b387' +
'0147197ba83ec55c8b02e6cbe7b49ce6d6c238edb89561bde6b4574a585c684379d8040888117866823258216344a7268dc696c3a2d192824a1e693609b44661fc2c',
/* privateKeyValue = */
'001e5410117d22e95c5768b82a786dd66fa8c326b938a3a81fdd6113499437ae9f74e9f876adf085c187c6a147abc13460b8ed3050a6b228005426b61f2b616a79c6',
/* salt = */ '00001111',
/* hkdfInfo = */ '1234123412341234',
/* outputLength = */ 32,
/* expectedOutput = */
'3f7f64c7aba2cb012c9b5a952385290604b3b5843ec6e6714647a9c9d6ac87be')
];