2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
20 package org.openecomp.sdc.vendorsoftwareproduct.security;
22 import com.google.common.collect.ImmutableSet;
23 import java.io.BufferedOutputStream;
24 import java.io.ByteArrayInputStream;
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;
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;
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.
84 public class SecurityManager {
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 private static final String EXTERNAL_CSAR_STORE = "externalCsarStore";
95 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
96 Security.addProvider(new BouncyCastleProvider());
100 private Set<X509Certificate> trustedCertificates = new HashSet<>();
101 private Set<X509Certificate> trustedCertificatesFromPackage = new HashSet<>();
102 private File certificateDirectory;
104 private SecurityManager() {
105 certificateDirectory = this.getcertDirectory(System.getenv("SDC_CERT_DIR"));
108 // Package level constructor use in tests to avoid power mock
109 SecurityManager(String sdcCertDir) {
110 certificateDirectory = this.getcertDirectory(sdcCertDir);
113 public static SecurityManager getInstance() {
114 return SecurityManagerInstanceHolder.instance;
118 * Checks the configured location for available trustedCertificates
120 * @return set of trustedCertificates
121 * @throws SecurityManagerException
123 public Set<X509Certificate> getTrustedCertificates() throws SecurityManagerException {
124 //if file number in certificate directory changed reload certs
125 String[] certFiles = certificateDirectory.list();
126 if (certFiles == null) {
127 LOGGER.error("Certificate directory is empty!");
128 return ImmutableSet.copyOf(new HashSet<>());
130 if (trustedCertificates.size() != certFiles.length) {
131 trustedCertificates = new HashSet<>();
132 processCertificateDir();
134 if (!trustedCertificatesFromPackage.isEmpty()) {
135 return Stream.concat(trustedCertificatesFromPackage.stream(), trustedCertificates.stream()).collect(Collectors.toUnmodifiableSet());
137 return ImmutableSet.copyOf(trustedCertificates);
141 * Cleans certificate collection
143 public void cleanTrustedCertificates() {
144 trustedCertificates.clear();
148 * Verifies if packaged signed with trusted certificate
150 * @param messageSyntaxSignature - signature data in cms format
151 * @param packageCert - package certificate if not part of cms signature, can be null
152 * @param innerPackageFile data package signed with cms signature
153 * @return true if signature verified
154 * @throws SecurityManagerException
156 public boolean verifySignedData(final byte[] messageSyntaxSignature,
157 final byte[] packageCert,
158 final byte[] innerPackageFile) throws SecurityManagerException {
159 try (final ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature);
160 final PEMParser pemParser = new PEMParser(new InputStreamReader(signatureStream))) {
161 final Object parsedObject = pemParser.readObject();
162 if (!(parsedObject instanceof ContentInfo)) {
163 throw new SecurityManagerException("Signature is not recognized");
165 return verify(packageCert, new CMSSignedData(new CMSProcessableByteArray(innerPackageFile), ContentInfo.getInstance(parsedObject)));
166 } catch (final IOException | CMSException e) {
167 LOGGER.error(e.getMessage(), e);
168 throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e);
172 public boolean verifyPackageSignedData(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo)
173 throws SecurityManagerException {
174 boolean fail = false;
176 final StorageFactory storageFactory = new StorageFactory();
177 final ArtifactStorageManager artifactStorageManager = storageFactory.createArtifactStorageManager();
178 final ArtifactStorageConfig storageConfiguration = artifactStorageManager.getStorageConfiguration();
180 final var fileContentHandler = signedPackage.getFileContentHandler();
181 byte[] packageCert = null;
182 final Optional<String> certificateFilePath = signedPackage.getCertificateFilePath();
183 if (certificateFilePath.isPresent()) {
184 packageCert = fileContentHandler.getFileContent(certificateFilePath.get());
187 final Path folder = Path.of(storageConfiguration.getTempPath());
189 Files.createDirectories(folder);
190 } catch (final IOException e) {
192 throw new SecurityManagerException(String.format("Failed to create directory '%s'", folder), e);
195 final var target = folder.resolve(UUID.randomUUID().toString());
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)) {
202 throw new SecurityManagerException("Signature is not recognized");
205 try (final InputStream inputStream = artifactStorageManager.get(artifactInfo)) {
206 if (!findCSARandExtract(inputStream, target)) {
211 final var verify = verify(packageCert, new CMSSignedData(new CMSProcessableFile(target.toFile()), ContentInfo.getInstance(parsedObject)));
214 } catch (final IOException e) {
216 LOGGER.error(e.getMessage(), e);
217 throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e);
218 } catch (final CMSException e) {
220 throw new SecurityManagerException(COULD_NOT_VERIFY_SIGNATURE, e);
221 } catch (final SecurityManagerException e) {
227 artifactStorageManager.delete(artifactInfo);
232 private void deleteFile(final Path filePath) {
234 Files.delete(filePath);
235 } catch (final IOException e) {
236 LOGGER.warn("Failed to delete '{}' after verifying package signed data", filePath, e);
240 private boolean verify(final byte[] packageCert, final CMSSignedData signedData) throws SecurityManagerException {
241 final SignerInformation firstSigner = signedData.getSignerInfos().getSigners().iterator().next();
242 final X509Certificate cert;
243 Collection<X509CertificateHolder> certs;
244 if (packageCert == null) {
245 certs = signedData.getCertificates().getMatches(null);
246 cert = readSignCert(certs, firstSigner)
247 .orElseThrow(() -> new SecurityManagerException("No certificate found in cms signature that should contain one!"));
250 certs = parseCertsFromPem(packageCert);
251 } catch (final IOException e) {
252 throw new SecurityManagerException("Failed to parse certificate from PEM", e);
254 cert = readSignCert(certs, firstSigner)
255 .orElseThrow(() -> new SecurityManagerException("No matching certificate found in certificate file that should contain one!"));
257 trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner);
258 if (verifyCertificate(cert, getTrustedCertificates()) == null) {
262 return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
263 } catch (CMSException | OperatorCreationException e) {
264 throw new SecurityManagerException("Failed to verify package signed data", e);
268 private boolean findCSARandExtract(final InputStream inputStream, final Path target) throws IOException {
269 final AtomicBoolean found = new AtomicBoolean(false);
271 final var zipInputStream = new ZipInputStream(inputStream);
273 byte[] buffer = new byte[2048];
274 while ((zipEntry = zipInputStream.getNextEntry()) != null) {
275 final var entryName = zipEntry.getName();
276 if (!zipEntry.isDirectory() && entryName.toLowerCase().endsWith(".csar")) {
277 try (final FileOutputStream fos = new FileOutputStream(target.toFile());
278 final BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length)) {
281 while ((len = zipInputStream.read(buffer)) > 0) {
282 bos.write(buffer, 0, len);
291 private Optional<X509Certificate> readSignCert(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
292 return certs.stream().filter(crt -> firstSigner.getSID().match(crt)).findAny().map(this::loadCertificate);
295 private Set<X509Certificate> readTrustedCerts(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
296 return certs.stream().filter(crt -> !firstSigner.getSID().match(crt)).map(this::loadCertificate).filter(Predicate.not(this::isSelfSigned))
297 .collect(Collectors.toSet());
300 private Set<X509CertificateHolder> parseCertsFromPem(final byte[] packageCert) throws IOException {
301 final ByteArrayInputStream packageCertStream = new ByteArrayInputStream(packageCert);
302 final PEMParser pemParser = new PEMParser(new InputStreamReader(packageCertStream));
303 Object readObject = pemParser.readObject();
304 Set<X509CertificateHolder> allCerts = new HashSet<>();
305 while (readObject != null) {
306 if (readObject instanceof X509CertificateHolder) {
307 allCerts.add((X509CertificateHolder) readObject);
309 readObject = pemParser.readObject();
314 private void processCertificateDir() throws SecurityManagerException {
315 if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) {
316 LOGGER.error("Issue with certificate directory, check if exists!");
319 File[] files = certificateDirectory.listFiles();
321 LOGGER.error("Certificate directory is empty!");
324 for (File f : files) {
325 trustedCertificates.add(loadCertificate(f));
329 private File getcertDirectory(String sdcCertDir) {
330 String certDirLocation = sdcCertDir;
331 if (certDirLocation == null) {
332 certDirLocation = CERTIFICATE_DEFAULT_LOCATION;
334 return new File(certDirLocation);
337 private X509Certificate loadCertificate(File certFile) throws SecurityManagerException {
338 try (FileInputStream fi = new FileInputStream(certFile)) {
339 return loadCertificateFactory(fi);
340 } catch (IOException e) {
341 throw new SecurityManagerException("Error during loading Certificate from file!", e);
345 private X509Certificate loadCertificate(X509CertificateHolder cert) {
347 return loadCertificateFactory(new ByteArrayInputStream(cert.getEncoded()));
348 } catch (IOException | SecurityManagerException e) {
349 throw new RuntimeException("Error during loading Certificate from bytes!", e);
353 private X509Certificate loadCertificateFactory(InputStream in) throws SecurityManagerException {
355 CertificateFactory factory = CertificateFactory.getInstance("X.509");
356 return (X509Certificate) factory.generateCertificate(in);
357 } catch (CertificateException e) {
358 throw new SecurityManagerException("Error during loading Certificate from bytes!", e);
362 private PKIXCertPathBuilderResult verifyCertificate(final X509Certificate cert,
363 final Set<X509Certificate> additionalCerts) throws SecurityManagerException {
365 throw new SecurityManagerException("The certificate is empty!");
367 if (isExpired(cert)) {
368 throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
370 if (isSelfSigned(cert)) {
371 throw new SecurityManagerException("The certificate is self-signed.");
373 Set<X509Certificate> trustedRootCerts = new HashSet<>();
374 Set<X509Certificate> intermediateCerts = new HashSet<>();
375 for (X509Certificate additionalCert : additionalCerts) {
376 if (isSelfSigned(additionalCert)) {
377 trustedRootCerts.add(additionalCert);
379 intermediateCerts.add(additionalCert);
383 return verifyCertificate(cert, trustedRootCerts, intermediateCerts);
384 } catch (final GeneralSecurityException e) {
385 throw new SecurityManagerException("Failed to verify certificate", e);
389 private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, Set<X509Certificate> allTrustedRootCerts,
390 Set<X509Certificate> allIntermediateCerts) throws GeneralSecurityException {
391 // Create the selector that specifies the starting certificate
392 X509CertSelector selector = new X509CertSelector();
393 selector.setCertificate(cert);
394 // Create the trust anchors (set of root CA certificates)
395 Set<TrustAnchor> trustAnchors = new HashSet<>();
396 for (X509Certificate trustedRootCert : allTrustedRootCerts) {
397 trustAnchors.add(new TrustAnchor(trustedRootCert, null));
399 // Configure the PKIX certificate builder algorithm parameters
400 PKIXBuilderParameters pkixParams;
402 pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
403 } catch (InvalidAlgorithmParameterException ex) {
404 throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex);
406 // Not supporting CRL checks for now
407 pkixParams.setRevocationEnabled(false);
408 Set<X509Certificate> certSet = new HashSet<>();
410 pkixParams.addCertStore(createCertStore(certSet));
411 pkixParams.addCertStore(createCertStore(allIntermediateCerts));
412 pkixParams.addCertStore(createCertStore(allTrustedRootCerts));
413 CertPathBuilder builder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType(), BouncyCastleProvider.PROVIDER_NAME);
414 return (PKIXCertPathBuilderResult) builder.build(pkixParams);
417 private CertStore createCertStore(Set<X509Certificate> certificateSet)
418 throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
419 return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet), BouncyCastleProvider.PROVIDER_NAME);
422 private boolean isExpired(X509Certificate cert) {
424 cert.checkValidity();
425 } catch (CertificateExpiredException e) {
426 LOGGER.error(e.getMessage(), e);
428 } catch (CertificateNotYetValidException e) {
429 LOGGER.error(e.getMessage(), e);
435 private boolean isSelfSigned(X509Certificate cert) {
436 return cert.getIssuerDN().equals(cert.getSubjectDN());
440 * Initialization on demand class / synchronized singleton pattern.
442 private static class SecurityManagerInstanceHolder {
444 private static final SecurityManager instance = new SecurityManager();