// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.networking.util;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.Random;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

/** A self-signed certificate authority (CA) cert for use in tests. */
// TODO(weiminyu): make this class test-only. Requires refactor in proxy and prober.
public class SelfSignedCaCertificate {

  private static final String DEFAULT_ISSUER_FQDN = "registry-test";
  private static final Date DEFAULT_NOT_BEFORE =
      Date.from(Instant.now().minus(Duration.ofHours(1)));
  private static final Date DEFAULT_NOT_AFTER = Date.from(Instant.now().plus(Duration.ofDays(1)));

  private static final Random RANDOM = new Random();
  private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();
  private static final KeyPairGenerator keyGen = createKeyPairGenerator();

  private final PrivateKey privateKey;
  private final X509Certificate cert;

  public SelfSignedCaCertificate(PrivateKey privateKey, X509Certificate cert) {
    this.privateKey = privateKey;
    this.cert = cert;
  }

  public PrivateKey key() {
    return privateKey;
  }

  public X509Certificate cert() {
    return cert;
  }

  public static SelfSignedCaCertificate create() throws Exception {
    return create(
        keyGen.generateKeyPair(), DEFAULT_ISSUER_FQDN, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER);
  }

  public static SelfSignedCaCertificate create(String fqdn) throws Exception {
    return create(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER);
  }

  public static SelfSignedCaCertificate create(String fqdn, Date from, Date to) throws Exception {
    return create(keyGen.generateKeyPair(), fqdn, from, to);
  }

  public static SelfSignedCaCertificate create(KeyPair keyPair, String fqdn, Date from, Date to)
      throws Exception {
    return new SelfSignedCaCertificate(keyPair.getPrivate(), createCaCert(keyPair, fqdn, from, to));
  }

  static KeyPairGenerator createKeyPairGenerator() {
    try {
      KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", PROVIDER);
      keyGen.initialize(2048, new SecureRandom());
      return keyGen;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /** Returns a self-signed Certificate Authority (CA) certificate. */
  static X509Certificate createCaCert(KeyPair keyPair, String fqdn, Date from, Date to)
      throws Exception {
    X500Name owner = new X500Name("CN=" + fqdn);
    ContentSigner signer =
        new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(keyPair.getPrivate());
    X509v3CertificateBuilder builder =
        new JcaX509v3CertificateBuilder(
            owner, new BigInteger(64, RANDOM), from, to, owner, keyPair.getPublic());

    // Mark cert as CA by adding basicConstraint with cA=true to the builder
    BasicConstraints basicConstraints = new BasicConstraints(true);
    builder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints);

    X509CertificateHolder certHolder = builder.build(signer);
    return new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certHolder);
  }
}