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.ByteArrayInputStream;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.security.GeneralSecurityException;
30 import java.security.InvalidAlgorithmParameterException;
31 import java.security.InvalidKeyException;
32 import java.security.NoSuchAlgorithmException;
33 import java.security.NoSuchProviderException;
34 import java.security.PublicKey;
35 import java.security.Security;
36 import java.security.SignatureException;
37 import java.security.cert.CertPathBuilder;
38 import java.security.cert.CertStore;
39 import java.security.cert.Certificate;
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;
53 import org.bouncycastle.asn1.cms.ContentInfo;
54 import org.bouncycastle.cert.X509CertificateHolder;
55 import org.bouncycastle.cms.CMSException;
56 import org.bouncycastle.cms.CMSProcessableByteArray;
57 import org.bouncycastle.cms.CMSSignedData;
58 import org.bouncycastle.cms.CMSTypedData;
59 import org.bouncycastle.cms.SignerInformation;
60 import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
61 import org.bouncycastle.jce.provider.BouncyCastleProvider;
62 import org.bouncycastle.openssl.PEMParser;
63 import org.bouncycastle.operator.OperatorCreationException;
64 import org.openecomp.sdc.logging.api.Logger;
65 import org.openecomp.sdc.logging.api.LoggerFactory;
68 * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be
69 * reviewed Class is responsible for providing root trustedCertificates from configured location in onboarding
72 public class SecurityManager {
74 private static final String CERTIFICATE_DEFAULT_LOCATION = "cert";
75 public static final Set<String> ALLOWED_SIGNATURE_EXTENSIONS = ImmutableSet.of("cms");
76 public static final Set<String> ALLOWED_CERTIFICATE_EXTENSIONS = ImmutableSet.of("cert", "crt");
78 private Logger logger = LoggerFactory.getLogger(SecurityManager.class);
79 private Set<X509Certificate> trustedCertificates = new HashSet<>();
80 private File certificateDirectory;
83 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
84 Security.addProvider(new BouncyCastleProvider());
88 private SecurityManager() {
89 certificateDirectory = this.getcertDirectory(System.getenv("SDC_CERT_DIR"));
93 // Package level constructor use in tests to avoid power mock
94 SecurityManager(String sdcCertDir) {
95 certificateDirectory = this.getcertDirectory(sdcCertDir);
98 public static SecurityManager getInstance() {
99 return SecurityManagerInstanceHolder.instance;
103 * Initialization on demand class / synchronized singleton pattern.
105 private static class SecurityManagerInstanceHolder {
107 private static final SecurityManager instance = new SecurityManager();
111 * Checks the configured location for available trustedCertificates
113 * @return set of trustedCertificates
114 * @throws SecurityManagerException
116 public Set<X509Certificate> getTrustedCertificates() throws SecurityManagerException {
117 //if file number in certificate directory changed reload certs
118 String[] certFiles = certificateDirectory.list();
119 if (certFiles == null) {
120 logger.error("Certificate directory is empty!");
121 return ImmutableSet.copyOf(new HashSet<>());
123 if (trustedCertificates.size() != certFiles.length) {
124 trustedCertificates = new HashSet<>();
125 processCertificateDir();
127 return ImmutableSet.copyOf(trustedCertificates);
131 * Cleans certificate collection
133 public void cleanTrustedCertificates() {
134 trustedCertificates.clear();
138 * Verifies if packaged signed with trusted certificate
140 * @param messageSyntaxSignature - signature data in cms format
141 * @param packageCert - package certificate if not part of cms signature, can be null
142 * @param innerPackageFile data package signed with cms signature
143 * @return true if signature verified
144 * @throws SecurityManagerException
146 public boolean verifySignedData(final byte[] messageSyntaxSignature, final byte[] packageCert,
147 final byte[] innerPackageFile) throws SecurityManagerException {
148 try (ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature);
149 final PEMParser pemParser = new PEMParser(new InputStreamReader(signatureStream))) {
150 final Object parsedObject = pemParser.readObject();
151 if (!(parsedObject instanceof ContentInfo)) {
152 throw new SecurityManagerException("Signature is not recognized");
154 final ContentInfo signature = ContentInfo.getInstance(parsedObject);
155 final CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile);
156 final CMSSignedData signedData = new CMSSignedData(signedContent, signature);
158 final Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners();
159 final SignerInformation firstSigner = signers.iterator().next();
160 final X509Certificate cert;
161 if (packageCert == null) {
162 final Collection<X509CertificateHolder> firstSignerCertificates = signedData.getCertificates()
163 .getMatches(firstSigner.getSID());
164 if (!firstSignerCertificates.iterator().hasNext()) {
165 throw new SecurityManagerException(
166 "No certificate found in cms signature that should contain one!");
168 cert = loadCertificate(firstSignerCertificates.iterator().next().getEncoded());
170 cert = loadCertificate(packageCert);
173 if (verifyCertificate(cert, getTrustedCertificates()) == null) {
177 return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
178 } catch (OperatorCreationException | IOException | CMSException e) {
179 logger.error(e.getMessage(), e);
180 throw new SecurityManagerException("Unexpected error occurred during signature validation!", e);
181 } catch (GeneralSecurityException e) {
182 throw new SecurityManagerException("Could not verify signature!", e);
186 private void processCertificateDir() throws SecurityManagerException {
187 if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) {
188 logger.error("Issue with certificate directory, check if exists!");
192 File[] files = certificateDirectory.listFiles();
194 logger.error("Certificate directory is empty!");
197 for (File f : files) {
198 trustedCertificates.add(loadCertificate(f));
202 private File getcertDirectory(String sdcCertDir) {
203 String certDirLocation = sdcCertDir;
204 if (certDirLocation == null) {
205 certDirLocation = CERTIFICATE_DEFAULT_LOCATION;
207 return new File(certDirLocation);
210 private X509Certificate loadCertificate(File certFile) throws SecurityManagerException {
211 try (InputStream fileInputStream = new FileInputStream(certFile)) {
212 CertificateFactory factory = CertificateFactory.getInstance("X.509");
213 return (X509Certificate) factory.generateCertificate(fileInputStream);
214 } catch (CertificateException | IOException e) {
215 throw new SecurityManagerException("Error during loading Certificate file!", e);
219 private X509Certificate loadCertificate(byte[] certFile) throws SecurityManagerException {
220 try (InputStream in = new ByteArrayInputStream(certFile)) {
221 CertificateFactory factory = CertificateFactory.getInstance("X.509");
222 return (X509Certificate) factory.generateCertificate(in);
223 } catch (CertificateException | IOException e) {
224 throw new SecurityManagerException("Error during loading Certificate from bytes!", e);
228 private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
229 Set<X509Certificate> additionalCerts)
230 throws GeneralSecurityException, SecurityManagerException {
232 throw new SecurityManagerException("The certificate is empty!");
235 if (isExpired(cert)) {
236 throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
239 if (isSelfSigned(cert)) {
240 throw new SecurityManagerException("The certificate is self-signed.");
243 Set<X509Certificate> trustedRootCerts = new HashSet<>();
244 Set<X509Certificate> intermediateCerts = new HashSet<>();
245 for (X509Certificate additionalCert : additionalCerts) {
246 if (isSelfSigned(additionalCert)) {
247 trustedRootCerts.add(additionalCert);
249 intermediateCerts.add(additionalCert);
253 return verifyCertificate(cert, trustedRootCerts, intermediateCerts);
256 private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
257 Set<X509Certificate> allTrustedRootCerts,
258 Set<X509Certificate> allIntermediateCerts)
259 throws GeneralSecurityException {
261 // Create the selector that specifies the starting certificate
262 X509CertSelector selector = new X509CertSelector();
263 selector.setCertificate(cert);
265 // Create the trust anchors (set of root CA certificates)
266 Set<TrustAnchor> trustAnchors = new HashSet<>();
267 for (X509Certificate trustedRootCert : allTrustedRootCerts) {
268 trustAnchors.add(new TrustAnchor(trustedRootCert, null));
271 // Configure the PKIX certificate builder algorithm parameters
272 PKIXBuilderParameters pkixParams;
274 pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
275 } catch (InvalidAlgorithmParameterException ex) {
276 throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex);
279 // Not supporting CRL checks for now
280 pkixParams.setRevocationEnabled(false);
282 Set<X509Certificate> certSet = new HashSet<>();
284 pkixParams.addCertStore(createCertStore(certSet));
285 pkixParams.addCertStore(createCertStore(allIntermediateCerts));
286 pkixParams.addCertStore(createCertStore(allTrustedRootCerts));
288 CertPathBuilder builder = CertPathBuilder
289 .getInstance(CertPathBuilder.getDefaultType(), BouncyCastleProvider.PROVIDER_NAME);
290 return (PKIXCertPathBuilderResult) builder.build(pkixParams);
293 private CertStore createCertStore(Set<X509Certificate> certificateSet) throws InvalidAlgorithmParameterException,
294 NoSuchAlgorithmException, NoSuchProviderException {
295 return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet),
296 BouncyCastleProvider.PROVIDER_NAME);
299 private boolean isExpired(X509Certificate cert) {
301 cert.checkValidity();
302 } catch (CertificateExpiredException e) {
303 logger.error(e.getMessage(), e);
305 } catch (CertificateNotYetValidException e) {
306 logger.error(e.getMessage(), e);
312 private boolean isSelfSigned(Certificate cert)
313 throws CertificateException, NoSuchAlgorithmException,
314 NoSuchProviderException {
316 // Try to verify certificate signature with its own public key
317 PublicKey key = cert.getPublicKey();
320 } catch (SignatureException | InvalidKeyException e) {
321 logger.error(e.getMessage(), e);