2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 Nordix Foundation.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.aaf.certservice.cmpv2client.impl;
23 import java.security.KeyPair;
24 import java.security.PublicKey;
26 import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.checkIfCmpResponseContainsError;
27 import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.getCertfromByteArray;
28 import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.verifyAndReturnCertChainAndTrustSTore;
29 import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.checkImplicitConfirm;
30 import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifyPasswordBasedProtection;
31 import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifySignature;
33 import java.io.IOException;
34 import java.security.cert.CertificateParsingException;
35 import java.security.cert.X509Certificate;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.Date;
39 import java.util.List;
40 import java.util.Objects;
41 import java.util.Optional;
43 import org.apache.http.impl.client.CloseableHttpClient;
44 import org.bouncycastle.asn1.cmp.CMPCertificate;
45 import org.bouncycastle.asn1.cmp.CertRepMessage;
46 import org.bouncycastle.asn1.cmp.CertResponse;
47 import org.bouncycastle.asn1.cmp.PKIBody;
48 import org.bouncycastle.asn1.cmp.PKIHeader;
49 import org.bouncycastle.asn1.cmp.PKIMessage;
50 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
51 import org.onap.aaf.certservice.certification.configuration.model.Cmpv2Server;
52 import org.onap.aaf.certservice.certification.model.CsrModel;
53 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
54 import org.onap.aaf.certservice.cmpv2client.api.CmpClient;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * Implementation of the CmpClient Interface conforming to RFC4210 (Certificate Management Protocol
60 * (CMP)) and RFC4211 (Certificate Request Message Format (CRMF)) standards.
62 public class CmpClientImpl implements CmpClient {
64 private static final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class);
65 private final CloseableHttpClient httpClient;
67 private static final String DEFAULT_PROFILE = "RA";
68 private static final String DEFAULT_CA_NAME = "Certification Authority";
70 public CmpClientImpl(CloseableHttpClient httpClient) {
71 this.httpClient = httpClient;
75 public List<List<X509Certificate>> createCertificate(
83 throws CmpClientException {
85 validate(csrModel, server, cert, caName, profile, httpClient, notBefore, notAfter);
86 KeyPair keyPair = new KeyPair(csrModel.getPublicKey(), csrModel.getPrivateKey());
88 final CreateCertRequest certRequest =
89 CmpMessageBuilder.of(CreateCertRequest::new)
90 .with(CreateCertRequest::setIssuerDn, server.getIssuerDN())
91 .with(CreateCertRequest::setSubjectDn, csrModel.getSubjectData())
92 .with(CreateCertRequest::setSansList, csrModel.getSans())
93 .with(CreateCertRequest::setSubjectKeyPair, keyPair)
94 .with(CreateCertRequest::setNotBefore, notBefore)
95 .with(CreateCertRequest::setNotAfter, notAfter)
96 .with(CreateCertRequest::setInitAuthPassword, server.getAuthentication().getIak())
97 .with(CreateCertRequest::setSenderKid, server.getAuthentication().getRv())
100 final PKIMessage pkiMessage = certRequest.generateCertReq();
101 Cmpv2HttpClient cmpv2HttpClient = new Cmpv2HttpClient(httpClient);
102 return retrieveCertificates(caName, csrModel, server, pkiMessage, cmpv2HttpClient);
106 public List<List<X509Certificate>> createCertificate(
107 String caName, String profile, CsrModel csrModel, Cmpv2Server server, X509Certificate csr)
108 throws CmpClientException {
109 return createCertificate(caName, profile, csrModel, server, csr, null, null);
112 private void checkCmpResponse(
113 final PKIMessage respPkiMessage, final PublicKey publicKey, final String initAuthPassword)
114 throws CmpClientException {
115 final PKIHeader header = respPkiMessage.getHeader();
116 final AlgorithmIdentifier protectionAlgo = header.getProtectionAlg();
117 verifySignatureWithPublicKey(respPkiMessage, publicKey);
118 verifyProtectionWithProtectionAlgo(respPkiMessage, initAuthPassword, header, protectionAlgo);
121 private void verifySignatureWithPublicKey(PKIMessage respPkiMessage, PublicKey publicKey)
122 throws CmpClientException {
123 if (Objects.nonNull(publicKey)) {
124 LOG.debug("Verifying signature of the response.");
125 verifySignature(respPkiMessage, publicKey);
127 LOG.error("Public Key is not available, therefore cannot verify signature");
128 throw new CmpClientException(
129 "Public Key is not available, therefore cannot verify signature");
133 private void verifyProtectionWithProtectionAlgo(
134 PKIMessage respPkiMessage,
135 String initAuthPassword,
137 AlgorithmIdentifier protectionAlgo)
138 throws CmpClientException {
139 if (Objects.nonNull(protectionAlgo)) {
140 LOG.debug("Verifying PasswordBased Protection of the Response.");
141 verifyPasswordBasedProtection(respPkiMessage, initAuthPassword, protectionAlgo);
142 checkImplicitConfirm(header);
145 "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
146 throw new CmpClientException(
147 "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
151 private List<List<X509Certificate>> checkCmpCertRepMessage(final PKIMessage respPkiMessage)
152 throws CmpClientException {
153 final PKIBody pkiBody = respPkiMessage.getBody();
154 if (Objects.nonNull(pkiBody) && pkiBody.getContent() instanceof CertRepMessage) {
155 final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent();
156 if (Objects.nonNull(certRepMessage)) {
157 final CertResponse certResponse =
158 getCertificateResponseContainingNewCertificate(certRepMessage);
160 return verifyReturnCertChainAndTrustStore(respPkiMessage, certRepMessage, certResponse);
161 } catch (IOException | CertificateParsingException ex) {
162 CmpClientException cmpClientException =
163 new CmpClientException(
164 "Exception occurred while retrieving Certificates from response", ex);
165 LOG.error("Exception occurred while retrieving Certificates from response", ex);
166 throw cmpClientException;
169 return new ArrayList<>(Collections.emptyList());
172 return new ArrayList<>(Collections.emptyList());
175 private List<List<X509Certificate>> verifyReturnCertChainAndTrustStore(
176 PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
177 throws CertificateParsingException, CmpClientException, IOException {
178 LOG.info("Verifying certificates returned as part of CertResponse.");
179 final CMPCertificate cmpCertificate =
180 certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
181 final Optional<X509Certificate> leafCertificate =
182 getCertfromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
183 if (leafCertificate.isPresent()) {
184 return verifyAndReturnCertChainAndTrustSTore(
185 respPkiMessage, certRepMessage, leafCertificate.get());
187 return Collections.emptyList();
190 private CertResponse getCertificateResponseContainingNewCertificate(
191 CertRepMessage certRepMessage) {
192 return certRepMessage.getResponse()[0];
196 * Validate inputs for Certificate Creation.
198 * @param csrModel Certificate Signing Request model. Must not be {@code null}.
199 * @param server CMPv2 Server. Must not be {@code null}.
200 * @param cert Certificate object needed to validate response from CA server.
201 * @param incomingCaName Date specifying certificate is not valid before this date.
202 * @param incomingProfile Date specifying certificate is not valid after this date.
203 * @throws IllegalArgumentException if Before Date is set after the After Date.
205 private static void validate(
206 final CsrModel csrModel,
207 final Cmpv2Server server,
208 final X509Certificate cert,
209 final String incomingCaName,
210 final String incomingProfile,
211 final CloseableHttpClient httpClient,
212 final Date notBefore,
213 final Date notAfter) {
215 String caName = CmpUtil.isNullOrEmpty(incomingCaName) ? incomingCaName : DEFAULT_CA_NAME;
216 String caProfile = CmpUtil.isNullOrEmpty(incomingProfile) ? incomingProfile : DEFAULT_PROFILE;
218 "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, caProfile);
220 CmpUtil.notNull(csrModel, "CsrModel Instance");
221 CmpUtil.notNull(csrModel.getSubjectData(), "Subject DN");
222 CmpUtil.notNull(csrModel.getPrivateKey(), "Subject private key");
223 CmpUtil.notNull(csrModel.getPublicKey(), "Subject public key");
224 CmpUtil.notNull(server.getIssuerDN(), "Issuer DN");
225 CmpUtil.notNull(server.getUrl(), "External CA URL");
226 CmpUtil.notNull(server.getAuthentication().getIak(), "IAK/RV Password");
227 CmpUtil.notNull(cert, "Certificate Signing Request (CSR)");
228 CmpUtil.notNull(httpClient, "Closeable Http Client");
230 if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
231 throw new IllegalArgumentException("Before Date is set after the After Date");
235 private List<List<X509Certificate>> retrieveCertificates(
236 String caName, CsrModel csrModel, Cmpv2Server server, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
237 throws CmpClientException {
238 final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, server.getUrl(), caName);
240 final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
241 LOG.info("Received response from Server");
242 checkIfCmpResponseContainsError(respPkiMessage);
243 checkCmpResponse(respPkiMessage, csrModel.getPublicKey(), server.getAuthentication().getIak());
244 return checkCmpCertRepMessage(respPkiMessage);
245 } catch (IllegalArgumentException iae) {
246 CmpClientException cmpClientException =
247 new CmpClientException(
248 "Error encountered while processing response from CA server ", iae);
249 LOG.error("Error encountered while processing response from CA server ", iae);
250 throw cmpClientException;