/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2016 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.wildfly.security.x500.cert;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.security.auth.x500.X500Principal;

import org.junit.Test;
import org.wildfly.security.asn1.ASN1Encodable;
import org.wildfly.security.x500.GeneralName;
import org.wildfly.security.x500.X500;
import org.wildfly.security.x500.X500AttributeTypeAndValue;
import org.wildfly.security.x500.X500PrincipalBuilder;

/**
 * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
 */
public class X509CertificateBuilderTest {

    static final PrivateKey signingKey;
    static final PublicKey publicKey;
    static final PublicKey signingPublicKey;

    static {
        KeyPairGenerator keyPairGenerator;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            throw new Error(e);
        }
        final KeyPair keyPair1 = keyPairGenerator.generateKeyPair();
        final KeyPair keyPair2 = keyPairGenerator.generateKeyPair();
        signingKey = keyPair1.getPrivate();
        signingPublicKey = keyPair1.getPublic();
        publicKey = keyPair2.getPublic();
    }

    private static X509CertificateBuilder populateBasicCertBuilder() throws NoSuchAlgorithmException {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        X500PrincipalBuilder principalBuilder = new X500PrincipalBuilder();

        principalBuilder.addItem(X500AttributeTypeAndValue.create(X500.OID_AT_COMMON_NAME, ASN1Encodable.ofUtf8String("jane")));
        final X500Principal dn = principalBuilder.build();
        builder.setIssuerDn(dn);
        builder.setSubjectDn(dn);
        builder.setSignatureAlgorithmName("SHA256withRSA");
        builder.setSigningKey(signingKey);
        builder.setPublicKey(publicKey);
        return builder;
    }

    @Test
    public void testBasic() throws Exception {
        X509CertificateBuilder builder = populateBasicCertBuilder();
        final X509Certificate certificate = builder.build();
        // basically check every field here
        assertTrue(Arrays.equals(certificate.getTBSCertificate(), builder.getTBSBytes()));
        // just to be sure!
        assertEquals("CN=jane", certificate.getIssuerX500Principal().getName());
        assertEquals("CN=jane", certificate.getSubjectX500Principal().getName());
        // if the TBS certs are equals, this should always work
        certificate.verify(signingPublicKey, KeyFactory.getInstance("RSA").getProvider());
    }

    @Test
    public void testAKIExtension() throws Exception {
        X509CertificateBuilder builder = populateBasicCertBuilder();
        builder.addExtension(new AuthorityKeyIdentifierExtension(new byte[] { 1, 2, 3, 4 }, Collections.singletonList(new GeneralName.DNSName("foo.com")), BigInteger.TEN));
        assertTrue(Arrays.equals(builder.build().getTBSCertificate(), builder.getTBSBytes()));
        builder = populateBasicCertBuilder();
        builder.addExtension(new AuthorityKeyIdentifierExtension(null, Collections.singletonList(new GeneralName.DNSName("foo.com")), BigInteger.TEN));
        assertTrue(Arrays.equals(builder.build().getTBSCertificate(), builder.getTBSBytes()));
        builder = populateBasicCertBuilder();
        builder.addExtension(new AuthorityKeyIdentifierExtension(new byte[] { 1, 2, 3, 4 }, null, BigInteger.TEN));
        assertTrue(Arrays.equals(builder.build().getTBSCertificate(), builder.getTBSBytes()));
        builder = populateBasicCertBuilder();
        builder.addExtension(new AuthorityKeyIdentifierExtension(new byte[] { 1, 2, 3, 4 }, Collections.singletonList(new GeneralName.DNSName("foo.com")), null));
        assertTrue(Arrays.equals(builder.build().getTBSCertificate(), builder.getTBSBytes()));
    }

    @Test
    public void testIssuerAltNamesExtension() throws Exception {
        X509CertificateBuilder builder = populateBasicCertBuilder();
        builder.addExtension(new IssuerAlternativeNamesExtension(true, Arrays.asList(new GeneralName.RFC822Name("elytron@wildfly.org"), new GeneralName.DNSName("elytron.wildfly.org"))));
        final X509Certificate certificate = builder.build();
        assertTrue(Arrays.equals(certificate.getTBSCertificate(), builder.getTBSBytes()));
        final Collection<List<?>> names = certificate.getIssuerAlternativeNames();
        assertEquals(2, names.size());
        final Iterator<List<?>> iterator = names.iterator();
        List<?> item = iterator.next();
        assertEquals(2, item.size());
        assertEquals(Integer.valueOf(GeneralName.RFC_822_NAME), item.get(0));
        assertEquals("elytron@wildfly.org", item.get(1));
        item = iterator.next();
        assertEquals(2, item.size());
        assertEquals(Integer.valueOf(GeneralName.DNS_NAME), item.get(0));
        assertEquals("elytron.wildfly.org", item.get(1));
    }

    @Test
    public void testSubjectAltNamesExtension() throws Exception {
        X509CertificateBuilder builder = populateBasicCertBuilder();
        builder.addExtension(new SubjectAlternativeNamesExtension(true, Arrays.asList(new GeneralName.RFC822Name("elytron@wildfly.org"), new GeneralName.DNSName("elytron.wildfly.org"))));
        final X509Certificate certificate = builder.build();
        assertTrue(Arrays.equals(certificate.getTBSCertificate(), builder.getTBSBytes()));
        final Collection<List<?>> names = certificate.getSubjectAlternativeNames();
        assertEquals(2, names.size());
        final Iterator<List<?>> iterator = names.iterator();
        List<?> item = iterator.next();
        assertEquals(2, item.size());
        assertEquals(Integer.valueOf(GeneralName.RFC_822_NAME), item.get(0));
        assertEquals("elytron@wildfly.org", item.get(1));
        item = iterator.next();
        assertEquals(2, item.size());
        assertEquals(Integer.valueOf(GeneralName.DNS_NAME), item.get(0));
        assertEquals("elytron.wildfly.org", item.get(1));
    }

    @Test
    public void testEKUExtension() throws Exception {
        X509CertificateBuilder builder = populateBasicCertBuilder();
        final List<String> usage = Arrays.asList(X500.OID_KP_CLIENT_AUTH, X500.OID_KP_SERVER_AUTH);
        builder.addExtension(new ExtendedKeyUsageExtension(true, usage));
        final X509Certificate certificate = builder.build();
        assertTrue(Arrays.equals(certificate.getTBSCertificate(), builder.getTBSBytes()));
        assertEquals(usage, certificate.getExtendedKeyUsage());
    }

    @Test
    public void testKUExtension() throws Exception {
        X509CertificateBuilder builder = populateBasicCertBuilder();
        builder.addExtension(new KeyUsageExtension(KeyUsage.digitalSignature, KeyUsage.keyAgreement, KeyUsage.keyEncipherment, KeyUsage.decipherOnly));
        final X509Certificate certificate = builder.build();
        assertTrue(Arrays.equals(certificate.getTBSCertificate(), builder.getTBSBytes()));
        final boolean[] keyUsage = certificate.getKeyUsage();
        assertNotNull(keyUsage);
        assertTrue(KeyUsage.digitalSignature.in(keyUsage));
        assertFalse(KeyUsage.nonRepudiation.in(keyUsage));
        assertTrue(KeyUsage.keyEncipherment.in(keyUsage));
        assertFalse(KeyUsage.dataEncipherment.in(keyUsage));
        assertTrue(KeyUsage.keyAgreement.in(keyUsage));
        assertFalse(KeyUsage.keyCertSign.in(keyUsage));
        assertFalse(KeyUsage.cRLSign.in(keyUsage));
        assertFalse(KeyUsage.encipherOnly.in(keyUsage));
        assertTrue(KeyUsage.decipherOnly.in(keyUsage));
    }
}
