package org.openecomp.sdc.vendorsoftwareproduct.security;
import com.google.common.collect.ImmutableSet;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cms.CMSException;
-import org.bouncycastle.cms.CMSProcessableByteArray;
-import org.bouncycastle.cms.CMSSignedData;
-import org.bouncycastle.cms.CMSTypedData;
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.util.Store;
-import org.openecomp.sdc.logging.api.Logger;
-import org.openecomp.sdc.logging.api.LoggerFactory;
-
+import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
-import java.security.PublicKey;
import java.security.Security;
-import java.security.SignatureException;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertStore;
-import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSProcessableFile;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.openecomp.sdc.be.csar.storage.ArtifactInfo;
+import org.openecomp.sdc.be.csar.storage.ArtifactStorageConfig;
+import org.openecomp.sdc.be.csar.storage.ArtifactStorageManager;
+import org.openecomp.sdc.be.csar.storage.StorageFactory;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage;
/**
- * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be reviewed
- * Class is responsible for providing root trustedCertificates from configured location in onboarding container.
+ * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be reviewed Class is responsible
+ * for providing root trustedCertificates from configured location in onboarding container.
*/
public class SecurityManager {
- private static final String CERTIFICATE_DEFAULT_LOCATION = "cert";
- private static final SecurityManager INSTANCE = new SecurityManager();
- private Logger logger = LoggerFactory.getLogger(SecurityManager.class);
- private Set<X509Certificate> trustedCertificates = new HashSet<>();
- private File certificateDirectory;
+ public static final Set<String> ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms");
+ public static final Set<String> ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt");
+ private static final String CERTIFICATE_DEFAULT_LOCATION = "cert";
+ private static final Logger LOGGER = LoggerFactory.getLogger(SecurityManager.class);
+ private static final String UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION = "Unexpected error occurred during signature validation!";
+ private static final String COULD_NOT_VERIFY_SIGNATURE = "Could not verify signature!";
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
}
}
+ private Set<X509Certificate> trustedCertificates = new HashSet<>();
+ private Set<X509Certificate> trustedCertificatesFromPackage = new HashSet<>();
+ private File certificateDirectory;
+
private SecurityManager() {
- certificateDirectory = this.getcertDirectory();
+ certificateDirectory = this.getcertDirectory(System.getenv("SDC_CERT_DIR"));
}
- public static SecurityManager getInstance(){
- return INSTANCE;
+ // Package level constructor use in tests to avoid power mock
+ SecurityManager(String sdcCertDir) {
+ certificateDirectory = this.getcertDirectory(sdcCertDir);
+ }
+
+ public static SecurityManager getInstance() {
+ return SecurityManagerInstanceHolder.instance;
}
/**
- *
* Checks the configured location for available trustedCertificates
*
* @return set of trustedCertificates
//if file number in certificate directory changed reload certs
String[] certFiles = certificateDirectory.list();
if (certFiles == null) {
- logger.error("Certificate directory is empty!");
+ LOGGER.error("Certificate directory is empty!");
return ImmutableSet.copyOf(new HashSet<>());
}
if (trustedCertificates.size() != certFiles.length) {
trustedCertificates = new HashSet<>();
processCertificateDir();
}
+ if (!trustedCertificatesFromPackage.isEmpty()) {
+ return Stream.concat(trustedCertificatesFromPackage.stream(), trustedCertificates.stream()).collect(Collectors.toUnmodifiableSet());
+ }
return ImmutableSet.copyOf(trustedCertificates);
}
/**
* Cleans certificate collection
*/
- public void cleanTrustedCertificates(){
+ public void cleanTrustedCertificates() {
trustedCertificates.clear();
}
/**
- *
* Verifies if packaged signed with trusted certificate
*
* @param messageSyntaxSignature - signature data in cms format
* @return true if signature verified
* @throws SecurityManagerException
*/
- public boolean verifySignedData(final byte[] messageSyntaxSignature, final byte[] packageCert,
- final byte[] innerPackageFile) throws SecurityManagerException{
- try (ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature)) {
- Object parsedObject = new PEMParser(new InputStreamReader(signatureStream)).readObject();
+ public boolean verifySignedData(final byte[] messageSyntaxSignature,
+ final byte[] packageCert,
+ final byte[] innerPackageFile) throws SecurityManagerException {
+ try (final ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature);
+ final PEMParser pemParser = new PEMParser(new InputStreamReader(signatureStream))) {
+ final Object parsedObject = pemParser.readObject();
+ if (!(parsedObject instanceof ContentInfo)) {
+ throw new SecurityManagerException("Signature is not recognized");
+ }
+ return verify(packageCert, new CMSSignedData(new CMSProcessableByteArray(innerPackageFile), ContentInfo.getInstance(parsedObject)));
+ } catch (final IOException | CMSException e) {
+ LOGGER.error(e.getMessage(), e);
+ throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e);
+ }
+ }
+
+ public boolean verifyPackageSignedData(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo)
+ throws SecurityManagerException {
+ boolean fail = false;
+
+ final StorageFactory storageFactory = new StorageFactory();
+ final ArtifactStorageManager artifactStorageManager = storageFactory.createArtifactStorageManager();
+ final ArtifactStorageConfig storageConfiguration = artifactStorageManager.getStorageConfiguration();
+
+ final var fileContentHandler = signedPackage.getFileContentHandler();
+ byte[] packageCert = null;
+ final Optional<String> certificateFilePath = signedPackage.getCertificateFilePath();
+ if (certificateFilePath.isPresent()) {
+ packageCert = fileContentHandler.getFileContent(certificateFilePath.get());
+ }
+
+ final Path folder = Path.of(storageConfiguration.getTempPath());
+ try {
+ Files.createDirectories(folder);
+ } catch (final IOException e) {
+ fail = true;
+ LOGGER.error("Failed to create directory '{}'", folder, e);
+ throw new SecurityManagerException(String.format("Failed to create directory '%s'", folder), e);
+ }
+
+ final var target = folder.resolve(UUID.randomUUID().toString());
+
+ try (final var signatureStream = new ByteArrayInputStream(fileContentHandler.getFileContent(signedPackage.getSignatureFilePath()));
+ final var pemParser = new PEMParser(new InputStreamReader(signatureStream))) {
+ final var parsedObject = pemParser.readObject();
if (!(parsedObject instanceof ContentInfo)) {
+ fail = true;
+ LOGGER.error("Signature is not recognized");
throw new SecurityManagerException("Signature is not recognized");
}
- ContentInfo signature = ContentInfo.getInstance(parsedObject);
- CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile);
- CMSSignedData signedData = new CMSSignedData(signedContent, signature);
-
- Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners();
- SignerInformation firstSigner = signers.iterator().next();
- Store certificates = signedData.getCertificates();
- X509Certificate cert;
- if (packageCert == null) {
- Collection<X509CertificateHolder> firstSignerCertificates = certificates.getMatches(firstSigner.getSID());
- if(!firstSignerCertificates.iterator().hasNext()){
- throw new SecurityManagerException("No certificate found in cms signature that should contain one!");
+
+ try (final InputStream inputStream = artifactStorageManager.get(artifactInfo)) {
+ if (!findCSARandExtract(inputStream, target)) {
+ fail = true;
+ return false;
}
- X509CertificateHolder firstSignerFirstCertificate = firstSignerCertificates.iterator().next();
- cert = loadCertificate(firstSignerFirstCertificate.getEncoded());
- } else {
- cert = loadCertificate(packageCert);
}
+ final var verify = verify(packageCert, new CMSSignedData(new CMSProcessableFile(target.toFile()), ContentInfo.getInstance(parsedObject)));
+ fail = !verify;
+ return verify;
+ } catch (final IOException e) {
+ fail = true;
+ LOGGER.error(e.getMessage(), e);
+ throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e);
+ } catch (final CMSException e) {
+ fail = true;
+ LOGGER.error(e.getMessage(), e);
+ throw new SecurityManagerException(COULD_NOT_VERIFY_SIGNATURE, e);
+ } catch (final SecurityManagerException e) {
+ fail = true;
+ LOGGER.error(e.getMessage(), e);
+ throw e;
+ } finally {
+ deleteFile(target);
+ if (fail) {
+ artifactStorageManager.delete(artifactInfo);
+ }
+ }
+ }
+
+ private void deleteFile(final Path filePath) {
+ try {
+ Files.delete(filePath);
+ } catch (final IOException e) {
+ LOGGER.warn("Failed to delete '{}' after verifying package signed data", filePath, e);
+ }
+ }
- PKIXCertPathBuilderResult result = verifyCertificate(cert, getTrustedCertificates());
+ private boolean verify(final byte[] packageCert, final CMSSignedData signedData) throws SecurityManagerException {
+ final SignerInformation firstSigner = signedData.getSignerInfos().getSigners().iterator().next();
+ final X509Certificate cert;
+ Collection<X509CertificateHolder> certs;
+ if (packageCert == null) {
+ certs = signedData.getCertificates().getMatches(null);
+ cert = readSignCert(certs, firstSigner)
+ .orElseThrow(() -> new SecurityManagerException("No certificate found in cms signature that should contain one!"));
+ } else {
+ try {
+ certs = parseCertsFromPem(packageCert);
+ } catch (final IOException e) {
+ LOGGER.error("Failed to parse certificate from PEM", e);
+ throw new SecurityManagerException("Failed to parse certificate from PEM", e);
+ }
+ cert = readSignCert(certs, firstSigner)
+ .orElseThrow(() -> new SecurityManagerException("No matching certificate found in certificate file that should contain one!"));
+ }
+ trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner);
+ if (verifyCertificate(cert, getTrustedCertificates()) == null) {
+ return false;
+ }
+ try {
+ return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
+ } catch (CMSException | OperatorCreationException e) {
+ LOGGER.error("Failed to verify package signed data", e);
+ throw new SecurityManagerException("Failed to verify package signed data", e);
+ }
+ }
- if (result == null) {
- return false;
+ private boolean findCSARandExtract(final InputStream inputStream, final Path target) throws IOException {
+ final AtomicBoolean found = new AtomicBoolean(false);
+
+ final var zipInputStream = new ZipInputStream(inputStream);
+ ZipEntry zipEntry;
+ byte[] buffer = new byte[2048];
+ while ((zipEntry = zipInputStream.getNextEntry()) != null) {
+ final var entryName = zipEntry.getName();
+ if (!zipEntry.isDirectory() && entryName.toLowerCase().endsWith(".csar")) {
+ try (final FileOutputStream fos = new FileOutputStream(target.toFile());
+ final BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length)) {
+
+ int len;
+ while ((len = zipInputStream.read(buffer)) > 0) {
+ bos.write(buffer, 0, len);
+ }
+ }
+ found.set(true);
}
+ }
+ return found.get();
+ }
- return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
- } catch (OperatorCreationException | IOException | CMSException e) {
- logger.error(e.getMessage(), e);
- throw new SecurityManagerException("Unexpected error occurred during signature validation!", e);
- } catch (GeneralSecurityException e){
- throw new SecurityManagerException("Could not verify signature!", e);
+ private Optional<X509Certificate> readSignCert(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
+ return certs.stream().filter(crt -> firstSigner.getSID().match(crt)).findAny().map(this::loadCertificate);
+ }
+
+ private Set<X509Certificate> readTrustedCerts(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
+ return certs.stream().filter(crt -> !firstSigner.getSID().match(crt)).map(this::loadCertificate).filter(Predicate.not(this::isSelfSigned))
+ .collect(Collectors.toSet());
+ }
+
+ private Set<X509CertificateHolder> parseCertsFromPem(final byte[] packageCert) throws IOException {
+ final ByteArrayInputStream packageCertStream = new ByteArrayInputStream(packageCert);
+ final PEMParser pemParser = new PEMParser(new InputStreamReader(packageCertStream));
+ Object readObject = pemParser.readObject();
+ Set<X509CertificateHolder> allCerts = new HashSet<>();
+ while (readObject != null) {
+ if (readObject instanceof X509CertificateHolder) {
+ allCerts.add((X509CertificateHolder) readObject);
+ }
+ readObject = pemParser.readObject();
}
+ return allCerts;
}
private void processCertificateDir() throws SecurityManagerException {
if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) {
- logger.error("Issue with certificate directory, check if exists!");
+ LOGGER.error("Issue with certificate directory, check if exists!");
return;
}
-
File[] files = certificateDirectory.listFiles();
if (files == null) {
- logger.error("Certificate directory is empty!");
+ LOGGER.error("Certificate directory is empty!");
return;
}
for (File f : files) {
}
}
- private File getcertDirectory() {
- String certDirLocation = System.getenv("SDC_CERT_DIR");
+ private File getcertDirectory(String sdcCertDir) {
+ String certDirLocation = sdcCertDir;
if (certDirLocation == null) {
certDirLocation = CERTIFICATE_DEFAULT_LOCATION;
}
}
private X509Certificate loadCertificate(File certFile) throws SecurityManagerException {
- try (InputStream fileInputStream = new FileInputStream(certFile)) {
- CertificateFactory factory = CertificateFactory.getInstance("X.509");
- return (X509Certificate) factory.generateCertificate(fileInputStream);
- } catch (CertificateException | IOException e) {
- throw new SecurityManagerException("Error during loading Certificate file!", e);
+ try (FileInputStream fi = new FileInputStream(certFile)) {
+ return loadCertificateFactory(fi);
+ } catch (IOException e) {
+ LOGGER.error("Error during loading Certificate from file!", e);
+ throw new SecurityManagerException("Error during loading Certificate from file!", e);
}
}
- private X509Certificate loadCertificate(byte[] certFile) throws SecurityManagerException {
- try (InputStream in = new ByteArrayInputStream(certFile)) {
+ private X509Certificate loadCertificate(X509CertificateHolder cert) {
+ try {
+ return loadCertificateFactory(new ByteArrayInputStream(cert.getEncoded()));
+ } catch (IOException | SecurityManagerException e) {
+ LOGGER.error("Error during loading Certificate from bytes!", e);
+ throw new RuntimeException("Error during loading Certificate from bytes!", e);
+ }
+ }
+
+ private X509Certificate loadCertificateFactory(InputStream in) throws SecurityManagerException {
+ try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(in);
- } catch (CertificateException | IOException e) {
+ } catch (CertificateException e) {
+ LOGGER.error("Error during loading Certificate from bytes!", e);
throw new SecurityManagerException("Error during loading Certificate from bytes!", e);
}
}
- private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
- Set<X509Certificate> additionalCerts) throws GeneralSecurityException, SecurityManagerException {
- if (null == cert) {
- throw new SecurityManagerException("The certificate is empty!");
- }
-
- if (isExpired(cert)) {
- throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
- }
-
- if (isSelfSigned(cert)) {
- throw new SecurityManagerException("The certificate is self-signed.");
- }
-
- Set<X509Certificate> trustedRootCerts = new HashSet<>();
- Set<X509Certificate> intermediateCerts = new HashSet<>();
- for (X509Certificate additionalCert : additionalCerts) {
- if (isSelfSigned(additionalCert)) {
- trustedRootCerts.add(additionalCert);
- } else {
- intermediateCerts.add(additionalCert);
- }
+ private PKIXCertPathBuilderResult verifyCertificate(final X509Certificate cert,
+ final Set<X509Certificate> additionalCerts) throws SecurityManagerException {
+ if (null == cert) {
+ LOGGER.error("The certificate is empty!");
+ throw new SecurityManagerException("The certificate is empty!");
+ }
+ if (isExpired(cert)) {
+ LOGGER.error("The certificate expired on: {}", cert.getNotAfter());
+ throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
+ }
+ if (isSelfSigned(cert)) {
+ LOGGER.error("The certificate is self-signed.");
+ throw new SecurityManagerException("The certificate is self-signed.");
+ }
+ Set<X509Certificate> trustedRootCerts = new HashSet<>();
+ Set<X509Certificate> intermediateCerts = new HashSet<>();
+ for (X509Certificate additionalCert : additionalCerts) {
+ if (isSelfSigned(additionalCert)) {
+ trustedRootCerts.add(additionalCert);
+ } else {
+ intermediateCerts.add(additionalCert);
}
-
+ }
+ try {
return verifyCertificate(cert, trustedRootCerts, intermediateCerts);
+ } catch (final GeneralSecurityException e) {
+ LOGGER.error("Failed to verify certificate", e);
+ throw new SecurityManagerException("Failed to verify certificate", e);
+ }
}
- private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
- Set<X509Certificate> allTrustedRootCerts,
- Set<X509Certificate> allIntermediateCerts)
- throws GeneralSecurityException {
-
+ private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, Set<X509Certificate> allTrustedRootCerts,
+ Set<X509Certificate> allIntermediateCerts) throws GeneralSecurityException {
// Create the selector that specifies the starting certificate
X509CertSelector selector = new X509CertSelector();
selector.setCertificate(cert);
-
// Create the trust anchors (set of root CA certificates)
Set<TrustAnchor> trustAnchors = new HashSet<>();
for (X509Certificate trustedRootCert : allTrustedRootCerts) {
trustAnchors.add(new TrustAnchor(trustedRootCert, null));
}
-
// Configure the PKIX certificate builder algorithm parameters
PKIXBuilderParameters pkixParams;
try {
pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
} catch (InvalidAlgorithmParameterException ex) {
+ LOGGER.error("No root CA has been found for this certificate", ex);
throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex);
}
-
// Not supporting CRL checks for now
pkixParams.setRevocationEnabled(false);
-
Set<X509Certificate> certSet = new HashSet<>();
certSet.add(cert);
pkixParams.addCertStore(createCertStore(certSet));
pkixParams.addCertStore(createCertStore(allIntermediateCerts));
pkixParams.addCertStore(createCertStore(allTrustedRootCerts));
-
CertPathBuilder builder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType(), BouncyCastleProvider.PROVIDER_NAME);
return (PKIXCertPathBuilderResult) builder.build(pkixParams);
}
- private CertStore createCertStore(Set<X509Certificate> certificateSet) throws InvalidAlgorithmParameterException,
- NoSuchAlgorithmException, NoSuchProviderException {
+ private CertStore createCertStore(Set<X509Certificate> certificateSet)
+ throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet), BouncyCastleProvider.PROVIDER_NAME);
}
try {
cert.checkValidity();
} catch (CertificateExpiredException e) {
- logger.error(e.getMessage(), e);
+ LOGGER.error(e.getMessage(), e);
return true;
} catch (CertificateNotYetValidException e) {
- logger.error(e.getMessage(), e);
+ LOGGER.error(e.getMessage(), e);
return false;
}
return false;
}
- private boolean isSelfSigned(Certificate cert)
- throws CertificateException, NoSuchAlgorithmException,
- NoSuchProviderException {
- try {
- // Try to verify certificate signature with its own public key
- PublicKey key = cert.getPublicKey();
- cert.verify(key);
- return true;
- } catch (SignatureException | InvalidKeyException e) {
- logger.error(e.getMessage(), e);
- //not self-signed
- return false;
- }
+ private boolean isSelfSigned(X509Certificate cert) {
+ return cert.getIssuerDN().equals(cert.getSubjectDN());
+ }
+
+ /**
+ * Initialization on demand class / synchronized singleton pattern.
+ */
+ private static class SecurityManagerInstanceHolder {
+
+ private static final SecurityManager instance = new SecurityManager();
}
}