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.CaMode;
52 import org.onap.aaf.certservice.certification.configuration.model.Cmpv2Server;
53 import org.onap.aaf.certservice.certification.model.CsrModel;
54 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
55 import org.onap.aaf.certservice.cmpv2client.api.CmpClient;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
60 * Implementation of the CmpClient Interface conforming to RFC4210 (Certificate Management Protocol
61 * (CMP)) and RFC4211 (Certificate Request Message Format (CRMF)) standards.
63 public class CmpClientImpl implements CmpClient {
65 private static final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class);
66 private final CloseableHttpClient httpClient;
68 private static final String DEFAULT_CA_NAME = "Certification Authority";
69 private static final String DEFAULT_PROFILE = CaMode.RA.getProfile();
71 public CmpClientImpl(CloseableHttpClient httpClient) {
72 this.httpClient = httpClient;
76 public List<List<X509Certificate>> createCertificate(
81 throws CmpClientException {
83 validate(csrModel, server, httpClient, notBefore, notAfter);
84 KeyPair keyPair = new KeyPair(csrModel.getPublicKey(), csrModel.getPrivateKey());
86 final CreateCertRequest certRequest =
87 CmpMessageBuilder.of(CreateCertRequest::new)
88 .with(CreateCertRequest::setIssuerDn, server.getIssuerDN())
89 .with(CreateCertRequest::setSubjectDn, csrModel.getSubjectData())
90 .with(CreateCertRequest::setSansList, csrModel.getSans())
91 .with(CreateCertRequest::setSubjectKeyPair, keyPair)
92 .with(CreateCertRequest::setNotBefore, notBefore)
93 .with(CreateCertRequest::setNotAfter, notAfter)
94 .with(CreateCertRequest::setInitAuthPassword, server.getAuthentication().getIak())
95 .with(CreateCertRequest::setSenderKid, server.getAuthentication().getRv())
98 final PKIMessage pkiMessage = certRequest.generateCertReq();
99 Cmpv2HttpClient cmpv2HttpClient = new Cmpv2HttpClient(httpClient);
100 return retrieveCertificates(csrModel, server, pkiMessage, cmpv2HttpClient);
104 public List<List<X509Certificate>> createCertificate(CsrModel csrModel, Cmpv2Server server)
105 throws CmpClientException {
106 return createCertificate(csrModel, server, null, null);
109 private void checkCmpResponse(
110 final PKIMessage respPkiMessage, final PublicKey publicKey, final String initAuthPassword)
111 throws CmpClientException {
112 final PKIHeader header = respPkiMessage.getHeader();
113 final AlgorithmIdentifier protectionAlgo = header.getProtectionAlg();
114 verifySignatureWithPublicKey(respPkiMessage, publicKey);
115 verifyProtectionWithProtectionAlgo(respPkiMessage, initAuthPassword, header, protectionAlgo);
118 private void verifySignatureWithPublicKey(PKIMessage respPkiMessage, PublicKey publicKey)
119 throws CmpClientException {
120 if (Objects.nonNull(publicKey)) {
121 LOG.debug("Verifying signature of the response.");
122 verifySignature(respPkiMessage, publicKey);
124 LOG.error("Public Key is not available, therefore cannot verify signature");
125 throw new CmpClientException(
126 "Public Key is not available, therefore cannot verify signature");
130 private void verifyProtectionWithProtectionAlgo(
131 PKIMessage respPkiMessage,
132 String initAuthPassword,
134 AlgorithmIdentifier protectionAlgo)
135 throws CmpClientException {
136 if (Objects.nonNull(protectionAlgo)) {
137 LOG.debug("Verifying PasswordBased Protection of the Response.");
138 verifyPasswordBasedProtection(respPkiMessage, initAuthPassword, protectionAlgo);
139 checkImplicitConfirm(header);
142 "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
143 throw new CmpClientException(
144 "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
148 private List<List<X509Certificate>> checkCmpCertRepMessage(final PKIMessage respPkiMessage)
149 throws CmpClientException {
150 final PKIBody pkiBody = respPkiMessage.getBody();
151 if (Objects.nonNull(pkiBody) && pkiBody.getContent() instanceof CertRepMessage) {
152 final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent();
153 if (Objects.nonNull(certRepMessage)) {
154 final CertResponse certResponse =
155 getCertificateResponseContainingNewCertificate(certRepMessage);
157 return verifyReturnCertChainAndTrustStore(respPkiMessage, certRepMessage, certResponse);
158 } catch (IOException | CertificateParsingException ex) {
159 CmpClientException cmpClientException =
160 new CmpClientException(
161 "Exception occurred while retrieving Certificates from response", ex);
162 LOG.error("Exception occurred while retrieving Certificates from response", ex);
163 throw cmpClientException;
166 return new ArrayList<>(Collections.emptyList());
169 return new ArrayList<>(Collections.emptyList());
172 private List<List<X509Certificate>> verifyReturnCertChainAndTrustStore(
173 PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
174 throws CertificateParsingException, CmpClientException, IOException {
175 LOG.info("Verifying certificates returned as part of CertResponse.");
176 final CMPCertificate cmpCertificate =
177 certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
178 final Optional<X509Certificate> leafCertificate =
179 getCertfromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
180 if (leafCertificate.isPresent()) {
181 return verifyAndReturnCertChainAndTrustSTore(
182 respPkiMessage, certRepMessage, leafCertificate.get());
184 return Collections.emptyList();
187 private CertResponse getCertificateResponseContainingNewCertificate(
188 CertRepMessage certRepMessage) {
189 return certRepMessage.getResponse()[0];
193 * Validate inputs for Certificate Creation.
195 * @param csrModel Certificate Signing Request model. Must not be {@code null}.
196 * @param server CMPv2 Server. Must not be {@code null}.
197 * @throws IllegalArgumentException if Before Date is set after the After Date.
199 private static void validate(
200 final CsrModel csrModel,
201 final Cmpv2Server server,
202 final CloseableHttpClient httpClient,
203 final Date notBefore,
204 final Date notAfter) {
206 String caName = CmpUtil.isNullOrEmpty(server.getCaName()) ? server.getCaName() : DEFAULT_CA_NAME;
207 String profile = server.getCaMode() != null ? server.getCaMode().getProfile() : DEFAULT_PROFILE;
209 "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, profile);
211 CmpUtil.notNull(csrModel, "CsrModel Instance");
212 CmpUtil.notNull(csrModel.getSubjectData(), "Subject DN");
213 CmpUtil.notNull(csrModel.getPrivateKey(), "Subject private key");
214 CmpUtil.notNull(csrModel.getPublicKey(), "Subject public key");
215 CmpUtil.notNull(server.getIssuerDN(), "Issuer DN");
216 CmpUtil.notNull(server.getUrl(), "External CA URL");
217 CmpUtil.notNull(server.getAuthentication().getIak(), "IAK/RV Password");
218 CmpUtil.notNull(httpClient, "Closeable Http Client");
220 if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
221 throw new IllegalArgumentException("Before Date is set after the After Date");
225 private List<List<X509Certificate>> retrieveCertificates(
226 CsrModel csrModel, Cmpv2Server server, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
227 throws CmpClientException {
228 final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, server.getUrl(), server.getCaName());
230 final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
231 LOG.info("Received response from Server");
232 checkIfCmpResponseContainsError(respPkiMessage);
233 checkCmpResponse(respPkiMessage, csrModel.getPublicKey(), server.getAuthentication().getIak());
234 return checkCmpCertRepMessage(respPkiMessage);
235 } catch (IllegalArgumentException iae) {
236 CmpClientException cmpClientException =
237 new CmpClientException(
238 "Error encountered while processing response from CA server ", iae);
239 LOG.error("Error encountered while processing response from CA server ", iae);
240 throw cmpClientException;