tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }