diff options
26 files changed, 915 insertions, 151 deletions
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/api/CmpClient.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/api/CmpClient.java index feee3eed..150f15d5 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/api/CmpClient.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/api/CmpClient.java @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,17 +12,18 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.api; -import java.io.IOException; import java.security.cert.X509Certificate; import java.util.Date; -import org.apache.http.impl.client.CloseableHttpClient; +import java.util.List; import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException; -import org.onap.aaf.certservice.cmpv2client.exceptions.PkiErrorException; import org.onap.aaf.certservice.cmpv2client.external.CSRMeta; /** @@ -50,7 +52,7 @@ public interface CmpClient { * @return {@link X509Certificate} The newly created Certificate. * @throws CmpClientException if client error occurs. */ - X509Certificate createCertificate( + List<List<X509Certificate>> createCertificate( String caName, String profile, CSRMeta csrMeta, @@ -74,7 +76,7 @@ public interface CmpClient { * @return {@link X509Certificate} The newly created Certificate. * @throws CmpClientException if client error occurs. */ - X509Certificate createCertificate( + List<List<X509Certificate>> createCertificate( String caName, String profile, CSRMeta csrMeta, diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/CmpClientException.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/CmpClientException.java index 7f7d4ae6..06bdfd80 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/CmpClientException.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/CmpClientException.java @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +12,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.exceptions; diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/PkiErrorException.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/PkiErrorException.java index 965ce6fb..9e6b9286 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/PkiErrorException.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/exceptions/PkiErrorException.java @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +12,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.exceptions; 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 fb43e3e8..7dacfc80 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 @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,14 +12,32 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.impl; +import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.checkIfCmpResponseContainsError; +import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.getCertfromByteArray; +import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.verifyAndReturnCertChainAndTrustSTore; + +import java.io.IOException; +import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import org.apache.http.impl.client.CloseableHttpClient; +import org.bouncycastle.asn1.cmp.CMPCertificate; +import org.bouncycastle.asn1.cmp.CertRepMessage; +import org.bouncycastle.asn1.cmp.CertResponse; +import org.bouncycastle.asn1.cmp.PKIBody; import org.bouncycastle.asn1.cmp.PKIMessage; import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException; import org.onap.aaf.certservice.cmpv2client.api.CmpClient; @@ -38,12 +57,12 @@ public class CmpClientImpl implements CmpClient { private static final String DEFAULT_PROFILE = "RA"; private static final String DEFAULT_CA_NAME = "Certification Authority"; - public CmpClientImpl(CloseableHttpClient httpClient){ + public CmpClientImpl(CloseableHttpClient httpClient) { this.httpClient = httpClient; } @Override - public X509Certificate createCertificate( + public List<List<X509Certificate>> createCertificate( String caName, String profile, CSRMeta csrMeta, @@ -67,23 +86,59 @@ public class CmpClientImpl implements CmpClient { final PKIMessage pkiMessage = certRequest.generateCertReq(); Cmpv2HttpClient cmpv2HttpClient = new Cmpv2HttpClient(httpClient); - final byte[] respBytes = - cmpv2HttpClient.postRequest(pkiMessage, csrMeta.caUrl(), caName); - final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes); - // todo: add response validation and return Certificate - return null; + return retrieveCertificates(caName, csrMeta, pkiMessage, cmpv2HttpClient); } @Override - public X509Certificate createCertificate( - String caName, - String profile, - CSRMeta csrMeta, - X509Certificate csr) + public List<List<X509Certificate>> createCertificate( + String caName, String profile, CSRMeta csrMeta, X509Certificate csr) throws CmpClientException { return createCertificate(caName, profile, csrMeta, csr, null, null); } + private List<List<X509Certificate>> checkCmpCertRepMessage(final PKIMessage respPkiMessage) + throws CmpClientException { + final PKIBody pkiBody = respPkiMessage.getBody(); + if (Objects.nonNull(pkiBody) && pkiBody.getContent() instanceof CertRepMessage) { + final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent(); + if (Objects.nonNull(certRepMessage)) { + final CertResponse certResponse = + getCertificateResponseContainingNewCertificate(certRepMessage); + try { + return verifyReturnCertChainAndTrustStore(respPkiMessage, certRepMessage, certResponse); + } catch (IOException | CertificateParsingException ex) { + CmpClientException cmpClientException = + new CmpClientException( + "Exception occurred while retrieving Certificates from response", ex); + LOG.error("Exception occurred while retrieving Certificates from response", ex); + throw cmpClientException; + } + } else { + return new ArrayList<>(Collections.emptyList()); + } + } + return new ArrayList<>(Collections.emptyList()); + } + + private List<List<X509Certificate>> verifyReturnCertChainAndTrustStore( + PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse) + throws CertificateParsingException, CmpClientException, IOException { + LOG.info("Verifying certificates returned as part of CertResponse."); + final CMPCertificate cmpCertificate = + certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate(); + final Optional<X509Certificate> leafCertificate = + getCertfromByteArray(cmpCertificate.getEncoded(), X509Certificate.class); + ArrayList<X509Certificate> certChain = new ArrayList<>(); + ArrayList<X509Certificate> trustStore = new ArrayList<>(); + return verifyAndReturnCertChainAndTrustSTore( + respPkiMessage, certRepMessage, leafCertificate.get(), certChain, trustStore); + } + + private CertResponse getCertificateResponseContainingNewCertificate( + CertRepMessage certRepMessage) { + return certRepMessage.getResponse()[0]; + } + /** * Validate inputs for Certificate Creation. * @@ -100,8 +155,7 @@ public class CmpClientImpl implements CmpClient { final String incomingProfile, final CloseableHttpClient httpClient, final Date notBefore, - final Date notAfter) - throws IllegalArgumentException { + final Date notAfter) { String caName; String caProfile; @@ -123,4 +177,22 @@ public class CmpClientImpl implements CmpClient { throw new IllegalArgumentException("Before Date is set after the After Date"); } } + + private List<List<X509Certificate>> retrieveCertificates( + String caName, CSRMeta csrMeta, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient) + throws CmpClientException { + final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, csrMeta.caUrl(), caName); + try { + final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes); + LOG.info("Recieved response from Server"); + checkIfCmpResponseContainsError(respPkiMessage); + return checkCmpCertRepMessage(respPkiMessage); + } catch (IllegalArgumentException iae) { + CmpClientException cmpClientException = + new CmpClientException( + "Error encountered while processing response from CA server ", iae); + LOG.error("Error encountered while processing response from CA server ", iae); + throw cmpClientException; + } + } } 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 index ee8129c6..2ab9b2cf 100644 --- 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 @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +12,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.impl; 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 index 8c470c7f..48b2336f 100644 --- 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 @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +12,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.impl; @@ -30,7 +34,6 @@ import java.security.SignatureException; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Optional; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -120,8 +123,8 @@ public final class CmpMessageHelper { } catch (IOException ioe) { CmpClientException cmpClientException = new CmpClientException( - "Exception occurred while creating proof of possession for PKIMessage", ioe); - LOG.error("Exception occurred while creating proof of possession for PKIMessage"); + "Exception occurred while creating extensions for PKIMessage", ioe); + LOG.error("Exception occurred while creating extensions for PKIMessage"); throw cmpClientException; } return extGenerator.generate(); @@ -173,7 +176,7 @@ public final class CmpMessageHelper { | SignatureException ex) { CmpClientException cmpClientException = new CmpClientException( - "Exception occurred while creating proof " + "of possession for PKIMessage", ex); + "Exception occurred while creating proof of possession for PKIMessage", ex); LOG.error("Exception occurred while creating proof of possession for PKIMessage"); throw cmpClientException; } @@ -230,7 +233,7 @@ public final class CmpMessageHelper { } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException ex) { CmpClientException cmpClientException = new CmpClientException( - "Exception occurred while generating " + "proof of possession for PKIMessage", ex); + "Exception occurred while generating proof of possession for PKIMessage", ex); LOG.error("Exception occured while generating the proof of possession for PKIMessage"); throw cmpClientException; } diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java new file mode 100644 index 00000000..8fdee2ed --- /dev/null +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java @@ -0,0 +1,345 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.aaf.certservice.cmpv2client.impl; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertPath; +import java.security.cert.CertPathValidator; +import java.security.cert.CertPathValidatorException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateParsingException; +import java.security.cert.PKIXCertPathChecker; +import java.security.cert.PKIXCertPathValidatorResult; +import java.security.cert.PKIXParameters; +import java.security.cert.TrustAnchor; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import org.bouncycastle.asn1.cmp.CMPCertificate; +import org.bouncycastle.asn1.cmp.CertRepMessage; +import org.bouncycastle.asn1.cmp.ErrorMsgContent; +import org.bouncycastle.asn1.cmp.PKIBody; +import org.bouncycastle.asn1.cmp.PKIMessage; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException; +import org.onap.aaf.certservice.cmpv2client.exceptions.PkiErrorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CmpResponseHelper { + + private static final Logger LOG = LoggerFactory.getLogger(CmpMessageHelper.class); + + public static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage) + throws CmpClientException { + if (respPkiMessage.getBody().getType() == PKIBody.TYPE_ERROR) { + final ErrorMsgContent errorMsgContent = + (ErrorMsgContent) respPkiMessage.getBody().getContent(); + PkiErrorException pkiErrorException = + new PkiErrorException( + errorMsgContent.getPKIStatusInfo().getStatusString().getStringAt(0).getString()); + CmpClientException cmpClientException = + new CmpClientException("Error in the PkiMessage response", pkiErrorException); + LOG.error("Error in the PkiMessage response: {} ", pkiErrorException.getMessage()); + throw cmpClientException; + } + } + + /** + * @param cert byte array that contains certificate + * @param returnType the type of Certificate to be returned, for example X509Certificate.class. + * Certificate.class can be used if certificate type is unknown. + * @throws CertificateParsingException if the byte array does not contain a proper certificate. + */ + public static <T extends Certificate> Optional<X509Certificate> getCertfromByteArray( + byte[] cert, Class<T> returnType) throws CertificateParsingException, CmpClientException { + LOG.debug("Retrieving certificate of type {} from byte array.", returnType); + return getCertfromByteArray(cert, BouncyCastleProvider.PROVIDER_NAME, returnType); + } + + /** + * @param cert byte array that contains certificate + * @param provider provider used to generate certificate from bytes + * @param returnType the type of Certificate to be returned, for example X509Certificate.class. + * Certificate.class can be used if certificate type is unknown. + * @throws CertificateParsingException if the byte array does not contain a proper certificate. + */ + public static <T extends Certificate> Optional<X509Certificate> getCertfromByteArray( + byte[] cert, String provider, Class<T> returnType) + throws CertificateParsingException, CmpClientException { + String prov = provider; + if (provider == null) { + prov = BouncyCastleProvider.PROVIDER_NAME; + } + + if (returnType.equals(X509Certificate.class)) { + return parseX509Certificate(prov, cert); + } + return Optional.empty(); + } + + /** + * Check the certificate with CA certificate. + * + * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a + * Collection with null entries. + * @throws CmpClientException if verification failed + */ + public static void verify(List<X509Certificate> caCertChain) throws CmpClientException { + int iterator = 1; + while (iterator < caCertChain.size()) { + verify(caCertChain.get(iterator - 1), caCertChain.get(iterator), null); + iterator += 1; + } + } + + /** + * Check the certificate with CA certificate. + * + * @param certificate X.509 certificate to verify. May not be null. + * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a + * Collection with null entries. + * @param date Date to verify at, or null to use current time. + * @param pkixCertPathCheckers optional PKIXCertPathChecker implementations to use during cert + * path validation + * @throws CmpClientException if certificate could not be validated + */ + public static void verify( + X509Certificate certificate, + X509Certificate caCertChain, + Date date, + PKIXCertPathChecker... pkixCertPathCheckers) + throws CmpClientException { + try { + verifyCertificates(certificate, caCertChain, date, pkixCertPathCheckers); + } catch (CertPathValidatorException cpve) { + CmpClientException cmpClientException = + new CmpClientException( + "Invalid certificate or certificate not issued by specified CA: ", cpve); + LOG.error("Invalid certificate or certificate not issued by specified CA: ", cpve); + throw cmpClientException; + } catch (CertificateException ce) { + CmpClientException cmpClientException = + new CmpClientException("Something was wrong with the supplied certificate", ce); + LOG.error("Something was wrong with the supplied certificate", ce); + throw cmpClientException; + } catch (NoSuchProviderException nspe) { + CmpClientException cmpClientException = + new CmpClientException("BouncyCastle provider not found.", nspe); + LOG.error("BouncyCastle provider not found.", nspe); + throw cmpClientException; + } catch (NoSuchAlgorithmException nsae) { + CmpClientException cmpClientException = + new CmpClientException("Algorithm PKIX was not found.", nsae); + LOG.error("Algorithm PKIX was not found.", nsae); + throw cmpClientException; + } catch (InvalidAlgorithmParameterException iape) { + CmpClientException cmpClientException = + new CmpClientException( + "Either ca certificate chain was empty," + + " or the certificate was on an inappropriate type for a PKIX path checker.", + iape); + LOG.error( + "Either ca certificate chain was empty, " + + "or the certificate was on an inappropriate type for a PKIX path checker.", + iape); + throw cmpClientException; + } + } + + public static void verifyCertificates( + X509Certificate certificate, + X509Certificate caCertChain, + Date date, + PKIXCertPathChecker[] pkixCertPathCheckers) + throws CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException, + NoSuchAlgorithmException, CertPathValidatorException { + LOG.debug( + "Verifying certificate {} as part of cert chain with certificate {}", + certificate.getSubjectDN().getName(), + caCertChain.getSubjectDN().getName()); + CertPath cp = getCertPath(certificate); + PKIXParameters params = getPkixParameters(caCertChain, date, pkixCertPathCheckers); + CertPathValidator cpv = + CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME); + PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params); + if (LOG.isDebugEnabled()) { + LOG.debug("Certificate verify result:{} ", result.toString()); + } + } + + public static PKIXParameters getPkixParameters( + X509Certificate caCertChain, Date date, PKIXCertPathChecker[] pkixCertPathCheckers) + throws InvalidAlgorithmParameterException { + TrustAnchor anchor = new TrustAnchor(caCertChain, null); + PKIXParameters params = new PKIXParameters(Collections.singleton(anchor)); + for (final PKIXCertPathChecker pkixCertPathChecker : pkixCertPathCheckers) { + params.addCertPathChecker(pkixCertPathChecker); + } + params.setRevocationEnabled(false); + params.setDate(date); + return params; + } + + public static CertPath getCertPath(X509Certificate certificate) + throws CertificateException, NoSuchProviderException { + ArrayList<X509Certificate> certlist = new ArrayList<>(); + certlist.add(certificate); + return CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME) + .generateCertPath(certlist); + } + + /** + * Parse a X509Certificate from an array of bytes + * + * @param provider a provider name + * @param cert a byte array containing an encoded certificate + * @return a decoded X509Certificate + * @throws CertificateParsingException if the byte array wasn't valid, or contained a certificate + * other than an X509 Certificate. + */ + public static Optional<X509Certificate> parseX509Certificate(String provider, byte[] cert) + throws CertificateParsingException, CmpClientException { + LOG.debug("Parsing X509Certificate from bytes with provider {}", provider); + final CertificateFactory cf = getCertificateFactory(provider); + X509Certificate result; + try { + result = + (X509Certificate) + Objects.requireNonNull(cf).generateCertificate(new ByteArrayInputStream(cert)); + } catch (CertificateException ce) { + throw new CertificateParsingException("Could not parse byte array as X509Certificate ", ce); + } + if (result != null) { + return Optional.of(result); + } else { + throw new CertificateParsingException("Could not parse byte array as X509Certificate."); + } + } + + /** + * Returns a CertificateFactory that can be used to create certificates from byte arrays and such. + * + * @param provider Security provider that should be used to create certificates, default BC is + * null is passed. + * @return CertificateFactory for creating certificate + */ + public static CertificateFactory getCertificateFactory(final String provider) + throws CmpClientException { + LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider); + final String prov; + prov = Objects.requireNonNullElse(provider, BouncyCastleProvider.PROVIDER_NAME); + try { + return CertificateFactory.getInstance("X.509", prov); + } catch (NoSuchProviderException nspe) { + CmpClientException cmpClientException = new CmpClientException("NoSuchProvider: ", nspe); + LOG.error("NoSuchProvider: ", nspe); + throw cmpClientException; + } catch (CertificateException ce) { + CmpClientException cmpClientException = new CmpClientException("CertificateException: ", ce); + LOG.error("CertificateException: ", ce); + throw cmpClientException; + } + } + + /** + * puts together certChain and Trust store and verifies the certChain + * + * @param respPkiMessage PKIMessage that may contain extra certs used for certchain + * @param certRepMessage CertRepMessage that should contain rootCA for certchain + * @param leafCertificate certificate returned from our original Cert Request + * @param certChain Array of certificates to be used for KeyStore + * @param trustStore Array of certificates to be used for TrustStore + * @return list of two lists, CertChain and TrustStore + * @throws CertificateParsingException thrown if error occurs while parsing certificate + * @throws IOException thrown if IOException occurs while parsing certificate + * @throws CmpClientException thrown if error occurs during the verification of the certChain + */ + public static List<List<X509Certificate>> verifyAndReturnCertChainAndTrustSTore( + PKIMessage respPkiMessage, + CertRepMessage certRepMessage, + X509Certificate leafCertificate, + ArrayList<X509Certificate> certChain, + ArrayList<X509Certificate> trustStore) + throws CertificateParsingException, IOException, CmpClientException { + List<String> certNames = getNamesOfCerts(certChain); + LOG.debug("Verifying the following certificates in the cert chain: {}", certNames); + certChain.add(leafCertificate); + addExtraCertsToChain(respPkiMessage, certRepMessage, certChain); + verify(certChain); + List<List<X509Certificate>> listOfArray = new ArrayList<>(); + listOfArray.add(certChain); + listOfArray.add(trustStore); + return listOfArray; + } + + public static List<String> getNamesOfCerts(List<X509Certificate> certChain) { + List<String> certNames = new ArrayList<>(); + certChain.forEach((cert) -> certNames.add(cert.getSubjectDN().getName())); + return certNames; + } + + /** + * checks whether PKIMessage contains extracerts to create certchain, if not creates from caPubs + * + * @param respPkiMessage PKIMessage that may contain extra certs used for certchain + * @param certRepMessage CertRepMessage that should contain rootCA for certchain + * @param certChain Array of certificates to be used for KeyStore + * @throws CertificateParsingException thrown if error occurs while parsing certificate + * @throws IOException thrown if IOException occurs while parsing certificate + * @throws CmpClientException thrown if there are errors creating CertificateFactory + */ + public static void addExtraCertsToChain( + PKIMessage respPkiMessage, + CertRepMessage certRepMessage, + ArrayList<X509Certificate> certChain) + throws CertificateParsingException, IOException, CmpClientException { + if (respPkiMessage.getExtraCerts() != null) { + final CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts(); + for (CMPCertificate cmpCert : extraCerts) { + Optional<X509Certificate> cert = + getCertfromByteArray(cmpCert.getEncoded(), X509Certificate.class); + LOG.debug("Adding certificate {} to cert chain", cert.get().getSubjectDN().getName()); + certChain.add(cert.get()); + } + } else { + final CMPCertificate respCmpCaCert = getRootCa(certRepMessage); + Optional<X509Certificate> cert = + getCertfromByteArray(respCmpCaCert.getEncoded(), X509Certificate.class); + LOG.debug("Adding certificate {} to TrustStore", cert.get().getSubjectDN().getName()); + certChain.add(cert.get()); + } + } + + private static CMPCertificate getRootCa(CertRepMessage certRepMessage) { + return certRepMessage.getCaPubs()[0]; + } +} 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 b7452fcf..86935135 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 @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +12,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.impl; 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 b1f96333..db86a1e3 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 @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2019 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +12,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.impl; @@ -33,16 +37,28 @@ class Cmpv2HttpClient { private static final String CONTENT_TYPE = "Content-type"; private static final String CMP_REQUEST_MIMETYPE = "application/pkixcmp"; - private CloseableHttpClient httpClient; + private final CloseableHttpClient httpClient; - public Cmpv2HttpClient(CloseableHttpClient httpClient){ + /** + * constructor for Cmpv2HttpClient + * + * @param httpClient CloseableHttpClient used for sending/recieve request. + */ + public Cmpv2HttpClient(CloseableHttpClient httpClient) { this.httpClient = httpClient; } + /** + * Send Post Request to Server + * + * @param pkiMessage PKIMessage to send to server + * @param urlString url for the server we're sending request + * @param caName name of CA server + * @return + * @throws CmpClientException thrown if problems with connecting or parsing response to server + */ public byte[] postRequest( - final PKIMessage pkiMessage, - final String urlString, - final String caName) + final PKIMessage pkiMessage, final String urlString, final String caName) throws CmpClientException { try (final ByteArrayOutputStream byteArrOutputStream = new ByteArrayOutputStream()) { final HttpPost postRequest = new HttpPost(urlString); @@ -57,7 +73,7 @@ class Cmpv2HttpClient { return byteArrOutputStream.toByteArray(); } catch (IOException ioe) { CmpClientException cmpClientException = - new CmpClientException("IOException error while trying to connect CA " + caName, ioe); + new CmpClientException(String.format("IOException error while trying to connect CA %s",caName), ioe); LOG.error("IOException error {}, while trying to connect CA {}", ioe.getMessage(), caName); throw cmpClientException; } 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 aa544e7f..25943321 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 @@ -1,6 +1,7 @@ -/* - * Copyright (C) 2020 Ericsson Software Technology AB. All rights reserved. - * +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +12,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= */ package org.onap.aaf.certservice.cmpv2client.impl; @@ -20,11 +24,9 @@ import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.createRandomByte import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.createRandomInt; import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.generatePkiHeader; -import java.io.IOException; import java.security.KeyPair; import java.util.Date; import java.util.List; -import java.util.Optional; import org.bouncycastle.asn1.DERUTF8String; import org.bouncycastle.asn1.cmp.PKIBody; import org.bouncycastle.asn1.cmp.PKIHeader; @@ -39,8 +41,6 @@ import org.bouncycastle.asn1.crmf.ProofOfPossession; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Implementation of the CmpClient Interface conforming to RFC4210 (Certificate Management Protocol @@ -48,8 +48,6 @@ import org.slf4j.LoggerFactory; */ class CreateCertRequest { - private static final Logger LOG = LoggerFactory.getLogger(CreateCertRequest.class); - private X500Name issuerDn; private X500Name subjectDn; private List<String> sansList; @@ -58,8 +56,8 @@ class CreateCertRequest { private Date notAfter; private String initAuthPassword; - private static final int iterations = createRandomInt(5000); - private static final byte[] salt = createRandomBytes(); + private static final int ITERATIONS = createRandomInt(5000); + private static final byte[] SALT = createRandomBytes(); private final int certReqId = createRandomInt(Integer.MAX_VALUE); public void setIssuerDn(X500Name issuerDn) { @@ -120,10 +118,10 @@ class CreateCertRequest { final PKIHeader pkiHeader = generatePkiHeader( - subjectDn, issuerDn, CmpMessageHelper.protectionAlgoIdentifier(iterations, salt)); + subjectDn, issuerDn, CmpMessageHelper.protectionAlgoIdentifier(ITERATIONS, SALT)); final PKIBody pkiBody = new PKIBody(PKIBody.TYPE_CERT_REQ, certReqMessages); return CmpMessageHelper.protectPkiMessage( - pkiHeader, pkiBody, initAuthPassword, iterations, salt); + pkiHeader, pkiBody, initAuthPassword, ITERATIONS, SALT); } } 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 74eb098f..26cf7e2d 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 @@ -15,7 +15,7 @@ */ package org.onap.aaf.certservice.cmpv2Client; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; @@ -32,14 +32,12 @@ import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; -import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Optional; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; @@ -128,10 +126,92 @@ class Cmpv2ClientTest { } CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient)); // when - Certificate certificate = + List<List<X509Certificate>> cmpClientResult = cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter); // then - assertNull(certificate); + assertNotNull(cmpClientResult); + } + + @Test + void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr2() + throws Exception { + // given + Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00"); + Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00"); + setCsrMetaValuesAndDateValues( + rdns, + "CN=CommonName", + "CN=ManagementCA", + "CommonName.com", + "CommonName@cn.com", + "password", + "http://127.0.0.1/ejbca/publicweb/cmp/cmp", + beforeDate, + afterDate); + when(httpClient.execute(any())).thenReturn(httpResponse); + when(httpResponse.getEntity()).thenReturn(httpEntity); + + try (final InputStream is = + this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile"); + BufferedInputStream bis = new BufferedInputStream(is)) { + + byte[] ba = IOUtils.toByteArray(bis); + doAnswer( + invocation -> { + OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0]; + os.write(ba); + return null; + }) + .when(httpEntity) + .writeTo(any(OutputStream.class)); + } + CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient)); + // when + List<List<X509Certificate>> cmpClientResult = + cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter); + // then + assertNotNull(cmpClientResult); + } + + @Test + void shouldReturnCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword() + throws Exception { + // given + Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00"); + Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00"); + setCsrMetaValuesAndDateValues( + rdns, + "CN=CommonName", + "CN=ManagementCA", + "CommonName.com", + "CommonName@cn.com", + "password", + "http://127.0.0.1/ejbca/publicweb/cmp/cmp", + beforeDate, + afterDate); + when(httpClient.execute(any())).thenReturn(httpResponse); + when(httpResponse.getEntity()).thenReturn(httpEntity); + + try (final InputStream is = + this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword"); + BufferedInputStream bis = new BufferedInputStream(is)) { + + byte[] ba = IOUtils.toByteArray(bis); + doAnswer( + invocation -> { + OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0]; + os.write(ba); + return null; + }) + .when(httpEntity) + .writeTo(any(OutputStream.class)); + } + CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient)); + + // then + Assertions.assertThrows( + CmpClientException.class, + () -> cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter)); } @Test @@ -154,9 +234,7 @@ class Cmpv2ClientTest { // then Assertions.assertThrows( IllegalArgumentException.class, - () -> - cmpClient.createCertificate( - "data", "RA", csrMeta, cert, notBefore, notAfter)); + () -> cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter)); } @Test @@ -180,9 +258,7 @@ class Cmpv2ClientTest { // then Assertions.assertThrows( CmpClientException.class, - () -> - cmpClient.createCertificate( - "data", "RA", csrMeta, cert, notBefore, notAfter)); + () -> cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter)); } private void setCsrMetaValuesAndDateValues( diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java index ac1062a0..3e8f73eb 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java @@ -20,6 +20,7 @@ package org.onap.aaf.certservice.client; import org.onap.aaf.certservice.client.api.ExitableException; +import org.onap.aaf.certservice.client.certification.CsrFactory; import org.onap.aaf.certservice.client.certification.KeyPairFactory; import org.onap.aaf.certservice.client.configuration.EnvsForClient; import org.onap.aaf.certservice.client.configuration.EnvsForCsr; @@ -29,8 +30,8 @@ import org.onap.aaf.certservice.client.configuration.model.ClientConfiguration; import org.onap.aaf.certservice.client.configuration.model.CsrConfiguration; import java.security.KeyPair; -import java.util.Optional; +import static org.onap.aaf.certservice.client.api.ExitCode.SUCCESS_EXIT_CODE; import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.KEY_SIZE; import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.RSA_ENCRYPTION_ALGORITHM; @@ -42,23 +43,16 @@ public class CertServiceClient { } public void run() { - ClientConfiguration clientConfiguration; - CsrConfiguration csrConfiguration; - clientConfiguration = new ClientConfigurationFactory(new EnvsForClient()).create(); - csrConfiguration = new CsrConfigurationFactory(new EnvsForCsr()).create(); - KeyPairFactory keyPairFactory = new KeyPairFactory(RSA_ENCRYPTION_ALGORITHM, KEY_SIZE); - Optional<KeyPair> keyPair = generateKeyPair(keyPairFactory); - - appExitHandler.exit(0); - } - - public Optional<KeyPair> generateKeyPair(KeyPairFactory keyPairFactory) { try { - return Optional.of(keyPairFactory.create()); + ClientConfiguration clientConfiguration = new ClientConfigurationFactory(new EnvsForClient()).create(); + CsrConfiguration csrConfiguration = new CsrConfigurationFactory(new EnvsForCsr()).create(); + KeyPair keyPair = keyPairFactory.create(); + CsrFactory csrFactory = new CsrFactory(csrConfiguration); + String csr = csrFactory.createEncodedCsr(keyPair); } catch (ExitableException e) { appExitHandler.exit(e.applicationExitCode()); } - return Optional.empty(); + appExitHandler.exit(SUCCESS_EXIT_CODE.getValue()); } } diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java index aed9f3fe..45f2c400 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java @@ -19,9 +19,11 @@ package org.onap.aaf.certservice.client.api; public enum ExitCode { + SUCCESS_EXIT_CODE(0), CLIENT_CONFIGURATION_EXCEPTION(1), CSR_CONFIGURATION_EXCEPTION(2), - KEY_PAIR_GENERATION_EXCEPTION(3); + KEY_PAIR_GENERATION_EXCEPTION(3), + CSR_GENERATION_EXCEPTION(4); private final int value; diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitableException.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitableException.java index e884d11a..51981a48 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitableException.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitableException.java @@ -18,7 +18,7 @@ */ package org.onap.aaf.certservice.client.api; -public abstract class ExitableException extends RuntimeException { +public abstract class ExitableException extends Exception { public ExitableException(Throwable e) { super(e); } diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/CsrFactory.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/CsrFactory.java new file mode 100644 index 00000000..f936636a --- /dev/null +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/CsrFactory.java @@ -0,0 +1,158 @@ +/*============LICENSE_START======================================================= + * aaf-certservice-client + * ================================================================================ + * Copyright (C) 2020 Nokia. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.aaf.certservice.client.certification; + +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +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.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; + +import org.onap.aaf.certservice.client.certification.exception.CsrGenerationException; +import org.onap.aaf.certservice.client.configuration.model.CsrConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.util.Base64; +import java.util.Optional; + +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.COMMON_NAME; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.COUNTRY; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.LOCATION; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.ORGANIZATION; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.ORGANIZATION_UNIT; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.SIGN_ALGORITHM; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.STATE; + + +public class CsrFactory { + + private final Logger LOGGER = LoggerFactory.getLogger(CsrFactory.class); + private static final String SANS_DELIMITER = ":"; + private final CsrConfiguration configuration; + + + public CsrFactory(CsrConfiguration configuration) { + this.configuration = configuration; + } + + + public String createEncodedCsr(KeyPair keyPair) throws CsrGenerationException { + PKCS10CertificationRequest request; + String csrParameters = getMandatoryParameters().append(getOptionalParameters()).toString(); + X500Principal subject = new X500Principal(csrParameters); + request = createPKCS10Csr(subject, keyPair); + return encodeToBase64(convertPKC10CsrToPem(request)); + } + + + private StringBuilder getMandatoryParameters() { + return new StringBuilder(String.format("%s=%s, %s=%s, %s=%s, %s=%s", + COMMON_NAME, configuration.getCommonName(), + COUNTRY, configuration.getCountry(), + STATE, configuration.getState(), + ORGANIZATION, configuration.getOrganization())); + } + + private String getOptionalParameters() { + StringBuilder optionalParameters = new StringBuilder(); + Optional.ofNullable(configuration.getOrganizationUnit()) + .filter(CsrFactory::isParameterPresent) + .map(unit -> optionalParameters.append(String.format(", %s=%s", ORGANIZATION_UNIT, unit))); + Optional.ofNullable(configuration.getLocation()) + .filter(CsrFactory::isParameterPresent) + .map(location -> optionalParameters.append(String.format(", %s=%s", LOCATION, location))); + return optionalParameters.toString(); + } + + private PKCS10CertificationRequest createPKCS10Csr(X500Principal subject, KeyPair keyPair) throws CsrGenerationException { + JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic()); + + if (isParameterPresent(configuration.getSans())) { + builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, generateSansExtension()); + } + + return builder.build(getContentSigner(keyPair)); + } + + private ContentSigner getContentSigner(KeyPair keyPair) throws CsrGenerationException { + ContentSigner contentSigner; + try { + contentSigner = new JcaContentSignerBuilder(SIGN_ALGORITHM).build(keyPair.getPrivate()); + } catch (OperatorCreationException e) { + LOGGER.error("Creation of PKCS10Csr failed, exception message: {}", e.getMessage()); + throw new CsrGenerationException(e); + + } + return contentSigner; + } + + private String convertPKC10CsrToPem(PKCS10CertificationRequest request) throws CsrGenerationException { + final StringWriter stringWriter = new StringWriter(); + try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(request); + } catch (IOException e) { + LOGGER.error("Conversion to PEM failed, exception message: {}", e.getMessage()); + throw new CsrGenerationException(e); + } + return stringWriter.toString(); + } + + private Extensions generateSansExtension() throws CsrGenerationException { + ExtensionsGenerator generator = new ExtensionsGenerator(); + try { + generator.addExtension(Extension.subjectAlternativeName, false, createGeneralNames()); + } catch (IOException e) { + LOGGER.error("Generation of SANs parameter failed, exception message: {}", e.getMessage()); + throw new CsrGenerationException(e); + } + return generator.generate(); + } + + private GeneralNames createGeneralNames() { + String[] sansTable = this.configuration.getSans().split(SANS_DELIMITER); + int length = sansTable.length; + GeneralName[] generalNames = new GeneralName[length]; + for (int i = 0; i < length; i++) { + generalNames[i] = new GeneralName(GeneralName.dNSName, sansTable[i]); + } + return new GeneralNames(generalNames); + } + + private static Boolean isParameterPresent(String parameter) { + return parameter != null && !"".equals(parameter); + } + + private static String encodeToBase64(String csrInPem) { + return Base64.getEncoder().encodeToString(csrInPem.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java index 2afdbee0..96b3650c 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java @@ -16,9 +16,22 @@ * limitations under the License. * ============LICENSE_END========================================================= */ + package org.onap.aaf.certservice.client.certification; -public class EncryptionAlgorithmConstants { +public final class EncryptionAlgorithmConstants { + + private EncryptionAlgorithmConstants() {} + public static final String RSA_ENCRYPTION_ALGORITHM = "RSA"; + public static final String SIGN_ALGORITHM = "SHA1withRSA"; public static final int KEY_SIZE = 2048; + + public static final String COMMON_NAME = "CN"; + public static final String ORGANIZATION = "O"; + public static final String ORGANIZATION_UNIT = "OU"; + public static final String LOCATION = "L"; + public static final String STATE = "ST"; + public static final String COUNTRY = "C"; + } diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/KeyPairFactory.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/KeyPairFactory.java index 6ad6528a..64136863 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/KeyPairFactory.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/KeyPairFactory.java @@ -37,7 +37,7 @@ public class KeyPairFactory { this.keySize = keySize; } - public KeyPair create() { + public KeyPair create() throws KeyPairGenerationException { try { return createKeyPairGenerator().generateKeyPair(); } catch (NoSuchAlgorithmException e) { diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/exception/CsrGenerationException.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/exception/CsrGenerationException.java new file mode 100644 index 00000000..c1d4afd2 --- /dev/null +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/exception/CsrGenerationException.java @@ -0,0 +1,35 @@ +/*============LICENSE_START======================================================= + * aaf-certservice-client + * ================================================================================ + * Copyright (C) 2020 Nokia. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.aaf.certservice.client.certification.exception; + +import org.onap.aaf.certservice.client.api.ExitCode; +import org.onap.aaf.certservice.client.api.ExitableException; + +public class CsrGenerationException extends ExitableException { + private static final ExitCode EXIT_CODE = ExitCode.CSR_GENERATION_EXCEPTION; + + public CsrGenerationException(Throwable e) { + super(e); + } + + public int applicationExitCode() { + return EXIT_CODE.getValue(); + } +} diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/EnvProvider.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/EnvProvider.java index 9592ac3e..beccd383 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/EnvProvider.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/EnvProvider.java @@ -17,14 +17,10 @@ * limitations under the License. * ============LICENSE_END========================================================= */ - package org.onap.aaf.certservice.client.configuration; - -import org.onap.aaf.certservice.client.configuration.exception.ClientConfigurationException; - public class EnvProvider { - public String readEnvVariable(String envVariable) throws ClientConfigurationException { + public String readEnvVariable(String envVariable) { return System.getProperty(envVariable); } } diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/factory/AbstractConfigurationFactory.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/factory/AbstractConfigurationFactory.java index 28a5cf41..2464cc58 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/factory/AbstractConfigurationFactory.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/factory/AbstractConfigurationFactory.java @@ -20,8 +20,10 @@ package org.onap.aaf.certservice.client.configuration.factory; +import org.onap.aaf.certservice.client.configuration.exception.ClientConfigurationException; +import org.onap.aaf.certservice.client.configuration.exception.CsrConfigurationException; import org.onap.aaf.certservice.client.configuration.model.ConfigurationModel; public interface AbstractConfigurationFactory<T extends ConfigurationModel> { - T create(); + T create() throws ClientConfigurationException, CsrConfigurationException; } diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java index 30caf42a..aaaf10fa 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java @@ -29,7 +29,7 @@ public class CsrConfiguration implements ConfigurationModel { private String country; private String organizationUnit; private String location; - private String subjectAlternativeNames; + private String sans; public String getCommonName() { @@ -86,12 +86,12 @@ public class CsrConfiguration implements ConfigurationModel { return this; } - public String getSubjectAlternativeNames() { - return subjectAlternativeNames; + public String getSans() { + return sans; } public CsrConfiguration setSubjectAlternativeNames(String subjectAlternativeNames) { - this.subjectAlternativeNames = subjectAlternativeNames; + this.sans = subjectAlternativeNames; return this; } } diff --git a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/CertServiceClientTest.java b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/CertServiceClientTest.java index 22baab50..9e733017 100644 --- a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/CertServiceClientTest.java +++ b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/CertServiceClientTest.java @@ -22,48 +22,26 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import org.onap.aaf.certservice.client.certification.KeyPairFactory; -import java.security.KeyPair; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.KEY_SIZE; -import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.RSA_ENCRYPTION_ALGORITHM; +import static org.onap.aaf.certservice.client.api.ExitCode.CLIENT_CONFIGURATION_EXCEPTION; +import static org.onap.aaf.certservice.client.api.ExitCode.SUCCESS_EXIT_CODE; @ExtendWith(MockitoExtension.class) class CertServiceClientTest { - private static final int DUMMY_EXIT_CODE = 888; @Spy AppExitHandler appExitHandler = new AppExitHandler(); - - @Test - public void shouldExitWithDefinedExitCode_onGenerateKeyPairCallWhereExitableExceptionIsThrown() { - // given - KeyPairFactory keyPairFactory = mock(KeyPairFactory.class); - when(keyPairFactory.create()).thenThrow(new DummyExitableException()); - doNothing().when(appExitHandler).exit(DUMMY_EXIT_CODE); - CertServiceClient certServiceClient = new CertServiceClient(appExitHandler); - // when - Optional<KeyPair> keyPair = certServiceClient.generateKeyPair(keyPairFactory); - // then - verify(appExitHandler).exit(DUMMY_EXIT_CODE); - assertThat(keyPair).isEmpty(); - } - @Test - public void shouldReturnKeyPair_onGenerateKeyPairCall() { + public void shouldExitWithDefinedExitCode_onRunCallWhenNoEnvsPresent() { // given - KeyPairFactory keyPairFactory = new KeyPairFactory(RSA_ENCRYPTION_ALGORITHM, KEY_SIZE); + doNothing().when(appExitHandler).exit(CLIENT_CONFIGURATION_EXCEPTION.getValue()); + doNothing().when(appExitHandler).exit(SUCCESS_EXIT_CODE.getValue()); CertServiceClient certServiceClient = new CertServiceClient(appExitHandler); // when - Optional<KeyPair> keyPair = certServiceClient.generateKeyPair(keyPairFactory); + certServiceClient.run(); // then - assertThat(keyPair).hasValueSatisfying(value -> assertThat(value).isInstanceOf(KeyPair.class)); + verify(appExitHandler).exit(CLIENT_CONFIGURATION_EXCEPTION.getValue()); + verify(appExitHandler).exit(SUCCESS_EXIT_CODE.getValue()); } - }
\ No newline at end of file diff --git a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/CsrFactoryTest.java b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/CsrFactoryTest.java new file mode 100644 index 00000000..16b5e03b --- /dev/null +++ b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/CsrFactoryTest.java @@ -0,0 +1,58 @@ +/*============LICENSE_START======================================================= + * aaf-certservice-client + * ================================================================================ + * Copyright (C) 2020 Nokia. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.aaf.certservice.client.certification; + + +import org.junit.jupiter.api.Test; +import org.onap.aaf.certservice.client.certification.exception.CsrGenerationException; +import org.onap.aaf.certservice.client.certification.exception.KeyPairGenerationException; +import org.onap.aaf.certservice.client.configuration.model.CsrConfiguration; + +import java.security.KeyPair; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class CsrFactoryTest { + + CsrConfiguration config = mock(CsrConfiguration.class); + + + + @Test + void createEncodedCsr_shouldSucceedWhenAllFieldsAreSetCorrectly() throws KeyPairGenerationException, CsrGenerationException { + + KeyPair keyPair = + new KeyPairFactory(EncryptionAlgorithmConstants.RSA_ENCRYPTION_ALGORITHM, EncryptionAlgorithmConstants.KEY_SIZE).create(); + + when(config.getCommonName()).thenReturn("onap.org"); + when(config.getSans()).thenReturn("onapexample.com:onapexample.com.pl:onapexample.pl"); + when(config.getCountry()).thenReturn("US"); + when(config.getLocation()).thenReturn("San-Francisco"); + when(config.getOrganization()).thenReturn("Linux-Foundation"); + when(config.getOrganizationUnit()).thenReturn("ONAP"); + when(config.getState()).thenReturn("California"); + + assertThat(new CsrFactory(config).createEncodedCsr(keyPair)).isNotEmpty(); + } +} + diff --git a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/KeyPairFactoryTest.java b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/KeyPairFactoryTest.java index b92660fa..6a4741a2 100644 --- a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/KeyPairFactoryTest.java +++ b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/KeyPairFactoryTest.java @@ -30,7 +30,7 @@ class KeyPairFactoryTest { private static final String NOT_EXISTING_ENCRYPTION_ALGORITHM = "FAKE_ALGORITHM"; @Test - public void shouldProvideKeyPair_whenCreateKeyPairCalledWithCorrectArguments() { + public void shouldProvideKeyPair_whenCreateKeyPairCalledWithCorrectArguments() throws KeyPairGenerationException { // given KeyPairFactory keyPairFactory = new KeyPairFactory(EncryptionAlgorithmConstants.RSA_ENCRYPTION_ALGORITHM, EncryptionAlgorithmConstants.KEY_SIZE); diff --git a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/ClientConfigurationFactoryTest.java b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/ClientConfigurationFactoryTest.java index 7cf9e0ce..f355de1a 100644 --- a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/ClientConfigurationFactoryTest.java +++ b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/ClientConfigurationFactoryTest.java @@ -43,7 +43,7 @@ public class ClientConfigurationFactoryTest { private EnvsForClient envsForClient = mock(EnvsForClient.class); @Test - void create_shouldReturnSuccessWhenAllVariablesAreSetAndValid() { + void create_shouldReturnSuccessWhenAllVariablesAreSetAndValid() throws ClientConfigurationException { // given when(envsForClient.getCaName()).thenReturn(CA_NAME_VALID); when(envsForClient.getOutputPath()).thenReturn(OUTPUT_PATH_VALID); @@ -61,7 +61,7 @@ public class ClientConfigurationFactoryTest { } @Test - void create_shouldReturnSuccessWhenDefaultVariablesAreNotSet() { + void create_shouldReturnSuccessWhenDefaultVariablesAreNotSet() throws ClientConfigurationException { // given when(envsForClient.getCaName()).thenReturn(CA_NAME_VALID); when(envsForClient.getOutputPath()).thenReturn(OUTPUT_PATH_VALID); diff --git a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java index 4a4eb247..695a9e4b 100644 --- a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java +++ b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java @@ -46,7 +46,7 @@ public class CsrConfigurationFactoryTest { @Test - void create_shouldReturnSuccessWhenAllVariablesAreSetAndValid() { + void create_shouldReturnSuccessWhenAllVariablesAreSetAndValid() throws CsrConfigurationException { // given when(envsForCsr.getCommonName()).thenReturn(COMMON_NAME_VALID); when(envsForCsr.getSubjectAlternativesName()).thenReturn(SANS_VALID); @@ -61,7 +61,7 @@ public class CsrConfigurationFactoryTest { // then assertThat(configuration.getCommonName()).isEqualTo(COMMON_NAME_VALID); - assertThat(configuration.getSubjectAlternativeNames()).isEqualTo(SANS_VALID); + assertThat(configuration.getSans()).isEqualTo(SANS_VALID); assertThat(configuration.getCountry()).isEqualTo(COUNTRY_VALID); assertThat(configuration.getLocation()).isEqualTo(LOCATION_VALID); assertThat(configuration.getOrganization()).isEqualTo(ORGANIZATION_VALID); @@ -70,7 +70,7 @@ public class CsrConfigurationFactoryTest { } @Test - void create_shouldReturnSuccessWhenNotRequiredVariablesAreNotSet() { + void create_shouldReturnSuccessWhenNotRequiredVariablesAreNotSet() throws CsrConfigurationException { // given when(envsForCsr.getCommonName()).thenReturn(COMMON_NAME_VALID); when(envsForCsr.getState()).thenReturn(STATE_VALID); |