aboutsummaryrefslogtreecommitdiffstats
path: root/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl
diff options
context:
space:
mode:
authorEmmettCox <emmett.cox@est.tech>2020-02-17 13:54:05 +0000
committerEmmettCox <emmett.cox@est.tech>2020-02-20 14:28:00 +0000
commit153a7ac15d804178e7c52f69117e1a9478862df1 (patch)
tree2a574f2a593b55531631ba02c97f318a136f1510 /certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl
parente93c679bc9c22e034ba93a48460830716e1f7457 (diff)
Refactoring of Cmpv2Client code for sending CertRequest
Issue-ID: AAF-1036 Signed-off-by: EmmettCox <emmett.cox@est.tech> Change-Id: Ic0d95b35abb3ca2406b77bbe6e0cd51da0968684
Diffstat (limited to 'certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl')
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java126
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageBuilder.java51
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageHelper.java241
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java141
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java65
-rw-r--r--certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java129
6 files changed, 753 insertions, 0 deletions
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
new file mode 100644
index 00000000..fb43e3e8
--- /dev/null
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
+ *
+ * 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
+ */
+
+package org.onap.aaf.certservice.cmpv2client.impl;
+
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
+import org.onap.aaf.certservice.cmpv2client.api.CmpClient;
+import org.onap.aaf.certservice.cmpv2client.external.CSRMeta;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the CmpClient Interface conforming to RFC4210 (Certificate Management Protocol
+ * (CMP)) and RFC4211 (Certificate Request Message Format (CRMF)) standards.
+ */
+public class CmpClientImpl implements CmpClient {
+
+ private final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class);
+ private final CloseableHttpClient httpClient;
+
+ private static final String DEFAULT_PROFILE = "RA";
+ private static final String DEFAULT_CA_NAME = "Certification Authority";
+
+ public CmpClientImpl(CloseableHttpClient httpClient){
+ this.httpClient = httpClient;
+ }
+
+ @Override
+ public X509Certificate createCertificate(
+ String caName,
+ String profile,
+ CSRMeta csrMeta,
+ X509Certificate cert,
+ Date notBefore,
+ Date notAfter)
+ throws CmpClientException {
+ // Validate inputs for Certificate Request
+ validate(csrMeta, cert, caName, profile, httpClient, notBefore, notAfter);
+
+ final CreateCertRequest certRequest =
+ CmpMessageBuilder.of(CreateCertRequest::new)
+ .with(CreateCertRequest::setIssuerDn, csrMeta.issuerx500Name())
+ .with(CreateCertRequest::setSubjectDn, csrMeta.x500Name())
+ .with(CreateCertRequest::setSansList, csrMeta.sans())
+ .with(CreateCertRequest::setSubjectKeyPair, csrMeta.keyPair())
+ .with(CreateCertRequest::setNotBefore, notBefore)
+ .with(CreateCertRequest::setNotAfter, notAfter)
+ .with(CreateCertRequest::setInitAuthPassword, csrMeta.password())
+ .build();
+
+ 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;
+ }
+
+ @Override
+ public X509Certificate createCertificate(
+ String caName,
+ String profile,
+ CSRMeta csrMeta,
+ X509Certificate csr)
+ throws CmpClientException {
+ return createCertificate(caName, profile, csrMeta, csr, null, null);
+ }
+
+ /**
+ * Validate inputs for Certificate Creation.
+ *
+ * @param csrMeta CSRMeta Object containing variables for creating a Certificate Request.
+ * @param cert Certificate object needed to validate response from CA server.
+ * @param incomingCaName Date specifying certificate is not valid before this date.
+ * @param incomingProfile Date specifying certificate is not valid after this date.
+ * @throws IllegalArgumentException if Before Date is set after the After Date.
+ */
+ private void validate(
+ final CSRMeta csrMeta,
+ final X509Certificate cert,
+ final String incomingCaName,
+ final String incomingProfile,
+ final CloseableHttpClient httpClient,
+ final Date notBefore,
+ final Date notAfter)
+ throws IllegalArgumentException {
+
+ String caName;
+ String caProfile;
+ caName = CmpUtil.isNullOrEmpty(incomingCaName) ? incomingCaName : DEFAULT_CA_NAME;
+ caProfile = CmpUtil.isNullOrEmpty(incomingProfile) ? incomingProfile : DEFAULT_PROFILE;
+ LOG.info(
+ "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, caProfile);
+
+ CmpUtil.notNull(csrMeta, "CSRMeta Instance");
+ CmpUtil.notNull(csrMeta.x500Name(), "Subject DN");
+ CmpUtil.notNull(csrMeta.issuerx500Name(), "Issuer DN");
+ CmpUtil.notNull(csrMeta.password(), "IAK/RV Password");
+ CmpUtil.notNull(cert, "Certificate Signing Request (CSR)");
+ CmpUtil.notNull(csrMeta.caUrl(), "External CA URL");
+ CmpUtil.notNull(csrMeta.keypair(), "Subject KeyPair");
+ CmpUtil.notNull(httpClient, "Closeable Http Client");
+
+ if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
+ throw new IllegalArgumentException("Before Date is set after the After Date");
+ }
+ }
+}
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageBuilder.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageBuilder.java
new file mode 100644
index 00000000..ee8129c6
--- /dev/null
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageBuilder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
+ *
+ * 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
+ */
+
+package org.onap.aaf.certservice.cmpv2client.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/** Generic Builder Class for creating CMP Message. */
+public class CmpMessageBuilder<T> {
+
+ private final Supplier<T> instantiator;
+ private final List<Consumer<T>> instanceModifiers = new ArrayList<>();
+
+ public CmpMessageBuilder(Supplier<T> instantiator) {
+ this.instantiator = instantiator;
+ }
+
+ public static <T> CmpMessageBuilder<T> of(Supplier<T> instantiator) {
+ return new CmpMessageBuilder<>(instantiator);
+ }
+
+ public <U> CmpMessageBuilder<T> with(BiConsumer<T, U> consumer, U value) {
+ Consumer<T> c = instance -> consumer.accept(instance, value);
+ instanceModifiers.add(c);
+ return this;
+ }
+
+ public T build() {
+ T value = instantiator.get();
+ instanceModifiers.forEach(modifier -> modifier.accept(value));
+ instanceModifiers.clear();
+ return value;
+ }
+}
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageHelper.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageHelper.java
new file mode 100644
index 00000000..8c470c7f
--- /dev/null
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpMessageHelper.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
+ *
+ * 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
+ */
+
+package org.onap.aaf.certservice.cmpv2client.impl;
+
+import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.generateProtectedBytes;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+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;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+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.crmf.CertRequest;
+import org.bouncycastle.asn1.crmf.OptionalValidity;
+import org.bouncycastle.asn1.crmf.POPOSigningKey;
+import org.bouncycastle.asn1.crmf.ProofOfPossession;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.Time;
+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 CmpMessageHelper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CmpMessageHelper.class);
+ private static final AlgorithmIdentifier OWF_ALGORITHM =
+ new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.3.14.3.2.26"));
+ private static final AlgorithmIdentifier MAC_ALGORITHM =
+ new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.2.9"));
+ private static final ASN1ObjectIdentifier PASSWORD_BASED_MAC =
+ new ASN1ObjectIdentifier("1.2.840.113533.7.66.13");
+
+ private CmpMessageHelper() {}
+
+ /**
+ * Creates an Optional Validity, which is used to specify how long the returned cert should be
+ * valid for.
+ *
+ * @param notBefore Date specifying certificate is not valid before this date.
+ * @param notAfter Date specifying certificate is not valid after this date.
+ * @return {@link OptionalValidity} that can be set for certificate on external CA.
+ */
+ public static OptionalValidity generateOptionalValidity(
+ final Date notBefore, final Date notAfter) {
+ LOG.info("Generating Optional Validity from Date objects");
+ ASN1EncodableVector optionalValidityV = new ASN1EncodableVector();
+ if (notBefore != null) {
+ Time nb = new Time(notBefore);
+ optionalValidityV.add(new DERTaggedObject(true, 0, nb));
+ }
+ if (notAfter != null) {
+ Time na = new Time(notAfter);
+ optionalValidityV.add(new DERTaggedObject(true, 1, na));
+ }
+ return OptionalValidity.getInstance(new DERSequence(optionalValidityV));
+ }
+
+ /**
+ * Create Extensions from Subject Alternative Names.
+ *
+ * @return {@link Extensions}.
+ */
+ public static Extensions generateExtension(final List<String> sansList)
+ throws CmpClientException {
+ LOG.info("Generating Extensions from Subject Alternative Names");
+ final ExtensionsGenerator extGenerator = new ExtensionsGenerator();
+ final GeneralName[] sansGeneralNames = getGeneralNames(sansList);
+ // KeyUsage
+ try {
+ final KeyUsage keyUsage =
+ new KeyUsage(
+ KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.nonRepudiation);
+ extGenerator.addExtension(Extension.keyUsage, false, new DERBitString(keyUsage));
+ extGenerator.addExtension(
+ Extension.subjectAlternativeName, false, new GeneralNames(sansGeneralNames));
+ } 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");
+ throw cmpClientException;
+ }
+ return extGenerator.generate();
+ }
+
+ public static GeneralName[] getGeneralNames(List<String> sansList) {
+ final List<GeneralName> nameList = new ArrayList<>();
+ for (String san : sansList) {
+ nameList.add(new GeneralName(GeneralName.dNSName, san));
+ }
+ final GeneralName[] sansGeneralNames = new GeneralName[nameList.size()];
+ nameList.toArray(sansGeneralNames);
+ return sansGeneralNames;
+ }
+
+ /**
+ * Method generates Proof-of-Possession (POP) of Private Key. To allow a CA/RA to properly
+ * validity binding between an End Entity and a Key Pair, the PKI Operations specified here make
+ * it possible for an End Entity to prove that it has possession of the Private Key corresponding
+ * to the Public Key for which a Certificate is requested.
+ *
+ * @param certRequest Certificate request that requires proof of possession
+ * @param keypair keypair associated with the subject sending the certificate request
+ * @return {@link ProofOfPossession}.
+ * @throws CmpClientException A general-purpose Cmp client exception.
+ */
+ public static ProofOfPossession generateProofOfPossession(
+ final CertRequest certRequest, final KeyPair keypair) throws CmpClientException {
+ ProofOfPossession proofOfPossession;
+ try (final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
+ final DEROutputStream derOutputStream = new DEROutputStream(byteArrayOutputStream);
+ derOutputStream.writeObject(certRequest);
+
+ byte[] popoProtectionBytes = byteArrayOutputStream.toByteArray();
+ final String sigalg = PKCSObjectIdentifiers.sha256WithRSAEncryption.getId();
+ final Signature signature = Signature.getInstance(sigalg, BouncyCastleProvider.PROVIDER_NAME);
+ signature.initSign(keypair.getPrivate());
+ signature.update(popoProtectionBytes);
+ DERBitString bs = new DERBitString(signature.sign());
+
+ proofOfPossession =
+ new ProofOfPossession(
+ new POPOSigningKey(
+ null, new AlgorithmIdentifier(new ASN1ObjectIdentifier(sigalg)), bs));
+ } catch (IOException
+ | NoSuchProviderException
+ | NoSuchAlgorithmException
+ | InvalidKeyException
+ | SignatureException ex) {
+ CmpClientException cmpClientException =
+ new CmpClientException(
+ "Exception occurred while creating proof " + "of possession for PKIMessage", ex);
+ LOG.error("Exception occurred while creating proof of possession for PKIMessage");
+ throw cmpClientException;
+ }
+ return proofOfPossession;
+ }
+
+ /**
+ * Generic code to create Algorithm Identifier for protection of PKIMessage.
+ *
+ * @return Algorithm Identifier
+ */
+ public static AlgorithmIdentifier protectionAlgoIdentifier(int iterations, byte[] salt) {
+ ASN1Integer iteration = new ASN1Integer(iterations);
+ DEROctetString derSalt = new DEROctetString(salt);
+
+ PBMParameter pp = new PBMParameter(derSalt, OWF_ALGORITHM, iteration, MAC_ALGORITHM);
+ return new AlgorithmIdentifier(PASSWORD_BASED_MAC, pp);
+ }
+
+ /**
+ * Adds protection to the PKIMessage via a specified protection algorithm.
+ *
+ * @param password password used to authenticate PkiMessage with external CA
+ * @param pkiHeader Header of PKIMessage containing generic details for any PKIMessage
+ * @param pkiBody Body of PKIMessage containing specific details for certificate request
+ * @return Protected Pki Message
+ * @throws CmpClientException Wraps several exceptions into one general-purpose exception.
+ */
+ public static PKIMessage protectPkiMessage(
+ PKIHeader pkiHeader, PKIBody pkiBody, String password, int iterations, byte[] salt)
+ throws CmpClientException {
+
+ byte[] raSecret = password.getBytes();
+ 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);
+ byte[] out;
+ try {
+ MessageDigest dig =
+ MessageDigest.getInstance(
+ OWF_ALGORITHM.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
+ for (int i = 0; i < iterations; i++) {
+ basekey = dig.digest(basekey);
+ dig.reset();
+ }
+ byte[] protectedBytes = generateProtectedBytes(pkiHeader, pkiBody);
+ Mac mac =
+ Mac.getInstance(MAC_ALGORITHM.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
+ SecretKey key = new SecretKeySpec(basekey, MAC_ALGORITHM.getAlgorithm().getId());
+ mac.init(key);
+ mac.reset();
+ mac.update(protectedBytes, 0, protectedBytes.length);
+ out = mac.doFinal();
+ } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException ex) {
+ CmpClientException cmpClientException =
+ new CmpClientException(
+ "Exception occurred while generating " + "proof of possession for PKIMessage", ex);
+ LOG.error("Exception occured while generating the proof of possession for PKIMessage");
+ throw cmpClientException;
+ }
+ DERBitString bs = new DERBitString(out);
+
+ return new PKIMessage(pkiHeader, pkiBody, bs);
+ }
+}
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
new file mode 100644
index 00000000..b7452fcf
--- /dev/null
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
+ *
+ * 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
+ */
+
+package org.onap.aaf.certservice.cmpv2client.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Date;
+import java.util.Objects;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+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.PKIBody;
+import org.bouncycastle.asn1.cmp.PKIHeader;
+import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class CmpUtil {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CmpUtil.class);
+ private static final SecureRandom secureRandom = new SecureRandom();
+
+ private CmpUtil() {}
+
+ /**
+ * Validates specified object reference is not null.
+ *
+ * @param argument T - the type of the reference.
+ * @param message message - detail message to be used in the event that a NullPointerException is
+ * thrown.
+ * @return The Object if not null
+ */
+ public static <T> T notNull(T argument, String message) {
+ return Objects.requireNonNull(argument, message + " must not be null");
+ }
+
+ /**
+ * Validates String object reference is not null and not empty.
+ *
+ * @param stringArg String Object that need to be validated.
+ * @return boolean
+ */
+ public static boolean isNullOrEmpty(String stringArg) {
+ return (stringArg != null && !stringArg.trim().isEmpty());
+ }
+
+ /**
+ * Creates a random number than can be used for sendernonce, transactionId and salts.
+ *
+ * @return bytes containing a random number string representing a nonce
+ */
+ static byte[] createRandomBytes() {
+ LOGGER.info("Generating random array of bytes");
+ byte[] randomBytes = new byte[16];
+ secureRandom.nextBytes(randomBytes);
+ return randomBytes;
+ }
+
+ /**
+ * Creates a random integer than can be used to represent a transactionId or determine the number
+ * iterations in a protection algorithm.
+ *
+ * @return bytes containing a random number string representing a nonce
+ */
+ static int createRandomInt(int range) {
+ LOGGER.info("Generating random integer");
+ return secureRandom.nextInt(range) + 1000;
+ }
+
+ /**
+ * Generates protected bytes of a combined PKIHeader and PKIBody.
+ *
+ * @param header Header of PKIMessage containing common parameters
+ * @param body Body of PKIMessage containing specific information for message
+ * @return bytes representing the PKIHeader and PKIBody thats to be protected
+ */
+ static byte[] generateProtectedBytes(PKIHeader header, PKIBody body) throws CmpClientException {
+ LOGGER.info("Generating array of bytes representing PkiHeader and PkiBody");
+ byte[] res;
+ ASN1EncodableVector vector = new ASN1EncodableVector();
+ vector.add(header);
+ vector.add(body);
+ ASN1Encodable protectedPart = new DERSequence(vector);
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ DEROutputStream out = new DEROutputStream(baos);
+ out.writeObject(protectedPart);
+ res = baos.toByteArray();
+ } catch (IOException ioe) {
+ CmpClientException cmpClientException =
+ new CmpClientException("IOException occurred while creating protectedBytes", ioe);
+ LOGGER.error("IOException occurred while creating protectedBytes");
+ throw cmpClientException;
+ }
+ return res;
+ }
+
+ /**
+ * Generates a PKIHeader Builder object.
+ *
+ * @param subjectDn distinguished name of Subject
+ * @param issuerDn distinguished name of external CA
+ * @param protectionAlg protection Algorithm used to protect PKIMessage
+ * @return PKIHeaderBuilder
+ */
+ static PKIHeader generatePkiHeader(
+ X500Name subjectDn, X500Name issuerDn, AlgorithmIdentifier protectionAlg) {
+ LOGGER.info("Generating a Pki Header Builder");
+ PKIHeaderBuilder pkiHeaderBuilder =
+ new PKIHeaderBuilder(
+ PKIHeader.CMP_2000, new GeneralName(subjectDn), new GeneralName(issuerDn));
+
+ pkiHeaderBuilder.setMessageTime(new ASN1GeneralizedTime(new Date()));
+ pkiHeaderBuilder.setSenderNonce(new DEROctetString(createRandomBytes()));
+ pkiHeaderBuilder.setTransactionID(new DEROctetString(createRandomBytes()));
+ pkiHeaderBuilder.setProtectionAlg(protectionAlg);
+
+ 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
new file mode 100644
index 00000000..b1f96333
--- /dev/null
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 Ericsson Software Technology AB. All rights reserved.
+ *
+ * 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
+ */
+
+package org.onap.aaf.certservice.cmpv2client.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class Cmpv2HttpClient {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Cmpv2HttpClient.class);
+
+ private static final String CONTENT_TYPE = "Content-type";
+ private static final String CMP_REQUEST_MIMETYPE = "application/pkixcmp";
+ private CloseableHttpClient httpClient;
+
+ public Cmpv2HttpClient(CloseableHttpClient httpClient){
+ this.httpClient = httpClient;
+ }
+
+ public byte[] postRequest(
+ final PKIMessage pkiMessage,
+ final String urlString,
+ final String caName)
+ throws CmpClientException {
+ try (final ByteArrayOutputStream byteArrOutputStream = new ByteArrayOutputStream()) {
+ final HttpPost postRequest = new HttpPost(urlString);
+ final byte[] requestBytes = pkiMessage.getEncoded();
+
+ postRequest.setEntity(new ByteArrayEntity(requestBytes));
+ postRequest.setHeader(CONTENT_TYPE, CMP_REQUEST_MIMETYPE);
+
+ try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
+ response.getEntity().writeTo(byteArrOutputStream);
+ }
+ return byteArrOutputStream.toByteArray();
+ } catch (IOException ioe) {
+ CmpClientException cmpClientException =
+ new CmpClientException("IOException error while trying to connect CA " + 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
new file mode 100644
index 00000000..aa544e7f
--- /dev/null
+++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved.
+ *
+ * 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
+ */
+
+package org.onap.aaf.certservice.cmpv2client.impl;
+
+import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.createRandomBytes;
+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;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.asn1.crmf.AttributeTypeAndValue;
+import org.bouncycastle.asn1.crmf.CRMFObjectIdentifiers;
+import org.bouncycastle.asn1.crmf.CertReqMessages;
+import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.crmf.CertRequest;
+import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
+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
+ * (CMP)) and RFC4211 (Certificate Request Message Format (CRMF)) standards.
+ */
+class CreateCertRequest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CreateCertRequest.class);
+
+ private X500Name issuerDn;
+ private X500Name subjectDn;
+ private List<String> sansList;
+ private KeyPair subjectKeyPair;
+ private Date notBefore;
+ private Date notAfter;
+ private String initAuthPassword;
+
+ 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) {
+ this.issuerDn = issuerDn;
+ }
+
+ public void setSubjectDn(X500Name subjectDn) {
+ this.subjectDn = subjectDn;
+ }
+
+ public void setSansList(List<String> sansList) {
+ this.sansList = sansList;
+ }
+
+ public void setSubjectKeyPair(KeyPair subjectKeyPair) {
+ this.subjectKeyPair = subjectKeyPair;
+ }
+
+ public void setNotBefore(Date notBefore) {
+ this.notBefore = notBefore;
+ }
+
+ public void setNotAfter(Date notAfter) {
+ this.notAfter = notAfter;
+ }
+
+ public void setInitAuthPassword(String initAuthPassword) {
+ this.initAuthPassword = initAuthPassword;
+ }
+
+ /**
+ * Method to create {@link PKIMessage} from {@link CertRequest},{@link ProofOfPossession}, {@link
+ * CertReqMsg}, {@link CertReqMessages}, {@link PKIHeader} and {@link PKIBody}.
+ *
+ * @return {@link PKIMessage}
+ */
+ public PKIMessage generateCertReq() throws CmpClientException {
+ final CertTemplateBuilder certTemplateBuilder =
+ new CertTemplateBuilder()
+ .setIssuer(issuerDn)
+ .setSubject(subjectDn)
+ .setExtensions(CmpMessageHelper.generateExtension(sansList))
+ .setValidity(CmpMessageHelper.generateOptionalValidity(notBefore, notAfter))
+ .setPublicKey(
+ SubjectPublicKeyInfo.getInstance(subjectKeyPair.getPublic().getEncoded()));
+
+ final CertRequest certRequest = new CertRequest(certReqId, certTemplateBuilder.build(), null);
+ final ProofOfPossession proofOfPossession =
+ CmpMessageHelper.generateProofOfPossession(certRequest, subjectKeyPair);
+
+ final AttributeTypeAndValue[] attrTypeVal = {
+ new AttributeTypeAndValue(
+ CRMFObjectIdentifiers.id_regCtrl_regToken, new DERUTF8String(initAuthPassword))
+ };
+
+ final CertReqMsg certReqMsg = new CertReqMsg(certRequest, proofOfPossession, attrTypeVal);
+ final CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
+
+ final PKIHeader pkiHeader =
+ generatePkiHeader(
+ subjectDn, issuerDn, CmpMessageHelper.protectionAlgoIdentifier(iterations, salt));
+ final PKIBody pkiBody = new PKIBody(PKIBody.TYPE_CERT_REQ, certReqMessages);
+
+ return CmpMessageHelper.protectPkiMessage(
+ pkiHeader, pkiBody, initAuthPassword, iterations, salt);
+ }
+}