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 String iak = server.getAuthentication().getIak();
87 final PkiMessageProtection pkiMessageProtection = new PasswordBasedProtection(iak);
88 final CreateCertRequest certRequest =
89 CmpMessageBuilder.of(CreateCertRequest::new)
90 .with(CreateCertRequest::setIssuerDn, server.getIssuerDN())
91 .with(CreateCertRequest::setSubjectDn, csrModel.getSubjectData())
92 .with(CreateCertRequest::setSansArray, csrModel.getSans())
93 .with(CreateCertRequest::setSubjectKeyPair, keyPair)
94 .with(CreateCertRequest::setNotBefore, notBefore)
95 .with(CreateCertRequest::setNotAfter, notAfter)
96 .with(CreateCertRequest::setSenderKid, server.getAuthentication().getRv())
97 .with(CreateCertRequest::setProtection, pkiMessageProtection)
100 final PKIMessage pkiMessage = certRequest.generateCertReq();
101 Cmpv2HttpClient cmpv2HttpClient = new Cmpv2HttpClient(httpClient);
102 return retrieveCertificates(csrModel, server, pkiMessage, cmpv2HttpClient);
106 public Cmpv2CertificationModel createCertificate(CsrModel csrModel, Cmpv2Server server)
107 throws CmpClientException {
108 return createCertificate(csrModel, server, null, null);
111 private void checkCmpResponse(
112 final PKIMessage respPkiMessage, final PublicKey publicKey, final String initAuthPassword)
113 throws CmpClientException {
114 final PKIHeader header = respPkiMessage.getHeader();
115 final AlgorithmIdentifier protectionAlgo = header.getProtectionAlg();
116 verifySignatureWithPublicKey(respPkiMessage, publicKey);
117 verifyProtectionWithProtectionAlgo(respPkiMessage, initAuthPassword, header, protectionAlgo);
120 private void verifySignatureWithPublicKey(PKIMessage respPkiMessage, PublicKey publicKey)
121 throws CmpClientException {
122 if (Objects.nonNull(publicKey)) {
123 LOG.debug("Verifying signature of the response.");
124 verifySignature(respPkiMessage, publicKey);
126 LOG.error("Public Key is not available, therefore cannot verify signature");
127 throw new CmpClientException(
128 "Public Key is not available, therefore cannot verify signature");
132 private void verifyProtectionWithProtectionAlgo(
133 PKIMessage respPkiMessage,
134 String initAuthPassword,
136 AlgorithmIdentifier protectionAlgo)
137 throws CmpClientException {
138 if (Objects.nonNull(protectionAlgo)) {
139 LOG.debug("Verifying PasswordBased Protection of the Response.");
140 verifyPasswordBasedProtection(respPkiMessage, initAuthPassword, protectionAlgo);
141 checkImplicitConfirm(header);
144 "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
145 throw new CmpClientException(
146 "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
150 private Cmpv2CertificationModel checkCmpCertRepMessage(final PKIMessage respPkiMessage)
151 throws CmpClientException {
152 final PKIBody pkiBody = respPkiMessage.getBody();
153 if (Objects.nonNull(pkiBody) && pkiBody.getContent() instanceof CertRepMessage) {
154 final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent();
155 if (Objects.nonNull(certRepMessage)) {
157 CertResponse certResponse = getCertificateResponseContainingNewCertificate(certRepMessage);
158 checkServerResponse(certResponse);
159 return verifyReturnCertChainAndTrustStore(respPkiMessage, certRepMessage, certResponse);
160 } catch (IOException | CertificateParsingException ex) {
161 CmpClientException cmpClientException =
162 new CmpClientException(
163 "Exception occurred while retrieving Certificates from response", ex);
164 LOG.error("Exception occurred while retrieving Certificates from response", ex);
165 throw cmpClientException;
168 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
171 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
174 private void checkServerResponse(CertResponse certResponse) {
175 if (certResponse.getStatus() != null && certResponse.getStatus().getStatus() != null) {
176 logServerResponse(certResponse);
177 if (certResponse.getStatus().getStatus().intValue() == PkiStatus.REJECTED.getCode()) {
178 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
179 throw new CmpServerException(Optional.ofNullable(serverMessage).orElse("N/A"));
184 private void logServerResponse(CertResponse certResponse) {
185 if (LOG.isInfoEnabled()) {
186 LOG.info("Response status code: {}", certResponse.getStatus().getStatus());
188 if (certResponse.getStatus().getStatusString() != null) {
189 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
190 LOG.warn("Response status text: {}", serverMessage);
192 if (LOG.isWarnEnabled() && certResponse.getStatus().getFailInfo() != null) {
193 LOG.warn("Response fail info: {}", certResponse.getStatus().getFailInfo());
197 private Cmpv2CertificationModel verifyReturnCertChainAndTrustStore(
198 PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
199 throws CertificateParsingException, CmpClientException, IOException {
200 LOG.info("Verifying certificates returned as part of CertResponse.");
201 final CMPCertificate cmpCertificate =
202 certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
203 final Optional<X509Certificate> leafCertificate =
204 getCertFromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
205 if (leafCertificate.isPresent()) {
206 return verifyAndReturnCertChainAndTrustSTore(
207 respPkiMessage, certRepMessage, leafCertificate.get());
209 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
212 private CertResponse getCertificateResponseContainingNewCertificate(
213 CertRepMessage certRepMessage) {
214 return certRepMessage.getResponse()[0];
218 * Validate inputs for Certificate Creation.
220 * @param csrModel Certificate Signing Request model. Must not be {@code null}.
221 * @param server CMPv2 Server. Must not be {@code null}.
222 * @throws IllegalArgumentException if Before Date is set after the After Date.
224 private static void validate(
225 final CsrModel csrModel,
226 final Cmpv2Server server,
227 final CloseableHttpClient httpClient,
228 final Date notBefore,
229 final Date notAfter) {
231 String caName = CmpUtil.isNullOrEmpty(server.getCaName()) ? server.getCaName() : DEFAULT_CA_NAME;
232 String profile = server.getCaMode() != null ? server.getCaMode().getProfile() : DEFAULT_PROFILE;
234 "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, profile);
236 CmpUtil.notNull(csrModel, "CsrModel Instance");
237 CmpUtil.notNull(csrModel.getSubjectData(), "Subject DN");
238 CmpUtil.notNull(csrModel.getPrivateKey(), "Subject private key");
239 CmpUtil.notNull(csrModel.getPublicKey(), "Subject public key");
240 CmpUtil.notNull(server.getIssuerDN(), "Issuer DN");
241 CmpUtil.notNull(server.getUrl(), "External CA URL");
242 CmpUtil.notNull(server.getAuthentication().getIak(), "IAK/RV Password");
243 CmpUtil.notNull(httpClient, "Closeable Http Client");
245 if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
246 throw new IllegalArgumentException("Before Date is set after the After Date");
250 private Cmpv2CertificationModel retrieveCertificates(
251 CsrModel csrModel, Cmpv2Server server, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
252 throws CmpClientException {
253 final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, server.getUrl(), server.getCaName());
255 final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
256 LOG.info("Received response from Server");
257 checkIfCmpResponseContainsError(respPkiMessage);
258 checkCmpResponse(respPkiMessage, csrModel.getPublicKey(), server.getAuthentication().getIak());
259 return checkCmpCertRepMessage(respPkiMessage);
260 } catch (IllegalArgumentException iae) {
261 CmpClientException cmpClientException =
262 new CmpClientException(
263 "Error encountered while processing response from CA server ", iae);
264 LOG.error("Error encountered while processing response from CA server ", iae);
265 throw cmpClientException;