2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 Nordix Foundation.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.oom.certservice.cmpv2client.impl;
23 import java.io.ByteArrayInputStream;
24 import java.io.IOException;
25 import java.security.InvalidAlgorithmParameterException;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.NoSuchProviderException;
28 import java.security.cert.CertPath;
29 import java.security.cert.CertPathValidator;
30 import java.security.cert.CertPathValidatorException;
31 import java.security.cert.Certificate;
32 import java.security.cert.CertificateException;
33 import java.security.cert.CertificateFactory;
34 import java.security.cert.CertificateParsingException;
35 import java.security.cert.PKIXCertPathChecker;
36 import java.security.cert.PKIXCertPathValidatorResult;
37 import java.security.cert.PKIXParameters;
38 import java.security.cert.TrustAnchor;
39 import java.security.cert.X509Certificate;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.Date;
43 import java.util.HashMap;
44 import java.util.List;
46 import java.util.Objects;
47 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.PkiErrorException;
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);
66 private CmpResponseHelper() {
69 static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage)
70 throws CmpClientException {
71 if (respPkiMessage.getBody().getType() == PKIBody.TYPE_ERROR) {
72 final ErrorMsgContent errorMsgContent =
73 (ErrorMsgContent) respPkiMessage.getBody().getContent();
74 PkiErrorException pkiErrorException =
75 new PkiErrorException(
76 errorMsgContent.getPKIStatusInfo().getStatusString().getStringAt(0).getString());
77 CmpClientException cmpClientException =
78 new CmpClientException("Error in the PkiMessage response", pkiErrorException);
79 LOG.error("Error in the PkiMessage response: {} ", pkiErrorException.getMessage());
80 throw cmpClientException;
86 * Puts together certChain and Trust store and verifies the certChain
88 * @param respPkiMessage PKIMessage that may contain extra certs used for certchain
89 * @param certRepMessage CertRepMessage that should contain rootCA for certchain
90 * @param leafCertificate certificate returned from our original Cert Request
91 * @return model for certification containing certificate chain and trusted certificates
92 * @throws CertificateParsingException thrown if error occurs while parsing certificate
93 * @throws IOException thrown if IOException occurs while parsing certificate
94 * @throws CmpClientException thrown if error occurs during the verification of the certChain
96 static Cmpv2CertificationModel verifyAndReturnCertChainAndTrustSTore(
97 PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCertificate)
98 throws CertificateParsingException, IOException, CmpClientException {
99 Map<X500Name, X509Certificate> certificates = mapAllCertificates(respPkiMessage, certRepMessage);
100 return extractCertificationModel(certificates, leafCertificate);
103 private static Map<X500Name, X509Certificate> mapAllCertificates(
104 PKIMessage respPkiMessage, CertRepMessage certRepMessage
106 throws IOException, CertificateParsingException, CmpClientException {
108 Map<X500Name, X509Certificate> certificates = new HashMap<>();
110 CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts();
111 certificates.putAll(mapCertificates(extraCerts));
113 CMPCertificate[] caPubsCerts = certRepMessage.getCaPubs();
114 certificates.putAll(mapCertificates(caPubsCerts));
119 private static Map<X500Name, X509Certificate> mapCertificates(
120 CMPCertificate[] cmpCertificates)
121 throws CertificateParsingException, CmpClientException, IOException {
123 Map<X500Name, X509Certificate> certificates = new HashMap<>();
124 if (cmpCertificates != null) {
125 for (CMPCertificate certificate : cmpCertificates) {
126 getCertFromByteArray(certificate.getEncoded(), X509Certificate.class)
127 .ifPresent(x509Certificate ->
128 certificates.put(extractSubjectDn(x509Certificate), x509Certificate)
136 private static Cmpv2CertificationModel extractCertificationModel(
137 Map<X500Name, X509Certificate> certificates, X509Certificate leafCertificate
139 throws CmpClientException {
140 List<X509Certificate> certificateChain = new ArrayList<>();
141 X509Certificate previousCertificateInChain;
142 X509Certificate nextCertificateInChain = leafCertificate;
144 certificateChain.add(nextCertificateInChain);
145 certificates.remove(extractSubjectDn(nextCertificateInChain));
146 previousCertificateInChain = nextCertificateInChain;
147 nextCertificateInChain = certificates.get(extractIssuerDn(nextCertificateInChain));
148 verify(previousCertificateInChain, nextCertificateInChain, null);
150 while (!isSelfSign(nextCertificateInChain));
151 List<X509Certificate> trustedCertificates = new ArrayList<>(certificates.values());
153 return new Cmpv2CertificationModel(certificateChain, trustedCertificates);
156 private static boolean isSelfSign(X509Certificate certificate) {
157 return extractIssuerDn(certificate).equals(extractSubjectDn(certificate));
160 private static X500Name extractIssuerDn(X509Certificate x509Certificate) {
161 return X500Name.getInstance(x509Certificate.getIssuerDN());
164 private static X500Name extractSubjectDn(X509Certificate x509Certificate) {
165 return X500Name.getInstance(x509Certificate.getSubjectDN());
170 * Check the certificate with CA certificate.
172 * @param certificate X.509 certificate to verify. May not be null.
173 * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a
174 * Collection with null entries.
175 * @param date Date to verify at, or null to use current time.
176 * @param pkixCertPathCheckers optional PKIXCertPathChecker implementations to use during cert
178 * @throws CmpClientException if certificate could not be validated
180 private static void verify(
181 X509Certificate certificate,
182 X509Certificate caCertChain,
184 PKIXCertPathChecker... pkixCertPathCheckers)
185 throws CmpClientException {
187 verifyCertificates(certificate, caCertChain, date, pkixCertPathCheckers);
188 } catch (CertPathValidatorException cpve) {
189 CmpClientException cmpClientException =
190 new CmpClientException(
191 "Invalid certificate or certificate not issued by specified CA: ", cpve);
192 LOG.error("Invalid certificate or certificate not issued by specified CA: ", cpve);
193 throw cmpClientException;
194 } catch (CertificateException ce) {
195 CmpClientException cmpClientException =
196 new CmpClientException("Something was wrong with the supplied certificate", ce);
197 LOG.error("Something was wrong with the supplied certificate", ce);
198 throw cmpClientException;
199 } catch (NoSuchProviderException nspe) {
200 CmpClientException cmpClientException =
201 new CmpClientException("BouncyCastle provider not found.", nspe);
202 LOG.error("BouncyCastle provider not found.", nspe);
203 throw cmpClientException;
204 } catch (NoSuchAlgorithmException nsae) {
205 CmpClientException cmpClientException =
206 new CmpClientException("Algorithm PKIX was not found.", nsae);
207 LOG.error("Algorithm PKIX was not found.", nsae);
208 throw cmpClientException;
209 } catch (InvalidAlgorithmParameterException iape) {
210 CmpClientException cmpClientException =
211 new CmpClientException(
212 "Either ca certificate chain was empty,"
213 + " or the certificate was on an inappropriate type for a PKIX path checker.",
216 "Either ca certificate chain was empty, "
217 + "or the certificate was on an inappropriate type for a PKIX path checker.",
219 throw cmpClientException;
223 private static void verifyCertificates(
224 X509Certificate certificate,
225 X509Certificate caCertChain,
227 PKIXCertPathChecker[] pkixCertPathCheckers)
228 throws CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException,
229 NoSuchAlgorithmException, CertPathValidatorException {
230 if (caCertChain == null) {
231 final String noRootCaCertificateMessage = "Server response does not contain proper root CA certificate";
232 throw new CertificateException(noRootCaCertificateMessage);
235 "Verifying certificate {} as part of cert chain with certificate {}",
236 certificate.getSubjectDN().getName(),
237 caCertChain.getSubjectDN().getName());
238 CertPath cp = getCertPath(certificate);
239 PKIXParameters params = getPkixParameters(caCertChain, date, pkixCertPathCheckers);
240 CertPathValidator cpv =
241 CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
242 PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
243 if (LOG.isDebugEnabled()) {
244 LOG.debug("Certificate verify result:{} ", result);
248 private static PKIXParameters getPkixParameters(
249 X509Certificate caCertChain, Date date, PKIXCertPathChecker[] pkixCertPathCheckers)
250 throws InvalidAlgorithmParameterException {
251 TrustAnchor anchor = new TrustAnchor(caCertChain, null);
252 PKIXParameters params = new PKIXParameters(Collections.singleton(anchor));
253 for (final PKIXCertPathChecker pkixCertPathChecker : pkixCertPathCheckers) {
254 params.addCertPathChecker(pkixCertPathChecker);
256 params.setRevocationEnabled(false);
257 params.setDate(date);
261 private static CertPath getCertPath(X509Certificate certificate)
262 throws CertificateException, NoSuchProviderException {
263 ArrayList<X509Certificate> certlist = new ArrayList<>();
264 certlist.add(certificate);
265 return CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME)
266 .generateCertPath(certlist);
270 * Returns a CertificateFactory that can be used to create certificates from byte arrays and such.
272 * @param provider Security provider that should be used to create certificates, default BC is
274 * @return CertificateFactory for creating certificate
276 private static CertificateFactory getCertificateFactory(final String provider)
277 throws CmpClientException {
278 LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider);
280 prov = Objects.requireNonNullElse(provider, BouncyCastleProvider.PROVIDER_NAME);
282 return CertificateFactory.getInstance("X.509", prov);
283 } catch (NoSuchProviderException nspe) {
284 CmpClientException cmpClientException = new CmpClientException("NoSuchProvider: ", nspe);
285 LOG.error("NoSuchProvider: ", nspe);
286 throw cmpClientException;
287 } catch (CertificateException ce) {
288 CmpClientException cmpClientException = new CmpClientException("CertificateException: ", ce);
289 LOG.error("CertificateException: ", ce);
290 throw cmpClientException;
295 * @param cert byte array that contains certificate
296 * @param returnType the type of Certificate to be returned, for example X509Certificate.class.
297 * Certificate.class can be used if certificate type is unknown.
298 * @throws CertificateParsingException if the byte array does not contain a proper certificate.
300 static <T extends Certificate> Optional<X509Certificate> getCertFromByteArray(
301 byte[] cert, Class<T> returnType) throws CertificateParsingException, CmpClientException {
302 LOG.debug("Retrieving certificate of type {} from byte array.", returnType);
303 String prov = BouncyCastleProvider.PROVIDER_NAME;
305 if (returnType.equals(X509Certificate.class)) {
306 return parseX509Certificate(prov, cert);
308 LOG.debug("Certificate of type {} was skipped, because type of certificate is not 'X509Certificate'.", returnType);
309 return Optional.empty();
315 * Parse a X509Certificate from an array of bytes
317 * @param provider a provider name
318 * @param cert a byte array containing an encoded certificate
319 * @return a decoded X509Certificate
320 * @throws CertificateParsingException if the byte array wasn't valid, or contained a certificate
321 * other than an X509 Certificate.
323 private static Optional<X509Certificate> parseX509Certificate(String provider, byte[] cert)
324 throws CertificateParsingException, CmpClientException {
325 LOG.debug("Parsing X509Certificate from bytes with provider {}", provider);
326 final CertificateFactory cf = getCertificateFactory(provider);
327 X509Certificate result;
329 result = (X509Certificate) Objects.requireNonNull(cf).generateCertificate(new ByteArrayInputStream(cert));
330 return Optional.ofNullable(result);
331 } catch (CertificateException ce) {
332 throw new CertificateParsingException("Could not parse byte array as X509Certificate ", ce);