2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 Nordix Foundation.
4 * ================================================================================
5 * Modification copyright 2021 Nokia
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.oom.certservice.cmpv2client.impl;
25 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseHelper.checkIfCmpResponseContainsError;
26 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseHelper.getCertFromByteArray;
27 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseHelper.verifyAndReturnCertChainAndTrustSTore;
28 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseValidationHelper.checkImplicitConfirm;
29 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifyPasswordBasedProtection;
30 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifySignature;
32 import java.io.IOException;
33 import java.security.KeyPair;
34 import java.security.PublicKey;
35 import java.security.cert.CertificateParsingException;
36 import java.security.cert.X509Certificate;
37 import java.util.Collections;
38 import java.util.Date;
39 import java.util.Objects;
40 import java.util.Optional;
41 import org.apache.http.impl.client.CloseableHttpClient;
42 import org.bouncycastle.asn1.cmp.CMPCertificate;
43 import org.bouncycastle.asn1.cmp.CertRepMessage;
44 import org.bouncycastle.asn1.cmp.CertResponse;
45 import org.bouncycastle.asn1.cmp.PKIBody;
46 import org.bouncycastle.asn1.cmp.PKIHeader;
47 import org.bouncycastle.asn1.cmp.PKIMessage;
48 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
49 import org.onap.oom.certservice.certification.configuration.model.CaMode;
50 import org.onap.oom.certservice.certification.configuration.model.Cmpv2Server;
51 import org.onap.oom.certservice.certification.model.CsrModel;
52 import org.onap.oom.certservice.cmpv2client.api.CmpClient;
53 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
54 import org.onap.oom.certservice.cmpv2client.exceptions.CmpServerException;
55 import org.onap.oom.certservice.cmpv2client.model.Cmpv2CertificationModel;
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 Cmpv2CertificationModel 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::setSansArray, 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 Cmpv2CertificationModel 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 Cmpv2CertificationModel 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)) {
155 CertResponse certResponse = getCertificateResponseContainingNewCertificate(certRepMessage);
156 checkServerResponse(certResponse);
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 Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
169 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
172 private void checkServerResponse(CertResponse certResponse) {
173 if (certResponse.getStatus() != null && certResponse.getStatus().getStatus() != null) {
174 logServerResponse(certResponse);
175 if (certResponse.getStatus().getStatus().intValue() == PkiStatus.REJECTED.getCode()) {
176 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
177 throw new CmpServerException(Optional.ofNullable(serverMessage).orElse("N/A"));
182 private void logServerResponse(CertResponse certResponse) {
183 LOG.info("Response status code: {}", certResponse.getStatus().getStatus().toString());
184 if (certResponse.getStatus().getStatusString() != null) {
185 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
186 LOG.warn("Response status text: {}", serverMessage);
188 if (certResponse.getStatus().getFailInfo() != null) {
189 LOG.warn("Response fail info: {}", certResponse.getStatus().getFailInfo().toString());
193 private Cmpv2CertificationModel verifyReturnCertChainAndTrustStore(
194 PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
195 throws CertificateParsingException, CmpClientException, IOException {
196 LOG.info("Verifying certificates returned as part of CertResponse.");
197 final CMPCertificate cmpCertificate =
198 certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
199 final Optional<X509Certificate> leafCertificate =
200 getCertFromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
201 if (leafCertificate.isPresent()) {
202 return verifyAndReturnCertChainAndTrustSTore(
203 respPkiMessage, certRepMessage, leafCertificate.get());
205 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
208 private CertResponse getCertificateResponseContainingNewCertificate(
209 CertRepMessage certRepMessage) {
210 return certRepMessage.getResponse()[0];
214 * Validate inputs for Certificate Creation.
216 * @param csrModel Certificate Signing Request model. Must not be {@code null}.
217 * @param server CMPv2 Server. Must not be {@code null}.
218 * @throws IllegalArgumentException if Before Date is set after the After Date.
220 private static void validate(
221 final CsrModel csrModel,
222 final Cmpv2Server server,
223 final CloseableHttpClient httpClient,
224 final Date notBefore,
225 final Date notAfter) {
227 String caName = CmpUtil.isNullOrEmpty(server.getCaName()) ? server.getCaName() : DEFAULT_CA_NAME;
228 String profile = server.getCaMode() != null ? server.getCaMode().getProfile() : DEFAULT_PROFILE;
230 "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, profile);
232 CmpUtil.notNull(csrModel, "CsrModel Instance");
233 CmpUtil.notNull(csrModel.getSubjectData(), "Subject DN");
234 CmpUtil.notNull(csrModel.getPrivateKey(), "Subject private key");
235 CmpUtil.notNull(csrModel.getPublicKey(), "Subject public key");
236 CmpUtil.notNull(server.getIssuerDN(), "Issuer DN");
237 CmpUtil.notNull(server.getUrl(), "External CA URL");
238 CmpUtil.notNull(server.getAuthentication().getIak(), "IAK/RV Password");
239 CmpUtil.notNull(httpClient, "Closeable Http Client");
241 if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
242 throw new IllegalArgumentException("Before Date is set after the After Date");
246 private Cmpv2CertificationModel retrieveCertificates(
247 CsrModel csrModel, Cmpv2Server server, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
248 throws CmpClientException {
249 final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, server.getUrl(), server.getCaName());
251 final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
252 LOG.info("Received response from Server");
253 checkIfCmpResponseContainsError(respPkiMessage);
254 checkCmpResponse(respPkiMessage, csrModel.getPublicKey(), server.getAuthentication().getIak());
255 return checkCmpCertRepMessage(respPkiMessage);
256 } catch (IllegalArgumentException iae) {
257 CmpClientException cmpClientException =
258 new CmpClientException(
259 "Error encountered while processing response from CA server ", iae);
260 LOG.error("Error encountered while processing response from CA server ", iae);
261 throw cmpClientException;