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 String iak = server.getAuthentication().getIak();
89 final PkiMessageProtection pkiMessageProtection = new PasswordBasedProtection(iak);
90 final CreateCertRequest certRequest =
91 CmpMessageBuilder.of(CreateCertRequest::new)
92 .with(CreateCertRequest::setIssuerDn, server.getIssuerDN())
93 .with(CreateCertRequest::setSubjectDn, csrModel.getSubjectData())
94 .with(CreateCertRequest::setSansArray, csrModel.getSans())
95 .with(CreateCertRequest::setSubjectKeyPair, keyPair)
96 .with(CreateCertRequest::setNotBefore, notBefore)
97 .with(CreateCertRequest::setNotAfter, notAfter)
98 .with(CreateCertRequest::setSenderKid, server.getAuthentication().getRv())
99 .with(CreateCertRequest::setProtection, pkiMessageProtection)
102 final PKIMessage pkiMessage = certRequest.generateCertReq();
103 Cmpv2HttpClient cmpv2HttpClient = new Cmpv2HttpClient(httpClient);
104 return retrieveCertificates(csrModel, server, pkiMessage, cmpv2HttpClient);
108 public Cmpv2CertificationModel createCertificate(CsrModel csrModel, Cmpv2Server server)
109 throws CmpClientException {
110 return createCertificate(csrModel, server, null, null);
113 private void checkCmpResponse(
114 final PKIMessage respPkiMessage, final PublicKey publicKey, final String initAuthPassword)
115 throws CmpClientException {
116 final PKIHeader header = respPkiMessage.getHeader();
117 final AlgorithmIdentifier protectionAlgo = header.getProtectionAlg();
118 verifySignatureWithPublicKey(respPkiMessage, publicKey);
119 if (isPasswordBasedMacAlgorithm(protectionAlgo)) {
120 LOG.info("CMP response is protected by Password Base Mac Algorithm. Attempt to verify protection");
121 verifyPasswordBasedMacProtection(respPkiMessage, initAuthPassword, header, protectionAlgo);
125 private boolean isPasswordBasedMacAlgorithm(AlgorithmIdentifier protectionAlgo) throws CmpClientException {
126 if (Objects.isNull(protectionAlgo)) {
127 LOG.error("CMP response does not contain Protection Algorithm field");
128 throw new CmpClientException("CMP response does not contain Protection Algorithm field");
130 return PASSWORD_BASED_MAC.equals(protectionAlgo.getAlgorithm());
133 private void verifySignatureWithPublicKey(PKIMessage respPkiMessage, PublicKey publicKey)
134 throws CmpClientException {
135 if (Objects.nonNull(publicKey)) {
136 LOG.debug("Verifying signature of the response.");
137 verifySignature(respPkiMessage, publicKey);
139 LOG.error("Public Key is not available, therefore cannot verify signature");
140 throw new CmpClientException(
141 "Public Key is not available, therefore cannot verify signature");
145 private void verifyPasswordBasedMacProtection(PKIMessage respPkiMessage, String initAuthPassword,
146 PKIHeader header, AlgorithmIdentifier protectionAlgo)
147 throws CmpClientException {
148 LOG.debug("Verifying PasswordBased Protection of the Response.");
149 verifyPasswordBasedProtection(respPkiMessage, initAuthPassword, protectionAlgo);
150 checkImplicitConfirm(header);
153 private Cmpv2CertificationModel checkCmpCertRepMessage(final PKIMessage respPkiMessage)
154 throws CmpClientException {
155 final PKIBody pkiBody = respPkiMessage.getBody();
156 if (Objects.nonNull(pkiBody) && pkiBody.getContent() instanceof CertRepMessage) {
157 final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent();
158 if (Objects.nonNull(certRepMessage)) {
160 CertResponse certResponse = getCertificateResponseContainingNewCertificate(certRepMessage);
161 checkServerResponse(certResponse);
162 return verifyReturnCertChainAndTrustStore(respPkiMessage, certRepMessage, certResponse);
163 } catch (IOException | CertificateParsingException ex) {
164 CmpClientException cmpClientException =
165 new CmpClientException(
166 "Exception occurred while retrieving Certificates from response", ex);
167 LOG.error("Exception occurred while retrieving Certificates from response", ex);
168 throw cmpClientException;
171 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
174 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
177 private void checkServerResponse(CertResponse certResponse) {
178 if (certResponse.getStatus() != null && certResponse.getStatus().getStatus() != null) {
179 logServerResponse(certResponse);
180 if (certResponse.getStatus().getStatus().intValue() == PkiStatus.REJECTED.getCode()) {
181 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
182 throw new CmpServerException(Optional.ofNullable(serverMessage).orElse("N/A"));
187 private void logServerResponse(CertResponse certResponse) {
188 if (LOG.isInfoEnabled()) {
189 LOG.info("Response status code: {}", certResponse.getStatus().getStatus());
191 if (certResponse.getStatus().getStatusString() != null) {
192 String serverMessage = certResponse.getStatus().getStatusString().getStringAt(0).getString();
193 LOG.warn("Response status text: {}", serverMessage);
195 if (LOG.isWarnEnabled() && certResponse.getStatus().getFailInfo() != null) {
196 LOG.warn("Response fail info: {}", certResponse.getStatus().getFailInfo());
200 private Cmpv2CertificationModel verifyReturnCertChainAndTrustStore(
201 PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
202 throws CertificateParsingException, CmpClientException, IOException {
203 LOG.info("Verifying certificates returned as part of CertResponse.");
204 final CMPCertificate cmpCertificate =
205 certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
206 final Optional<X509Certificate> leafCertificate =
207 getCertFromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
208 if (leafCertificate.isPresent()) {
209 return verifyAndReturnCertChainAndTrustSTore(
210 respPkiMessage, certRepMessage, leafCertificate.get());
212 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
215 private CertResponse getCertificateResponseContainingNewCertificate(
216 CertRepMessage certRepMessage) {
217 return certRepMessage.getResponse()[0];
221 * Validate inputs for Certificate Creation.
223 * @param csrModel Certificate Signing Request model. Must not be {@code null}.
224 * @param server CMPv2 Server. Must not be {@code null}.
225 * @throws IllegalArgumentException if Before Date is set after the After Date.
227 private static void validate(
228 final CsrModel csrModel,
229 final Cmpv2Server server,
230 final CloseableHttpClient httpClient,
231 final Date notBefore,
232 final Date notAfter) {
234 String caName = CmpUtil.isNullOrEmpty(server.getCaName()) ? server.getCaName() : DEFAULT_CA_NAME;
235 String profile = server.getCaMode() != null ? server.getCaMode().getProfile() : DEFAULT_PROFILE;
237 "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, profile);
239 CmpUtil.notNull(csrModel, "CsrModel Instance");
240 CmpUtil.notNull(csrModel.getSubjectData(), "Subject DN");
241 CmpUtil.notNull(csrModel.getPrivateKey(), "Subject private key");
242 CmpUtil.notNull(csrModel.getPublicKey(), "Subject public key");
243 CmpUtil.notNull(server.getIssuerDN(), "Issuer DN");
244 CmpUtil.notNull(server.getUrl(), "External CA URL");
245 CmpUtil.notNull(server.getAuthentication().getIak(), "IAK/RV Password");
246 CmpUtil.notNull(httpClient, "Closeable Http Client");
248 if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
249 throw new IllegalArgumentException("Before Date is set after the After Date");
253 private Cmpv2CertificationModel retrieveCertificates(
254 CsrModel csrModel, Cmpv2Server server, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
255 throws CmpClientException {
256 final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, server.getUrl(), server.getCaName());
258 final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
259 LOG.info("Received response from Server");
260 checkIfCmpResponseContainsError(respPkiMessage);
261 checkCmpResponse(respPkiMessage, csrModel.getPublicKey(), server.getAuthentication().getIak());
262 return checkCmpCertRepMessage(respPkiMessage);
263 } catch (IllegalArgumentException iae) {
264 CmpClientException cmpClientException =
265 new CmpClientException(
266 "Error encountered while processing response from CA server ", iae);
267 LOG.error("Error encountered while processing response from CA server ", iae);
268 throw cmpClientException;