56d0142e3bc896949eeb4c3336d8ec2dd0d73f5f
[sdc.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2019, Nordix Foundation. All rights reserved.
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  * ============LICENSE_END=========================================================
19  */
20 package org.openecomp.sdc.vendorsoftwareproduct.security;
21
22 import com.google.common.collect.ImmutableSet;
23
24 import java.io.*;
25 import java.security.GeneralSecurityException;
26 import java.security.InvalidAlgorithmParameterException;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.NoSuchProviderException;
29 import java.security.Security;
30 import java.security.cert.CertPathBuilder;
31 import java.security.cert.CertStore;
32 import java.security.cert.CertificateException;
33 import java.security.cert.CertificateExpiredException;
34 import java.security.cert.CertificateFactory;
35 import java.security.cert.CertificateNotYetValidException;
36 import java.security.cert.CollectionCertStoreParameters;
37 import java.security.cert.PKIXBuilderParameters;
38 import java.security.cert.PKIXCertPathBuilderResult;
39 import java.security.cert.TrustAnchor;
40 import java.security.cert.X509CertSelector;
41 import java.security.cert.X509Certificate;
42 import java.util.Collection;
43 import java.util.HashSet;
44 import java.util.function.Predicate;
45 import java.util.Optional;
46 import java.util.stream.Collectors;
47 import java.util.stream.Stream;
48 import java.util.Set;
49
50 import org.bouncycastle.asn1.cms.ContentInfo;
51 import org.bouncycastle.cert.X509CertificateHolder;
52 import org.bouncycastle.cms.CMSException;
53 import org.bouncycastle.cms.CMSProcessableByteArray;
54 import org.bouncycastle.cms.CMSSignedData;
55 import org.bouncycastle.cms.CMSTypedData;
56 import org.bouncycastle.cms.SignerInformation;
57 import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
58 import org.bouncycastle.jce.provider.BouncyCastleProvider;
59 import org.bouncycastle.openssl.PEMParser;
60 import org.bouncycastle.operator.OperatorCreationException;
61 import org.openecomp.sdc.logging.api.Logger;
62 import org.openecomp.sdc.logging.api.LoggerFactory;
63
64 /**
65  * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be
66  * reviewed Class is responsible for providing root trustedCertificates from configured location in onboarding
67  * container.
68  */
69 public class SecurityManager {
70
71     private static final String CERTIFICATE_DEFAULT_LOCATION = "cert";
72     public static final Set<String> ALLOWED_SIGNATURE_EXTENSIONS = ImmutableSet.of("cms");
73     public static final Set<String> ALLOWED_CERTIFICATE_EXTENSIONS = ImmutableSet.of("cert", "crt");
74
75     private Logger logger = LoggerFactory.getLogger(SecurityManager.class);
76     private Set<X509Certificate> trustedCertificates = new HashSet<>();
77     private Set<X509Certificate> trustedCertificatesFromPackage = new HashSet<>();
78     private File certificateDirectory;
79
80     static {
81         if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
82             Security.addProvider(new BouncyCastleProvider());
83         }
84     }
85
86     private SecurityManager() {
87         certificateDirectory = this.getcertDirectory(System.getenv("SDC_CERT_DIR"));
88     }
89
90
91     // Package level constructor use in tests to avoid power mock
92     SecurityManager(String sdcCertDir) {
93         certificateDirectory = this.getcertDirectory(sdcCertDir);
94     }
95
96     public static SecurityManager getInstance() {
97         return SecurityManagerInstanceHolder.instance;
98     }
99
100     /**
101      * Initialization on demand class / synchronized singleton pattern.
102      */
103     private static class SecurityManagerInstanceHolder {
104
105         private static final SecurityManager instance = new SecurityManager();
106     }
107
108     /**
109      * Checks the configured location for available trustedCertificates
110      *
111      * @return set of trustedCertificates
112      * @throws SecurityManagerException
113      */
114     public Set<X509Certificate> getTrustedCertificates() throws SecurityManagerException, FileNotFoundException {
115         //if file number in certificate directory changed reload certs
116         String[] certFiles = certificateDirectory.list();
117         if (certFiles == null) {
118             logger.error("Certificate directory is empty!");
119             return ImmutableSet.copyOf(new HashSet<>());
120         }
121         if (trustedCertificates.size() != certFiles.length) {
122             trustedCertificates = new HashSet<>();
123             processCertificateDir();
124         }
125         if (!trustedCertificatesFromPackage.isEmpty()) {
126             return Stream.concat(trustedCertificatesFromPackage.stream(), trustedCertificates.stream())
127                     .collect(Collectors.toUnmodifiableSet());
128         }
129         return ImmutableSet.copyOf(trustedCertificates);
130     }
131
132     /**
133      * Cleans certificate collection
134      */
135     public void cleanTrustedCertificates() {
136         trustedCertificates.clear();
137     }
138
139     /**
140      * Verifies if packaged signed with trusted certificate
141      *
142      * @param messageSyntaxSignature - signature data in cms format
143      * @param packageCert            - package certificate if not part of cms signature, can be null
144      * @param innerPackageFile       data package signed with cms signature
145      * @return true if signature verified
146      * @throws SecurityManagerException
147      */
148     public boolean verifySignedData(final byte[] messageSyntaxSignature, final byte[] packageCert,
149                                     final byte[] innerPackageFile) throws SecurityManagerException {
150         try (ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature);
151              final PEMParser pemParser = new PEMParser(new InputStreamReader(signatureStream))) {
152             final Object parsedObject = pemParser.readObject();
153             if (!(parsedObject instanceof ContentInfo)) {
154                 throw new SecurityManagerException("Signature is not recognized");
155             }
156             final ContentInfo signature = ContentInfo.getInstance(parsedObject);
157             final CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile);
158             final CMSSignedData signedData = new CMSSignedData(signedContent, signature);
159
160             final Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners();
161             final SignerInformation firstSigner = signers.iterator().next();
162             final X509Certificate cert;
163             Collection<X509CertificateHolder> certs;
164             if (packageCert == null) {
165                 certs = signedData.getCertificates().getMatches(null);
166                 cert = readSignCert(certs, firstSigner).orElseThrow(() -> new SecurityManagerException(
167                         "No certificate found in cms signature that should contain one!"));
168             } else {
169                 certs = parseCertsFromPem(packageCert);
170                 cert = readSignCert(certs, firstSigner).orElseThrow(() -> new SecurityManagerException(
171                         "No matching certificate found in certificate file that should contain one!"));
172             }
173             trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner);
174
175             if (verifyCertificate(cert, getTrustedCertificates()) == null) {
176                 return false;
177             }
178
179             return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
180         } catch (OperatorCreationException | IOException | CMSException e) {
181             logger.error(e.getMessage(), e);
182             throw new SecurityManagerException("Unexpected error occurred during signature validation!", e);
183         } catch (GeneralSecurityException e) {
184             throw new SecurityManagerException("Could not verify signature!", e);
185         }
186     }
187
188     private Optional<X509Certificate> readSignCert(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
189         return certs.stream()
190                .filter(crt -> firstSigner.getSID().match(crt))
191                .findAny()
192                .map(this::loadCertificate);
193     }
194
195     private Set<X509Certificate> readTrustedCerts(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
196         return certs.stream()
197                 .filter(crt -> !firstSigner.getSID().match(crt))
198                 .map(this::loadCertificate)
199                 .filter(Predicate.not(this::isSelfSigned))
200                 .collect(Collectors.toSet());
201     }
202
203     private Set<X509CertificateHolder> parseCertsFromPem(final byte[] packageCert) throws IOException {
204         final ByteArrayInputStream packageCertStream = new ByteArrayInputStream(packageCert);
205         final PEMParser pemParser = new PEMParser(new InputStreamReader(packageCertStream));
206         Object readObject = pemParser.readObject();
207         Set<X509CertificateHolder> allCerts = new HashSet<>();
208         while (readObject != null) {
209             if (readObject instanceof X509CertificateHolder) {
210                 allCerts.add((X509CertificateHolder) readObject);
211             }
212             readObject = pemParser.readObject();
213         }
214         return allCerts;
215     }
216
217     private void processCertificateDir() throws SecurityManagerException, FileNotFoundException {
218         if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) {
219             logger.error("Issue with certificate directory, check if exists!");
220             return;
221         }
222
223         File[] files = certificateDirectory.listFiles();
224         if (files == null) {
225             logger.error("Certificate directory is empty!");
226             return;
227         }
228         for (File f : files) {
229             trustedCertificates.add(loadCertificate(f));
230         }
231     }
232
233     private File getcertDirectory(String sdcCertDir) {
234         String certDirLocation = sdcCertDir;
235         if (certDirLocation == null) {
236             certDirLocation = CERTIFICATE_DEFAULT_LOCATION;
237         }
238         return new File(certDirLocation);
239     }
240
241     private X509Certificate loadCertificate(File certFile) throws SecurityManagerException, FileNotFoundException {
242         return loadCertificateFactory(new FileInputStream(certFile));
243     }
244
245     private X509Certificate loadCertificate(X509CertificateHolder cert) {
246         try {
247             return loadCertificateFactory(new ByteArrayInputStream(cert.getEncoded()));
248         } catch (IOException | SecurityManagerException e) {
249             throw new RuntimeException("Error during loading Certificate from bytes!", e);
250         }
251     }
252
253     private X509Certificate loadCertificateFactory(InputStream in) throws SecurityManagerException {
254         try {
255             CertificateFactory factory = CertificateFactory.getInstance("X.509");
256             return (X509Certificate) factory.generateCertificate(in);
257         } catch (CertificateException e) {
258             throw new SecurityManagerException("Error during loading Certificate from bytes!", e);
259         }
260     }
261
262     private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
263                                                         Set<X509Certificate> additionalCerts)
264             throws GeneralSecurityException, SecurityManagerException {
265         if (null == cert) {
266             throw new SecurityManagerException("The certificate is empty!");
267         }
268
269         if (isExpired(cert)) {
270             throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
271         }
272
273         if (isSelfSigned(cert)) {
274             throw new SecurityManagerException("The certificate is self-signed.");
275         }
276
277         Set<X509Certificate> trustedRootCerts = new HashSet<>();
278         Set<X509Certificate> intermediateCerts = new HashSet<>();
279         for (X509Certificate additionalCert : additionalCerts) {
280             if (isSelfSigned(additionalCert)) {
281                 trustedRootCerts.add(additionalCert);
282             } else {
283                 intermediateCerts.add(additionalCert);
284             }
285         }
286
287         return verifyCertificate(cert, trustedRootCerts, intermediateCerts);
288     }
289
290     private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
291                                                         Set<X509Certificate> allTrustedRootCerts,
292                                                         Set<X509Certificate> allIntermediateCerts)
293             throws GeneralSecurityException {
294
295         // Create the selector that specifies the starting certificate
296         X509CertSelector selector = new X509CertSelector();
297         selector.setCertificate(cert);
298
299         // Create the trust anchors (set of root CA certificates)
300         Set<TrustAnchor> trustAnchors = new HashSet<>();
301         for (X509Certificate trustedRootCert : allTrustedRootCerts) {
302             trustAnchors.add(new TrustAnchor(trustedRootCert, null));
303         }
304
305         // Configure the PKIX certificate builder algorithm parameters
306         PKIXBuilderParameters pkixParams;
307         try {
308             pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
309         } catch (InvalidAlgorithmParameterException ex) {
310             throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex);
311         }
312
313         // Not supporting CRL checks for now
314         pkixParams.setRevocationEnabled(false);
315
316         Set<X509Certificate> certSet = new HashSet<>();
317         certSet.add(cert);
318         pkixParams.addCertStore(createCertStore(certSet));
319         pkixParams.addCertStore(createCertStore(allIntermediateCerts));
320         pkixParams.addCertStore(createCertStore(allTrustedRootCerts));
321
322         CertPathBuilder builder = CertPathBuilder
323                 .getInstance(CertPathBuilder.getDefaultType(), BouncyCastleProvider.PROVIDER_NAME);
324         return (PKIXCertPathBuilderResult) builder.build(pkixParams);
325     }
326
327     private CertStore createCertStore(Set<X509Certificate> certificateSet) throws InvalidAlgorithmParameterException,
328             NoSuchAlgorithmException, NoSuchProviderException {
329         return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet),
330                 BouncyCastleProvider.PROVIDER_NAME);
331     }
332
333     private boolean isExpired(X509Certificate cert) {
334         try {
335             cert.checkValidity();
336         } catch (CertificateExpiredException e) {
337             logger.error(e.getMessage(), e);
338             return true;
339         } catch (CertificateNotYetValidException e) {
340             logger.error(e.getMessage(), e);
341             return false;
342         }
343         return false;
344     }
345
346     private boolean isSelfSigned(X509Certificate cert) {
347         return cert.getIssuerDN().equals(cert.getSubjectDN());
348     }
349 }