Verifying certchain and returning certchain and TrustStore
authorEmmettCox <emmett.cox@est.tech>
Mon, 24 Feb 2020 13:55:34 +0000 (13:55 +0000)
committerEmmettCox <emmett.cox@est.tech>
Thu, 27 Feb 2020 10:28:47 +0000 (10:28 +0000)
Issue-ID: AAF-1037
Signed-off-by: EmmettCox <emmett.cox@est.tech>
Change-Id: Iaab754ff5f568b2f2e1aeac8dbed279e20b09b3b

certService/src/main/java/org/onap/aaf/certservice/cmpv2client/api/CmpClient.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/CmpClientException.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/PkiErrorException.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageBuilder.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageHelper.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java [new file with mode: 0644]
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java
certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java

index feee3ee..150f15d 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.api;
 
-import java.io.IOException;
 import java.security.cert.X509Certificate;
 import java.util.Date;
-import org.apache.http.impl.client.CloseableHttpClient;
+import java.util.List;
 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
-import org.onap.aaf.certservice.cmpv2client.exceptions.PkiErrorException;
 import org.onap.aaf.certservice.cmpv2client.external.CSRMeta;
 
 /**
@@ -50,7 +52,7 @@ public interface CmpClient {
    * @return {@link X509Certificate} The newly created Certificate.
    * @throws CmpClientException if client error occurs.
    */
