/*
 * Copyright (C) 2018 Square, 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 okhttp3.tls;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okio.ByteString;
import org.bouncycastle.asn1.x509.GeneralName;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public final class HeldCertificateTest {
  @Test public void defaultCertificate() throws CertificateParsingException {
    long now = System.currentTimeMillis();
    HeldCertificate heldCertificate = new HeldCertificate.Builder().build();

    X509Certificate certificate = heldCertificate.certificate();
    assertEquals("self-signed",
        certificate.getIssuerX500Principal().getName(),
        certificate.getSubjectX500Principal().getName());
    assertTrue(certificate.getIssuerX500Principal().getName().matches("CN=[0-9a-f-]{36}"));
    assertEquals(BigInteger.ONE, certificate.getSerialNumber());
    assertNull(certificate.getSubjectAlternativeNames());

    double deltaMillis = 1000.0;
    long durationMillis = TimeUnit.MINUTES.toMillis(60 * 24);
    assertEquals((double) now, certificate.getNotBefore().getTime(), deltaMillis);
    assertEquals((double) now + durationMillis, certificate.getNotAfter().getTime(), deltaMillis);
  }

  @Test public void customInterval() {
    // 5 seconds starting on 1970-01-01.
    HeldCertificate heldCertificate = new HeldCertificate.Builder()
        .validityInterval(5_000L, 10_000L)
        .build();
    X509Certificate certificate = heldCertificate.certificate();
    assertEquals(5_000L, certificate.getNotBefore().getTime());
    assertEquals(10_000L, certificate.getNotAfter().getTime());
  }

  @Test public void customDuration() {
    long now = System.currentTimeMillis();

    HeldCertificate heldCertificate = new HeldCertificate.Builder()
        .duration(5, TimeUnit.SECONDS)
        .build();
    X509Certificate certificate = heldCertificate.certificate();

    double deltaMillis = 1000.0;
    long durationMillis = 5_000L;
    assertEquals((double) now, certificate.getNotBefore().getTime(), deltaMillis);
    assertEquals((double) now + durationMillis, certificate.getNotAfter().getTime(), deltaMillis);
  }

  @Test public void subjectAlternativeNames() throws CertificateParsingException {
    HeldCertificate heldCertificate = new HeldCertificate.Builder()
        .addSubjectAlternativeName("1.1.1.1")
        .addSubjectAlternativeName("cash.app")
        .build();

    X509Certificate certificate = heldCertificate.certificate();
    List<List<?>> subjectAlternativeNames = new ArrayList<>(
        certificate.getSubjectAlternativeNames());
    assertEquals(subjectAlternativeNames, Arrays.asList(
        Arrays.asList(GeneralName.iPAddress, "1.1.1.1"),
        Arrays.asList(GeneralName.dNSName, "cash.app")));
  }

  @Test public void commonName() {
    HeldCertificate heldCertificate = new HeldCertificate.Builder()
        .commonName("cash.app")
        .build();

    X509Certificate certificate = heldCertificate.certificate();
    assertEquals("CN=cash.app", certificate.getSubjectX500Principal().getName());
  }

  @Test public void organizationalUnit() {
    HeldCertificate heldCertificate = new HeldCertificate.Builder()
        .commonName("cash.app")
        .organizationalUnit("cash")
        .build();

    X509Certificate certificate = heldCertificate.certificate();
    assertEquals("CN=cash.app,OU=cash", certificate.getSubjectX500Principal().getName());
  }

  /** Confirm golden values of encoded PEMs. */
  @Test public void pems() throws Exception {
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");

    ByteString publicKeyBytes = ByteString.decodeBase64("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCApF"
        + "HhtrLan28q+oMolZuaTfWBA0V5aMIvq32BsloQu6LlvX1wJ4YEoUCjDlPOtpht7XLbUmBnbIzN89XK4UJVM6Sqp3"
        + "K88Km8z7gMrdrfTom/274wL25fICR+yDEQ5fUVYBmJAKXZF1aoI0mIoEx0xFsQhIJ637v2MxJDupd61wIDAQAB");
    PublicKey publicKey = keyFactory.generatePublic(
        new X509EncodedKeySpec(publicKeyBytes.toByteArray()));

    ByteString privateKeyBytes = ByteString.decodeBase64("MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbA"
        + "gEAAoGBAICkUeG2stqfbyr6gyiVm5pN9YEDRXlowi+rfYGyWhC7ouW9fXAnhgShQKMOU862mG3tcttSYGdsjM3z1"
        + "crhQlUzpKqncrzwqbzPuAyt2t9Oib/bvjAvbl8gJH7IMRDl9RVgGYkApdkXVqgjSYigTHTEWxCEgnrfu/YzEkO6l"
        + "3rXAgMBAAECgYB99mhnB6piADOuddXv626NzUBTr4xbsYRTgSxHzwf50oFTTBSDuW+1IOBVyTWu94SSPyt0LllPb"
        + "C8Di3sQSTnVGpSqAvEXknBMzIc0UO74Rn9p3gZjEenPt1l77fIBa2nK06/rdsJCoE/1P1JSfM9w7LU1RsTmseYML"
        + "eJl5F79gQJBAO/BbAKqg1yzK7VijygvBoUrr+rt2lbmKgcUQ/rxu8IIQk0M/xgJqSkXDXuOnboGM7sQSKfJAZUtT"
        + "7xozvLzV7ECQQCJW59w7NIM0qZ/gIX2gcNZr1B/V3zcGlolTDciRm+fnKGNt2EEDKnVL3swzbEfTCa48IT0QKgZJ"
        + "qpXZERa26UHAkBLXmiP5f5pk8F3wcXzAeVw06z3k1IB41Tu6MX+CyPU+TeudRlz+wV8b0zDvK+EnRKCCbptVFj1B"
        + "kt8lQ4JfcnhAkAk2Y3Gz+HySrkcT7Cg12M/NkdUQnZe3jr88pt/+IGNwomc6Wt/mJ4fcWONTkGMcfOZff1NQeNXD"
        + "AZ6941XCsIVAkASOg02PlVHLidU7mIE65swMM5/RNhS4aFjez/MwxFNOHaxc9VgCwYPXCLOtdf7AVovdyG0XWgbU"
        + "XH+NyxKwboE");
    PrivateKey privateKey = keyFactory.generatePrivate(
        new PKCS8EncodedKeySpec(privateKeyBytes.toByteArray()));

    HeldCertificate heldCertificate = new HeldCertificate.Builder()
        .keyPair(publicKey, privateKey)
        .commonName("cash.app")
        .validityInterval(0L, 1_000L)
        .rsa2048()
        .build();

    assertEquals(heldCertificate.certificatePem(), ""
        + "-----BEGIN CERTIFICATE-----\n"
        + "MIIBmjCCAQOgAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhjYXNo\n"
        + "LmFwcDAeFw03MDAxMDEwMDAwMDBaFw03MDAxMDEwMDAwMDFaMBMxETAPBgNVBAMT\n"
        + "CGNhc2guYXBwMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCApFHhtrLan28q\n"
        + "+oMolZuaTfWBA0V5aMIvq32BsloQu6LlvX1wJ4YEoUCjDlPOtpht7XLbUmBnbIzN\n"
        + "89XK4UJVM6Sqp3K88Km8z7gMrdrfTom/274wL25fICR+yDEQ5fUVYBmJAKXZF1ao\n"
        + "I0mIoEx0xFsQhIJ637v2MxJDupd61wIDAQABMA0GCSqGSIb3DQEBCwUAA4GBADam\n"
        + "UVwKh5Ry7es3OxtY3IgQunPUoLc0Gw71gl9Z+7t2FJ5VkcI5gWfutmdxZ2bDXCI8\n"
        + "8V0vxo1pHXnbBrnxhS/Z3TBerw8RyQqcaWOdp+pBXyIWmR+jHk9cHZCqQveTIBsY\n"
        + "jaA9VEhgdaVhxBsT2qzUNDsXlOzGsliznDfoqETb\n"
        + "-----END CERTIFICATE-----\n");

    assertEquals(heldCertificate.privateKeyPkcs1Pem(), ""
        + "-----BEGIN RSA PRIVATE KEY-----\n"
        + "MIICWwIBAAKBgQCApFHhtrLan28q+oMolZuaTfWBA0V5aMIvq32BsloQu6LlvX1w\n"
        + "J4YEoUCjDlPOtpht7XLbUmBnbIzN89XK4UJVM6Sqp3K88Km8z7gMrdrfTom/274w\n"
        + "L25fICR+yDEQ5fUVYBmJAKXZF1aoI0mIoEx0xFsQhIJ637v2MxJDupd61wIDAQAB\n"
        + "AoGAffZoZweqYgAzrnXV7+tujc1AU6+MW7GEU4EsR88H+dKBU0wUg7lvtSDgVck1\n"
        + "rveEkj8rdC5ZT2wvA4t7EEk51RqUqgLxF5JwTMyHNFDu+EZ/ad4GYxHpz7dZe+3y\n"
        + "AWtpytOv63bCQqBP9T9SUnzPcOy1NUbE5rHmDC3iZeRe/YECQQDvwWwCqoNcsyu1\n"
        + "Yo8oLwaFK6/q7dpW5ioHFEP68bvCCEJNDP8YCakpFw17jp26BjO7EEinyQGVLU+8\n"
        + "aM7y81exAkEAiVufcOzSDNKmf4CF9oHDWa9Qf1d83BpaJUw3IkZvn5yhjbdhBAyp\n"
        + "1S97MM2xH0wmuPCE9ECoGSaqV2REWtulBwJAS15oj+X+aZPBd8HF8wHlcNOs95NS\n"
        + "AeNU7ujF/gsj1Pk3rnUZc/sFfG9Mw7yvhJ0Sggm6bVRY9QZLfJUOCX3J4QJAJNmN\n"
        + "xs/h8kq5HE+woNdjPzZHVEJ2Xt46/PKbf/iBjcKJnOlrf5ieH3FjjU5BjHHzmX39\n"
        + "TUHjVwwGeveNVwrCFQJAEjoNNj5VRy4nVO5iBOubMDDOf0TYUuGhY3s/zMMRTTh2\n"
        + "sXPVYAsGD1wizrXX+wFaL3chtF1oG1Fx/jcsSsG6BA==\n"
        + "-----END RSA PRIVATE KEY-----\n");

    assertEquals(heldCertificate.privateKeyPkcs8Pem(), ""
        + "-----BEGIN PRIVATE KEY-----\n"
        + "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAICkUeG2stqfbyr6\n"
        + "gyiVm5pN9YEDRXlowi+rfYGyWhC7ouW9fXAnhgShQKMOU862mG3tcttSYGdsjM3z\n"
        + "1crhQlUzpKqncrzwqbzPuAyt2t9Oib/bvjAvbl8gJH7IMRDl9RVgGYkApdkXVqgj\n"
        + "SYigTHTEWxCEgnrfu/YzEkO6l3rXAgMBAAECgYB99mhnB6piADOuddXv626NzUBT\n"
        + "r4xbsYRTgSxHzwf50oFTTBSDuW+1IOBVyTWu94SSPyt0LllPbC8Di3sQSTnVGpSq\n"
        + "AvEXknBMzIc0UO74Rn9p3gZjEenPt1l77fIBa2nK06/rdsJCoE/1P1JSfM9w7LU1\n"
        + "RsTmseYMLeJl5F79gQJBAO/BbAKqg1yzK7VijygvBoUrr+rt2lbmKgcUQ/rxu8II\n"
        + "Qk0M/xgJqSkXDXuOnboGM7sQSKfJAZUtT7xozvLzV7ECQQCJW59w7NIM0qZ/gIX2\n"
        + "gcNZr1B/V3zcGlolTDciRm+fnKGNt2EEDKnVL3swzbEfTCa48IT0QKgZJqpXZERa\n"
        + "26UHAkBLXmiP5f5pk8F3wcXzAeVw06z3k1IB41Tu6MX+CyPU+TeudRlz+wV8b0zD\n"
        + "vK+EnRKCCbptVFj1Bkt8lQ4JfcnhAkAk2Y3Gz+HySrkcT7Cg12M/NkdUQnZe3jr8\n"
        + "8pt/+IGNwomc6Wt/mJ4fcWONTkGMcfOZff1NQeNXDAZ6941XCsIVAkASOg02PlVH\n"
        + "LidU7mIE65swMM5/RNhS4aFjez/MwxFNOHaxc9VgCwYPXCLOtdf7AVovdyG0XWgb\n"
        + "UXH+NyxKwboE\n"
        + "-----END PRIVATE KEY-----\n");
  }

  @Test public void ecdsaSignedByRsa() {
    HeldCertificate root = new HeldCertificate.Builder()
        .certificateAuthority(0)
        .rsa2048()
        .build();
    HeldCertificate leaf = new HeldCertificate.Builder()
        .certificateAuthority(0)
        .ecdsa256()
        .signedBy(root)
        .build();

    assertEquals("SHA256WITHRSA", root.certificate().getSigAlgName());
    assertEquals("SHA256WITHRSA", leaf.certificate().getSigAlgName());
  }

  @Test public void rsaSignedByEcdsa() {
    HeldCertificate root = new HeldCertificate.Builder()
        .certificateAuthority(0)
        .ecdsa256()
        .build();
    HeldCertificate leaf = new HeldCertificate.Builder()
        .certificateAuthority(0)
        .rsa2048()
        .signedBy(root)
        .build();

    assertEquals("SHA256WITHECDSA", root.certificate().getSigAlgName());
    assertEquals("SHA256WITHECDSA", leaf.certificate().getSigAlgName());
  }
}
