Remove temp file if Minio-upload failed
[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.BufferedOutputStream;
24 import java.io.ByteArrayInputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.InputStreamReader;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.security.GeneralSecurityException;
34 import java.security.InvalidAlgorithmParameterException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.NoSuchProviderException;
37 import java.security.Security;
38 import java.security.cert.CertPathBuilder;
39 import java.security.cert.CertStore;
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.Optional;
53 import java.util.Set;
54 import java.util.UUID;
55 import java.util.concurrent.atomic.AtomicBoolean;
56 import java.util.function.Predicate;
57 import java.util.stream.Collectors;
58 import java.util.stream.Stream;
59 import java.util.zip.ZipEntry;
60 import java.util.zip.ZipInputStream;
61 import org.bouncycastle.asn1.cms.ContentInfo;
62 import org.bouncycastle.cert.X509CertificateHolder;
63 import org.bouncycastle.cms.CMSException;
64 import org.bouncycastle.cms.CMSProcessableByteArray;
65 import org.bouncycastle.cms.CMSProcessableFile;
66 import org.bouncycastle.cms.CMSSignedData;
67 import org.bouncycastle.cms.SignerInformation;
68 import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
69 import org.bouncycastle.jce.provider.BouncyCastleProvider;
70 import org.bouncycastle.openssl.PEMParser;
71 import org.bouncycastle.operator.OperatorCreationException;
72 import org.openecomp.sdc.be.csar.storage.ArtifactInfo;
73 import org.openecomp.sdc.be.csar.storage.ArtifactStorageConfig;
74 import org.openecomp.sdc.be.csar.storage.ArtifactStorageManager;
75 import org.openecomp.sdc.be.csar.storage.StorageFactory;
76 import org.openecomp.sdc.logging.api.Logger;
77 import org.openecomp.sdc.logging.api.LoggerFactory;
78 import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage;
79
80 /**
81  * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be reviewed Class is responsible
82  * for providing root trustedCertificates from configured location in onboarding container.
83  */
84 public class SecurityManager {
85
86     public static final Set<String> ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms");
87     public static final Set<String> ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt");
88     private static final String CERTIFICATE_DEFAULT_LOCATION = "cert";
89     private static final Logger LOGGER = LoggerFactory.getLogger(SecurityManager.class);
90     private static final String UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION = "Unexpected error occurred during signature validation!";
91     private static final String COULD_NOT_VERIFY_SIGNATURE = "Could not verify signature!";
92
93     static {
94         if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
95             Security.addProvider(new BouncyCastleProvider());
96         }
97     }
98
99     private Set<X509Certificate> trustedCertificates = new HashSet<>();
100     private Set<X509Certificate> trustedCertificatesFromPackage = new HashSet<>();
101     private File certificateDirectory;
102
103     private SecurityManager() {
104         certificateDirectory = this.getcertDirectory(System.getenv("SDC_CERT_DIR"));
105     }
106
107     // Package level constructor use in tests to avoid power mock
108     SecurityManager(String sdcCertDir) {
109         certificateDirectory = this.getcertDirectory(sdcCertDir);
110     }
111
112     public static SecurityManager getInstance() {
113         return SecurityManagerInstanceHolder.instance;
114     }
115
116     /**
117      * Checks the configured location for available trustedCertificates
118      *
119      * @return set of trustedCertificates
120      * @throws SecurityManagerException
121      */
122     public Set<X509Certificate> getTrustedCertificates() throws SecurityManagerException {
123         //if file number in certificate directory changed reload certs
124         String[] certFiles = certificateDirectory.list();
125         if (certFiles == null) {
126             LOGGER.error("Certificate directory is empty!");
127             return ImmutableSet.copyOf(new HashSet<>());
128         }
129         if (trustedCertificates.size() != certFiles.length) {
130             trustedCertificates = new HashSet<>();
131             processCertificateDir();
132         }
133         if (!trustedCertificatesFromPackage.isEmpty()) {
134             return Stream.concat(trustedCertificatesFromPackage.stream(), trustedCertificates.stream()).collect(Collectors.toUnmodifiableSet());
135         }
136         return ImmutableSet.copyOf(trustedCertificates);
137     }
138
139     /**
140      * Cleans certificate collection
141      */
142     public void cleanTrustedCertificates() {
143         trustedCertificates.clear();
144     }
145
146     /**
147      * Verifies if packaged signed with trusted certificate
148      *
149      * @param messageSyntaxSignature - signature data in cms format
150      * @param packageCert            - package certificate if not part of cms signature, can be null
151      * @param innerPackageFile       data package signed with cms signature
152      * @return true if signature verified
153      * @throws SecurityManagerException
154      */
155     public boolean verifySignedData(final byte[] messageSyntaxSignature,
156                                     final byte[] packageCert,
157                                     final byte[] innerPackageFile) throws SecurityManagerException {
158         try (final ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature);
159             final PEMParser pemParser = new PEMParser(new InputStreamReader(signatureStream))) {
160             final Object parsedObject = pemParser.readObject();
161             if (!(parsedObject instanceof ContentInfo)) {
162                 throw new SecurityManagerException("Signature is not recognized");
163             }
164             return verify(packageCert, new CMSSignedData(new CMSProcessableByteArray(innerPackageFile), ContentInfo.getInstance(parsedObject)));
165         } catch (final IOException | CMSException e) {
166             LOGGER.error(e.getMessage(), e);
167             throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e);
168         }
169     }
170
171     public boolean verifyPackageSignedData(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo)
172         throws SecurityManagerException {
173         boolean fail = false;
174
175         final StorageFactory storageFactory = new StorageFactory();
176         final ArtifactStorageManager artifactStorageManager = storageFactory.createArtifactStorageManager();
177         final ArtifactStorageConfig storageConfiguration = artifactStorageManager.getStorageConfiguration();
178
179         final var fileContentHandler = signedPackage.getFileContentHandler();
180         byte[] packageCert = null;
181         final Optional<String> certificateFilePath = signedPackage.getCertificateFilePath();
182         if (certificateFilePath.isPresent()) {
183             packageCert = fileContentHandler.getFileContent(certificateFilePath.get());
184         }
185
186         final Path folder = Path.of(storageConfiguration.getTempPath());
187         try {
188             Files.createDirectories(folder);
189         } catch (final IOException e) {
190             fail = true;
191             LOGGER.error("Failed to create directory '{}'", folder, e);
192             throw new SecurityManagerException(String.format("Failed to create directory '%s'", folder), e);
193         }
194
195         final var target = folder.resolve(UUID.randomUUID().toString());
196
197         try (final var signatureStream = new ByteArrayInputStream(fileContentHandler.getFileContent(signedPackage.getSignatureFilePath()));
198             final var pemParser = new PEMParser(new InputStreamReader(signatureStream))) {
199             final var parsedObject = pemParser.readObject();
200             if (!(parsedObject instanceof ContentInfo)) {
201                 fail = true;
202                 LOGGER.error("Signature is not recognized");
203                 throw new SecurityManagerException("Signature is not recognized");
204             }
205
206             try (final InputStream inputStream = artifactStorageManager.get(artifactInfo)) {
207                 if (!findCSARandExtract(inputStream, target)) {
208                     fail = true;
209                     return false;
210                 }
211             }
212             final var verify = verify(packageCert, new CMSSignedData(new CMSProcessableFile(target.toFile()), ContentInfo.getInstance(parsedObject)));
213             fail = !verify;
214             return verify;
215         } catch (final IOException e) {
216             fail = true;
217             LOGGER.error(e.getMessage(), e);
218             throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e);
219         } catch (final CMSException e) {
220             fail = true;
221             LOGGER.error(e.getMessage(), e);
222             throw new SecurityManagerException(COULD_NOT_VERIFY_SIGNATURE, e);
223         } catch (final SecurityManagerException e) {
224             fail = true;
225             LOGGER.error(e.getMessage(), e);
226             throw e;
227         } finally {
228             deleteFile(target);
229             if (fail) {
230                 artifactStorageManager.delete(artifactInfo);
231             }
232         }
233     }
234
235     private void deleteFile(final Path filePath) {
236         if (Files.exists(filePath)) {
237             try {
238                 Files.delete(filePath);
239             } catch (final IOException e) {
240                 LOGGER.warn("Failed to delete '{}' after verifying package signed data", filePath, e);
241             }
242         }
243     }
244
245     private boolean verify(final byte[] packageCert, final CMSSignedData signedData) throws SecurityManagerException {
246         final SignerInformation firstSigner = signedData.getSignerInfos().getSigners().iterator().next();
247         final X509Certificate cert;
248         Collection<X509CertificateHolder> certs;
249         if (packageCert == null) {
250             certs = signedData.getCertificates().getMatches(null);
251             cert = readSignCert(certs, firstSigner)
252                 .orElseThrow(() -> new SecurityManagerException("No certificate found in cms signature that should contain one!"));
253         } else {
254             try {
255                 certs = parseCertsFromPem(packageCert);
256             } catch (final IOException e) {
257                 LOGGER.error("Failed to parse certificate from PEM", e);
258                 throw new SecurityManagerException("Failed to parse certificate from PEM", e);
259             }
260             cert = readSignCert(certs, firstSigner)
261                 .orElseThrow(() -> new SecurityManagerException("No matching certificate found in certificate file that should contain one!"));
262         }
263         trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner);
264         if (verifyCertificate(cert, getTrustedCertificates()) == null) {
265             return false;
266         }
267         try {
268             return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
269         } catch (CMSException | OperatorCreationException e) {
270             LOGGER.error("Failed to verify package signed data", e);
271             throw new SecurityManagerException("Failed to verify package signed data", e);
272         }
273     }
274
275     private boolean findCSARandExtract(final InputStream inputStream, final Path target) throws IOException {
276         final AtomicBoolean found = new AtomicBoolean(false);
277
278         final var zipInputStream = new ZipInputStream(inputStream);
279         ZipEntry zipEntry;
280         byte[] buffer = new byte[2048];
281         while ((zipEntry = zipInputStream.getNextEntry()) != null) {
282             final var entryName = zipEntry.getName();
283             if (!zipEntry.isDirectory() && entryName.toLowerCase().endsWith(".csar")) {
284                 try (final FileOutputStream fos = new FileOutputStream(target.toFile());
285                     final BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length)) {
286
287                     int len;
288                     while ((len = zipInputStream.read(buffer)) > 0) {
289                         bos.write(buffer, 0, len);
290                     }
291                 }
292                 found.set(true);
293             }
294         }
295         return found.get();
296     }
297
298     private Optional<X509Certificate> readSignCert(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
299         return certs.stream().filter(crt -> firstSigner.getSID().match(crt)).findAny().map(this::loadCertificate);
300     }
301
302     private Set<X509Certificate> readTrustedCerts(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
303         return certs.stream().filter(crt -> !firstSigner.getSID().match(crt)).map(this::loadCertificate).filter(Predicate.not(this::isSelfSigned))
304             .collect(Collectors.toSet());
305     }
306
307     private Set<X509CertificateHolder> parseCertsFromPem(final byte[] packageCert) throws IOException {
308         final ByteArrayInputStream packageCertStream = new ByteArrayInputStream(packageCert);
309         final PEMParser pemParser = new PEMParser(new InputStreamReader(packageCertStream));
310         Object readObject = pemParser.readObject();
311         Set<X509CertificateHolder> allCerts = new HashSet<>();
312         while (readObject != null) {
313             if (readObject instanceof X509CertificateHolder) {
314                 allCerts.add((X509CertificateHolder) readObject);
315             }
316             readObject = pemParser.readObject();
317         }
318         return allCerts;
319     }
320
321     private void processCertificateDir() throws SecurityManagerException {
322         if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) {
323             LOGGER.error("Issue with certificate directory, check if exists!");
324             return;
325         }
326         File[] files = certificateDirectory.listFiles();
327         if (files == null) {
328             LOGGER.error("Certificate directory is empty!");
329             return;
330         }
331         for (File f : files) {
332             trustedCertificates.add(loadCertificate(f));
333         }
334     }
335
336     private File getcertDirectory(String sdcCertDir) {
337         String certDirLocation = sdcCertDir;
338         if (certDirLocation == null) {
339             certDirLocation = CERTIFICATE_DEFAULT_LOCATION;
340         }
341         return new File(certDirLocation);
342     }
343
344     private X509Certificate loadCertificate(File certFile) throws SecurityManagerException {
345         try (FileInputStream fi = new FileInputStream(certFile)) {
346             return loadCertificateFactory(fi);
347         } catch (IOException e) {
348             LOGGER.error("Error during loading Certificate from file!", e);
349             throw new SecurityManagerException("Error during loading Certificate from file!", e);
350         }
351     }
352
353     private X509Certificate loadCertificate(X509CertificateHolder cert) {
354         try {
355             return loadCertificateFactory(new ByteArrayInputStream(cert.getEncoded()));
356         } catch (IOException | SecurityManagerException e) {
357             LOGGER.error("Error during loading Certificate from bytes!", e);
358             throw new RuntimeException("Error during loading Certificate from bytes!", e);
359         }
360     }
361
362     private X509Certificate loadCertificateFactory(InputStream in) throws SecurityManagerException {
363         try {
364             CertificateFactory factory = CertificateFactory.getInstance("X.509");
365             return (X509Certificate) factory.generateCertificate(in);
366         } catch (CertificateException e) {
367             LOGGER.error("Error during loading Certificate from bytes!", e);
368             throw new SecurityManagerException("Error during loading Certificate from bytes!", e);
369         }
370     }
371
372     private PKIXCertPathBuilderResult verifyCertificate(final X509Certificate cert,
373                                                         final Set<X509Certificate> additionalCerts) throws SecurityManagerException {
374         if (null == cert) {
375             LOGGER.error("The certificate is empty!");
376             throw new SecurityManagerException("The certificate is empty!");
377         }
378         if (isExpired(cert)) {
379             LOGGER.error("The certificate expired on: {}", cert.getNotAfter());
380             throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
381         }
382         if (isSelfSigned(cert)) {
383             LOGGER.error("The certificate is self-signed.");
384             throw new SecurityManagerException("The certificate is self-signed.");
385         }
386         Set<X509Certificate> trustedRootCerts = new HashSet<>();
387         Set<X509Certificate> intermediateCerts = new HashSet<>();
388         for (X509Certificate additionalCert : additionalCerts) {
389             if (isSelfSigned(additionalCert)) {
390                 trustedRootCerts.add(additionalCert);
391             } else {
392                 intermediateCerts.add(additionalCert);
393             }
394         }
395         try {
396             return verifyCertificate(cert, trustedRootCerts, intermediateCerts);
397         } catch (final GeneralSecurityException e) {
398             LOGGER.error("Failed to verify certificate", e);
399             throw new SecurityManagerException("Failed to verify certificate", e);
400         }
401     }
402
403     private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, Set<X509Certificate> allTrustedRootCerts,
404                                                         Set<X509Certificate> allIntermediateCerts) throws GeneralSecurityException {
405         // Create the selector that specifies the starting certificate
406         X509CertSelector selector = new X509CertSelector();
407         selector.setCertificate(cert);
408         // Create the trust anchors (set of root CA certificates)
409         Set<TrustAnchor> trustAnchors = new HashSet<>();
410         for (X509Certificate trustedRootCert : allTrustedRootCerts) {
411             trustAnchors.add(new TrustAnchor(trustedRootCert, null));
412         }
413         // Configure the PKIX certificate builder algorithm parameters
414         PKIXBuilderParameters pkixParams;
415         try {
416             pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
417         } catch (InvalidAlgorithmParameterException ex) {
418             LOGGER.error("No root CA has been found for this certificate", ex);
419             throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex);
420         }
421         // Not supporting CRL checks for now
422         pkixParams.setRevocationEnabled(false);
423         Set<X509Certificate> certSet = new HashSet<>();
424         certSet.add(cert);
425         pkixParams.addCertStore(createCertStore(certSet));
426         pkixParams.addCertStore(createCertStore(allIntermediateCerts));
427         pkixParams.addCertStore(createCertStore(allTrustedRootCerts));
428         CertPathBuilder builder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType(), BouncyCastleProvider.PROVIDER_NAME);
429         return (PKIXCertPathBuilderResult) builder.build(pkixParams);
430     }
431
432     private CertStore createCertStore(Set<X509Certificate> certificateSet)
433         throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
434         return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet), BouncyCastleProvider.PROVIDER_NAME);
435     }
436
437     private boolean isExpired(X509Certificate cert) {
438         try {
439             cert.checkValidity();
440         } catch (CertificateExpiredException e) {
441             LOGGER.error(e.getMessage(), e);
442             return true;
443         } catch (CertificateNotYetValidException e) {
444             LOGGER.error(e.getMessage(), e);
445             return false;
446         }
447         return false;
448     }
449
450     private boolean isSelfSigned(X509Certificate cert) {
451         return cert.getIssuerDN().equals(cert.getSubjectDN());
452     }
453
454     /**
455      * Initialization on demand class / synchronized singleton pattern.
456      */
457     private static class SecurityManagerInstanceHolder {
458
459         private static final SecurityManager instance = new SecurityManager();
460     }
461 }