text: // 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.aead.AesGcmKeyManagerTest');
  goog.setTestOnly('tink.aead.AesGcmKeyManagerTest');
  
  const Aead = goog.require('tink.Aead');
  const AesGcmKeyManager = goog.require('tink.aead.AesGcmKeyManager');
  const Mac = goog.require('tink.Mac');
  const Random = goog.require('tink.subtle.Random');
  const {PbAesCtrKey, PbAesCtrKeyFormat, PbAesGcmKey, PbAesGcmKeyFormat, PbKeyData} = goog.require('google3.third_party.tink.javascript.internal.proto');
  
  const KEY_TYPE = 'type.googleapis.com/google.crypto.tink.AesGcmKey';
  const VERSION = 0;
  const PRIMITIVE = Aead;
  
  describe('aes gcm key manager test', function() {
    /////////////////////////////////////////////////////////////////////////////
    // tests for newKey method
  
    // newKey method -- key formats
    it('new key, invalid key format', function() {
      const keyFormat = new PbAesCtrKeyFormat();
      const manager = new AesGcmKeyManager();
  
      try {
        manager.getKeyFactory().newKey(keyFormat);
        fail('An exception should be thrown.');
      } catch (e) {
        expect(e.toString()).toBe(ExceptionText.invalidKeyFormat());
      }
    });
  
    it('new key, invalid serialized key format', function() {
      const keyFormat = new Uint8Array(0);
      const manager = new AesGcmKeyManager();
  
      try {
        manager.getKeyFactory().newKey(keyFormat);
        fail('An exception should be thrown.');
      } catch (e) {
        expect(e.toString()).toBe(ExceptionText.invalidSerializedKeyFormat());
      }
    });
  
    it('new key, unsupported key sizes', function() {
      const manager = new AesGcmKeyManager();
  
      for (let keySize = 0; keySize < 40; keySize++) {
        if (keySize === 16 || keySize === 32) {
          // Keys of size 16 and 32 bytes are supported.
          continue;
        }
        const keyFormat = createTestKeyFormat(keySize);
  
        try {
          manager.getKeyFactory().newKey(keyFormat);
          fail('An exception should be thrown.');
        } catch (e) {
          expect(e.toString()).toBe(ExceptionText.unsupportedKeySize(keySize));
        }
      }
    });
  
    it('new key, via format proto', function() {
      const manager = new AesGcmKeyManager();
  
      const keyFormat = createTestKeyFormat();
  
      const key =
          /** @type {!PbAesGcmKey}*/ (manager.getKeyFactory().newKey(keyFormat));
  
      expect(key.getKeyValue().length).toBe(keyFormat.getKeySize());
    });
  
    it('new key, via serialized format proto', function() {
      const manager = new AesGcmKeyManager();
  
      const keyFormat = createTestKeyFormat();
      const serializedKeyFormat = keyFormat.serializeBinary();
  
      const key = /** @type {!PbAesGcmKey} */ (
          manager.getKeyFactory().newKey(serializedKeyFormat));
  
      expect(key.getKeyValue().length).toBe(keyFormat.getKeySize());
    });
  
    /////////////////////////////////////////////////////////////////////////////
    // tests for NewKeyData method
  
    it('new key data, should work', function() {
      const keyFormat = createTestKeyFormat();
      const serializedKeyFormat = keyFormat.serializeBinary();
      const manager = new AesGcmKeyManager();
  
      const keyData = manager.getKeyFactory().newKeyData(serializedKeyFormat);
  
      expect(keyData.getTypeUrl()).toBe(KEY_TYPE);
      expect(keyData.getKeyMaterialType())
          .toBe(PbKeyData.KeyMaterialType.SYMMETRIC);
  
      const key = PbAesGcmKey.deserializeBinary(keyData.getValue());
  
      expect(key.getKeyValue().length).toBe(keyFormat.getKeySize());
    });
  
    /////////////////////////////////////////////////////////////////////////////
    // tests for getPrimitive method
  
    it('get primitive, unsupported key data type', async function() {
      const manager = new AesGcmKeyManager();
      const keyData = createTestKeyData().setTypeUrl('bad_type_url');
  
      try {
        await manager.getPrimitive(PRIMITIVE, keyData);
        fail('An exception should be thrown');
      } catch (e) {
        expect(e.toString())
            .toBe(ExceptionText.unsupportedKeyType(keyData.getTypeUrl()));
      }
    });
  
    it('get primitive, unsupported key type', async function() {
      const manager = new AesGcmKeyManager();
      const key = new PbAesCtrKey();
  
      try {
        await manager.getPrimitive(PRIMITIVE, key);
        fail('An exception should be thrown');
      } catch (e) {
        expect(e.toString()).toBe(ExceptionText.unsupportedKeyType());
      }
    });
  
    it('get primitive, bad version', async function() {
      const version = 1;
      const manager = new AesGcmKeyManager();
      const key = createTestKey().setVersion(version);
  
      try {
        await manager.getPrimitive(PRIMITIVE, key);
        fail('An exception should be thrown');
      } catch (e) {
        expect(e.toString()).toBe(ExceptionText.versionOutOfBounds());
      }
    });
  
    it('get primitive, unsupported key sizes', async function() {
      const manager = new AesGcmKeyManager();
  
      for (let keySize = 0; keySize < 40; keySize++) {
        if (keySize === 16 || keySize === 32) {
          // Keys of sizes 16 and 32 bytes are supported.
          continue;
        }
  
        const /** !PbAesGcmKey */ key = createTestKey(keySize);
        try {
          await manager.getPrimitive(PRIMITIVE, key);
          fail('An exception should be thrown');
        } catch (e) {
          expect(e.toString()).toBe(ExceptionText.unsupportedKeySize(keySize));
        }
      }
    });
  
    it('get primitive, bad serialization', async function() {
      const manager = new AesGcmKeyManager();
      const keyData = createTestKeyData().setValue(new Uint8Array([]));
  
      try {
        await manager.getPrimitive(PRIMITIVE, keyData);
        fail('An exception should be thrown');
      } catch (e) {
        expect(e.toString()).toBe(ExceptionText.invalidSerializedKey());
      }
    });
  
    it('get primitive, unsupported primitive', async function() {
      const manager = new AesGcmKeyManager();
      const keyData = createTestKeyData();
  
      try {
        await manager.getPrimitive(Mac, keyData);
        fail('An exception should be thrown.');
      } catch (e) {
        expect(e.toString()).toBe(ExceptionText.unsupportedPrimitive());
      }
    });
  
    // Tests for getting primitive from valid key/keyData.
    it('get primitive, from key', async function() {
      const manager = new AesGcmKeyManager();
      const key = createTestKey();
  
      // Get the primitive from key manager.
      const /** Aead */ primitive = await manager.getPrimitive(PRIMITIVE, key);
  
      // Test the returned primitive.
      const plaintext = Random.randBytes(8);
      const aad = Random.randBytes(8);
      const ciphertext = await primitive.encrypt(plaintext, aad);
      const decryptedCiphertext = await primitive.decrypt(ciphertext, aad);
  
      expect(decryptedCiphertext).toEqual(plaintext);
    });
  
    it('get primitive, from key data', async function() {
      const manager = new AesGcmKeyManager();
      const keyData = createTestKeyData();
  
      // Get primitive.
      const /** Aead */ primitive =
          await manager.getPrimitive(PRIMITIVE, keyData);
  
      // Test the returned primitive.
      const plaintext = Random.randBytes(8);
      const aad = Random.randBytes(8);
      const ciphertext = await primitive.encrypt(plaintext, aad);
      const decryptedCiphertext = await primitive.decrypt(ciphertext, aad);
  
      expect(decryptedCiphertext).toEqual(plaintext);
    });
  
    /////////////////////////////////////////////////////////////////////////////
    // tests for getVersion, getKeyType and doesSupport methods
  
    it('get version, should be zero', function() {
      const manager = new AesGcmKeyManager();
      expect(manager.getVersion()).toBe(0);
    });
  
    it('get key type, should be aes gcm key type', function() {
      const manager = new AesGcmKeyManager();
      expect(manager.getKeyType()).toBe(KEY_TYPE);
    });
  
    it('does support, should support aes gcm key type', function() {
      const manager = new AesGcmKeyManager();
      expect(manager.doesSupport(KEY_TYPE)).toBe(true);
    });
  
    it('get primitive type, should be aead', function() {
      const manager = new AesGcmKeyManager();
      expect(manager.getPrimitiveType()).toBe(PRIMITIVE);
    });
  });
  
  /////////////////////////////////////////////////////////////////////////////
  // Helper functions for tests
  
  class ExceptionText {
    /** @return {string} */
    static unsupportedPrimitive() {
      return 'SecurityException: Requested primitive type which is not supported ' +
          'by this key manager.';
    }
  
    /**
     * @param {number} keySize
     * @return {string}
     */
    static unsupportedKeySize(keySize) {
      return 'InvalidArgumentsException: unsupported AES key size: ' + keySize;
    }
  
    /**
     * @return {string}
     */
    static versionOutOfBounds() {
      return 'SecurityException: Version is out of bound, must be between 0 and ' +
          VERSION + '.';
    }
  
    /**
     * @param {string=} opt_unsupportedKeyType
     * @return {string}
     */
    static unsupportedKeyType(opt_unsupportedKeyType) {
      const prefix = 'SecurityException: Key type';
      const suffix =
          'is not supported. This key manager supports ' + KEY_TYPE + '.';
  
      if (opt_unsupportedKeyType) {
        return prefix + ' ' + opt_unsupportedKeyType + ' ' + suffix;
      } else {
        return prefix + ' ' + suffix;
      }
    }
  
    /**
     * @return {string}
     */
    static invalidSerializedKey() {
      return 'SecurityException: Could not parse the input as a serialized proto of ' +
          KEY_TYPE + ' key.';
    }
  
    static invalidSerializedKeyFormat() {
      return 'SecurityException: Could not parse the input as a serialized proto of ' +
          KEY_TYPE + ' key format.';
    }
  
    /**
     * @return {string}
     */
    static invalidKeyFormat() {
      return 'SecurityException: Expected AesGcmKeyFormat-proto';
    }
  }
  
  
  /**
   * @param {number=} opt_keySize
   *
   * @return {!PbAesGcmKeyFormat}
   */
  const createTestKeyFormat = function(opt_keySize = 16) {
    const keyFormat = new PbAesGcmKeyFormat().setKeySize(opt_keySize);
    return keyFormat;
  };
  
  /**
   * @param {number=} opt_keySize
   * @return {!PbAesGcmKey}
   */
  const createTestKey = function(opt_keySize = 16) {
    const key = new PbAesGcmKey().setVersion(0).setKeyValue(
        Random.randBytes(opt_keySize));
  
    return key;
  };
  
  /**
   * @param {number=} opt_keySize
   * @return {!PbKeyData}
   */
  const createTestKeyData = function(opt_keySize) {
    const keyData = new PbKeyData()
                        .setTypeUrl(KEY_TYPE)
                        .setValue(createTestKey(opt_keySize).serializeBinary())
                        .setKeyMaterialType(PbKeyData.KeyMaterialType.SYMMETRIC);
  
    return keyData;
  };
  