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