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