aboutsummaryrefslogtreecommitdiffstats
path: root/certService/src
diff options
context:
space:
mode:
authorBogumil Zebek <bogumil.zebek@nokia.com>2020-03-04 16:03:36 +0000
committerGerrit Code Review <gerrit@onap.org>2020-03-04 16:03:36 +0000
commit8f26d1f4274f18bd9502386700919933045e2316 (patch)
treed4272a9d867ed67aedc72bd79efaf33c3642dac6 /certService/src
parentddc9e99c372c32d9c62014ac1751f5840c734410 (diff)
parent9b682503a32af10dd6335c897e73e0e63f688210 (diff)
Merge "Authenticate response from CMP server"
Diffstat (limited to 'certService/src')
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java10
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java60
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java66
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseValidationHelper.java238
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java6
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java5
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java12
-rw-r--r--certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java68
-rw-r--r--certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFilebin4297 -> 2759 bytes
-rw-r--r--certService/src/test/resources/privateKeybin0 -> 1218 bytes
-rw-r--r--certService/src/test/resources/publicKeybin0 -> 294 bytes
11 files changed, 417 insertions, 48 deletions
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java
index 7655b025..e9f7a483 100644
--- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java
@@ -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;
}
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java
index 7dacfc80..29bd671d 100644
--- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java
@@ -20,9 +20,13 @@
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 =
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
index 8fdee2ed..60d91c5f 100644
--- 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
@@ -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
index 00000000..8191c69d
--- /dev/null
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseValidationHelper.java
@@ -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;
+ }
+}
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java
index 86935135..1bda4ac1 100644
--- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java
@@ -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();
}
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java
index db86a1e3..682e410d 100644
--- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java
@@ -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;
}
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java
index 25943321..b185c92a 100644
--- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java
@@ -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);
diff --git a/certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java b/certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java
index 26cf7e2d..713a2d00 100644
--- a/certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java
+++ b/certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java
@@ -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;
diff --git a/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile b/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile
index 94cc3461..e4a1d7b9 100644
--- a/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile
+++ b/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile
Binary files differ
diff --git a/certService/src/test/resources/privateKey b/certService/src/test/resources/privateKey
new file mode 100644
index 00000000..216714c9
--- /dev/null
+++ b/certService/src/test/resources/privateKey
Binary files differ
diff --git a/certService/src/test/resources/publicKey b/certService/src/test/resources/publicKey
new file mode 100644
index 00000000..e5c63be8
--- /dev/null
+++ b/certService/src/test/resources/publicKey
Binary files differ