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.ASN1ObjectIdentifier;
43 import org.bouncycastle.asn1.cmp.CMPCertificate;
44 import org.bouncycastle.asn1.cmp.CertRepMessage;
45 import org.bouncycastle.asn1.cmp.CertResponse;
46 import org.bouncycastle.asn1.cmp.PKIBody;
47 import org.bouncycastle.asn1.cmp.PKIHeader;
48 import org.bouncycastle.asn1.cmp.PKIMessage;
49 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
50 import org.onap.oom.certservice.certification.configuration.model.CaMode;
51 import org.onap.oom.certservice.certification.configuration.model.Cmpv2Server;
52 import org.onap.oom.certservice.certification.model.CsrModel;
53 import org.onap.oom.certservice.cmpv2client.api.CmpClient;
54 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
55 import org.onap.oom.certservice.cmpv2client.exceptions.CmpServerException;
56 import org.onap.oom.certservice.cmpv2client.model.Cmpv2CertificationModel;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
61 * Implementation of the CmpClient Interface conforming to RFC4210 (Certificate Management Protocol
62 * (CMP)) and RFC4211 (Certificate Request Message Format (CRMF)) standards.
64 public class CmpClientImpl implements CmpClient {
66 private static final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class);
67 private final CloseableHttpClient httpClient;
69 private static final String DEFAULT_CA_NAME = "Certification Authority";
70 private static final String DEFAULT_PROFILE = CaMode.RA.getProfile();
71 private static final ASN1ObjectIdentifier PASSWORD_BASED_MAC = new ASN1ObjectIdentifier("1.2.840.113533.7.66.13");
73 public CmpClientImpl(CloseableHttpClient httpClient) {
74 this.httpClient = httpClient;
78 public Cmpv2CertificationModel createCertificate(
83 throws CmpClientException {
85 validate(csrModel, server, 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::setSansArray, 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(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 if (isPasswordBasedMacAlgorithm(protectionAlgo)) {
118 LOG.info("CMP response is protected by Password Base Mac Algorithm. Attempt to verify protection");
119 verifyPasswordBasedMacProtection(respPkiMessage, initAuthPassword, header, protectionAlgo);
123 private boolean isPasswordBasedMacAlgorithm(AlgorithmIdentifier protectionAlgo) throws CmpClientException {
124 if (Objects.isNull(protectionAlgo)) {
125 LOG.error("CMP response does not contain Protection Algorithm field");
126 throw new CmpClientException("CMP response does not contain Protection Algorithm field");
128 return PASSWORD_BASED_MAC.equals(protectionAlgo.getAlgorithm());
131 private void verifySignatureWithPublicKey(PKIMessage respPkiMessage, PublicKey publicKey)
132 throws CmpClientException {
133 if (Objects.nonNull(publicKey)) {
134 LOG.debug("Verifying signature of the response.");
135 verifySignature(respPkiMessage, publicKey);
137 LOG.error("Public Key is not available, therefore cannot verify signature");
138 throw new CmpClientException(
139 "Public Key is not available, therefore cannot verify signature");
143 private void verifyPasswordBasedMacProtection(PKIMessage respPkiMessage, String initAuthPassword,
144 PKIHeader header, AlgorithmIdentifier protectionAlgo)
145 throws CmpClientException {
146 LOG.debug("Verifying PasswordBased Protection of the Response.");
147 verifyPasswordBasedProtection(respPkiMessage, initAuthPassword, protectionAlgo);
148 checkImplicitConfirm(header);
151 private Cmpv2CertificationModel 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)) {
158 CertResponse certResponse = getCertificateResponseContainingNewCertificate(certRepMessage);
159 checkServerResponse(certResponse);
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 Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
172 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
175 private void checkServerResponse(CertResponse certResponse) {
176 if (certResponse.getStatus() != null && certResponse.getStatus().getStatus() != null) {
177 logServerResponse(certResponse);
178 if (certResponse.getStatus().getStatus().intValue() == PkiStatus.REJECTED.getCode()) {
179 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
180 throw new CmpServerException(Optional.ofNullable(serverMessage).orElse("N/A"));
185 private void logServerResponse(CertResponse certResponse) {
186 LOG.info("Response status code: {}", certResponse.getStatus().getStatus().toString());
187 if (certResponse.getStatus().getStatusString() != null) {
188 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
189 LOG.warn("Response status text: {}", serverMessage);
191 if (certResponse.getStatus().getFailInfo() != null) {
192 LOG.warn("Response fail info: {}", certResponse.getStatus().getFailInfo().toString());
196 private Cmpv2CertificationModel verifyReturnCertChainAndTrustStore(
197 PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
198 throws CertificateParsingException, CmpClientException, IOException {
199 LOG.info("Verifying certificates returned as part of CertResponse.");
200 final CMPCertificate cmpCertificate =
201 certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
202 final Optional<X509Certificate> leafCertificate =
203 getCertFromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
204 if (leafCertificate.isPresent()) {
205 return verifyAndReturnCertChainAndTrustSTore(
206 respPkiMessage, certRepMessage, leafCertificate.get());
208 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
211 private CertResponse getCertificateResponseContainingNewCertificate(
212 CertRepMessage certRepMessage) {
213 return certRepMessage.getResponse()[0];
217 * Validate inputs for Certificate Creation.
219 * @param csrModel Certificate Signing Request model. Must not be {@code null}.
220 * @param server CMPv2 Server. Must not be {@code null}.
221 * @throws IllegalArgumentException if Before Date is set after the After Date.
223 private static void validate(
224 final CsrModel csrModel,
225 final Cmpv2Server server,
226 final CloseableHttpClient httpClient,
227 final Date notBefore,
228 final Date notAfter) {
230 String caName = CmpUtil.isNullOrEmpty(server.getCaName()) ? server.getCaName() : DEFAULT_CA_NAME;
231 String profile = server.getCaMode() != null ? server.getCaMode().getProfile() : DEFAULT_PROFILE;
233 "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, profile);
235 CmpUtil.notNull(csrModel, "CsrModel Instance");
236 CmpUtil.notNull(csrModel.getSubjectData(), "Subject DN");
237 CmpUtil.notNull(csrModel.getPrivateKey(), "Subject private key");
238 CmpUtil.notNull(csrModel.getPublicKey(), "Subject public key");
239 CmpUtil.notNull(server.getIssuerDN(), "Issuer DN");
240 CmpUtil.notNull(server.getUrl(), "External CA URL");
241 CmpUtil.notNull(server.getAuthentication().getIak(), "IAK/RV Password");
242 CmpUtil.notNull(httpClient, "Closeable Http Client");
244 if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
245 throw new IllegalArgumentException("Before Date is set after the After Date");
249 private Cmpv2CertificationModel retrieveCertificates(
250 CsrModel csrModel, Cmpv2Server server, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
251 throws CmpClientException {
252 final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, server.getUrl(), server.getCaName());
254 final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
255 LOG.info("Received response from Server");
256 checkIfCmpResponseContainsError(respPkiMessage);
257 checkCmpResponse(respPkiMessage, csrModel.getPublicKey(), server.getAuthentication().getIak());
258 return checkCmpCertRepMessage(respPkiMessage);
259 } catch (IllegalArgumentException iae) {
260 CmpClientException cmpClientException =
261 new CmpClientException(
262 "Error encountered while processing response from CA server ", iae);
263 LOG.error("Error encountered while processing response from CA server ", iae);
264 throw cmpClientException;