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 if (LOG.isInfoEnabled()) {
184 LOG.info("Response status code: {}", certResponse.getStatus().getStatus());
186 if (certResponse.getStatus().getStatusString() != null) {
187 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
188 LOG.warn("Response status text: {}", serverMessage);
190 if (LOG.isWarnEnabled() && certResponse.getStatus().getFailInfo() != null) {
191 LOG.warn("Response fail info: {}", certResponse.getStatus().getFailInfo());
195 private Cmpv2CertificationModel verifyReturnCertChainAndTrustStore(
196 PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
197 throws CertificateParsingException, CmpClientException, IOException {
198 LOG.info("Verifying certificates returned as part of CertResponse.");
199 final CMPCertificate cmpCertificate =
200 certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
201 final Optional<X509Certificate> leafCertificate =
202 getCertFromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
203 if (leafCertificate.isPresent()) {
204 return verifyAndReturnCertChainAndTrustSTore(
205 respPkiMessage, certRepMessage, leafCertificate.get());
207 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
210 private CertResponse getCertificateResponseContainingNewCertificate(
211 CertRepMessage certRepMessage) {
212 return certRepMessage.getResponse()[0];
216 * Validate inputs for Certificate Creation.
218 * @param csrModel Certificate Signing Request model. Must not be {@code null}.
219 * @param server CMPv2 Server. Must not be {@code null}.
220 * @throws IllegalArgumentException if Before Date is set after the After Date.
222 private static void validate(
223 final CsrModel csrModel,
224 final Cmpv2Server server,
225 final CloseableHttpClient httpClient,
226 final Date notBefore,
227 final Date notAfter) {
229 String caName = CmpUtil.isNullOrEmpty(server.getCaName()) ? server.getCaName() : DEFAULT_CA_NAME;
230 String profile = server.getCaMode() != null ? server.getCaMode().getProfile() : DEFAULT_PROFILE;
232 "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, profile);
234 CmpUtil.notNull(csrModel, "CsrModel Instance");
235 CmpUtil.notNull(csrModel.getSubjectData(), "Subject DN");
236 CmpUtil.notNull(csrModel.getPrivateKey(), "Subject private key");
237 CmpUtil.notNull(csrModel.getPublicKey(), "Subject public key");
238 CmpUtil.notNull(server.getIssuerDN(), "Issuer DN");
239 CmpUtil.notNull(server.getUrl(), "External CA URL");
240 CmpUtil.notNull(server.getAuthentication().getIak(), "IAK/RV Password");
241 CmpUtil.notNull(httpClient, "Closeable Http Client");
243 if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
244 throw new IllegalArgumentException("Before Date is set after the After Date");
248 private Cmpv2CertificationModel retrieveCertificates(
249 CsrModel csrModel, Cmpv2Server server, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
250 throws CmpClientException {
251 final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, server.getUrl(), server.getCaName());
253 final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
254 LOG.info("Received response from Server");
255 checkIfCmpResponseContainsError(respPkiMessage);
256 checkCmpResponse(respPkiMessage, csrModel.getPublicKey(), server.getAuthentication().getIak());
257 return checkCmpCertRepMessage(respPkiMessage);
258 } catch (IllegalArgumentException iae) {
259 CmpClientException cmpClientException =
260 new CmpClientException(
261 "Error encountered while processing response from CA server ", iae);
262 LOG.error("Error encountered while processing response from CA server ", iae);
263 throw cmpClientException;