Source code

Revision control

Copy as Markdown

Other Tools

// Test implementation for ML-KEM encapsulate and decapsulate operations
function define_tests() {
var subtle = self.crypto.subtle;
// Test data for all ML-KEM variants
var variants = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024'];
variants.forEach(function (algorithmName) {
var testVector = ml_kem_vectors[algorithmName];
// Test encapsulateBits operation
promise_test(async function (test) {
// Generate a key pair for testing
var keyPair = await subtle.generateKey({ name: algorithmName }, false, [
'encapsulateBits',
'decapsulateBits',
]);
// Test encapsulateBits
var encapsulatedBits = await subtle.encapsulateBits(
{ name: algorithmName },
keyPair.publicKey
);
assert_true(
encapsulatedBits instanceof Object,
'encapsulateBits should return an object'
);
assert_true(
encapsulatedBits.hasOwnProperty('sharedKey'),
'Result should have sharedKey property'
);
assert_true(
encapsulatedBits.hasOwnProperty('ciphertext'),
'Result should have ciphertext property'
);
assert_true(
encapsulatedBits.sharedKey instanceof ArrayBuffer,
'sharedKey should be ArrayBuffer'
);
assert_true(
encapsulatedBits.ciphertext instanceof ArrayBuffer,
'ciphertext should be ArrayBuffer'
);
// Verify sharedKey length (should be 32 bytes for all ML-KEM variants)
assert_equals(
encapsulatedBits.sharedKey.byteLength,
32,
'Shared key should be 32 bytes'
);
// Verify ciphertext length based on algorithm variant
var expectedCiphertextLength;
switch (algorithmName) {
case 'ML-KEM-512':
expectedCiphertextLength = 768;
break;
case 'ML-KEM-768':
expectedCiphertextLength = 1088;
break;
case 'ML-KEM-1024':
expectedCiphertextLength = 1568;
break;
}
assert_equals(
encapsulatedBits.ciphertext.byteLength,
expectedCiphertextLength,
'Ciphertext should be ' +
expectedCiphertextLength +
' bytes for ' +
algorithmName
);
}, algorithmName + ' encapsulateBits basic functionality');
// Test decapsulateBits operation
promise_test(async function (test) {
// Generate a key pair for testing
var keyPair = await subtle.generateKey({ name: algorithmName }, false, [
'encapsulateBits',
'decapsulateBits',
]);
// First encapsulate to get ciphertext
var encapsulatedBits = await subtle.encapsulateBits(
{ name: algorithmName },
keyPair.publicKey
);
// Then decapsulate using the private key
var decapsulatedBits = await subtle.decapsulateBits(
{ name: algorithmName },
keyPair.privateKey,
encapsulatedBits.ciphertext
);
assert_true(
decapsulatedBits instanceof ArrayBuffer,
'decapsulateBits should return ArrayBuffer'
);
assert_equals(
decapsulatedBits.byteLength,
32,
'Decapsulated bits should be 32 bytes'
);
// The decapsulated shared secret should match the original
assert_true(
equalBuffers(decapsulatedBits, encapsulatedBits.sharedKey),
'Decapsulated shared secret should match original'
);
}, algorithmName + ' decapsulateBits basic functionality');
// Test encapsulateKey operation
promise_test(async function (test) {
// Generate a key pair for testing
var keyPair = await subtle.generateKey({ name: algorithmName }, false, [
'encapsulateKey',
'decapsulateKey',
]);
// Test encapsulateKey with AES-GCM as the shared key algorithm
var encapsulatedKey = await subtle.encapsulateKey(
{ name: algorithmName },
keyPair.publicKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
assert_true(
encapsulatedKey instanceof Object,
'encapsulateKey should return an object'
);
assert_true(
encapsulatedKey.hasOwnProperty('sharedKey'),
'Result should have sharedKey property'
);
assert_true(
encapsulatedKey.hasOwnProperty('ciphertext'),
'Result should have ciphertext property'
);
assert_true(
encapsulatedKey.sharedKey instanceof CryptoKey,
'sharedKey should be a CryptoKey'
);
assert_true(
encapsulatedKey.ciphertext instanceof ArrayBuffer,
'ciphertext should be ArrayBuffer'
);
// Verify the shared key properties
assert_equals(
encapsulatedKey.sharedKey.type,
'secret',
'Shared key should be secret type'
);
assert_equals(
encapsulatedKey.sharedKey.algorithm.name,
'AES-GCM',
'Shared key algorithm should be AES-GCM'
);
assert_equals(
encapsulatedKey.sharedKey.algorithm.length,
256,
'Shared key length should be 256'
);
assert_true(
encapsulatedKey.sharedKey.extractable,
'Shared key should be extractable as specified'
);
assert_array_equals(
encapsulatedKey.sharedKey.usages,
['encrypt', 'decrypt'],
'Shared key should have correct usages'
);
}, algorithmName + ' encapsulateKey basic functionality');
// Test decapsulateKey operation
promise_test(async function (test) {
// Generate a key pair for testing
var keyPair = await subtle.generateKey({ name: algorithmName }, false, [
'encapsulateKey',
'decapsulateKey',
]);
// First encapsulate to get ciphertext
var encapsulatedKey = await subtle.encapsulateKey(
{ name: algorithmName },
keyPair.publicKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
// Then decapsulate using the private key
var decapsulatedKey = await subtle.decapsulateKey(
{ name: algorithmName },
keyPair.privateKey,
encapsulatedKey.ciphertext,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
assert_true(
decapsulatedKey instanceof CryptoKey,
'decapsulateKey should return a CryptoKey'
);
assert_equals(
decapsulatedKey.type,
'secret',
'Decapsulated key should be secret type'
);
assert_equals(
decapsulatedKey.algorithm.name,
'AES-GCM',
'Decapsulated key algorithm should be AES-GCM'
);
assert_equals(
decapsulatedKey.algorithm.length,
256,
'Decapsulated key length should be 256'
);
assert_true(
decapsulatedKey.extractable,
'Decapsulated key should be extractable as specified'
);
assert_array_equals(
decapsulatedKey.usages,
['encrypt', 'decrypt'],
'Decapsulated key should have correct usages'
);
// Extract both keys and verify they are identical
var originalKeyMaterial = await subtle.exportKey(
'raw',
encapsulatedKey.sharedKey
);
var decapsulatedKeyMaterial = await subtle.exportKey(
'raw',
decapsulatedKey
);
assert_true(
equalBuffers(originalKeyMaterial, decapsulatedKeyMaterial),
'Decapsulated key material should match original'
);
}, algorithmName + ' decapsulateKey basic functionality');
// Test error cases for encapsulateBits
promise_test(async function (test) {
var keyPair = await subtle.generateKey({ name: algorithmName }, false, [
'encapsulateBits',
'decapsulateBits',
]);
// Test with wrong key type (private key instead of public)
await promise_rejects_dom(
test,
'InvalidAccessError',
subtle.encapsulateBits({ name: algorithmName }, keyPair.privateKey),
'encapsulateBits should reject private key'
);
// Test with wrong algorithm name
await promise_rejects_dom(
test,
'InvalidAccessError',
subtle.encapsulateBits({ name: 'AES-GCM' }, keyPair.publicKey),
'encapsulateBits should reject mismatched algorithm'
);
}, algorithmName + ' encapsulateBits error cases');
// Test error cases for decapsulateBits
promise_test(async function (test) {
var keyPair = await subtle.generateKey({ name: algorithmName }, false, [
'encapsulateBits',
'decapsulateBits',
]);
var encapsulatedBits = await subtle.encapsulateBits(
{ name: algorithmName },
keyPair.publicKey
);
// Test with wrong key type (public key instead of private)
await promise_rejects_dom(
test,
'InvalidAccessError',
subtle.decapsulateBits(
{ name: algorithmName },
keyPair.publicKey,
encapsulatedBits.ciphertext
),
'decapsulateBits should reject public key'
);
// Test with wrong algorithm name
await promise_rejects_dom(
test,
'InvalidAccessError',
subtle.decapsulateBits(
{ name: 'AES-GCM' },
keyPair.privateKey,
encapsulatedBits.ciphertext
),
'decapsulateBits should reject mismatched algorithm'
);
// Test with invalid ciphertext
var invalidCiphertext = new Uint8Array(10); // Wrong size
await promise_rejects_dom(
test,
'OperationError',
subtle.decapsulateBits(
{ name: algorithmName },
keyPair.privateKey,
invalidCiphertext
),
'decapsulateBits should reject invalid ciphertext'
);
}, algorithmName + ' decapsulateBits error cases');
// Test error cases for encapsulateKey
promise_test(async function (test) {
var keyPair = await subtle.generateKey({ name: algorithmName }, false, [
'encapsulateKey',
'decapsulateKey',
]);
// Test with key without encapsulateKey usage
var wrongKeyPair = await subtle.generateKey(
{ name: algorithmName },
false,
['decapsulateKey'] // Missing encapsulateKey usage
);
await promise_rejects_dom(
test,
'InvalidAccessError',
subtle.encapsulateKey(
{ name: algorithmName },
wrongKeyPair.publicKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
),
'encapsulateKey should reject key without encapsulateKey usage'
);
}, algorithmName + ' encapsulateKey error cases');
// Test error cases for decapsulateKey
promise_test(async function (test) {
var keyPair = await subtle.generateKey({ name: algorithmName }, false, [
'encapsulateKey',
'decapsulateKey',
]);
var encapsulatedKey = await subtle.encapsulateKey(
{ name: algorithmName },
keyPair.publicKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
// Test with key without decapsulateKey usage
var wrongKeyPair = await subtle.generateKey(
{ name: algorithmName },
false,
['encapsulateKey'] // Missing decapsulateKey usage
);
await promise_rejects_dom(
test,
'InvalidAccessError',
subtle.decapsulateKey(
{ name: algorithmName },
wrongKeyPair.privateKey,
encapsulatedKey.ciphertext,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
),
'decapsulateKey should reject key without decapsulateKey usage'
);
}, algorithmName + ' decapsulateKey error cases');
});
}
// Helper function to compare two ArrayBuffers
function equalBuffers(a, b) {
if (a.byteLength !== b.byteLength) {
return false;
}
var aBytes = new Uint8Array(a);
var bBytes = new Uint8Array(b);
for (var i = 0; i < a.byteLength; i++) {
if (aBytes[i] !== bBytes[i]) {
return false;
}
}
return true;
}
function run_test() {
define_tests();
}