ml_kem_encap_decap.js (12283B)
1 // Test implementation for ML-KEM encapsulate and decapsulate operations 2 3 function define_tests() { 4 var subtle = self.crypto.subtle; 5 6 // Test data for all ML-KEM variants 7 var variants = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']; 8 9 variants.forEach(function (algorithmName) { 10 var testVector = ml_kem_vectors[algorithmName]; 11 12 // Test encapsulateBits operation 13 promise_test(async function (test) { 14 // Generate a key pair for testing 15 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 16 'encapsulateBits', 17 'decapsulateBits', 18 ]); 19 20 // Test encapsulateBits 21 var encapsulatedBits = await subtle.encapsulateBits( 22 { name: algorithmName }, 23 keyPair.publicKey 24 ); 25 26 assert_true( 27 encapsulatedBits instanceof Object, 28 'encapsulateBits should return an object' 29 ); 30 assert_true( 31 encapsulatedBits.hasOwnProperty('sharedKey'), 32 'Result should have sharedKey property' 33 ); 34 assert_true( 35 encapsulatedBits.hasOwnProperty('ciphertext'), 36 'Result should have ciphertext property' 37 ); 38 assert_true( 39 encapsulatedBits.sharedKey instanceof ArrayBuffer, 40 'sharedKey should be ArrayBuffer' 41 ); 42 assert_true( 43 encapsulatedBits.ciphertext instanceof ArrayBuffer, 44 'ciphertext should be ArrayBuffer' 45 ); 46 47 // Verify sharedKey length (should be 32 bytes for all ML-KEM variants) 48 assert_equals( 49 encapsulatedBits.sharedKey.byteLength, 50 32, 51 'Shared key should be 32 bytes' 52 ); 53 54 // Verify ciphertext length based on algorithm variant 55 var expectedCiphertextLength; 56 switch (algorithmName) { 57 case 'ML-KEM-512': 58 expectedCiphertextLength = 768; 59 break; 60 case 'ML-KEM-768': 61 expectedCiphertextLength = 1088; 62 break; 63 case 'ML-KEM-1024': 64 expectedCiphertextLength = 1568; 65 break; 66 } 67 assert_equals( 68 encapsulatedBits.ciphertext.byteLength, 69 expectedCiphertextLength, 70 'Ciphertext should be ' + 71 expectedCiphertextLength + 72 ' bytes for ' + 73 algorithmName 74 ); 75 }, algorithmName + ' encapsulateBits basic functionality'); 76 77 // Test decapsulateBits operation 78 promise_test(async function (test) { 79 // Generate a key pair for testing 80 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 81 'encapsulateBits', 82 'decapsulateBits', 83 ]); 84 85 // First encapsulate to get ciphertext 86 var encapsulatedBits = await subtle.encapsulateBits( 87 { name: algorithmName }, 88 keyPair.publicKey 89 ); 90 91 // Then decapsulate using the private key 92 var decapsulatedBits = await subtle.decapsulateBits( 93 { name: algorithmName }, 94 keyPair.privateKey, 95 encapsulatedBits.ciphertext 96 ); 97 98 assert_true( 99 decapsulatedBits instanceof ArrayBuffer, 100 'decapsulateBits should return ArrayBuffer' 101 ); 102 assert_equals( 103 decapsulatedBits.byteLength, 104 32, 105 'Decapsulated bits should be 32 bytes' 106 ); 107 108 // The decapsulated shared secret should match the original 109 assert_true( 110 equalBuffers(decapsulatedBits, encapsulatedBits.sharedKey), 111 'Decapsulated shared secret should match original' 112 ); 113 }, algorithmName + ' decapsulateBits basic functionality'); 114 115 // Test encapsulateKey operation 116 promise_test(async function (test) { 117 // Generate a key pair for testing 118 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 119 'encapsulateKey', 120 'decapsulateKey', 121 ]); 122 123 // Test encapsulateKey with AES-GCM as the shared key algorithm 124 var encapsulatedKey = await subtle.encapsulateKey( 125 { name: algorithmName }, 126 keyPair.publicKey, 127 { name: 'AES-GCM', length: 256 }, 128 true, 129 ['encrypt', 'decrypt'] 130 ); 131 132 assert_true( 133 encapsulatedKey instanceof Object, 134 'encapsulateKey should return an object' 135 ); 136 assert_true( 137 encapsulatedKey.hasOwnProperty('sharedKey'), 138 'Result should have sharedKey property' 139 ); 140 assert_true( 141 encapsulatedKey.hasOwnProperty('ciphertext'), 142 'Result should have ciphertext property' 143 ); 144 assert_true( 145 encapsulatedKey.sharedKey instanceof CryptoKey, 146 'sharedKey should be a CryptoKey' 147 ); 148 assert_true( 149 encapsulatedKey.ciphertext instanceof ArrayBuffer, 150 'ciphertext should be ArrayBuffer' 151 ); 152 153 // Verify the shared key properties 154 assert_equals( 155 encapsulatedKey.sharedKey.type, 156 'secret', 157 'Shared key should be secret type' 158 ); 159 assert_equals( 160 encapsulatedKey.sharedKey.algorithm.name, 161 'AES-GCM', 162 'Shared key algorithm should be AES-GCM' 163 ); 164 assert_equals( 165 encapsulatedKey.sharedKey.algorithm.length, 166 256, 167 'Shared key length should be 256' 168 ); 169 assert_true( 170 encapsulatedKey.sharedKey.extractable, 171 'Shared key should be extractable as specified' 172 ); 173 assert_array_equals( 174 encapsulatedKey.sharedKey.usages, 175 ['encrypt', 'decrypt'], 176 'Shared key should have correct usages' 177 ); 178 }, algorithmName + ' encapsulateKey basic functionality'); 179 180 // Test decapsulateKey operation 181 promise_test(async function (test) { 182 // Generate a key pair for testing 183 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 184 'encapsulateKey', 185 'decapsulateKey', 186 ]); 187 188 // First encapsulate to get ciphertext 189 var encapsulatedKey = await subtle.encapsulateKey( 190 { name: algorithmName }, 191 keyPair.publicKey, 192 { name: 'AES-GCM', length: 256 }, 193 true, 194 ['encrypt', 'decrypt'] 195 ); 196 197 // Then decapsulate using the private key 198 var decapsulatedKey = await subtle.decapsulateKey( 199 { name: algorithmName }, 200 keyPair.privateKey, 201 encapsulatedKey.ciphertext, 202 { name: 'AES-GCM', length: 256 }, 203 true, 204 ['encrypt', 'decrypt'] 205 ); 206 207 assert_true( 208 decapsulatedKey instanceof CryptoKey, 209 'decapsulateKey should return a CryptoKey' 210 ); 211 assert_equals( 212 decapsulatedKey.type, 213 'secret', 214 'Decapsulated key should be secret type' 215 ); 216 assert_equals( 217 decapsulatedKey.algorithm.name, 218 'AES-GCM', 219 'Decapsulated key algorithm should be AES-GCM' 220 ); 221 assert_equals( 222 decapsulatedKey.algorithm.length, 223 256, 224 'Decapsulated key length should be 256' 225 ); 226 assert_true( 227 decapsulatedKey.extractable, 228 'Decapsulated key should be extractable as specified' 229 ); 230 assert_array_equals( 231 decapsulatedKey.usages, 232 ['encrypt', 'decrypt'], 233 'Decapsulated key should have correct usages' 234 ); 235 236 // Extract both keys and verify they are identical 237 var originalKeyMaterial = await subtle.exportKey( 238 'raw', 239 encapsulatedKey.sharedKey 240 ); 241 var decapsulatedKeyMaterial = await subtle.exportKey( 242 'raw', 243 decapsulatedKey 244 ); 245 246 assert_true( 247 equalBuffers(originalKeyMaterial, decapsulatedKeyMaterial), 248 'Decapsulated key material should match original' 249 ); 250 }, algorithmName + ' decapsulateKey basic functionality'); 251 252 // Test error cases for encapsulateBits 253 promise_test(async function (test) { 254 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 255 'encapsulateBits', 256 'decapsulateBits', 257 ]); 258 259 // Test with wrong key type (private key instead of public) 260 await promise_rejects_dom( 261 test, 262 'InvalidAccessError', 263 subtle.encapsulateBits({ name: algorithmName }, keyPair.privateKey), 264 'encapsulateBits should reject private key' 265 ); 266 267 // Test with wrong algorithm name 268 await promise_rejects_dom( 269 test, 270 'InvalidAccessError', 271 subtle.encapsulateBits({ name: 'AES-GCM' }, keyPair.publicKey), 272 'encapsulateBits should reject mismatched algorithm' 273 ); 274 }, algorithmName + ' encapsulateBits error cases'); 275 276 // Test error cases for decapsulateBits 277 promise_test(async function (test) { 278 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 279 'encapsulateBits', 280 'decapsulateBits', 281 ]); 282 283 var encapsulatedBits = await subtle.encapsulateBits( 284 { name: algorithmName }, 285 keyPair.publicKey 286 ); 287 288 // Test with wrong key type (public key instead of private) 289 await promise_rejects_dom( 290 test, 291 'InvalidAccessError', 292 subtle.decapsulateBits( 293 { name: algorithmName }, 294 keyPair.publicKey, 295 encapsulatedBits.ciphertext 296 ), 297 'decapsulateBits should reject public key' 298 ); 299 300 // Test with wrong algorithm name 301 await promise_rejects_dom( 302 test, 303 'InvalidAccessError', 304 subtle.decapsulateBits( 305 { name: 'AES-GCM' }, 306 keyPair.privateKey, 307 encapsulatedBits.ciphertext 308 ), 309 'decapsulateBits should reject mismatched algorithm' 310 ); 311 312 // Test with invalid ciphertext 313 var invalidCiphertext = new Uint8Array(10); // Wrong size 314 await promise_rejects_dom( 315 test, 316 'OperationError', 317 subtle.decapsulateBits( 318 { name: algorithmName }, 319 keyPair.privateKey, 320 invalidCiphertext 321 ), 322 'decapsulateBits should reject invalid ciphertext' 323 ); 324 }, algorithmName + ' decapsulateBits error cases'); 325 326 // Test error cases for encapsulateKey 327 promise_test(async function (test) { 328 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 329 'encapsulateKey', 330 'decapsulateKey', 331 ]); 332 333 // Test with key without encapsulateKey usage 334 var wrongKeyPair = await subtle.generateKey( 335 { name: algorithmName }, 336 false, 337 ['decapsulateKey'] // Missing encapsulateKey usage 338 ); 339 340 await promise_rejects_dom( 341 test, 342 'InvalidAccessError', 343 subtle.encapsulateKey( 344 { name: algorithmName }, 345 wrongKeyPair.publicKey, 346 { name: 'AES-GCM', length: 256 }, 347 true, 348 ['encrypt', 'decrypt'] 349 ), 350 'encapsulateKey should reject key without encapsulateKey usage' 351 ); 352 }, algorithmName + ' encapsulateKey error cases'); 353 354 // Test error cases for decapsulateKey 355 promise_test(async function (test) { 356 var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ 357 'encapsulateKey', 358 'decapsulateKey', 359 ]); 360 361 var encapsulatedKey = await subtle.encapsulateKey( 362 { name: algorithmName }, 363 keyPair.publicKey, 364 { name: 'AES-GCM', length: 256 }, 365 true, 366 ['encrypt', 'decrypt'] 367 ); 368 369 // Test with key without decapsulateKey usage 370 var wrongKeyPair = await subtle.generateKey( 371 { name: algorithmName }, 372 false, 373 ['encapsulateKey'] // Missing decapsulateKey usage 374 ); 375 376 await promise_rejects_dom( 377 test, 378 'InvalidAccessError', 379 subtle.decapsulateKey( 380 { name: algorithmName }, 381 wrongKeyPair.privateKey, 382 encapsulatedKey.ciphertext, 383 { name: 'AES-GCM', length: 256 }, 384 true, 385 ['encrypt', 'decrypt'] 386 ), 387 'decapsulateKey should reject key without decapsulateKey usage' 388 ); 389 }, algorithmName + ' decapsulateKey error cases'); 390 }); 391 } 392 393 // Helper function to compare two ArrayBuffers 394 function equalBuffers(a, b) { 395 if (a.byteLength !== b.byteLength) { 396 return false; 397 } 398 var aBytes = new Uint8Array(a); 399 var bBytes = new Uint8Array(b); 400 for (var i = 0; i < a.byteLength; i++) { 401 if (aBytes[i] !== bBytes[i]) { 402 return false; 403 } 404 } 405 return true; 406 } 407 408 function run_test() { 409 define_tests(); 410 }