// Copyright 2017 Google Inc.
//
// 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.
//
////////////////////////////////////////////////////////////////////////////////

package com.google.crypto.tink.aead;

import static com.google.common.truth.Truth.assertThat;
import static com.google.crypto.tink.internal.Util.isPrefix;
import static org.junit.Assert.assertThrows;

import com.google.crypto.tink.Aead;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.KmsClients;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.TinkProtoKeysetFormat;
import com.google.crypto.tink.internal.KeyManagerRegistry;
import com.google.crypto.tink.subtle.Random;
import com.google.crypto.tink.testing.FakeKmsClient;
import com.google.crypto.tink.testing.TestUtil;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for KmsAeadKeyManager. */
@RunWith(JUnit4.class)
public class KmsAeadKeyManagerTest {
  @Before
  public void setUp() throws Exception {
    KmsClients.add(new FakeKmsClient());
    AeadConfig.register();
  }

  @Test
  public void testKeyManagerRegistered() throws Exception {
    assertThat(
            KeyManagerRegistry.globalInstance()
                .getKeyManager("type.googleapis.com/google.crypto.tink.KmsAeadKey", Aead.class))
        .isNotNull();
  }

  @Test
  public void testKmsAead_success() throws Exception {
    String keyUri = FakeKmsClient.createFakeKeyUri();
    KeysetHandle keysetHandle =
        KeysetHandle.generateNew(KmsAeadKeyManager.createKeyTemplate(keyUri));
    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class));
  }

  @Test
  public void createAeadFromLegacyKmsAeadKey_works() throws Exception {
    String keyUri = FakeKmsClient.createFakeKeyUri();
    LegacyKmsAeadParameters parameters = LegacyKmsAeadParameters.create(keyUri);
    LegacyKmsAeadKey key = LegacyKmsAeadKey.create(parameters);
    KeysetHandle keysetHandle =
        KeysetHandle.newBuilder()
            .addEntry(KeysetHandle.importKey(key).withRandomId().makePrimary())
            .build();

    Aead aead = keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
    TestUtil.runBasicAeadTests(aead);

    Aead clientAead = new FakeKmsClient().getAead(keyUri);
    byte[] plaintext = Random.randBytes(20);
    byte[] associatedData = Random.randBytes(20);
    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
    byte[] decrypted = clientAead.decrypt(ciphertext, associatedData);
    assertThat(decrypted).isEqualTo(plaintext);
  }

  @Test
  public void createAeadFromLegacyKmsAeadKeyWithTinkPrefix_works() throws Exception {
    String keyUri = FakeKmsClient.createFakeKeyUri();
    LegacyKmsAeadParameters parameters =
        LegacyKmsAeadParameters.create(keyUri, LegacyKmsAeadParameters.Variant.TINK);
    LegacyKmsAeadKey key = LegacyKmsAeadKey.create(parameters, 0x11223344);
    KeysetHandle keysetHandle =
        KeysetHandle.newBuilder()
            .addEntry(KeysetHandle.importKey(key).withFixedId(0x11223344).makePrimary())
            .build();

    Aead aead = keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
    TestUtil.runBasicAeadTests(aead);

    byte[] plaintext = Random.randBytes(20);
    byte[] associatedData = Random.randBytes(20);
    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
    assertThat(
            isPrefix(
                new byte[] {(byte) 0x01, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44},
                ciphertext))
        .isTrue();

    Aead clientAead = new FakeKmsClient().getAead(keyUri);
    // test that clientAead can decrypt ciphertext if the 5 byte prefix is removed.
    byte[] ciphertextWithoutPrefix = Arrays.copyOfRange(ciphertext, 5, ciphertext.length);
    byte[] decrypted = clientAead.decrypt(ciphertextWithoutPrefix, associatedData);
    assertThat(decrypted).isEqualTo(plaintext);
  }

  @Test
  public void createAeadInvalidUri_throws() throws Exception {
    LegacyKmsAeadParameters parameters = LegacyKmsAeadParameters.create("wrong uri");
    LegacyKmsAeadKey key = LegacyKmsAeadKey.create(parameters);
    KeysetHandle keysetHandle =
        KeysetHandle.newBuilder()
            .addEntry(KeysetHandle.importKey(key).withRandomId().makePrimary())
            .build();

    assertThrows(
        GeneralSecurityException.class,
        () -> keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class));
  }

  @Test
  public void createKeyTemplateGenerateNewGetPrimitive_isSameAs_clientGetAead()
      throws Exception {
    String keyUri = FakeKmsClient.createFakeKeyUri();

    // Create Aead primitive using createKeyTemplate, generateNew, and getPrimitive.
    // This requires that a KmsClient that supports keyUri is registered.
    KeysetHandle keysetHandle =
        KeysetHandle.generateNew(KmsAeadKeyManager.createKeyTemplate(keyUri));
    Aead aead1 = keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);

    // Create Aead using FakeKmsClient.getAead.
    // No KmsClient needs to be registered.
    Aead aead2 = new FakeKmsClient().getAead(keyUri);

    // Test that aead1 and aead2 are the same.
    byte[] plaintext = Random.randBytes(20);
    byte[] associatedData = Random.randBytes(20);
    byte[] ciphertext = aead1.encrypt(plaintext, associatedData);
    byte[] decrypted = aead2.decrypt(ciphertext, associatedData);
    assertThat(decrypted).isEqualTo(plaintext);
  }

  @Test
  public void createKeyTemplate() throws Exception {
    String keyUri = "some example KEK URI";
    assertThat(KmsAeadKeyManager.createKeyTemplate(keyUri).toParameters())
        .isEqualTo(LegacyKmsAeadParameters.create(keyUri));
  }

  @Test
  public void generateNewFromParams_works() throws Exception {
    LegacyKmsAeadParameters parameters = LegacyKmsAeadParameters.create("some example KEK URI");
    KeysetHandle keysetHandle1 = KeysetHandle.generateNew(parameters);
    KeysetHandle keysetHandle2 = KeysetHandle.generateNew(parameters);
    // For LegacyKmsAeadParameters we expect both keysets to be the same -- however, the ID of the
    // keys may differ.
    assertThat(keysetHandle1.getAt(0).getKey().equalsKey(keysetHandle2.getAt(0).getKey())).isTrue();
  }

  @Test
  public void serializeAndParse_works() throws Exception {
    LegacyKmsAeadParameters parameters = LegacyKmsAeadParameters.create("some example KEK URI");
    KeysetHandle keysetHandle1 = KeysetHandle.generateNew(parameters);
    byte[] serialized = TinkProtoKeysetFormat.serializeKeysetWithoutSecret(keysetHandle1);
    KeysetHandle keysetHandle2 = TinkProtoKeysetFormat.parseKeysetWithoutSecret(serialized);
    assertThat(keysetHandle1.equalsKeyset(keysetHandle2)).isTrue();
  }
}
