// 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;

import com.google.crypto.tink.internal.MutablePrimitiveRegistry;
import com.google.crypto.tink.proto.KeyTypeEntry;
import com.google.crypto.tink.proto.RegistryConfig;
import java.security.GeneralSecurityException;

/**
 * Static methods for handling of Tink configurations.
 *
 * <p>Configurations, i.e., a collection of key types and their corresponding key managers supported
 * by a specific run-time environment enable control of Tink setup via JSON-formatted config files
 * that determine which key types are supported, and provide a mechanism for deprecation of
 * obsolete/outdated cryptographic schemes (see <a
 * href="https://github.com/google/tink/blob/master/proto/config.proto">config.proto</a> for more
 * info).
 *
 * <h3>Usage</h3>
 *
 * <pre>{@code
 * RegistryConfig registryConfig = ...;
 * Config.register(registryConfig);
 * }</pre>
 *
 * @since 1.0.0
 */
public final class Config {
  /** Returns a {@link KeyTypeEntry} for Tink key types with the specified properties. */
  public static KeyTypeEntry getTinkKeyTypeEntry(
      String catalogueName,
      String primitiveName,
      String keyProtoName,
      int keyManagerVersion,
      boolean newKeyAllowed) {
    return KeyTypeEntry.newBuilder()
        .setPrimitiveName(primitiveName)
        .setTypeUrl("type.googleapis.com/google.crypto.tink." + keyProtoName)
        .setKeyManagerVersion(keyManagerVersion)
        .setNewKeyAllowed(newKeyAllowed)
        .setCatalogueName(catalogueName)
        .build();
  }

  /**
   * Tries to register key managers according to the specification in {@code config}.
   *
   * @throws GeneralSecurityException if cannot register this config with the {@link Registry}. This
   *     usually happens when either {@code config} contains any {@link KeyTypeEntry} that is
   *     already registered or the Registry cannot find any {@link
   *     com.google.crypto.tink.KeyManager} or {@link com.google.crypto.tink.Catalogue} that can
   *     handle the entry. In both cases the error message should show how to resolve it.
   */
  public static void register(RegistryConfig config) throws GeneralSecurityException {
    for (KeyTypeEntry entry : config.getEntryList()) {
      registerKeyType(entry);
    }
  }

  /**
   * Tries to register a key manager according to the specification in {@code entry}.
   *
   * @throws GeneralSecurityException if cannot register this config with the {@link Registry}. This
   *     usually happens when {@code entry} is already registered or the Registry cannot find any
   *     {@link com.google.crypto.tink.KeyManager} or {@link com.google.crypto.tink.Catalogue} that
   *     can handle the entry. In both cases the error message should show how to resolve it.
   */
  public static void registerKeyType(KeyTypeEntry entry) throws GeneralSecurityException {
    validate(entry);
    // Catalogues are no longer supported; we simply return on those catalogues which have been
    // removed, as the key managers will be registered already.
    if (entry.getCatalogueName().equals("TinkAead")
        || entry.getCatalogueName().equals("TinkMac")
        || entry.getCatalogueName().equals("TinkHybridDecrypt")
        || entry.getCatalogueName().equals("TinkHybridEncrypt")
        || entry.getCatalogueName().equals("TinkPublicKeySign")
        || entry.getCatalogueName().equals("TinkPublicKeyVerify")
        || entry.getCatalogueName().equals("TinkStreamingAead")
        || entry.getCatalogueName().equals("TinkDeterministicAead")) {
      return;
    }
    Catalogue<?> catalogue = Registry.getCatalogue(entry.getCatalogueName());
    MutablePrimitiveRegistry.globalInstance()
        .registerPrimitiveWrapper(catalogue.getPrimitiveWrapper());
    KeyManager<?> keyManager =
        catalogue.getKeyManager(
            entry.getTypeUrl(), entry.getPrimitiveName(), entry.getKeyManagerVersion());
    Registry.registerKeyManager(keyManager, entry.getNewKeyAllowed());
  }

  private static void validate(KeyTypeEntry entry) throws GeneralSecurityException {
    if (entry.getTypeUrl().isEmpty()) {
      throw new GeneralSecurityException("Missing type_url.");
    }
    if (entry.getPrimitiveName().isEmpty()) {
      throw new GeneralSecurityException("Missing primitive_name.");
    }
    if (entry.getCatalogueName().isEmpty()) {
      throw new GeneralSecurityException("Missing catalogue_name.");
    }
  }

  private Config() {}
}
