From b2f9dc5d3bc02564b4d952caa0bf2ccd20dfc6af Mon Sep 17 00:00:00 2001 From: kooper Date: Tue, 2 Apr 2019 09:22:01 +0000 Subject: Verify signature Change-Id: I8fc5d50d74d3dd8031c96ee16708489dc7c789b8 Issue-ID: SDC-2163 Signed-off-by: kooper --- .../security/SecurityManager.java | 269 ++++++++++++++++++--- .../security/SecurityManagerException.java | 27 ++- 2 files changed, 264 insertions(+), 32 deletions(-) (limited to 'openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp') diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java index d2da7ef20f..7b1890dcaa 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java @@ -20,83 +20,292 @@ 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.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +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.CertificateNotYetValidException; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.PKIXCertPathBuilderResult; +import java.security.cert.TrustAnchor; +import java.security.cert.X509CertSelector; +import java.security.cert.X509Certificate; +import java.util.Collection; import java.util.HashSet; import java.util.Set; /** - * This is temporary solution. When AAF provides functionality for verifying certificates, this class should be reviewed - * Class is responsible for providing root certificates 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 = "/root/cert"; + private static final String CERTIFICATE_DEFAULT_LOCATION = "cert"; + private static final SecurityManager INSTANCE = new SecurityManager(); private Logger logger = LoggerFactory.getLogger(SecurityManager.class); - private Set certificates = new HashSet<>(); + private Set trustedCertificates = new HashSet<>(); private File certificateDirectory; + static { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } - public SecurityManager(){ + private SecurityManager() { certificateDirectory = this.getcertDirectory(); } - private void processCertificateDir() { - if(!certificateDirectory.exists() || !certificateDirectory.isDirectory()){ + public static SecurityManager getInstance(){ + return INSTANCE; + } + + /** + * + * Checks the configured location for available trustedCertificates + * + * @return set of trustedCertificates + * @throws SecurityManagerException + */ + public Set getTrustedCertificates() throws SecurityManagerException { + //if file number in certificate directory changed reload certs + String[] certFiles = certificateDirectory.list(); + if (certFiles == null) { + logger.error("Certificate directory is empty!"); + return ImmutableSet.copyOf(new HashSet<>()); + } + if (trustedCertificates.size() != certFiles.length) { + trustedCertificates = new HashSet<>(); + processCertificateDir(); + } + return ImmutableSet.copyOf(trustedCertificates); + } + + /** + * Cleans certificate collection + */ + public void cleanTrustedCertificates(){ + trustedCertificates.clear(); + } + + /** + * + * Verifies if packaged signed with trusted certificate + * + * @param messageSyntaxSignature - signature data in cms format + * @param packageCert - package certificate if not part of cms signature, can be null + * @param innerPackageFile data package signed with cms signature + * @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(); + if (!(parsedObject instanceof ContentInfo)) { + throw new SecurityManagerException("Signature is not recognized"); + } + ContentInfo signature = ContentInfo.getInstance(parsedObject); + CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile); + CMSSignedData signedData = new CMSSignedData(signedContent, signature); + + Collection signers = signedData.getSignerInfos().getSigners(); + SignerInformation firstSigner = signers.iterator().next(); + Store certificates = signedData.getCertificates(); + X509Certificate cert; + if (packageCert == null) { + Collection firstSignerCertificates = certificates.getMatches(firstSigner.getSID()); + if(!firstSignerCertificates.iterator().hasNext()){ + throw new SecurityManagerException("No certificate found in cms signature that should contain one!"); + } + X509CertificateHolder firstSignerFirstCertificate = firstSignerCertificates.iterator().next(); + cert = loadCertificate(firstSignerFirstCertificate.getEncoded()); + } else { + cert = loadCertificate(packageCert); + } + + PKIXCertPathBuilderResult result = verifyCertificate(cert, getTrustedCertificates()); + + if (result == null) { + return false; + } + + 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 void processCertificateDir() throws SecurityManagerException { + if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) { logger.error("Issue with certificate directory, check if exists!"); return; } - File [] files = certificateDirectory.listFiles(); - if(files == null){ + File[] files = certificateDirectory.listFiles(); + if (files == null) { logger.error("Certificate directory is empty!"); return; } - for(File f : files) { - certificates.add(loadCertificate(f)); + for (File f : files) { + trustedCertificates.add(loadCertificate(f)); } } private File getcertDirectory() { String certDirLocation = System.getenv("SDC_CERT_DIR"); - if(certDirLocation == null){ + if (certDirLocation == null) { certDirLocation = CERTIFICATE_DEFAULT_LOCATION; } return new File(certDirLocation); } - private Certificate loadCertificate(File certFile){ - try (InputStream fileInputStream = new FileInputStream(certFile)){ + private X509Certificate loadCertificate(File certFile) throws SecurityManagerException { + try (InputStream fileInputStream = new FileInputStream(certFile)) { CertificateFactory factory = CertificateFactory.getInstance("X.509"); - return factory.generateCertificate(fileInputStream); - } catch (CertificateException|IOException e) { + return (X509Certificate) factory.generateCertificate(fileInputStream); + } catch (CertificateException | IOException e) { throw new SecurityManagerException("Error during loading Certificate file!", e); } } - /** - * Checks the configured location for available certificates - * @return set of certificates - */ - public Set getCertificates() { - //if file number in certificate directory changed reload certs - String[] certFiles = certificateDirectory.list(); - if(certFiles == null){ - logger.error("Certificate directory is empty!"); - return ImmutableSet.copyOf(new HashSet<>()); + private X509Certificate loadCertificate(byte[] certFile) throws SecurityManagerException { + try (InputStream in = new ByteArrayInputStream(certFile)) { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) factory.generateCertificate(in); + } catch (CertificateException | IOException e) { + throw new SecurityManagerException("Error during loading Certificate from bytes!", e); } - if(certificates.size() != certFiles.length){ - certificates = new HashSet<>(); - processCertificateDir(); + } + + private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, + Set 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 trustedRootCerts = new HashSet<>(); + Set intermediateCerts = new HashSet<>(); + for (X509Certificate additionalCert : additionalCerts) { + if (isSelfSigned(additionalCert)) { + trustedRootCerts.add(additionalCert); + } else { + intermediateCerts.add(additionalCert); + } + } + + return verifyCertificate(cert, trustedRootCerts, intermediateCerts); + } + + private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, + Set allTrustedRootCerts, + Set 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 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) { + throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex); + } + + // Not supporting CRL checks for now + pkixParams.setRevocationEnabled(false); + + Set 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 certificateSet) throws InvalidAlgorithmParameterException, + NoSuchAlgorithmException, NoSuchProviderException { + return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet), BouncyCastleProvider.PROVIDER_NAME); + } + + private boolean isExpired(X509Certificate cert) { + try { + cert.checkValidity(); + } catch (CertificateExpiredException e) { + logger.error(e.getMessage(), e); + return true; + } catch (CertificateNotYetValidException 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; } - return ImmutableSet.copyOf(certificates); } } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerException.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerException.java index 5c5a23a5f8..cdba2f8f0b 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerException.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerException.java @@ -1,8 +1,31 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2019, Nordix Foundation. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ package org.openecomp.sdc.vendorsoftwareproduct.security; -public class SecurityManagerException extends RuntimeException { +public class SecurityManagerException extends Exception { - public SecurityManagerException(String s, Throwable t) { + public SecurityManagerException(String s) { super(s); } + + public SecurityManagerException(String s, Throwable t) { + super(s, t); + } } -- cgit 1.2.3-korg