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 java.io.ByteArrayInputStream;
26 import java.io.IOException;
27 import java.security.InvalidAlgorithmParameterException;
28 import java.security.NoSuchAlgorithmException;
29 import java.security.NoSuchProviderException;
30 import java.security.cert.CertPath;
31 import java.security.cert.CertPathValidator;
32 import java.security.cert.CertPathValidatorException;
33 import java.security.cert.Certificate;
34 import java.security.cert.CertificateException;
35 import java.security.cert.CertificateFactory;
36 import java.security.cert.CertificateParsingException;
37 import java.security.cert.PKIXCertPathChecker;
38 import java.security.cert.PKIXCertPathValidatorResult;
39 import java.security.cert.PKIXParameters;
40 import java.security.cert.TrustAnchor;
41 import java.security.cert.X509Certificate;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.Date;
45 import java.util.HashMap;
46 import java.util.List;
48 import java.util.Objects;
49 import java.util.Optional;
50 import org.bouncycastle.asn1.cmp.CMPCertificate;
51 import org.bouncycastle.asn1.cmp.CertRepMessage;
52 import org.bouncycastle.asn1.cmp.ErrorMsgContent;
53 import org.bouncycastle.asn1.cmp.PKIBody;
54 import org.bouncycastle.asn1.cmp.PKIMessage;
55 import org.bouncycastle.asn1.x500.X500Name;
56 import org.bouncycastle.jce.provider.BouncyCastleProvider;
57 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
58 import org.onap.oom.certservice.cmpv2client.exceptions.CmpServerException;
59 import org.onap.oom.certservice.cmpv2client.model.Cmpv2CertificationModel;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
63 public final class CmpResponseHelper {
65 private static final Logger LOG = LoggerFactory.getLogger(CmpResponseHelper.class);
67 private CmpResponseHelper() {
70 static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage) {
71 LOG.info("Response type: {} ", respPkiMessage.getBody().getType());
72 if (respPkiMessage.getBody().getType() == PKIBody.TYPE_ERROR) {
73 final ErrorMsgContent errorMsgContent =
74 (ErrorMsgContent) respPkiMessage.getBody().getContent();
75 String text = errorMsgContent.getPKIStatusInfo().getStatusString().getStringAt(0).getString();
76 LOG.error("Error in the PkiMessage response: {} ", text);
77 throw new CmpServerException(Optional.ofNullable(text).orElse("N/A"));
83 * Puts together certChain and Trust store and verifies the certChain
85 * @param respPkiMessage PKIMessage that may contain extra certs used for certchain
86 * @param certRepMessage CertRepMessage that should contain rootCA for certchain
87 * @param leafCertificate certificate returned from our original Cert Request
88 * @return model for certification containing certificate chain and trusted certificates
89 * @throws CertificateParsingException thrown if error occurs while parsing certificate
90 * @throws IOException thrown if IOException occurs while parsing certificate
91 * @throws CmpClientException thrown if error occurs during the verification of the certChain
93 static Cmpv2CertificationModel verifyAndReturnCertChainAndTrustSTore(
94 PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCertificate)
95 throws CertificateParsingException, IOException, CmpClientException {
96 Map<X500Name, X509Certificate> certificates = mapAllCertificates(respPkiMessage, certRepMessage);
97 return extractCertificationModel(certificates, leafCertificate);
100 private static Map<X500Name, X509Certificate> mapAllCertificates(
101 PKIMessage respPkiMessage, CertRepMessage certRepMessage
103 throws IOException, CertificateParsingException, CmpClientException {
105 Map<X500Name, X509Certificate> certificates = new HashMap<>();
107 CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts();
108 certificates.putAll(mapCertificates(extraCerts));
110 CMPCertificate[] caPubsCerts = certRepMessage.getCaPubs();
111 certificates.putAll(mapCertificates(caPubsCerts));
116 private static Map<X500Name, X509Certificate> mapCertificates(
117 CMPCertificate[] cmpCertificates)
118 throws CertificateParsingException, CmpClientException, IOException {
120 Map<X500Name, X509Certificate> certificates = new HashMap<>();
121 if (cmpCertificates != null) {
122 for (CMPCertificate certificate : cmpCertificates) {
123 getCertFromByteArray(certificate.getEncoded(), X509Certificate.class)
124 .ifPresent(x509Certificate ->
125 certificates.put(extractSubjectDn(x509Certificate), x509Certificate)
133 private static Cmpv2CertificationModel extractCertificationModel(
134 Map<X500Name, X509Certificate> certificates, X509Certificate leafCertificate
136 throws CmpClientException {
137 List<X509Certificate> certificateChain = new ArrayList<>();
138 X509Certificate previousCertificateInChain;
139 X509Certificate nextCertificateInChain = leafCertificate;
141 certificateChain.add(nextCertificateInChain);
142 certificates.remove(extractSubjectDn(nextCertificateInChain));
143 previousCertificateInChain = nextCertificateInChain;
144 nextCertificateInChain = certificates.get(extractIssuerDn(nextCertificateInChain));
145 verify(previousCertificateInChain, nextCertificateInChain, null);
147 while (!isSelfSign(nextCertificateInChain));
148 List<X509Certificate> trustedCertificates = new ArrayList<>(certificates.values());
150 return new Cmpv2CertificationModel(certificateChain, trustedCertificates);
153 private static boolean isSelfSign(X509Certificate certificate) {
154 return extractIssuerDn(certificate).equals(extractSubjectDn(certificate));
157 private static X500Name extractIssuerDn(X509Certificate x509Certificate) {
158 return X500Name.getInstance(x509Certificate.getIssuerDN());
161 private static X500Name extractSubjectDn(X509Certificate x509Certificate) {
162 return X500Name.getInstance(x509Certificate.getSubjectDN());
167 * Check the certificate with CA certificate.
169 * @param certificate X.509 certificate to verify. May not be null.
170 * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a Collection with
172 * @param date Date to verify at, or null to use current time.
173 * @param pkixCertPathCheckers optional PKIXCertPathChecker implementations to use during cert path validation
174 * @throws CmpClientException if certificate could not be validated
176 private static void verify(
177 X509Certificate certificate,
178 X509Certificate caCertChain,
180 PKIXCertPathChecker... pkixCertPathCheckers)
181 throws CmpClientException {
183 verifyCertificates(certificate, caCertChain, date, pkixCertPathCheckers);
184 } catch (CertPathValidatorException cpve) {
185 CmpClientException cmpClientException =
186 new CmpClientException(
187 "Invalid certificate or certificate not issued by specified CA: ", cpve);
188 LOG.error("Invalid certificate or certificate not issued by specified CA: ", cpve);
189 throw cmpClientException;
190 } catch (CertificateException ce) {
191 CmpClientException cmpClientException =
192 new CmpClientException("Something was wrong with the supplied certificate", ce);
193 LOG.error("Something was wrong with the supplied certificate", ce);
194 throw cmpClientException;
195 } catch (NoSuchProviderException nspe) {
196 CmpClientException cmpClientException =
197 new CmpClientException("BouncyCastle provider not found.", nspe);
198 LOG.error("BouncyCastle provider not found.", nspe);
199 throw cmpClientException;
200 } catch (NoSuchAlgorithmException nsae) {
201 CmpClientException cmpClientException =
202 new CmpClientException("Algorithm PKIX was not found.", nsae);
203 LOG.error("Algorithm PKIX was not found.", nsae);
204 throw cmpClientException;
205 } catch (InvalidAlgorithmParameterException iape) {
206 CmpClientException cmpClientException =
207 new CmpClientException(
208 "Either ca certificate chain was empty,"
209 + " or the certificate was on an inappropriate type for a PKIX path checker.",
212 "Either ca certificate chain was empty, "
213 + "or the certificate was on an inappropriate type for a PKIX path checker.",
215 throw cmpClientException;
219 private static void verifyCertificates(
220 X509Certificate certificate,
221 X509Certificate caCertChain,
223 PKIXCertPathChecker[] pkixCertPathCheckers)
224 throws CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException,
225 NoSuchAlgorithmException, CertPathValidatorException {
226 if (caCertChain == null) {
227 final String noRootCaCertificateMessage = "Server response does not contain proper root CA certificate";
228 throw new CertificateException(noRootCaCertificateMessage);
231 "Verifying certificate {} as part of cert chain with certificate {}",
232 certificate.getSubjectDN().getName(),
233 caCertChain.getSubjectDN().getName());
234 CertPath cp = getCertPath(certificate);
235 PKIXParameters params = getPkixParameters(caCertChain, date, pkixCertPathCheckers);
236 CertPathValidator cpv =
237 CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
238 PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
239 if (LOG.isDebugEnabled()) {
240 LOG.debug("Certificate verify result:{} ", result);
244 private static PKIXParameters getPkixParameters(
245 X509Certificate caCertChain, Date date, PKIXCertPathChecker[] pkixCertPathCheckers)
246 throws InvalidAlgorithmParameterException {
247 TrustAnchor anchor = new TrustAnchor(caCertChain, null);
248 PKIXParameters params = new PKIXParameters(Collections.singleton(anchor));
249 for (final PKIXCertPathChecker pkixCertPathChecker : pkixCertPathCheckers) {
250 params.addCertPathChecker(pkixCertPathChecker);
252 params.setRevocationEnabled(false);
253 params.setDate(date);
257 private static CertPath getCertPath(X509Certificate certificate)
258 throws CertificateException, NoSuchProviderException {
259 ArrayList<X509Certificate> certlist = new ArrayList<>();
260 certlist.add(certificate);
261 return CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME)
262 .generateCertPath(certlist);
266 * Returns a CertificateFactory that can be used to create certificates from byte arrays and such.
268 * @param provider Security provider that should be used to create certificates, default BC is null is passed.
269 * @return CertificateFactory for creating certificate
271 private static CertificateFactory getCertificateFactory(final String provider)
272 throws CmpClientException {
273 LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider);
275 prov = Objects.requireNonNullElse(provider, BouncyCastleProvider.PROVIDER_NAME);
277 return CertificateFactory.getInstance("X.509", prov);
278 } catch (NoSuchProviderException nspe) {
279 CmpClientException cmpClientException = new CmpClientException("NoSuchProvider: ", nspe);
280 LOG.error("NoSuchProvider: ", nspe);
281 throw cmpClientException;
282 } catch (CertificateException ce) {
283 CmpClientException cmpClientException = new CmpClientException("CertificateException: ", ce);
284 LOG.error("CertificateException: ", ce);
285 throw cmpClientException;
290 * @param cert byte array that contains certificate
291 * @param returnType the type of Certificate to be returned, for example X509Certificate.class. Certificate.class
292 * can be used if certificate type is unknown.
293 * @throws CertificateParsingException if the byte array does not contain a proper certificate.
295 static <T extends Certificate> Optional<X509Certificate> getCertFromByteArray(
296 byte[] cert, Class<T> returnType) throws CertificateParsingException, CmpClientException {
297 LOG.debug("Retrieving certificate of type {} from byte array.", returnType);
298 String prov = BouncyCastleProvider.PROVIDER_NAME;
300 if (returnType.equals(X509Certificate.class)) {
301 return parseX509Certificate(prov, cert);
303 LOG.debug("Certificate of type {} was skipped, because type of certificate is not 'X509Certificate'.",
305 return Optional.empty();
311 * Parse a X509Certificate from an array of bytes
313 * @param provider a provider name
314 * @param cert a byte array containing an encoded certificate
315 * @return a decoded X509Certificate
316 * @throws CertificateParsingException if the byte array wasn't valid, or contained a certificate other than an X509
319 private static Optional<X509Certificate> parseX509Certificate(String provider, byte[] cert)
320 throws CertificateParsingException, CmpClientException {
321 LOG.debug("Parsing X509Certificate from bytes with provider {}", provider);
322 final CertificateFactory cf = getCertificateFactory(provider);
323 X509Certificate result;
325 result = (X509Certificate) Objects.requireNonNull(cf).generateCertificate(new ByteArrayInputStream(cert));
326 return Optional.ofNullable(result);
327 } catch (CertificateException ce) {
328 throw new CertificateParsingException("Could not parse byte array as X509Certificate ", ce);