Refactor CmpResponseHelper to support intermediate certificate
[oom/platform/cert-service.git] / certService / src / main / java / org / onap / aaf / certservice / cmpv2client / impl / CmpResponseHelper.java
index b2a7b29..3cb0b0c 100644 (file)
@@ -40,7 +40,9 @@ import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 
@@ -49,9 +51,11 @@ import org.bouncycastle.asn1.cmp.CertRepMessage;
 import org.bouncycastle.asn1.cmp.ErrorMsgContent;
 import org.bouncycastle.asn1.cmp.PKIBody;
 import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
 import org.onap.aaf.certservice.cmpv2client.exceptions.PkiErrorException;
+import org.onap.aaf.certservice.cmpv2client.model.Cmpv2CertificationModel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,7 +66,7 @@ public final class CmpResponseHelper {
     private CmpResponseHelper() {
     }
 
-    public static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage)
+    static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage)
             throws CmpClientException {
         if (respPkiMessage.getBody().getType() == PKIBody.TYPE_ERROR) {
             final ErrorMsgContent errorMsgContent =
@@ -77,54 +81,91 @@ public final class CmpResponseHelper {
         }
     }
 
+
     /**
-     * @param cert       byte array that contains certificate
-     * @param returnType the type of Certificate to be returned, for example X509Certificate.class.
-     *                   Certificate.class can be used if certificate type is unknown.
-     * @throws CertificateParsingException if the byte array does not contain a proper certificate.
+     * Puts together certChain and Trust store and verifies the certChain
+     *
+     * @param respPkiMessage  PKIMessage that may contain extra certs used for certchain
+     * @param certRepMessage  CertRepMessage that should contain rootCA for certchain
+     * @param leafCertificate certificate returned from our original Cert Request
+     * @return model for certification containing certificate chain and trusted certificates
+     * @throws CertificateParsingException thrown if error occurs while parsing certificate
+     * @throws IOException                 thrown if IOException occurs while parsing certificate
+     * @throws CmpClientException          thrown if error occurs during the verification of the certChain
      */
-    public static <T extends Certificate> Optional<X509Certificate> getCertfromByteArray(
-            byte[] cert, Class<T> returnType) throws CertificateParsingException, CmpClientException {
-        LOG.debug("Retrieving certificate of type {} from byte array.", returnType);
-        return getCertfromByteArray(cert, BouncyCastleProvider.PROVIDER_NAME, returnType);
+    static Cmpv2CertificationModel verifyAndReturnCertChainAndTrustSTore(
+            PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCertificate)
+            throws CertificateParsingException, IOException, CmpClientException {
+        Map<X500Name, X509Certificate> certificates = mapAllCertificates(respPkiMessage, certRepMessage);
+        return extractCertificationModel(certificates, leafCertificate);
     }
 
-    /**
-     * @param cert       byte array that contains certificate
-     * @param provider   provider used to generate certificate from bytes
-     * @param returnType the type of Certificate to be returned, for example X509Certificate.class.
-     *                   Certificate.class can be used if certificate type is unknown.
-     * @throws CertificateParsingException if the byte array does not contain a proper certificate.
-     */
-    public static <T extends Certificate> Optional<X509Certificate> getCertfromByteArray(
-            byte[] cert, String provider, Class<T> returnType)
-            throws CertificateParsingException, CmpClientException {
-        String prov = provider;
-        if (provider == null) {
-            prov = BouncyCastleProvider.PROVIDER_NAME;
-        }
+    private static Map<X500Name, X509Certificate> mapAllCertificates(
+            PKIMessage respPkiMessage, CertRepMessage certRepMessage
+    )
+            throws IOException, CertificateParsingException, CmpClientException {
 
-        if (returnType.equals(X509Certificate.class)) {
-            return parseX509Certificate(prov, cert);
+        Map<X500Name, X509Certificate> certificates = new HashMap<>();
+
+        CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts();
+        certificates.putAll(mapCertificates(extraCerts));
+
+        CMPCertificate[] caPubsCerts = certRepMessage.getCaPubs();
+        certificates.putAll(mapCertificates(caPubsCerts));
+
+        return certificates;
+    }
+
+    private static Map<X500Name, X509Certificate> mapCertificates(
+            CMPCertificate[] cmpCertificates)
+            throws CertificateParsingException, CmpClientException, IOException {
+
+        Map<X500Name, X509Certificate> certificates = new HashMap<>();
+        if (cmpCertificates != null) {
+            for (CMPCertificate certificate : cmpCertificates) {
+                getCertFromByteArray(certificate.getEncoded(), X509Certificate.class)
+                        .ifPresent(x509Certificate ->
+                                certificates.put(extractSubjectDn(x509Certificate), x509Certificate)
+                        );
+            }
         }
-        return Optional.empty();
+
+        return certificates;
     }
 
-    /**
-     * Check the certificate with CA certificate.
-     *
-     * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a
-     *                    Collection with null entries.
-     * @throws CmpClientException if verification failed
-     */
-    public static void verify(List<X509Certificate> caCertChain) throws CmpClientException {
-        int iterator = 1;
-        while (iterator < caCertChain.size()) {
-            verify(caCertChain.get(iterator - 1), caCertChain.get(iterator), null);
-            iterator += 1;
+    private static Cmpv2CertificationModel extractCertificationModel(
+            Map<X500Name, X509Certificate> certificates, X509Certificate leafCertificate
+    )
+            throws CmpClientException {
+        List<X509Certificate> certificateChain = new ArrayList<>();
+        X509Certificate previousCertificateInChain;
+        X509Certificate nextCertificateInChain = leafCertificate;
+        do {
+            certificateChain.add(nextCertificateInChain);
+            certificates.remove(extractSubjectDn(nextCertificateInChain));
+            previousCertificateInChain = nextCertificateInChain;
+            nextCertificateInChain = certificates.get(extractIssuerDn(nextCertificateInChain));
+            verify(previousCertificateInChain, nextCertificateInChain, null);
         }
+        while (!isSelfSign(nextCertificateInChain));
+        List<X509Certificate> trustedCertificates = new ArrayList<>(certificates.values());
+
+        return new Cmpv2CertificationModel(certificateChain, trustedCertificates);
+    }
+
+    private static boolean isSelfSign(X509Certificate certificate) {
+        return extractIssuerDn(certificate).equals(extractSubjectDn(certificate));
+    }
+
+    private static X500Name extractIssuerDn(X509Certificate x509Certificate) {
+        return X500Name.getInstance(x509Certificate.getIssuerDN());
+    }
+
+    private static X500Name extractSubjectDn(X509Certificate x509Certificate) {
+        return X500Name.getInstance(x509Certificate.getSubjectDN());
     }
 
+
     /**
      * Check the certificate with CA certificate.
      *
@@ -136,7 +177,7 @@ public final class CmpResponseHelper {
      *                             path validation
      * @throws CmpClientException if certificate could not be validated
      */
-    public static void verify(
+    private static void verify(
             X509Certificate certificate,
             X509Certificate caCertChain,
             Date date,
@@ -179,13 +220,17 @@ public final class CmpResponseHelper {
         }
     }
 
-    public static void verifyCertificates(
+    private static void verifyCertificates(
             X509Certificate certificate,
             X509Certificate caCertChain,
             Date date,
             PKIXCertPathChecker[] pkixCertPathCheckers)
             throws CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException,
             NoSuchAlgorithmException, CertPathValidatorException {
+        if (caCertChain == null) {
+            final String noRootCaCertificateMessage = "Server response does not contain proper root CA certificate";
+            throw new CertificateException(noRootCaCertificateMessage);
+        }
         LOG.debug(
                 "Verifying certificate {} as part of cert chain with certificate {}",
                 certificate.getSubjectDN().getName(),
@@ -200,7 +245,7 @@ public final class CmpResponseHelper {
         }
     }
 
-    public static PKIXParameters getPkixParameters(
+    private static PKIXParameters getPkixParameters(
             X509Certificate caCertChain, Date date, PKIXCertPathChecker[] pkixCertPathCheckers)
             throws InvalidAlgorithmParameterException {
         TrustAnchor anchor = new TrustAnchor(caCertChain, null);
@@ -213,7 +258,7 @@ public final class CmpResponseHelper {
         return params;
     }
 
-    public static CertPath getCertPath(X509Certificate certificate)
+    private static CertPath getCertPath(X509Certificate certificate)
             throws CertificateException, NoSuchProviderException {
         ArrayList<X509Certificate> certlist = new ArrayList<>();
         certlist.add(certificate);
@@ -221,34 +266,6 @@ public final class CmpResponseHelper {
                 .generateCertPath(certlist);
     }
 
-    /**
-     * Parse a X509Certificate from an array of bytes
-     *
-     * @param provider a provider name
-     * @param cert     a byte array containing an encoded certificate
-     * @return a decoded X509Certificate
-     * @throws CertificateParsingException if the byte array wasn't valid, or contained a certificate
-     *                                     other than an X509 Certificate.
-     */
-    public static Optional<X509Certificate> parseX509Certificate(String provider, byte[] cert)
-            throws CertificateParsingException, CmpClientException {
-        LOG.debug("Parsing X509Certificate from bytes with provider {}", provider);
-        final CertificateFactory cf = getCertificateFactory(provider);
-        X509Certificate result;
-        try {
-            result =
-                    (X509Certificate)
-                            Objects.requireNonNull(cf).generateCertificate(new ByteArrayInputStream(cert));
-        } catch (CertificateException ce) {
-            throw new CertificateParsingException("Could not parse byte array as X509Certificate ", ce);
-        }
-        if (result != null) {
-            return Optional.of(result);
-        } else {
-            throw new CertificateParsingException("Could not parse byte array as X509Certificate.");
-        }
-    }
-
     /**
      * Returns a CertificateFactory that can be used to create certificates from byte arrays and such.
      *
@@ -256,7 +273,7 @@ public final class CmpResponseHelper {
      *                 null is passed.
      * @return CertificateFactory for creating certificate
      */
-    public static CertificateFactory getCertificateFactory(final String provider)
+    private static CertificateFactory getCertificateFactory(final String provider)
             throws CmpClientException {
         LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider);
         final String prov;
@@ -275,99 +292,44 @@ public final class CmpResponseHelper {
     }
 
     /**
-     * puts together certChain and Trust store and verifies the certChain
-     *
-     * @param respPkiMessage  PKIMessage that may contain extra certs used for certchain
-     * @param certRepMessage  CertRepMessage that should contain rootCA for certchain
-     * @param leafCertificate certificate returned from our original Cert Request
-     * @return list of two lists, CertChain and TrustStore
-     * @throws CertificateParsingException thrown if error occurs while parsing certificate
-     * @throws IOException                 thrown if IOException occurs while parsing certificate
-     * @throws CmpClientException          thrown if error occurs during the verification of the certChain
+     * @param cert       byte array that contains certificate
+     * @param returnType the type of Certificate to be returned, for example X509Certificate.class.
+     *                   Certificate.class can be used if certificate type is unknown.
+     * @throws CertificateParsingException if the byte array does not contain a proper certificate.
      */
-    public static List<List<X509Certificate>> verifyAndReturnCertChainAndTrustSTore(
-            PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCertificate)
-            throws CertificateParsingException, IOException, CmpClientException {
-        List<X509Certificate> certChain =
-                addExtraCertsToChain(respPkiMessage, certRepMessage, leafCertificate);
-        List<String> certNames = getNamesOfCerts(certChain);
-        LOG.debug("Verifying the following certificates in the cert chain: {}", certNames);
-        verify(certChain);
-        ArrayList<X509Certificate> trustStore = new ArrayList<>();
-        final int rootCaIndex = certChain.size() - 1;
-        trustStore.add(certChain.get(rootCaIndex));
-        certChain.remove(rootCaIndex);
-        List<List<X509Certificate>> listOfArray = new ArrayList<>();
-        listOfArray.add(certChain);
-        listOfArray.add(trustStore);
-        return listOfArray;
-    }
+    static <T extends Certificate> Optional<X509Certificate> getCertFromByteArray(
+            byte[] cert, Class<T> returnType) throws CertificateParsingException, CmpClientException {
+        LOG.debug("Retrieving certificate of type {} from byte array.", returnType);
+        String prov = BouncyCastleProvider.PROVIDER_NAME;
 
-    public static List<String> getNamesOfCerts(List<X509Certificate> certChain) {
-        List<String> certNames = new ArrayList<>();
-        certChain.forEach(cert -> certNames.add(cert.getSubjectDN().getName()));
-        return certNames;
+        if (returnType.equals(X509Certificate.class)) {
+            return parseX509Certificate(prov, cert);
+        } else {
+            LOG.debug("Certificate of type {} was skipped, because type of certificate is not 'X509Certificate'.", returnType);
+            return Optional.empty();
+        }
     }
 
+
     /**
-     * checks whether PKIMessage contains extracerts to create certchain, if not creates from caPubs
+     * Parse a X509Certificate from an array of bytes
      *
-     * @param respPkiMessage PKIMessage that may contain extra certs used for certchain
-     * @param certRepMessage CertRepMessage that should contain rootCA for certchain
-     * @param leafCert       certificate at top of certChain.
-     * @throws CertificateParsingException thrown if error occurs while parsing certificate
-     * @throws IOException                 thrown if IOException occurs while parsing certificate
-     * @throws CmpClientException          thrown if there are errors creating CertificateFactory
+     * @param provider a provider name
+     * @param cert     a byte array containing an encoded certificate
+     * @return a decoded X509Certificate
+     * @throws CertificateParsingException if the byte array wasn't valid, or contained a certificate
+     *                                     other than an X509 Certificate.
      */
-    public static List<X509Certificate> addExtraCertsToChain(
-            PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCert)
-            throws CertificateParsingException, IOException, CmpClientException {
-        List<X509Certificate> certChain = new ArrayList<>();
-        certChain.add(leafCert);
-        if (respPkiMessage.getExtraCerts() != null) {
-            final CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts();
-            for (CMPCertificate cmpCert : extraCerts) {
-                Optional<X509Certificate> cert =
-                        getCertfromByteArray(cmpCert.getEncoded(), X509Certificate.class);
-                certChain =
-                        ifCertPresent(
-                                certChain,
-                                cert,
-                                "Adding certificate from extra certs {} to cert chain",
-                                "Couldn't add certificate from extra certs, certificate wasn't an X509Certificate");
-                return certChain;
-            }
-        } else {
-            final CMPCertificate respCmpCaCert = getRootCa(certRepMessage);
-            Optional<X509Certificate> cert =
-                    getCertfromByteArray(respCmpCaCert.getEncoded(), X509Certificate.class);
-            certChain =
-                    ifCertPresent(
-                            certChain,
-                            cert,
-                            "Adding certificate from CaPubs {} to TrustStore",
-                            "Couldn't add certificate from CaPubs, certificate wasn't an X509Certificate");
-            return certChain;
-        }
-        return Collections.emptyList();
-    }
-
-    public static List<X509Certificate> ifCertPresent(
-            List<X509Certificate> certChain,
-            Optional<X509Certificate> cert,
-            String certPresentString,
-            String certUnavailableString) {
-        if (cert.isPresent()) {
-            LOG.debug(certPresentString, cert.get().getSubjectDN().getName());
-            certChain.add(cert.get());
-            return certChain;
-        } else {
-            LOG.debug(certUnavailableString);
-            return certChain;
+    private static Optional<X509Certificate> parseX509Certificate(String provider, byte[] cert)
+            throws CertificateParsingException, CmpClientException {
+        LOG.debug("Parsing X509Certificate from bytes with provider {}", provider);
+        final CertificateFactory cf = getCertificateFactory(provider);
+        X509Certificate result;
+        try {
+            result = (X509Certificate) Objects.requireNonNull(cf).generateCertificate(new ByteArrayInputStream(cert));
+            return Optional.ofNullable(result);
+        } catch (CertificateException ce) {
+            throw new CertificateParsingException("Could not parse byte array as X509Certificate ", ce);
         }
     }
-
-    private static CMPCertificate getRootCa(CertRepMessage certRepMessage) {
-        return certRepMessage.getCaPubs()[0];
-    }
 }