// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.security;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.util.io.pem.PemObject;

import javax.naming.NamingException;
import javax.naming.ldap.LdapName;
import javax.security.auth.x500.X500Principal;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.yahoo.security.Extension.SUBJECT_ALTERNATIVE_NAMES;
import static java.util.stream.Collectors.toList;

/**
 * @author bjorncs
 */
public class X509CertificateUtils {

    private X509CertificateUtils() {}

    public static X509Certificate fromPem(String pem) {
        try (PEMParser parser = new PEMParser(new StringReader(pem))) {
            return toX509Certificate(parser.readObject());
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (CertificateException e) {
            throw new RuntimeException(e);
        }
    }

    public static List<X509Certificate> certificateListFromPem(String pem) {
        try (PEMParser parser = new PEMParser(new StringReader(pem))) {
            List<X509Certificate> list = new ArrayList<>();
            Object pemObject;
            while ((pemObject = parser.readObject()) != null) {
                list.add(toX509Certificate(pemObject));
            }
            return list;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (CertificateException e) {
            throw new RuntimeException(e);
        }
    }

    private static X509Certificate toX509Certificate(Object pemObject) throws CertificateException {
        if (pemObject instanceof X509Certificate) {
            return (X509Certificate) pemObject;
        }
        if (pemObject instanceof X509CertificateHolder) {
            return new JcaX509CertificateConverter()
                    .setProvider(BouncyCastleProviderHolder.getInstance())
                    .getCertificate((X509CertificateHolder) pemObject);
        }
        throw new IllegalArgumentException("Invalid type of PEM object: " + pemObject);
    }

    public static String toPem(X509Certificate certificate) {
        try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
            pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded()));
            pemWriter.flush();
            return stringWriter.toString();
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public static String toPem(List<X509Certificate> certificates) {
        try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
            for (X509Certificate certificate : certificates) {
                pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded()));
            }
            pemWriter.flush();
            return stringWriter.toString();
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public static List<String> getSubjectCommonNames(X509Certificate certificate) {
        return getCommonNames(certificate.getSubjectX500Principal());
    }

    public static List<String> getIssuerCommonNames(X509Certificate certificate) {
        return getCommonNames(certificate.getIssuerX500Principal());
    }

    public static List<String> getCommonNames(X500Principal subject) {
        try {
            String subjectPrincipal = subject.getName();
            return new LdapName(subjectPrincipal).getRdns().stream()
                    .filter(rdn -> rdn.getType().equalsIgnoreCase("cn"))
                    .map(rdn -> rdn.getValue().toString())
                    .collect(toList());
        } catch (NamingException e) {
            throw new IllegalArgumentException("Invalid CN: " + e, e);
        }

    }

    public static List<SubjectAlternativeName> getSubjectAlternativeNames(X509Certificate certificate) {
        try {
            byte[] extensionValue = certificate.getExtensionValue(SUBJECT_ALTERNATIVE_NAMES.getOId());
            if (extensionValue == null) return Collections.emptyList();
            ASN1Encodable asn1Encodable = ASN1Primitive.fromByteArray(extensionValue);
            if (asn1Encodable instanceof ASN1OctetString) {
                asn1Encodable = ASN1Primitive.fromByteArray(((ASN1OctetString) asn1Encodable).getOctets());
            }
            GeneralNames names = GeneralNames.getInstance(asn1Encodable);
            return SubjectAlternativeName.fromGeneralNames(names);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

}

@@ -4315,128 +4315,392 @@
  get
-CommonNames(X500Principal subject) %7B%0A        try %7B
+SubjectOrganizationalUnits(X509Certificate certificate) %7B%0A        return getRdns(certificate.getSubjectX500Principal(), %22OU%22);%0A    %7D%0A%0A    public static List%3CString%3E getCommonNames(X500Principal distinguishedName) %7B%0A        return getRdns(distinguishedName, %22CN%22);
 %0A    
+%7D%0A%0A
     
-    String subjectPrincipal = subject.get
+private static List%3CString%3E getRdns(X500Principal distinguishedName, String rdn
 Name
-();
+) %7B
 %0A    
+    try %7B%0A
         
 retu
@@ -4687,32 +4687,36 @@
   try %7B%0A        
+    
 return new LdapN
@@ -4723,24 +4723,35 @@
 ame(
-subjectPrincipal
+distinguishedName.getName()
 ).ge
@@ -4837,12 +4837,15 @@
 ase(
-%22cn%22
+rdnName
 ))%0A 
@@ -5040,17 +5040,43 @@
 lid 
-C
+D
 N: %22 + 
-e
+distinguishedName.getName()
 , e)
@@ -5083,25 +5083,24 @@
 ;%0A        %7D%0A
-%0A
     %7D%0A%0A    p

Add getSubjectOrganizationalUnits() to X509CertificateUtils