Authenticate response from CMP server
authorEmmettCox <emmett.cox@est.tech>
Thu, 27 Feb 2020 17:19:47 +0000 (17:19 +0000)
committerEmmettCox <emmett.cox@est.tech>
Wed, 4 Mar 2020 09:16:08 +0000 (09:16 +0000)
Issue-ID: AAF-1037
Signed-off-by: EmmettCox <emmett.cox@est.tech>
Change-Id: I6f52627a169359067ddd928d1e895e8d6237c7b5

certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java
certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseValidationHelper.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
certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile
certService/src/test/resources/privateKey [new file with mode: 0644]
certService/src/test/resources/publicKey [new file with mode: 0644]

index 7655b02..e9f7a48 100644 (file)
@@ -48,7 +48,7 @@ public class CSRMeta {
     private X500Name name;
     private X500Name issuerName;
     private Certificate certificate;
-    private SecureRandom random = new SecureRandom();
+    private String senderKid;
 
     public CSRMeta(List<RDN> rdns) {
         this.rdns = rdns;
@@ -188,6 +188,14 @@ public class CSRMeta {
         CaUrl = caUrl;
     }
 
+    public String senderKid() {
+        return senderKid;
+    }
+
+    public void senderKid(String senderKid) {
+        this.senderKid = senderKid;
+    }
+
     public String issuerCn() {
         return issuerCn;
     }
index 7dacfc8..29bd671 100644 (file)
 
 package org.onap.aaf.certservice.cmpv2client.impl;
 
+import java.security.PublicKey;
 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 static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.checkImplicitConfirm;
+import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifyPasswordBasedProtection;
+import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifySignature;
 
 import java.io.IOException;
 import java.security.cert.CertificateParsingException;
@@ -38,7 +42,9 @@ 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.PKIHeader;
 import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
 import org.onap.aaf.certservice.cmpv2client.api.CmpClient;
 import org.onap.aaf.certservice.cmpv2client.external.CSRMeta;
@@ -51,7 +57,7 @@ import org.slf4j.LoggerFactory;
  */
 public class CmpClientImpl implements CmpClient {
 
-  private final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class);
+  private static final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class);
   private final CloseableHttpClient httpClient;
 
   private static final String DEFAULT_PROFILE = "RA";
@@ -82,6 +88,7 @@ public class CmpClientImpl implements CmpClient {
             .with(CreateCertRequest::setNotBefore, notBefore)
             .with(CreateCertRequest::setNotAfter, notAfter)
             .with(CreateCertRequest::setInitAuthPassword, csrMeta.password())
+            .with(CreateCertRequest::setSenderKid, csrMeta.senderKid())
             .build();
 
     final PKIMessage pkiMessage = certRequest.generateCertReq();
@@ -96,6 +103,45 @@ public class CmpClientImpl implements CmpClient {
     return createCertificate(caName, profile, csrMeta, csr, null, null);
   }
 
+  private void checkCmpResponse(
+      final PKIMessage respPkiMessage, final PublicKey publicKey, final String initAuthPassword)
+      throws CmpClientException {
+    final PKIHeader header = respPkiMessage.getHeader();
+    final AlgorithmIdentifier protectionAlgo = header.getProtectionAlg();
+    verifySignatureWithPublicKey(respPkiMessage, publicKey);
+    verifyProtectionWithProtectionAlgo(respPkiMessage, initAuthPassword, header, protectionAlgo);
+  }
+
+  private void verifySignatureWithPublicKey(PKIMessage respPkiMessage, PublicKey publicKey)
+      throws CmpClientException {
+    if (Objects.nonNull(publicKey)) {
+      LOG.debug("Verifying signature of the response.");
+      verifySignature(respPkiMessage, publicKey);
+    } else {
+      LOG.error("Public Key is not available, therefore cannot verify signature");
+      throw new CmpClientException(
+          "Public Key is not available, therefore cannot verify signature");
+    }
+  }
+
+  private void verifyProtectionWithProtectionAlgo(
+      PKIMessage respPkiMessage,
+      String initAuthPassword,
+      PKIHeader header,
+      AlgorithmIdentifier protectionAlgo)
+      throws CmpClientException {
+    if (Objects.nonNull(protectionAlgo)) {
+      LOG.debug("Verifying PasswordBased Protection of the Response.");
+      verifyPasswordBasedProtection(respPkiMessage, initAuthPassword, protectionAlgo);
+      checkImplicitConfirm(header);
+    } else {
+      LOG.error(
+          "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
+      throw new CmpClientException(
+          "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
+    }
+  }
+
   private List<List<X509Certificate>> checkCmpCertRepMessage(final PKIMessage respPkiMessage)
       throws CmpClientException {
     final PKIBody pkiBody = respPkiMessage.getBody();
@@ -128,10 +174,11 @@ public class CmpClientImpl implements CmpClient {
         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);
+    if (leafCertificate.isPresent()) {
+      return verifyAndReturnCertChainAndTrustSTore(
+          respPkiMessage, certRepMessage, leafCertificate.get());
+    }
+    return Collections.emptyList();
   }
 
   private CertResponse getCertificateResponseContainingNewCertificate(
@@ -184,8 +231,9 @@ public class CmpClientImpl implements CmpClient {
     final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, csrMeta.caUrl(), caName);
     try {
       final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
-      LOG.info("Recieved response from Server");
+      LOG.info("Received response from Server");
       checkIfCmpResponseContainsError(respPkiMessage);
+      checkCmpResponse(respPkiMessage, csrMeta.keypair().getPublic(), csrMeta.password());
       return checkCmpCertRepMessage(respPkiMessage);
     } catch (IllegalArgumentException iae) {
       CmpClientException cmpClientException =
index 8fdee2e..60d91c5 100644 (file)
@@ -56,7 +56,9 @@ import org.slf4j.LoggerFactory;
 
 public class CmpResponseHelper {
 
-  private static final Logger LOG = LoggerFactory.getLogger(CmpMessageHelper.class);
+  private static final Logger LOG = LoggerFactory.getLogger(CmpResponseHelper.class);
+
+  private CmpResponseHelper() {}
 
   public static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage)
       throws CmpClientException {
@@ -192,7 +194,7 @@ public class CmpResponseHelper {
         CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
     PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
     if (LOG.isDebugEnabled()) {
-      LOG.debug("Certificate verify result:{} ", result.toString());
+      LOG.debug("Certificate verify result:{} ", result);
     }
   }
 
@@ -276,25 +278,23 @@ public class CmpResponseHelper {
    * @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)
+      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);
-    certChain.add(leafCertificate);
-    addExtraCertsToChain(respPkiMessage, certRepMessage, certChain);
     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);
@@ -303,7 +303,7 @@ public class CmpResponseHelper {
 
   public static List<String> getNamesOfCerts(List<X509Certificate> certChain) {
     List<String> certNames = new ArrayList<>();
-    certChain.forEach((cert) -> certNames.add(cert.getSubjectDN().getName()));
+    certChain.forEach(cert -> certNames.add(cert.getSubjectDN().getName()));
     return certNames;
   }
 
@@ -312,30 +312,56 @@ public class CmpResponseHelper {
    *
    * @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
+   * @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
    */
-  public static void addExtraCertsToChain(
-      PKIMessage respPkiMessage,
-      CertRepMessage certRepMessage,
-      ArrayList<X509Certificate> certChain)
+  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);
-        LOG.debug("Adding certificate {} to cert chain", cert.get().getSubjectDN().getName());
-        certChain.add(cert.get());
+        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);
-      LOG.debug("Adding certificate {} to TrustStore", cert.get().getSubjectDN().getName());
+      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;
     }
   }
 
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseValidationHelper.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseValidationHelper.java
new file mode 100644 (file)
index 0000000..8191c69
--- /dev/null
@@ -0,0 +1,238 @@
+/*-
+ * ============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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.Arrays;
+import java.util.Objects;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers;
+import org.bouncycastle.asn1.cmp.InfoTypeAndValue;
+import org.bouncycastle.asn1.cmp.PBMParameter;
+import org.bouncycastle.asn1.cmp.PKIBody;
+import org.bouncycastle.asn1.cmp.PKIHeader;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class CmpResponseValidationHelper {
+
+  private static final Logger LOG = LoggerFactory.getLogger(CmpResponseValidationHelper.class);
+
+  private CmpResponseValidationHelper() {}
+
+  /**
+   * Create a base key to use for verifying the PasswordBasedMac on a PKIMessage
+   *
+   * @param pbmParamSeq parameters recieved in PKIMessage used with password
+   * @param initAuthPassword password used to decrypt the basekey
+   * @return bytes representing the basekey
+   * @throws CmpClientException thrown if algorithem exceptions occur for the message digest
+   */
+  public static byte[] getBaseKeyFromPbmParameters(
+      PBMParameter pbmParamSeq, String initAuthPassword) throws CmpClientException {
+    final int iterationCount = pbmParamSeq.getIterationCount().getPositiveValue().intValue();
+    LOG.info("Iteration count is: {}", iterationCount);
+    final AlgorithmIdentifier owfAlg = pbmParamSeq.getOwf();
+    LOG.info("One Way Function type is: {}", owfAlg.getAlgorithm().getId());
+    final byte[] salt = pbmParamSeq.getSalt().getOctets();
+    final byte[] raSecret = initAuthPassword != null ? initAuthPassword.getBytes() : new byte[0];
+    byte[] basekey = new byte[raSecret.length + salt.length];
+    System.arraycopy(raSecret, 0, basekey, 0, raSecret.length);
+    System.arraycopy(salt, 0, basekey, raSecret.length, salt.length);
+    try {
+      final MessageDigest messageDigest =
+          MessageDigest.getInstance(
+              owfAlg.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
+      for (int i = 0; i < iterationCount; i++) {
+        basekey = messageDigest.digest(basekey);
+        messageDigest.reset();
+      }
+    } catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
+      LOG.error("ProtectionBytes don't match passwordBasedProtection, authentication failed");
+      throw new CmpClientException(
+          "ProtectionBytes don't match passwordBasedProtection, authentication failed", ex);
+    }
+    return basekey;
+  }
+
+  /**
+   * Verifies the signature of the response message using our public key
+   *
+   * @param respPkiMessage PKIMessage we wish to verify signature for
+   * @param pk public key used to verify signature.
+   * @throws CmpClientException
+   */
+  public static void verifySignature(PKIMessage respPkiMessage, PublicKey pk)
+      throws CmpClientException {
+    final byte[] protBytes = getProtectedBytes(respPkiMessage);
+    final DERBitString derBitString = respPkiMessage.getProtection();
+    try {
+      final Signature signature =
+          Signature.getInstance(
+              PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(),
+              BouncyCastleProvider.PROVIDER_NAME);
+      signature.initVerify(pk);
+      signature.update(protBytes);
+      signature.verify(derBitString.getBytes());
+    } catch (NoSuchAlgorithmException
+        | NoSuchProviderException
+        | InvalidKeyException
+        | SignatureException e) {
+      CmpClientException clientException =
+          new CmpClientException("Signature Verification failed", e);
+      LOG.error("Signature Verification failed", e);
+      throw clientException;
+    }
+  }
+  /**
+   * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
+   * array
+   *
+   * @param msg PKIMessage to get protected bytes from
+   * @return the PKIMessage's header and body in byte array
+   */
+  public static byte[] getProtectedBytes(PKIMessage msg) throws CmpClientException {
+    return getProtectedBytes(msg.getHeader(), msg.getBody());
+  }
+
+  /**
+   * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
+   * array
+   *
+   * @param header PKIHeader to be converted
+   * @param body PKIMessage to be converted
+   * @return the PKIMessage's header and body in byte array
+   */
+  public static byte[] getProtectedBytes(PKIHeader header, PKIBody body) throws CmpClientException {
+    byte[] res;
+    ASN1EncodableVector v = new ASN1EncodableVector();
+    v.add(header);
+    v.add(body);
+    ASN1Encodable protectedPart = new DERSequence(v);
+    try {
+      ByteArrayOutputStream bao = new ByteArrayOutputStream();
+      DEROutputStream out = new DEROutputStream(bao);
+      out.writeObject(protectedPart);
+      res = bao.toByteArray();
+    } catch (IOException ioe) {
+      CmpClientException cmpClientException =
+          new CmpClientException("Error occured while getting protected bytes", ioe);
+      LOG.error("Error occured while getting protected bytes", ioe);
+      throw cmpClientException;
+    }
+    return res;
+  }
+
+  /**
+   * verify the password based protection within the response message
+   *
+   * @param respPkiMessage PKIMessage we want to verify password based protection for
+   * @param initAuthPassword password used to decrypt protection
+   * @param protectionAlgo protection algorithm we can use to decrypt protection
+   * @throws CmpClientException
+   */
+  public static void verifyPasswordBasedProtection(
+      PKIMessage respPkiMessage, String initAuthPassword, AlgorithmIdentifier protectionAlgo)
+      throws CmpClientException {
+    final byte[] protectedBytes = getProtectedBytes(respPkiMessage);
+    final PBMParameter pbmParamSeq = PBMParameter.getInstance(protectionAlgo.getParameters());
+    if (Objects.nonNull(pbmParamSeq)) {
+      try {
+        byte[] basekey = getBaseKeyFromPbmParameters(pbmParamSeq, initAuthPassword);
+        final Mac mac = getMac(protectedBytes, pbmParamSeq, basekey);
+        final byte[] outBytes = mac.doFinal();
+        final byte[] protectionBytes = respPkiMessage.getProtection().getBytes();
+        if (!Arrays.equals(outBytes, protectionBytes)) {
+          LOG.error("protectionBytes don't match passwordBasedProtection, authentication failed");
+          throw new CmpClientException(
+              "protectionBytes don't match passwordBasedProtection, authentication failed");
+        }
+      } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException ex) {
+        CmpClientException cmpClientException =
+            new CmpClientException("Error while validating CMP response ", ex);
+        LOG.error("Error while validating CMP response ", ex);
+        throw cmpClientException;
+      }
+    }
+  }
+
+  public static void checkImplicitConfirm(PKIHeader header) {
+    InfoTypeAndValue[] infos = header.getGeneralInfo();
+    if (Objects.nonNull(infos)) {
+      if (CMPObjectIdentifiers.it_implicitConfirm.equals(getImplicitConfirm(infos))) {
+        LOG.info("Implicit Confirm on certificate from server.");
+      } else {
+        LOG.debug("No Implicit confirm in Response");
+      }
+    } else {
+      LOG.debug("No general Info in header of response, cannot verify implicit confirm");
+    }
+  }
+
+  public static ASN1ObjectIdentifier getImplicitConfirm(InfoTypeAndValue[] info) {
+    return info[0].getInfoType();
+  }
+
+  /**
+   * Get cryptographical Mac we can use to decrypt our PKIMessage
+   *
+   * @param protectedBytes Protected bytes representing the PKIMessage
+   * @param pbmParamSeq Parameters used to decrypt PKIMessage, including mac algorithm used
+   * @param basekey Key used alongside mac Oid to create secret key for decrypting PKIMessage
+   * @return Mac that's ready to return decrypted bytes
+   * @throws NoSuchAlgorithmException Possibly thrown trying to get mac instance
+   * @throws NoSuchProviderException Possibly thrown trying to get mac instance
+   * @throws InvalidKeyException Possibly thrown trying to initialize mac using secretkey
+   */
+  public static Mac getMac(byte[] protectedBytes, PBMParameter pbmParamSeq, byte[] basekey)
+      throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {
+    final AlgorithmIdentifier macAlg = pbmParamSeq.getMac();
+    LOG.info("Mac type is: {}", macAlg.getAlgorithm().getId());
+    final String macOid = macAlg.getAlgorithm().getId();
+    final Mac mac = Mac.getInstance(macOid, BouncyCastleProvider.PROVIDER_NAME);
+    final SecretKey key = new SecretKeySpec(basekey, macOid);
+    mac.init(key);
+    mac.reset();
+    mac.update(protectedBytes, 0, protectedBytes.length);
+    return mac;
+  }
+}
index 8693513..1bda4ac 100644 (file)
@@ -31,6 +31,8 @@ import org.bouncycastle.asn1.ASN1GeneralizedTime;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers;
+import org.bouncycastle.asn1.cmp.InfoTypeAndValue;
 import org.bouncycastle.asn1.cmp.PKIBody;
 import org.bouncycastle.asn1.cmp.PKIHeader;
 import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
@@ -129,7 +131,7 @@ public final class CmpUtil {
    * @return PKIHeaderBuilder
    */
   static PKIHeader generatePkiHeader(
-      X500Name subjectDn, X500Name issuerDn, AlgorithmIdentifier protectionAlg) {
+      X500Name subjectDn, X500Name issuerDn, AlgorithmIdentifier protectionAlg, String senderKid) {
     LOGGER.info("Generating a Pki Header Builder");
     PKIHeaderBuilder pkiHeaderBuilder =
         new PKIHeaderBuilder(
@@ -139,6 +141,8 @@ public final class CmpUtil {
     pkiHeaderBuilder.setSenderNonce(new DEROctetString(createRandomBytes()));
     pkiHeaderBuilder.setTransactionID(new DEROctetString(createRandomBytes()));
     pkiHeaderBuilder.setProtectionAlg(protectionAlg);
+    pkiHeaderBuilder.setGeneralInfo(new InfoTypeAndValue(CMPObjectIdentifiers.it_implicitConfirm));
+    pkiHeaderBuilder.setSenderKID(new DEROctetString(senderKid.getBytes()));
 
     return pkiHeaderBuilder.build();
   }
index db86a1e..682e410 100644 (file)
@@ -54,7 +54,7 @@ class Cmpv2HttpClient {
    * @param pkiMessage PKIMessage to send to server
    * @param urlString url for the server we're sending request
    * @param caName name of CA server
-   * @return
+   * @return PKIMessage received from CMPServer
    * @throws CmpClientException thrown if problems with connecting or parsing response to server
    */
   public byte[] postRequest(
@@ -73,7 +73,8 @@ class Cmpv2HttpClient {
       return byteArrOutputStream.toByteArray();
     } catch (IOException ioe) {
       CmpClientException cmpClientException =
-          new CmpClientException(String.format("IOException error while trying to connect CA %s",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 2594332..b185c92 100644 (file)
@@ -55,6 +55,7 @@ class CreateCertRequest {
   private Date notBefore;
   private Date notAfter;
   private String initAuthPassword;
+  private String senderKid;
 
   private static final int ITERATIONS = createRandomInt(5000);
   private static final byte[] SALT = createRandomBytes();
@@ -88,6 +89,10 @@ class CreateCertRequest {
     this.initAuthPassword = initAuthPassword;
   }
 
+  public void setSenderKid(String senderKid) {
+    this.senderKid = senderKid;
+  }
+
   /**
    * Method to create {@link PKIMessage} from {@link CertRequest},{@link ProofOfPossession}, {@link
    * CertReqMsg}, {@link CertReqMessages}, {@link PKIHeader} and {@link PKIBody}.
@@ -118,8 +123,11 @@ class CreateCertRequest {
 
     final PKIHeader pkiHeader =
         generatePkiHeader(
-            subjectDn, issuerDn, CmpMessageHelper.protectionAlgoIdentifier(ITERATIONS, SALT));
-    final PKIBody pkiBody = new PKIBody(PKIBody.TYPE_CERT_REQ, certReqMessages);
+            subjectDn,
+            issuerDn,
+            CmpMessageHelper.protectionAlgoIdentifier(ITERATIONS, SALT),
+            senderKid);
+    final PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages);
 
     return CmpMessageHelper.protectPkiMessage(
         pkiHeader, pkiBody, initAuthPassword, ITERATIONS, SALT);
index 26cf7e2..713a2d0 100644 (file)
@@ -27,12 +27,18 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.Security;
 import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -77,11 +83,13 @@ class Cmpv2ClientTest {
   private static ArrayList<RDN> rdns;
 
   @BeforeEach
-  void setUp() throws NoSuchProviderException, NoSuchAlgorithmException {
+  void setUp()
+      throws NoSuchProviderException, NoSuchAlgorithmException, IOException,
+          InvalidKeySpecException {
     KeyPairGenerator keyGenerator;
     keyGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
     keyGenerator.initialize(2048);
-    keyPair = keyGenerator.generateKeyPair();
+    keyPair = LoadKeyPair();
     rdns = new ArrayList<>();
     try {
       rdns.add(new RDN("O=CommonCompany"));
@@ -91,6 +99,27 @@ class Cmpv2ClientTest {
     initMocks(this);
   }
 
+  public KeyPair LoadKeyPair()
+      throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
+          NoSuchProviderException {
+
+    final InputStream privateInputStream = this.getClass().getResourceAsStream("/privateKey");
+    final InputStream publicInputStream = this.getClass().getResourceAsStream("/publicKey");
+    BufferedInputStream bis = new BufferedInputStream(privateInputStream);
+    byte[] privateBytes = IOUtils.toByteArray(bis);
+    bis = new BufferedInputStream(publicInputStream);
+    byte[] publicBytes = IOUtils.toByteArray(bis);
+
+    KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
+    X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicBytes);
+    PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
+
+    PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateBytes);
+    PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
+
+    return new KeyPair(publicKey, privateKey);
+  }
+
   @Test
   void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr()
       throws Exception {
@@ -103,8 +132,9 @@ class Cmpv2ClientTest {
         "CN=ManagementCA",
         "CommonName.com",
         "CommonName@cn.com",
-        "password",
+        "mypassword",
         "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
+        "senderKID",
         beforeDate,
         afterDate);
     when(httpClient.execute(any())).thenReturn(httpResponse);
@@ -133,8 +163,9 @@ class Cmpv2ClientTest {
   }
 
   @Test
-  void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr2()
-      throws Exception {
+  void
+      shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse()
+          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");
@@ -146,35 +177,35 @@ class Cmpv2ClientTest {
         "CommonName@cn.com",
         "password",
         "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
+        "senderKID",
         beforeDate,
         afterDate);
     when(httpClient.execute(any())).thenReturn(httpResponse);
     when(httpResponse.getEntity()).thenReturn(httpEntity);
 
     try (final InputStream is =
-        this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
+            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;
-          })
+              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);
+    Assertions.assertThrows(
+        CmpClientException.class,
+        () -> cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter));
   }
 
   @Test
-  void shouldReturnCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
+  void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
       throws Exception {
     // given
     Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
@@ -187,6 +218,7 @@ class Cmpv2ClientTest {
         "CommonName@cn.com",
         "password",
         "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
+        "senderKID",
         beforeDate,
         afterDate);
     when(httpClient.execute(any())).thenReturn(httpResponse);
@@ -228,6 +260,7 @@ class Cmpv2ClientTest {
         "CommonName@cn.com",
         "password",
         "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
+        "senderKID",
         beforeDate,
         afterDate);
     CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
@@ -251,6 +284,7 @@ class Cmpv2ClientTest {
         "Common@cn.com",
         "myPassword",
         "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest",
+        "sender",
         beforeDate,
         afterDate);
     when(httpClient.execute(any())).thenThrow(IOException.class);
@@ -269,6 +303,7 @@ class Cmpv2ClientTest {
       String email,
       String password,
       String externalCaUrl,
+      String senderKid,
       Date notBefore,
       Date notAfter) {
     csrMeta = new CSRMeta(rdns);
@@ -280,6 +315,7 @@ class Cmpv2ClientTest {
     when(kpg.generateKeyPair()).thenReturn(keyPair);
     csrMeta.keypair();
     csrMeta.caUrl(externalCaUrl);
+    csrMeta.senderKid(senderKid);
 
     this.notBefore = notBefore;
     this.notAfter = notAfter;
index 94cc346..e4a1d7b 100644 (file)
Binary files a/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile and b/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile differ
diff --git a/certService/src/test/resources/privateKey b/certService/src/test/resources/privateKey
new file mode 100644 (file)
index 0000000..216714c
Binary files /dev/null and b/certService/src/test/resources/privateKey differ
diff --git a/certService/src/test/resources/publicKey b/certService/src/test/resources/publicKey
new file mode 100644 (file)
index 0000000..e5c63be
Binary files /dev/null and b/certService/src/test/resources/publicKey differ