3cb0b0c50769c92fccb492895cbbdd3152a4d964
[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.HashMap;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Objects;
47 import java.util.Optional;
48
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.aaf.certservice.cmpv2client.exceptions.CmpClientException;
57 import org.onap.aaf.certservice.cmpv2client.exceptions.PkiErrorException;
58 import org.onap.aaf.certservice.cmpv2client.model.Cmpv2CertificationModel;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 public final class CmpResponseHelper {
63
64     private static final Logger LOG = LoggerFactory.getLogger(CmpResponseHelper.class);
65
66     private CmpResponseHelper() {
67     }
68
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;
81         }
82     }
83
84
85     /**
86      * Puts together certChain and Trust store and verifies the certChain
87      *
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
95      */
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);
101     }
102
103     private static Map<X500Name, X509Certificate> mapAllCertificates(
104             PKIMessage respPkiMessage, CertRepMessage certRepMessage
105     )
106             throws IOException, CertificateParsingException, CmpClientException {
107
108         Map<X500Name, X509Certificate> certificates = new HashMap<>();
109
110         CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts();
111         certificates.putAll(mapCertificates(extraCerts));
112
113         CMPCertificate[] caPubsCerts = certRepMessage.getCaPubs();
114         certificates.putAll(mapCertificates(caPubsCerts));
115
116         return certificates;
117     }
118
119     private static Map<X500Name, X509Certificate> mapCertificates(
120             CMPCertificate[] cmpCertificates)
121             throws CertificateParsingException, CmpClientException, IOException {
122
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)
129                         );
130             }
131         }
132
133         return certificates;
134     }
135
136     private static Cmpv2CertificationModel extractCertificationModel(
137             Map<X500Name, X509Certificate> certificates, X509Certificate leafCertificate
138     )
139             throws CmpClientException {
140         List<X509Certificate> certificateChain = new ArrayList<>();
141         X509Certificate previousCertificateInChain;
142         X509Certificate nextCertificateInChain = leafCertificate;
143         do {
144             certificateChain.add(nextCertificateInChain);
145             certificates.remove(extractSubjectDn(nextCertificateInChain));
146             previousCertificateInChain = nextCertificateInChain;
147             nextCertificateInChain = certificates.get(extractIssuerDn(nextCertificateInChain));
148             verify(previousCertificateInChain, nextCertificateInChain, null);
149         }
150         while (!isSelfSign(nextCertificateInChain));
151         List<X509Certificate> trustedCertificates = new ArrayList<>(certificates.values());
152
153         return new Cmpv2CertificationModel(certificateChain, trustedCertificates);
154     }
155
156     private static boolean isSelfSign(X509Certificate certificate) {
157         return extractIssuerDn(certificate).equals(extractSubjectDn(certificate));
158     }
159
160     private static X500Name extractIssuerDn(X509Certificate x509Certificate) {
161         return X500Name.getInstance(x509Certificate.getIssuerDN());
162     }
163
164     private static X500Name extractSubjectDn(X509Certificate x509Certificate) {
165         return X500Name.getInstance(x509Certificate.getSubjectDN());
166     }
167
168
169     /**
170      * Check the certificate with CA certificate.
171      *
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
177      *                             path validation
178      * @throws CmpClientException if certificate could not be validated
179      */
180     private static void verify(
181             X509Certificate certificate,
182             X509Certificate caCertChain,
183             Date date,
184             PKIXCertPathChecker... pkixCertPathCheckers)
185             throws CmpClientException {
186         try {
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.",
214                             iape);
215             LOG.error(
216                     "Either ca certificate chain was empty, "
217                             + "or the certificate was on an inappropriate type for a PKIX path checker.",
218                     iape);
219             throw cmpClientException;
220         }
221     }
222
223     private static void verifyCertificates(
224             X509Certificate certificate,
225             X509Certificate caCertChain,
226             Date date,
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);
233         }
234         LOG.debug(
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);
245         }
246     }
247
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);
255         }
256         params.setRevocationEnabled(false);
257         params.setDate(date);
258         return params;
259     }
260
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);
267     }
268
269     /**
270      * Returns a CertificateFactory that can be used to create certificates from byte arrays and such.
271      *
272      * @param provider Security provider that should be used to create certificates, default BC is
273      *                 null is passed.
274      * @return CertificateFactory for creating certificate
275      */
276     private static CertificateFactory getCertificateFactory(final String provider)
277             throws CmpClientException {
278         LOG.debug("Creating certificate Factory to generate certificate using provider {}", provider);
279         final String prov;
280         prov = Objects.requireNonNullElse(provider, BouncyCastleProvider.PROVIDER_NAME);
281         try {
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;
291         }
292     }
293
294     /**
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.
299      */
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;
304
305         if (returnType.equals(X509Certificate.class)) {
306             return parseX509Certificate(prov, cert);
307         } else {
308             LOG.debug("Certificate of type {} was skipped, because type of certificate is not 'X509Certificate'.", returnType);
309             return Optional.empty();
310         }
311     }
312
313
314     /**
315      * Parse a X509Certificate from an array of bytes
316      *
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.
322      */
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;
328         try {
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);
333         }
334     }
335 }