b2a7b29e3d6a48b4465011fb1e46a6d784e4cfc8
[oom/platform/cert-service.git] / certService / src / main / java / org / onap / aaf / certservice / cmpv2client / impl / CmpResponseHelper.java
1 /*-
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
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.aaf.certservice.cmpv2client.impl;
22
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;
46
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;
57
58 public final class CmpResponseHelper {
59
60     private static final Logger LOG = LoggerFactory.getLogger(CmpResponseHelper.class);
61
62     private CmpResponseHelper() {
63     }
64
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;
77         }
78     }
79
80     /**
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.
85      */
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);
90     }
91
92     /**
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.
98      */
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;
105         }
106
107         if (returnType.equals(X509Certificate.class)) {
108             return parseX509Certificate(prov, cert);
109         }
110         return Optional.empty();
111     }
112
113     /**
114      * Check the certificate with CA certificate.
115      *
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
119      */
120     public static void verify(List<X509Certificate> caCertChain) throws CmpClientException {
121         int iterator = 1;
122         while (iterator < caCertChain.size()) {
123             verify(caCertChain.get(iterator - 1), caCertChain.get(iterator), null);
124             iterator += 1;
125         }
126     }
127
128     /**
129      * Check the certificate with CA certificate.
130      *
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
136      *                             path validation
137      * @throws CmpClientException if certificate could not be validated
138      */
139     public static void verify(
140             X509Certificate certificate,
141             X509Certificate caCertChain,
142             Date date,
143             PKIXCertPathChecker... pkixCertPathCheckers)
144             throws CmpClientException {
145         try {
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.",
173                             iape);
174             LOG.error(
175                     "Either ca certificate chain was empty, "
176                             + "or the certificate was on an inappropriate type for a PKIX path checker.",
177                     iape);
178             throw cmpClientException;
179         }
180     }
181
182     public static void verifyCertificates(
183             X509Certificate certificate,
184             X509Certificate caCertChain,
185             Date date,
186             PKIXCertPathChecker[] pkixCertPathCheckers)
187             throws CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException,
188             NoSuchAlgorithmException, CertPathValidatorException {
189         LOG.debug(
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);
200         }
201     }
202
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);
210         }
211         params.setRevocationEnabled(false);
212         params.setDate(date);
213         return params;
214     }
215
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);
222     }
223
224     /**
225      * Parse a X509Certificate from an array of bytes
226      *
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.
232      */
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;
238         try {
239             result =
240                     (X509Certificate)
241                             Objects.requireNonNull(cf).generateCertificate(new ByteArrayInputStream(cert));
242         } catch (CertificateException ce) {
243             throw new CertificateParsingException("Could not parse byte array as X509Certificate ", ce);
244         }
245         if (result != null) {
246             return Optional.of(result);
247         } else {
248             throw new CertificateParsingException("Could not parse byte array as X509Certificate.");
249         }
250     }
251
252     /**
253      * Returns a CertificateFactory that can be used to create certificates from byte arrays and such.
254      *
255      * @param provider Security provider that should be used to create certificates, default BC is
256      *                 null is passed.
257      * @return CertificateFactory for creating certificate
258      */
259     public static CertificateFactory getCertificateFactory(final String provider)
260             throws CmpClientException {
261         LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider);
262         final String prov;
263         prov = Objects.requireNonNullElse(provider, BouncyCastleProvider.PROVIDER_NAME);
264         try {
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;
274         }
275     }
276
277     /**
278      * puts together certChain and Trust store and verifies the certChain
279      *
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
287      */
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);
295         verify(certChain);
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);
303         return listOfArray;
304     }
305
306     public static List<String> getNamesOfCerts(List<X509Certificate> certChain) {
307         List<String> certNames = new ArrayList<>();
308         certChain.forEach(cert -> certNames.add(cert.getSubjectDN().getName()));
309         return certNames;
310     }
311
312     /**
313      * checks whether PKIMessage contains extracerts to create certchain, if not creates from caPubs
314      *
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
321      */
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);
332                 certChain =
333                         ifCertPresent(
334                                 certChain,
335                                 cert,
336                                 "Adding certificate from extra certs {} to cert chain",
337                                 "Couldn't add certificate from extra certs, certificate wasn't an X509Certificate");
338                 return certChain;
339             }
340         } else {
341             final CMPCertificate respCmpCaCert = getRootCa(certRepMessage);
342             Optional<X509Certificate> cert =
343                     getCertfromByteArray(respCmpCaCert.getEncoded(), X509Certificate.class);
344             certChain =
345                     ifCertPresent(
346                             certChain,
347                             cert,
348                             "Adding certificate from CaPubs {} to TrustStore",
349                             "Couldn't add certificate from CaPubs, certificate wasn't an X509Certificate");
350             return certChain;
351         }
352         return Collections.emptyList();
353     }
354
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());
363             return certChain;
364         } else {
365             LOG.debug(certUnavailableString);
366             return certChain;
367         }
368     }
369
370     private static CMPCertificate getRootCa(CertRepMessage certRepMessage) {
371         return certRepMessage.getCaPubs()[0];
372     }
373 }