-  X509Certificate createCertificate(
+  List<List<X509Certificate>> createCertificate(
       String caName,
       String profile,
       CSRMeta csrMeta,
@@ -74,7 +76,7 @@ public interface CmpClient {
    * @return {@link X509Certificate} The newly created Certificate.
    * @throws CmpClientException if client error occurs.
    */
-  X509Certificate createCertificate(
+  List<List<X509Certificate>> createCertificate(
       String caName,
       String profile,
       CSRMeta csrMeta,
index 7f7d4ae..06bdfd8 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.exceptions;
index 965ce6f..9e6b928 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.exceptions;
index fb43e3e..7dacfc8 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.impl;
 
+import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.checkIfCmpResponseContainsError;
+import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.getCertfromByteArray;
+import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.verifyAndReturnCertChainAndTrustSTore;
+
+import java.io.IOException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.bouncycastle.asn1.cmp.CMPCertificate;
+import org.bouncycastle.asn1.cmp.CertRepMessage;
+import org.bouncycastle.asn1.cmp.CertResponse;
+import org.bouncycastle.asn1.cmp.PKIBody;
 import org.bouncycastle.asn1.cmp.PKIMessage;
 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
 import org.onap.aaf.certservice.cmpv2client.api.CmpClient;
@@ -38,12 +57,12 @@ public class CmpClientImpl implements CmpClient {
   private static final String DEFAULT_PROFILE = "RA";
   private static final String DEFAULT_CA_NAME = "Certification Authority";
 
-  public CmpClientImpl(CloseableHttpClient httpClient){
+  public CmpClientImpl(CloseableHttpClient httpClient) {
     this.httpClient = httpClient;
   }
 
   @Override
-  public X509Certificate createCertificate(
+  public List<List<X509Certificate>> createCertificate(
       String caName,
       String profile,
       CSRMeta csrMeta,
@@ -67,23 +86,59 @@ public class CmpClientImpl implements CmpClient {
 
     final PKIMessage pkiMessage = certRequest.generateCertReq();
     Cmpv2HttpClient cmpv2HttpClient = new Cmpv2HttpClient(httpClient);
-    final byte[] respBytes =
-        cmpv2HttpClient.postRequest(pkiMessage, csrMeta.caUrl(), caName);
-    final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
-    // todo: add response validation and return Certificate
-    return null;
+    return retrieveCertificates(caName, csrMeta, pkiMessage, cmpv2HttpClient);
   }
 
   @Override
-  public X509Certificate createCertificate(
-      String caName,
-      String profile,
-      CSRMeta csrMeta,
-      X509Certificate csr)
+  public List<List<X509Certificate>> createCertificate(
+      String caName, String profile, CSRMeta csrMeta, X509Certificate csr)
       throws CmpClientException {
     return createCertificate(caName, profile, csrMeta, csr, null, null);
   }
 
+  private List<List<X509Certificate>> checkCmpCertRepMessage(final PKIMessage respPkiMessage)
+      throws CmpClientException {
+    final PKIBody pkiBody = respPkiMessage.getBody();
+    if (Objects.nonNull(pkiBody) && pkiBody.getContent() instanceof CertRepMessage) {
+      final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent();
+      if (Objects.nonNull(certRepMessage)) {
+        final CertResponse certResponse =
+            getCertificateResponseContainingNewCertificate(certRepMessage);
+        try {
+          return verifyReturnCertChainAndTrustStore(respPkiMessage, certRepMessage, certResponse);
+        } catch (IOException | CertificateParsingException ex) {
+          CmpClientException cmpClientException =
+              new CmpClientException(
+                  "Exception occurred while retrieving Certificates from response", ex);
+          LOG.error("Exception occurred while retrieving Certificates from response", ex);
+          throw cmpClientException;
+        }
+      } else {
+        return new ArrayList<>(Collections.emptyList());
+      }
+    }
+    return new ArrayList<>(Collections.emptyList());
+  }
+
+  private List<List<X509Certificate>> verifyReturnCertChainAndTrustStore(
+      PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
+      throws CertificateParsingException, CmpClientException, IOException {
+    LOG.info("Verifying certificates returned as part of CertResponse.");
+    final CMPCertificate cmpCertificate =
+        certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
+    final Optional<X509Certificate> leafCertificate =
+        getCertfromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
+    ArrayList<X509Certificate> certChain = new ArrayList<>();
+    ArrayList<X509Certificate> trustStore = new ArrayList<>();
+    return verifyAndReturnCertChainAndTrustSTore(
+        respPkiMessage, certRepMessage, leafCertificate.get(), certChain, trustStore);
+  }
+
+  private CertResponse getCertificateResponseContainingNewCertificate(
+      CertRepMessage certRepMessage) {
+    return certRepMessage.getResponse()[0];
+  }
+
   /**
    * Validate inputs for Certificate Creation.
    *
@@ -100,8 +155,7 @@ public class CmpClientImpl implements CmpClient {
       final String incomingProfile,
       final CloseableHttpClient httpClient,
       final Date notBefore,
-      final Date notAfter)
-      throws IllegalArgumentException {
+      final Date notAfter) {
 
     String caName;
     String caProfile;
@@ -123,4 +177,22 @@ public class CmpClientImpl implements CmpClient {
       throw new IllegalArgumentException("Before Date is set after the After Date");
     }
   }
+
+  private List<List<X509Certificate>> retrieveCertificates(
+      String caName, CSRMeta csrMeta, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
+      throws CmpClientException {
+    final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, csrMeta.caUrl(), caName);
+    try {
+      final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
+      LOG.info("Recieved response from Server");
+      checkIfCmpResponseContainsError(respPkiMessage);
+      return checkCmpCertRepMessage(respPkiMessage);
+    } catch (IllegalArgumentException iae) {
+      CmpClientException cmpClientException =
+          new CmpClientException(
+              "Error encountered while processing response from CA server ", iae);
+      LOG.error("Error encountered while processing response from CA server ", iae);
+      throw cmpClientException;
+    }
+  }
 }
index ee8129c..2ab9b2c 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.impl;
index 8c470c7..48b2336 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.impl;
@@ -30,7 +34,6 @@ import java.security.SignatureException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
-import java.util.Optional;
 import javax.crypto.Mac;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
@@ -120,8 +123,8 @@ public final class CmpMessageHelper {
     } catch (IOException ioe) {
       CmpClientException cmpClientException =
           new CmpClientException(
-              "Exception occurred while creating proof of possession for PKIMessage", ioe);
-      LOG.error("Exception occurred while creating proof of possession for PKIMessage");
+              "Exception occurred while creating extensions for PKIMessage", ioe);
+      LOG.error("Exception occurred while creating extensions for PKIMessage");
       throw cmpClientException;
     }
     return extGenerator.generate();
@@ -173,7 +176,7 @@ public final class CmpMessageHelper {
         | SignatureException ex) {
       CmpClientException cmpClientException =
           new CmpClientException(
-              "Exception occurred while creating proof " + "of possession for PKIMessage", ex);
+              "Exception occurred while creating proof of possession for PKIMessage", ex);
       LOG.error("Exception occurred while creating proof of possession for PKIMessage");
       throw cmpClientException;
     }
@@ -230,7 +233,7 @@ public final class CmpMessageHelper {
     } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException ex) {
       CmpClientException cmpClientException =
           new CmpClientException(
-              "Exception occurred while generating " + "proof of possession for PKIMessage", ex);
+              "Exception occurred while generating proof of possession for PKIMessage", ex);
       LOG.error("Exception occured while generating the proof of possession for PKIMessage");
       throw cmpClientException;
     }
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java
new file mode 100644 (file)
index 0000000..8fdee2e
--- /dev/null
@@ -0,0 +1,345 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.aaf.certservice.cmpv2client.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.PKIXCertPathChecker;
+import java.security.cert.PKIXCertPathValidatorResult;
+import java.security.cert.PKIXParameters;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import org.bouncycastle.asn1.cmp.CMPCertificate;
+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.jce.provider.BouncyCastleProvider;
+import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
+import org.onap.aaf.certservice.cmpv2client.exceptions.PkiErrorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CmpResponseHelper {
+
+  private static final Logger LOG = LoggerFactory.getLogger(CmpMessageHelper.class);
+
+  public static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage)
+      throws CmpClientException {
+    if (respPkiMessage.getBody().getType() == PKIBody.TYPE_ERROR) {
+      final ErrorMsgContent errorMsgContent =
+          (ErrorMsgContent) respPkiMessage.getBody().getContent();
+      PkiErrorException pkiErrorException =
+          new PkiErrorException(
+              errorMsgContent.getPKIStatusInfo().getStatusString().getStringAt(0).getString());
+      CmpClientException cmpClientException =
+          new CmpClientException("Error in the PkiMessage response", pkiErrorException);
+      LOG.error("Error in the PkiMessage response: {} ", pkiErrorException.getMessage());
+      throw cmpClientException;
+    }
+  }
+
+  /**
+   * @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 <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);
+  }
+
+  /**
+   * @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;
+    }
+
+    if (returnType.equals(X509Certificate.class)) {
+      return parseX509Certificate(prov, cert);
+    }
+    return Optional.empty();
+  }
+
+  /**
+   * 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;
+    }
+  }
+
+  /**
+   * Check the certificate with CA certificate.
+   *
+   * @param certificate X.509 certificate to verify. May not be null.
+   * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a
+   *     Collection with null entries.
+   * @param date Date to verify at, or null to use current time.
+   * @param pkixCertPathCheckers optional PKIXCertPathChecker implementations to use during cert
+   *     path validation
+   * @throws CmpClientException if certificate could not be validated
+   */
+  public static void verify(
+      X509Certificate certificate,
+      X509Certificate caCertChain,
+      Date date,
+      PKIXCertPathChecker... pkixCertPathCheckers)
+      throws CmpClientException {
+    try {
+      verifyCertificates(certificate, caCertChain, date, pkixCertPathCheckers);
+    } catch (CertPathValidatorException cpve) {
+      CmpClientException cmpClientException =
+          new CmpClientException(
+              "Invalid certificate or certificate not issued by specified CA: ", cpve);
+      LOG.error("Invalid certificate or certificate not issued by specified CA: ", cpve);
+      throw cmpClientException;
+    } catch (CertificateException ce) {
+      CmpClientException cmpClientException =
+          new CmpClientException("Something was wrong with the supplied certificate", ce);
+      LOG.error("Something was wrong with the supplied certificate", ce);
+      throw cmpClientException;
+    } catch (NoSuchProviderException nspe) {
+      CmpClientException cmpClientException =
+          new CmpClientException("BouncyCastle provider not found.", nspe);
+      LOG.error("BouncyCastle provider not found.", nspe);
+      throw cmpClientException;
+    } catch (NoSuchAlgorithmException nsae) {
+      CmpClientException cmpClientException =
+          new CmpClientException("Algorithm PKIX was not found.", nsae);
+      LOG.error("Algorithm PKIX was not found.", nsae);
+      throw cmpClientException;
+    } catch (InvalidAlgorithmParameterException iape) {
+      CmpClientException cmpClientException =
+          new CmpClientException(
+              "Either ca certificate chain was empty,"
+                  + " or the certificate was on an inappropriate type for a PKIX path checker.",
+              iape);
+      LOG.error(
+          "Either ca certificate chain was empty, "
+              + "or the certificate was on an inappropriate type for a PKIX path checker.",
+          iape);
+      throw cmpClientException;
+    }
+  }
+
+  public static void verifyCertificates(
+      X509Certificate certificate,
+      X509Certificate caCertChain,
+      Date date,
+      PKIXCertPathChecker[] pkixCertPathCheckers)
+      throws CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException,
+          NoSuchAlgorithmException, CertPathValidatorException {
+    LOG.debug(
+        "Verifying certificate {} as part of cert chain with certificate {}",
+        certificate.getSubjectDN().getName(),
+        caCertChain.getSubjectDN().getName());
+    CertPath cp = getCertPath(certificate);
+    PKIXParameters params = getPkixParameters(caCertChain, date, pkixCertPathCheckers);
+    CertPathValidator cpv =
+        CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
+    PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Certificate verify result:{} ", result.toString());
+    }
+  }
+
+  public static PKIXParameters getPkixParameters(
+      X509Certificate caCertChain, Date date, PKIXCertPathChecker[] pkixCertPathCheckers)
+      throws InvalidAlgorithmParameterException {
+    TrustAnchor anchor = new TrustAnchor(caCertChain, null);
+    PKIXParameters params = new PKIXParameters(Collections.singleton(anchor));
+    for (final PKIXCertPathChecker pkixCertPathChecker : pkixCertPathCheckers) {
+      params.addCertPathChecker(pkixCertPathChecker);
+    }
+    params.setRevocationEnabled(false);
+    params.setDate(date);
+    return params;
+  }
+
+  public static CertPath getCertPath(X509Certificate certificate)
+      throws CertificateException, NoSuchProviderException {
+    ArrayList<X509Certificate> certlist = new ArrayList<>();
+    certlist.add(certificate);
+    return CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME)
+        .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.
+   *
+   * @param provider Security provider that should be used to create certificates, default BC is
+   *     null is passed.
+   * @return CertificateFactory for creating certificate
+   */
+  public static CertificateFactory getCertificateFactory(final String provider)
+      throws CmpClientException {
+    LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider);
+    final String prov;
+    prov = Objects.requireNonNullElse(provider, BouncyCastleProvider.PROVIDER_NAME);
+    try {
+      return CertificateFactory.getInstance("X.509", prov);
+    } catch (NoSuchProviderException nspe) {
+      CmpClientException cmpClientException = new CmpClientException("NoSuchProvider: ", nspe);
+      LOG.error("NoSuchProvider: ", nspe);
+      throw cmpClientException;
+    } catch (CertificateException ce) {
+      CmpClientException cmpClientException = new CmpClientException("CertificateException: ", ce);
+      LOG.error("CertificateException: ", ce);
+      throw cmpClientException;
+    }
+  }
+
+  /**
+   * 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
+   * @param certChain Array of certificates to be used for KeyStore
+   * @param trustStore Array of certificates to be used for TrustStore
+   * @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
+   */
+  public static List<List<X509Certificate>> verifyAndReturnCertChainAndTrustSTore(
+      PKIMessage respPkiMessage,
+      CertRepMessage certRepMessage,
+      X509Certificate leafCertificate,
+      ArrayList<X509Certificate> certChain,
+      ArrayList<X509Certificate> trustStore)
+      throws CertificateParsingException, IOException, CmpClientException {
+    List<String> certNames = getNamesOfCerts(certChain);
+    LOG.debug("Verifying the following certificates in the cert chain: {}", certNames);
+    certChain.add(leafCertificate);
+    addExtraCertsToChain(respPkiMessage, certRepMessage, certChain);
+    verify(certChain);
+    List<List<X509Certificate>> listOfArray = new ArrayList<>();
+    listOfArray.add(certChain);
+    listOfArray.add(trustStore);
+    return listOfArray;
+  }
+
+  public static List<String> getNamesOfCerts(List<X509Certificate> certChain) {
+    List<String> certNames = new ArrayList<>();
+    certChain.forEach((cert) -> certNames.add(cert.getSubjectDN().getName()));
+    return certNames;
+  }
+
+  /**
+   * checks whether PKIMessage contains extracerts to create certchain, if not creates from caPubs
+   *
+   * @param respPkiMessage PKIMessage that may contain extra certs used for certchain
+   * @param certRepMessage CertRepMessage that should contain rootCA for certchain
+   * @param certChain Array of certificates to be used for KeyStore
+   * @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
+   */
+  public static void addExtraCertsToChain(
+      PKIMessage respPkiMessage,
+      CertRepMessage certRepMessage,
+      ArrayList<X509Certificate> certChain)
+      throws CertificateParsingException, IOException, CmpClientException {
+    if (respPkiMessage.getExtraCerts() != null) {
+      final CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts();
+      for (CMPCertificate cmpCert : extraCerts) {
+        Optional<X509Certificate> cert =
+            getCertfromByteArray(cmpCert.getEncoded(), X509Certificate.class);
+        LOG.debug("Adding certificate {} to cert chain", cert.get().getSubjectDN().getName());
+        certChain.add(cert.get());
+      }
+    } else {
+      final CMPCertificate respCmpCaCert = getRootCa(certRepMessage);
+      Optional<X509Certificate> cert =
+          getCertfromByteArray(respCmpCaCert.getEncoded(), X509Certificate.class);
+      LOG.debug("Adding certificate {} to TrustStore", cert.get().getSubjectDN().getName());
+      certChain.add(cert.get());
+    }
+  }
+
+  private static CMPCertificate getRootCa(CertRepMessage certRepMessage) {
+    return certRepMessage.getCaPubs()[0];
+  }
+}
index b7452fc..8693513 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.impl;
index b1f9633..db86a1e 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2019 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.impl;
@@ -33,16 +37,28 @@ class Cmpv2HttpClient {
 
   private static final String CONTENT_TYPE = "Content-type";
   private static final String CMP_REQUEST_MIMETYPE = "application/pkixcmp";
-  private CloseableHttpClient httpClient;
+  private final CloseableHttpClient httpClient;
 
-  public Cmpv2HttpClient(CloseableHttpClient httpClient){
+  /**
+   * constructor for Cmpv2HttpClient
+   *
+   * @param httpClient CloseableHttpClient used for sending/recieve request.
+   */
+  public Cmpv2HttpClient(CloseableHttpClient httpClient) {
     this.httpClient = httpClient;
   }
 
+  /**
+   * Send Post Request to Server
+   *
+   * @param pkiMessage PKIMessage to send to server
+   * @param urlString url for the server we're sending request
+   * @param caName name of CA server
+   * @return
+   * @throws CmpClientException thrown if problems with connecting or parsing response to server
+   */
   public byte[] postRequest(
-      final PKIMessage pkiMessage,
-      final String urlString,
-      final String caName)
+      final PKIMessage pkiMessage, final String urlString, final String caName)
       throws CmpClientException {
     try (final ByteArrayOutputStream byteArrOutputStream = new ByteArrayOutputStream()) {
       final HttpPost postRequest = new HttpPost(urlString);
@@ -57,7 +73,7 @@ class Cmpv2HttpClient {
       return byteArrOutputStream.toByteArray();
     } catch (IOException ioe) {
       CmpClientException cmpClientException =
-          new CmpClientException("IOException error while trying to connect CA " + caName, ioe);
+          new CmpClientException(String.format("IOException error while trying to connect CA %s",caName), ioe);
       LOG.error("IOException error {}, while trying to connect CA {}", ioe.getMessage(), caName);
       throw cmpClientException;
     }
index aa544e7..2594332 100644 (file)
@@ -1,6 +1,7 @@
-/*
- * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
- *
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
  * 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
  * 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
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
  */
 
 package org.onap.aaf.certservice.cmpv2client.impl;
@@ -20,11 +24,9 @@ import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.createRandomByte
 import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.createRandomInt;
 import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.generatePkiHeader;
 
-import java.io.IOException;
 import java.security.KeyPair;
 import java.util.Date;
 import java.util.List;
-import java.util.Optional;
 import org.bouncycastle.asn1.DERUTF8String;
 import org.bouncycastle.asn1.cmp.PKIBody;
 import org.bouncycastle.asn1.cmp.PKIHeader;
@@ -39,8 +41,6 @@ import org.bouncycastle.asn1.crmf.ProofOfPossession;
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Implementation of the CmpClient Interface conforming to RFC4210 (Certificate Management Protocol
@@ -48,8 +48,6 @@ import org.slf4j.LoggerFactory;
  */
 class CreateCertRequest {
 
-  private static final Logger LOG = LoggerFactory.getLogger(CreateCertRequest.class);
-
   private X500Name issuerDn;
   private X500Name subjectDn;
   private List<String> sansList;
@@ -58,8 +56,8 @@ class CreateCertRequest {
   private Date notAfter;
   private String initAuthPassword;
 
-  private static final int iterations = createRandomInt(5000);
-  private static final byte[] salt = createRandomBytes();
+  private static final int ITERATIONS = createRandomInt(5000);
+  private static final byte[] SALT = createRandomBytes();
   private final int certReqId = createRandomInt(Integer.MAX_VALUE);
 
   public void setIssuerDn(X500Name issuerDn) {
@@ -120,10 +118,10 @@ class CreateCertRequest {
 
     final PKIHeader pkiHeader =
         generatePkiHeader(
-            subjectDn, issuerDn, CmpMessageHelper.protectionAlgoIdentifier(iterations, salt));
+            subjectDn, issuerDn, CmpMessageHelper.protectionAlgoIdentifier(ITERATIONS, SALT));
     final PKIBody pkiBody = new PKIBody(PKIBody.TYPE_CERT_REQ, certReqMessages);
 
     return CmpMessageHelper.protectPkiMessage(
-        pkiHeader, pkiBody, initAuthPassword, iterations, salt);
+        pkiHeader, pkiBody, initAuthPassword, ITERATIONS, SALT);
   }
 }
index 74eb098..26cf7e2 100644 (file)
@@ -15,7 +15,7 @@
  */
 package org.onap.aaf.certservice.cmpv2Client;
 
-import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.spy;
@@ -32,14 +32,12 @@ import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Security;
-import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
-import java.util.Optional;
 import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpEntity;
 import org.apache.http.client.methods.CloseableHttpResponse;
@@ -128,10 +126,92 @@ class Cmpv2ClientTest {
     }
     CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
     // when
-    Certificate certificate =
+    List<List<X509Certificate>> cmpClientResult =
         cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter);
     // then
-    assertNull(certificate);
+    assertNotNull(cmpClientResult);
+  }
+
+  @Test
+  void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr2()
+      throws Exception {
+    // given
+    Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
+    Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
+    setCsrMetaValuesAndDateValues(
+        rdns,
+        "CN=CommonName",
+        "CN=ManagementCA",
+        "CommonName.com",
+        "CommonName@cn.com",
+        "password",
+        "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
+        beforeDate,
+        afterDate);
+    when(httpClient.execute(any())).thenReturn(httpResponse);
+    when(httpResponse.getEntity()).thenReturn(httpEntity);
+
+    try (final InputStream is =
+        this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
+        BufferedInputStream bis = new BufferedInputStream(is)) {
+
+      byte[] ba = IOUtils.toByteArray(bis);
+      doAnswer(
+          invocation -> {
+            OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
+            os.write(ba);
+            return null;
+          })
+          .when(httpEntity)
+          .writeTo(any(OutputStream.class));
+    }
+    CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
+    // when
+    List<List<X509Certificate>> cmpClientResult =
+        cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter);
+    // then
+    assertNotNull(cmpClientResult);
+  }
+
+  @Test
+  void shouldReturnCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
+      throws Exception {
+    // given
+    Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
+    Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
+    setCsrMetaValuesAndDateValues(
+        rdns,
+        "CN=CommonName",
+        "CN=ManagementCA",
+        "CommonName.com",
+        "CommonName@cn.com",
+        "password",
+        "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
+        beforeDate,
+        afterDate);
+    when(httpClient.execute(any())).thenReturn(httpResponse);
+    when(httpResponse.getEntity()).thenReturn(httpEntity);
+
+    try (final InputStream is =
+            this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword");
+        BufferedInputStream bis = new BufferedInputStream(is)) {
+
+      byte[] ba = IOUtils.toByteArray(bis);
+      doAnswer(
+              invocation -> {
+                OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
+                os.write(ba);
+                return null;
+              })
+          .when(httpEntity)
+          .writeTo(any(OutputStream.class));
+    }
+    CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
+
+    // then
+    Assertions.assertThrows(
+        CmpClientException.class,
+        () -> cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter));
   }
 
   @Test
@@ -154,9 +234,7 @@ class Cmpv2ClientTest {
     // then
     Assertions.assertThrows(
         IllegalArgumentException.class,
-        () ->
-            cmpClient.createCertificate(
-                "data", "RA", csrMeta, cert, notBefore, notAfter));
+        () -> cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter));
   }
 
   @Test
@@ -180,9 +258,7 @@ class Cmpv2ClientTest {
     // then
     Assertions.assertThrows(
         CmpClientException.class,
-        () ->
-            cmpClient.createCertificate(
-                "data", "RA", csrMeta, cert, notBefore, notAfter));
+        () -> cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter));
   }
 
   private void setCsrMetaValuesAndDateValues(