2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 Nordix Foundation.
4 * Copyright (C) 2021 Nokia.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.oom.certservice.cmpv2client.impl;
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.security.InvalidAlgorithmParameterException;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.NoSuchProviderException;
29 import java.security.cert.CertPath;
30 import java.security.cert.CertPathValidator;
31 import java.security.cert.CertPathValidatorException;
32 import java.security.cert.Certificate;
33 import java.security.cert.CertificateException;
34 import java.security.cert.CertificateFactory;
35 import java.security.cert.CertificateParsingException;
36 import java.security.cert.PKIXCertPathChecker;
37 import java.security.cert.PKIXCertPathValidatorResult;
38 import java.security.cert.PKIXParameters;
39 import java.security.cert.TrustAnchor;
40 import java.security.cert.X509Certificate;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.Date;
44 import java.util.HashMap;
45 import java.util.List;
47 import java.util.Objects;
48 import java.util.Optional;
49 import org.bouncycastle.asn1.cmp.CMPCertificate;
50 import org.bouncycastle.asn1.cmp.CertRepMessage;
51 import org.bouncycastle.asn1.cmp.ErrorMsgContent;
52 import org.bouncycastle.asn1.cmp.PKIBody;
53 import org.bouncycastle.asn1.cmp.PKIMessage;
54 import org.bouncycastle.asn1.x500.X500Name;
55 import org.bouncycastle.jce.provider.BouncyCastleProvider;
56 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
57 import org.onap.oom.certservice.cmpv2client.exceptions.CmpServerException;
58 import org.onap.oom.certservice.cmpv2client.model.Cmpv2CertificationModel;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
62 public final class CmpResponseHelper {
64 private static final Logger LOG = LoggerFactory.getLogger(CmpResponseHelper.class);
65 private static final Map<Integer, String> RESPONSE_TYPE_TO_STRING = Map.of(
66 PKIBody.TYPE_INIT_REP, "INIT_REP",
67 PKIBody.TYPE_CERT_REP, "CERT_REP",
68 PKIBody.TYPE_KEY_UPDATE_REP, "KEY_UPDATE_REP");
70 private CmpResponseHelper() {
73 static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage) {
74 final int responseType = respPkiMessage.getBody().getType();
75 final String responseTypeName = RESPONSE_TYPE_TO_STRING.getOrDefault(responseType, Integer.toString(responseType));
76 LOG.info("Response type is: {} ", responseTypeName);
78 if (responseType == PKIBody.TYPE_ERROR) {
79 final ErrorMsgContent errorMsgContent =
80 (ErrorMsgContent) respPkiMessage.getBody().getContent();
81 String text = errorMsgContent.getPKIStatusInfo().getStatusString().getStringAt(0).getString();
82 LOG.error("Error in the PkiMessage response: {} ", text);
83 throw new CmpServerException(Optional.ofNullable(text).orElse("N/A"));
89 * Puts together certChain and Trust store and verifies the certChain
91 * @param respPkiMessage PKIMessage that may contain extra certs used for certchain
92 * @param certRepMessage CertRepMessage that should contain rootCA for certchain
93 * @param leafCertificate certificate returned from our original Cert Request
94 * @return model for certification containing certificate chain and trusted certificates
95 * @throws CertificateParsingException thrown if error occurs while parsing certificate
96 * @throws IOException thrown if IOException occurs while parsing certificate
97 * @throws CmpClientException thrown if error occurs during the verification of the certChain
99 static Cmpv2CertificationModel verifyAndReturnCertChainAndTrustSTore(
100 PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCertificate)
101 throws CertificateParsingException, IOException, CmpClientException {
102 Map<X500Name, X509Certificate> certificates = mapAllCertificates(respPkiMessage, certRepMessage);
103 return extractCertificationModel(certificates, leafCertificate);
106 private static Map<X500Name, X509Certificate> mapAllCertificates(
107 PKIMessage respPkiMessage, CertRepMessage certRepMessage
109 throws IOException, CertificateParsingException, CmpClientException {
111 Map<X500Name, X509Certificate> certificates = new HashMap<>();
113 CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts();
114 certificates.putAll(mapCertificates(extraCerts));
116 CMPCertificate[] caPubsCerts = certRepMessage.getCaPubs();
117 certificates.putAll(mapCertificates(caPubsCerts));
122 private static Map<X500Name, X509Certificate> mapCertificates(
123 CMPCertificate[] cmpCertificates)
124 throws CertificateParsingException, CmpClientException, IOException {
126 Map<X500Name, X509Certificate> certificates = new HashMap<>();
127 if (cmpCertificates != null) {
128 for (CMPCertificate certificate : cmpCertificates) {
129 getCertFromByteArray(certificate.getEncoded(), X509Certificate.class)
130 .ifPresent(x509Certificate ->
131 certificates.put(extractSubjectDn(x509Certificate), x509Certificate)
139 private static Cmpv2CertificationModel extractCertificationModel(
140 Map<X500Name, X509Certificate> certificates, X509Certificate leafCertificate
142 throws CmpClientException {
143 List<X509Certificate> certificateChain = new ArrayList<>();
144 X509Certificate previousCertificateInChain;
145 X509Certificate nextCertificateInChain = leafCertificate;
147 certificateChain.add(nextCertificateInChain);
148 certificates.remove(extractSubjectDn(nextCertificateInChain));
149 previousCertificateInChain = nextCertificateInChain;
150 nextCertificateInChain = certificates.get(extractIssuerDn(nextCertificateInChain));
151 verify(previousCertificateInChain, nextCertificateInChain, null);
153 while (!isSelfSign(nextCertificateInChain));
154 List<X509Certificate> trustedCertificates = new ArrayList<>(certificates.values());
156 return new Cmpv2CertificationModel(certificateChain, trustedCertificates);
159 private static boolean isSelfSign(X509Certificate certificate) {
160 return extractIssuerDn(certificate).equals(extractSubjectDn(certificate));
163 private static X500Name extractIssuerDn(X509Certificate x509Certificate) {
164 return X500Name.getInstance(x509Certificate.getIssuerDN());
167 private static X500Name extractSubjectDn(X509Certificate x509Certificate) {
168 return X500Name.getInstance(x509Certificate.getSubjectDN());
173 * Check the certificate with CA certificate.
175 * @param certificate X.509 certificate to verify. May not be null.
176 * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a Collection with
178 * @param date Date to verify at, or null to use current time.
179 * @param pkixCertPathCheckers optional PKIXCertPathChecker implementations to use during cert path validation
180 * @throws CmpClientException if certificate could not be validated
182 private static void verify(
183 X509Certificate certificate,
184 X509Certificate caCertChain,
186 PKIXCertPathChecker... pkixCertPathCheckers)
187 throws CmpClientException {
189 verifyCertificates(certificate, caCertChain, date, pkixCertPathCheckers);
190 } catch (CertPathValidatorException cpve) {
191 CmpClientException cmpClientException =
192 new CmpClientException(
193 "Invalid certificate or certificate not issued by specified CA: ", cpve);
194 LOG.error("Invalid certificate or certificate not issued by specified CA: ", cpve);
195 throw cmpClientException;
196 } catch (CertificateException ce) {
197 CmpClientException cmpClientException =
198 new CmpClientException("Something was wrong with the supplied certificate", ce);
199 LOG.error("Something was wrong with the supplied certificate", ce);
200 throw cmpClientException;
201 } catch (NoSuchProviderException nspe) {
202 CmpClientException cmpClientException =
203 new CmpClientException("BouncyCastle provider not found.", nspe);
204 LOG.error("BouncyCastle provider not found.", nspe);
205 throw cmpClientException;
206 } catch (NoSuchAlgorithmException nsae) {
207 CmpClientException cmpClientException =
208 new CmpClientException("Algorithm PKIX was not found.", nsae);
209 LOG.error("Algorithm PKIX was not found.", nsae);
210 throw cmpClientException;
211 } catch (InvalidAlgorithmParameterException iape) {
212 CmpClientException cmpClientException =
213 new CmpClientException(
214 "Either ca certificate chain was empty,"
215 + " or the certificate was on an inappropriate type for a PKIX path checker.",
218 "Either ca certificate chain was empty, "
219 + "or the certificate was on an inappropriate type for a PKIX path checker.",
221 throw cmpClientException;
225 private static void verifyCertificates(
226 X509Certificate certificate,
227 X509Certificate caCertChain,
229 PKIXCertPathChecker[] pkixCertPathCheckers)
230 throws CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException,
231 NoSuchAlgorithmException, CertPathValidatorException {
232 if (caCertChain == null) {
233 final String noRootCaCertificateMessage = "Server response does not contain proper root CA certificate";
234 throw new CertificateException(noRootCaCertificateMessage);
237 "Verifying certificate {} as part of cert chain with certificate {}",
238 certificate.getSubjectDN().getName(),
239 caCertChain.getSubjectDN().getName());
240 CertPath cp = getCertPath(certificate);
241 PKIXParameters params = getPkixParameters(caCertChain, date, pkixCertPathCheckers);
242 CertPathValidator cpv =
243 CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
244 PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
245 if (LOG.isDebugEnabled()) {
246 LOG.debug("Certificate verify result:{} ", result);
250 private static PKIXParameters getPkixParameters(
251 X509Certificate caCertChain, Date date, PKIXCertPathChecker[] pkixCertPathCheckers)
252 throws InvalidAlgorithmParameterException {
253 TrustAnchor anchor = new TrustAnchor(caCertChain, null);
254 PKIXParameters params = new PKIXParameters(Collections.singleton(anchor));
255 for (final PKIXCertPathChecker pkixCertPathChecker : pkixCertPathCheckers) {
256 params.addCertPathChecker(pkixCertPathChecker);
258 params.setRevocationEnabled(false);
259 params.setDate(date);
263 private static CertPath getCertPath(X509Certificate certificate)
264 throws CertificateException, NoSuchProviderException {
265 ArrayList<X509Certificate> certlist = new ArrayList<>();
266 certlist.add(certificate);
267 return CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME)
268 .generateCertPath(certlist);
272 * Returns a CertificateFactory that can be used to create certificates from byte arrays and such.
274 * @param provider Security provider that should be used to create certificates, default BC is null is passed.
275 * @return CertificateFactory for creating certificate
277 private static CertificateFactory getCertificateFactory(final String provider)
278 throws CmpClientException {
279 LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider);
281 prov = Objects.requireNonNullElse(provider, BouncyCastleProvider.PROVIDER_NAME);
283 return CertificateFactory.getInstance("X.509", prov);
284 } catch (NoSuchProviderException nspe) {
285 CmpClientException cmpClientException = new CmpClientException("NoSuchProvider: ", nspe);
286 LOG.error("NoSuchProvider: ", nspe);
287 throw cmpClientException;
288 } catch (CertificateException ce) {
289 CmpClientException cmpClientException = new CmpClientException("CertificateException: ", ce);
290 LOG.error("CertificateException: ", ce);
291 throw cmpClientException;
296 * @param cert byte array that contains certificate
297 * @param returnType the type of Certificate to be returned, for example X509Certificate.class. Certificate.class
298 * can be used if certificate type is unknown.
299 * @throws CertificateParsingException if the byte array does not contain a proper certificate.
301 static <T extends Certificate> Optional<X509Certificate> getCertFromByteArray(
302 byte[] cert, Class<T> returnType) throws CertificateParsingException, CmpClientException {
303 LOG.debug("Retrieving certificate of type {} from byte array.", returnType);
304 String prov = BouncyCastleProvider.PROVIDER_NAME;
306 if (returnType.equals(X509Certificate.class)) {
307 return parseX509Certificate(prov, cert);
309 LOG.debug("Certificate of type {} was skipped, because type of certificate is not 'X509Certificate'.",
311 return Optional.empty();
317 * Parse a X509Certificate from an array of bytes
319 * @param provider a provider name
320 * @param cert a byte array containing an encoded certificate
321 * @return a decoded X509Certificate
322 * @throws CertificateParsingException if the byte array wasn't valid, or contained a certificate other than an X509
325 private static Optional<X509Certificate> parseX509Certificate(String provider, byte[] cert)
326 throws CertificateParsingException, CmpClientException {
327 LOG.debug("Parsing X509Certificate from bytes with provider {}", provider);
328 final CertificateFactory cf = getCertificateFactory(provider);
329 X509Certificate result;
331 result = (X509Certificate) Objects.requireNonNull(cf).generateCertificate(new ByteArrayInputStream(cert));
332 return Optional.ofNullable(result);
333 } catch (CertificateException ce) {
334 throw new CertificateParsingException("Could not parse byte array as X509Certificate ", ce);