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