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.aaf.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.List;
44 import java.util.Objects;
45 import java.util.Optional;
47 import org.bouncycastle.asn1.cmp.CMPCertificate;
48 import org.bouncycastle.asn1.cmp.CertRepMessage;
49 import org.bouncycastle.asn1.cmp.ErrorMsgContent;
50 import org.bouncycastle.asn1.cmp.PKIBody;
51 import org.bouncycastle.asn1.cmp.PKIMessage;
52 import org.bouncycastle.jce.provider.BouncyCastleProvider;
53 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
54 import org.onap.aaf.certservice.cmpv2client.exceptions.PkiErrorException;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
58 public final class CmpResponseHelper {
60 private static final Logger LOG = LoggerFactory.getLogger(CmpResponseHelper.class);
62 private CmpResponseHelper() {
65 public static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage)
66 throws CmpClientException {
67 if (respPkiMessage.getBody().getType() == PKIBody.TYPE_ERROR) {
68 final ErrorMsgContent errorMsgContent =
69 (ErrorMsgContent) respPkiMessage.getBody().getContent();
70 PkiErrorException pkiErrorException =
71 new PkiErrorException(
72 errorMsgContent.getPKIStatusInfo().getStatusString().getStringAt(0).getString());
73 CmpClientException cmpClientException =
74 new CmpClientException("Error in the PkiMessage response", pkiErrorException);
75 LOG.error("Error in the PkiMessage response: {} ", pkiErrorException.getMessage());
76 throw cmpClientException;
81 * @param cert byte array that contains certificate
82 * @param returnType the type of Certificate to be returned, for example X509Certificate.class.
83 * Certificate.class can be used if certificate type is unknown.
84 * @throws CertificateParsingException if the byte array does not contain a proper certificate.
86 public static <T extends Certificate> Optional<X509Certificate> getCertfromByteArray(
87 byte[] cert, Class<T> returnType) throws CertificateParsingException, CmpClientException {
88 LOG.debug("Retrieving certificate of type {} from byte array.", returnType);
89 return getCertfromByteArray(cert, BouncyCastleProvider.PROVIDER_NAME, returnType);
93 * @param cert byte array that contains certificate
94 * @param provider provider used to generate certificate from bytes
95 * @param returnType the type of Certificate to be returned, for example X509Certificate.class.
96 * Certificate.class can be used if certificate type is unknown.
97 * @throws CertificateParsingException if the byte array does not contain a proper certificate.
99 public static <T extends Certificate> Optional<X509Certificate> getCertfromByteArray(
100 byte[] cert, String provider, Class<T> returnType)
101 throws CertificateParsingException, CmpClientException {
102 String prov = provider;
103 if (provider == null) {
104 prov = BouncyCastleProvider.PROVIDER_NAME;
107 if (returnType.equals(X509Certificate.class)) {
108 return parseX509Certificate(prov, cert);
110 return Optional.empty();
114 * Check the certificate with CA certificate.
116 * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a
117 * Collection with null entries.
118 * @throws CmpClientException if verification failed
120 public static void verify(List<X509Certificate> caCertChain) throws CmpClientException {
122 while (iterator < caCertChain.size()) {
123 verify(caCertChain.get(iterator - 1), caCertChain.get(iterator), null);
129 * Check the certificate with CA certificate.
131 * @param certificate X.509 certificate to verify. May not be null.
132 * @param caCertChain Collection of X509Certificates. May not be null, an empty list or a
133 * Collection with null entries.
134 * @param date Date to verify at, or null to use current time.
135 * @param pkixCertPathCheckers optional PKIXCertPathChecker implementations to use during cert
137 * @throws CmpClientException if certificate could not be validated
139 public static void verify(
140 X509Certificate certificate,
141 X509Certificate caCertChain,
143 PKIXCertPathChecker... pkixCertPathCheckers)
144 throws CmpClientException {
146 verifyCertificates(certificate, caCertChain, date, pkixCertPathCheckers);
147 } catch (CertPathValidatorException cpve) {
148 CmpClientException cmpClientException =
149 new CmpClientException(
150 "Invalid certificate or certificate not issued by specified CA: ", cpve);
151 LOG.error("Invalid certificate or certificate not issued by specified CA: ", cpve);
152 throw cmpClientException;
153 } catch (CertificateException ce) {
154 CmpClientException cmpClientException =
155 new CmpClientException("Something was wrong with the supplied certificate", ce);
156 LOG.error("Something was wrong with the supplied certificate", ce);
157 throw cmpClientException;
158 } catch (NoSuchProviderException nspe) {
159 CmpClientException cmpClientException =
160 new CmpClientException("BouncyCastle provider not found.", nspe);
161 LOG.error("BouncyCastle provider not found.", nspe);
162 throw cmpClientException;
163 } catch (NoSuchAlgorithmException nsae) {
164 CmpClientException cmpClientException =
165 new CmpClientException("Algorithm PKIX was not found.", nsae);
166 LOG.error("Algorithm PKIX was not found.", nsae);
167 throw cmpClientException;
168 } catch (InvalidAlgorithmParameterException iape) {
169 CmpClientException cmpClientException =
170 new CmpClientException(
171 "Either ca certificate chain was empty,"
172 + " or the certificate was on an inappropriate type for a PKIX path checker.",
175 "Either ca certificate chain was empty, "
176 + "or the certificate was on an inappropriate type for a PKIX path checker.",
178 throw cmpClientException;
182 public static void verifyCertificates(
183 X509Certificate certificate,
184 X509Certificate caCertChain,
186 PKIXCertPathChecker[] pkixCertPathCheckers)
187 throws CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException,
188 NoSuchAlgorithmException, CertPathValidatorException {
190 "Verifying certificate {} as part of cert chain with certificate {}",
191 certificate.getSubjectDN().getName(),
192 caCertChain.getSubjectDN().getName());
193 CertPath cp = getCertPath(certificate);
194 PKIXParameters params = getPkixParameters(caCertChain, date, pkixCertPathCheckers);
195 CertPathValidator cpv =
196 CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
197 PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
198 if (LOG.isDebugEnabled()) {
199 LOG.debug("Certificate verify result:{} ", result);
203 public static PKIXParameters getPkixParameters(
204 X509Certificate caCertChain, Date date, PKIXCertPathChecker[] pkixCertPathCheckers)
205 throws InvalidAlgorithmParameterException {
206 TrustAnchor anchor = new TrustAnchor(caCertChain, null);
207 PKIXParameters params = new PKIXParameters(Collections.singleton(anchor));
208 for (final PKIXCertPathChecker pkixCertPathChecker : pkixCertPathCheckers) {
209 params.addCertPathChecker(pkixCertPathChecker);
211 params.setRevocationEnabled(false);
212 params.setDate(date);
216 public static CertPath getCertPath(X509Certificate certificate)
217 throws CertificateException, NoSuchProviderException {
218 ArrayList<X509Certificate> certlist = new ArrayList<>();
219 certlist.add(certificate);
220 return CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME)
221 .generateCertPath(certlist);
225 * Parse a X509Certificate from an array of bytes
227 * @param provider a provider name
228 * @param cert a byte array containing an encoded certificate
229 * @return a decoded X509Certificate
230 * @throws CertificateParsingException if the byte array wasn't valid, or contained a certificate
231 * other than an X509 Certificate.
233 public static Optional<X509Certificate> parseX509Certificate(String provider, byte[] cert)
234 throws CertificateParsingException, CmpClientException {
235 LOG.debug("Parsing X509Certificate from bytes with provider {}", provider);
236 final CertificateFactory cf = getCertificateFactory(provider);
237 X509Certificate result;
241 Objects.requireNonNull(cf).generateCertificate(new ByteArrayInputStream(cert));
242 } catch (CertificateException ce) {
243 throw new CertificateParsingException("Could not parse byte array as X509Certificate ", ce);
245 if (result != null) {
246 return Optional.of(result);
248 throw new CertificateParsingException("Could not parse byte array as X509Certificate.");
253 * Returns a CertificateFactory that can be used to create certificates from byte arrays and such.
255 * @param provider Security provider that should be used to create certificates, default BC is
257 * @return CertificateFactory for creating certificate
259 public static CertificateFactory getCertificateFactory(final String provider)
260 throws CmpClientException {
261 LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider);
263 prov = Objects.requireNonNullElse(provider, BouncyCastleProvider.PROVIDER_NAME);
265 return CertificateFactory.getInstance("X.509", prov);
266 } catch (NoSuchProviderException nspe) {
267 CmpClientException cmpClientException = new CmpClientException("NoSuchProvider: ", nspe);
268 LOG.error("NoSuchProvider: ", nspe);
269 throw cmpClientException;
270 } catch (CertificateException ce) {
271 CmpClientException cmpClientException = new CmpClientException("CertificateException: ", ce);
272 LOG.error("CertificateException: ", ce);
273 throw cmpClientException;
278 * puts together certChain and Trust store and verifies the certChain
280 * @param respPkiMessage PKIMessage that may contain extra certs used for certchain
281 * @param certRepMessage CertRepMessage that should contain rootCA for certchain
282 * @param leafCertificate certificate returned from our original Cert Request
283 * @return list of two lists, CertChain and TrustStore
284 * @throws CertificateParsingException thrown if error occurs while parsing certificate
285 * @throws IOException thrown if IOException occurs while parsing certificate
286 * @throws CmpClientException thrown if error occurs during the verification of the certChain
288 public static List<List<X509Certificate>> verifyAndReturnCertChainAndTrustSTore(
289 PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCertificate)
290 throws CertificateParsingException, IOException, CmpClientException {
291 List<X509Certificate> certChain =
292 addExtraCertsToChain(respPkiMessage, certRepMessage, leafCertificate);
293 List<String> certNames = getNamesOfCerts(certChain);
294 LOG.debug("Verifying the following certificates in the cert chain: {}", certNames);
296 ArrayList<X509Certificate> trustStore = new ArrayList<>();
297 final int rootCaIndex = certChain.size() - 1;
298 trustStore.add(certChain.get(rootCaIndex));
299 certChain.remove(rootCaIndex);
300 List<List<X509Certificate>> listOfArray = new ArrayList<>();
301 listOfArray.add(certChain);
302 listOfArray.add(trustStore);
306 public static List<String> getNamesOfCerts(List<X509Certificate> certChain) {
307 List<String> certNames = new ArrayList<>();
308 certChain.forEach(cert -> certNames.add(cert.getSubjectDN().getName()));
313 * checks whether PKIMessage contains extracerts to create certchain, if not creates from caPubs
315 * @param respPkiMessage PKIMessage that may contain extra certs used for certchain
316 * @param certRepMessage CertRepMessage that should contain rootCA for certchain
317 * @param leafCert certificate at top of certChain.
318 * @throws CertificateParsingException thrown if error occurs while parsing certificate
319 * @throws IOException thrown if IOException occurs while parsing certificate
320 * @throws CmpClientException thrown if there are errors creating CertificateFactory
322 public static List<X509Certificate> addExtraCertsToChain(
323 PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCert)
324 throws CertificateParsingException, IOException, CmpClientException {
325 List<X509Certificate> certChain = new ArrayList<>();
326 certChain.add(leafCert);
327 if (respPkiMessage.getExtraCerts() != null) {
328 final CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts();
329 for (CMPCertificate cmpCert : extraCerts) {
330 Optional<X509Certificate> cert =
331 getCertfromByteArray(cmpCert.getEncoded(), X509Certificate.class);
336 "Adding certificate from extra certs {} to cert chain",
337 "Couldn't add certificate from extra certs, certificate wasn't an X509Certificate");
341 final CMPCertificate respCmpCaCert = getRootCa(certRepMessage);
342 Optional<X509Certificate> cert =
343 getCertfromByteArray(respCmpCaCert.getEncoded(), X509Certificate.class);
348 "Adding certificate from CaPubs {} to TrustStore",
349 "Couldn't add certificate from CaPubs, certificate wasn't an X509Certificate");
352 return Collections.emptyList();
355 public static List<X509Certificate> ifCertPresent(
356 List<X509Certificate> certChain,
357 Optional<X509Certificate> cert,
358 String certPresentString,
359 String certUnavailableString) {
360 if (cert.isPresent()) {
361 LOG.debug(certPresentString, cert.get().getSubjectDN().getName());
362 certChain.add(cert.get());
365 LOG.debug(certUnavailableString);
370 private static CMPCertificate getRootCa(CertRepMessage certRepMessage) {
371 return certRepMessage.getCaPubs()[0];