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;
25 import java.security.GeneralSecurityException;
26 import java.security.InvalidAlgorithmParameterException;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.NoSuchProviderException;
29 import java.security.Security;
30 import java.security.cert.CertPathBuilder;
31 import java.security.cert.CertStore;
32 import java.security.cert.CertificateException;
33 import java.security.cert.CertificateExpiredException;
34 import java.security.cert.CertificateFactory;
35 import java.security.cert.CertificateNotYetValidException;
36 import java.security.cert.CollectionCertStoreParameters;
37 import java.security.cert.PKIXBuilderParameters;
38 import java.security.cert.PKIXCertPathBuilderResult;
39 import java.security.cert.TrustAnchor;
40 import java.security.cert.X509CertSelector;
41 import java.security.cert.X509Certificate;
42 import java.util.Collection;
43 import java.util.HashSet;
44 import java.util.function.Predicate;
45 import java.util.Optional;
46 import java.util.stream.Collectors;
47 import java.util.stream.Stream;
50 import org.bouncycastle.asn1.cms.ContentInfo;
51 import org.bouncycastle.cert.X509CertificateHolder;
52 import org.bouncycastle.cms.CMSException;
53 import org.bouncycastle.cms.CMSProcessableByteArray;
54 import org.bouncycastle.cms.CMSSignedData;
55 import org.bouncycastle.cms.CMSTypedData;
56 import org.bouncycastle.cms.SignerInformation;
57 import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
58 import org.bouncycastle.jce.provider.BouncyCastleProvider;
59 import org.bouncycastle.openssl.PEMParser;
60 import org.bouncycastle.operator.OperatorCreationException;
61 import org.openecomp.sdc.logging.api.Logger;
62 import org.openecomp.sdc.logging.api.LoggerFactory;
65 * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be
66 * reviewed Class is responsible for providing root trustedCertificates from configured location in onboarding
69 public class SecurityManager {
71 private static final String CERTIFICATE_DEFAULT_LOCATION = "cert";
72 public static final Set<String> ALLOWED_SIGNATURE_EXTENSIONS = ImmutableSet.of("cms");
73 public static final Set<String> ALLOWED_CERTIFICATE_EXTENSIONS = ImmutableSet.of("cert", "crt");
75 private Logger logger = LoggerFactory.getLogger(SecurityManager.class);
76 private Set<X509Certificate> trustedCertificates = new HashSet<>();
77 private Set<X509Certificate> trustedCertificatesFromPackage = new HashSet<>();
78 private File certificateDirectory;
81 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
82 Security.addProvider(new BouncyCastleProvider());
86 private SecurityManager() {
87 certificateDirectory = this.getcertDirectory(System.getenv("SDC_CERT_DIR"));
91 // Package level constructor use in tests to avoid power mock
92 SecurityManager(String sdcCertDir) {
93 certificateDirectory = this.getcertDirectory(sdcCertDir);
96 public static SecurityManager getInstance() {
97 return SecurityManagerInstanceHolder.instance;
101 * Initialization on demand class / synchronized singleton pattern.
103 private static class SecurityManagerInstanceHolder {
105 private static final SecurityManager instance = new SecurityManager();
109 * Checks the configured location for available trustedCertificates
111 * @return set of trustedCertificates
112 * @throws SecurityManagerException
114 public Set<X509Certificate> getTrustedCertificates() throws SecurityManagerException, FileNotFoundException {
115 //if file number in certificate directory changed reload certs
116 String[] certFiles = certificateDirectory.list();
117 if (certFiles == null) {
118 logger.error("Certificate directory is empty!");
119 return ImmutableSet.copyOf(new HashSet<>());
121 if (trustedCertificates.size() != certFiles.length) {
122 trustedCertificates = new HashSet<>();
123 processCertificateDir();
125 if (!trustedCertificatesFromPackage.isEmpty()) {
126 return Stream.concat(trustedCertificatesFromPackage.stream(), trustedCertificates.stream())
127 .collect(Collectors.toUnmodifiableSet());
129 return ImmutableSet.copyOf(trustedCertificates);
133 * Cleans certificate collection
135 public void cleanTrustedCertificates() {
136 trustedCertificates.clear();
140 * Verifies if packaged signed with trusted certificate
142 * @param messageSyntaxSignature - signature data in cms format
143 * @param packageCert - package certificate if not part of cms signature, can be null
144 * @param innerPackageFile data package signed with cms signature
145 * @return true if signature verified
146 * @throws SecurityManagerException
148 public boolean verifySignedData(final byte[] messageSyntaxSignature, final byte[] packageCert,
149 final byte[] innerPackageFile) throws SecurityManagerException {
150 try (ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature);
151 final PEMParser pemParser = new PEMParser(new InputStreamReader(signatureStream))) {
152 final Object parsedObject = pemParser.readObject();
153 if (!(parsedObject instanceof ContentInfo)) {
154 throw new SecurityManagerException("Signature is not recognized");
156 final ContentInfo signature = ContentInfo.getInstance(parsedObject);
157 final CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile);
158 final CMSSignedData signedData = new CMSSignedData(signedContent, signature);
160 final Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners();
161 final SignerInformation firstSigner = signers.iterator().next();
162 final X509Certificate cert;
163 Collection<X509CertificateHolder> certs;
164 if (packageCert == null) {
165 certs = signedData.getCertificates().getMatches(null);
166 cert = readSignCert(certs, firstSigner).orElseThrow(() -> new SecurityManagerException(
167 "No certificate found in cms signature that should contain one!"));
169 certs = parseCertsFromPem(packageCert);
170 cert = readSignCert(certs, firstSigner).orElseThrow(() -> new SecurityManagerException(
171 "No matching certificate found in certificate file that should contain one!"));
173 trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner);
175 if (verifyCertificate(cert, getTrustedCertificates()) == null) {
179 return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
180 } catch (OperatorCreationException | IOException | CMSException e) {
181 logger.error(e.getMessage(), e);
182 throw new SecurityManagerException("Unexpected error occurred during signature validation!", e);
183 } catch (GeneralSecurityException e) {
184 throw new SecurityManagerException("Could not verify signature!", e);
188 private Optional<X509Certificate> readSignCert(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
189 return certs.stream()
190 .filter(crt -> firstSigner.getSID().match(crt))
192 .map(this::loadCertificate);
195 private Set<X509Certificate> readTrustedCerts(final Collection<X509CertificateHolder> certs, final SignerInformation firstSigner) {
196 return certs.stream()
197 .filter(crt -> !firstSigner.getSID().match(crt))
198 .map(this::loadCertificate)
199 .filter(Predicate.not(this::isSelfSigned))
200 .collect(Collectors.toSet());
203 private Set<X509CertificateHolder> parseCertsFromPem(final byte[] packageCert) throws IOException {
204 final ByteArrayInputStream packageCertStream = new ByteArrayInputStream(packageCert);
205 final PEMParser pemParser = new PEMParser(new InputStreamReader(packageCertStream));
206 Object readObject = pemParser.readObject();
207 Set<X509CertificateHolder> allCerts = new HashSet<>();
208 while (readObject != null) {
209 if (readObject instanceof X509CertificateHolder) {
210 allCerts.add((X509CertificateHolder) readObject);
212 readObject = pemParser.readObject();
217 private void processCertificateDir() throws SecurityManagerException, FileNotFoundException {
218 if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) {
219 logger.error("Issue with certificate directory, check if exists!");
223 File[] files = certificateDirectory.listFiles();
225 logger.error("Certificate directory is empty!");
228 for (File f : files) {
229 trustedCertificates.add(loadCertificate(f));
233 private File getcertDirectory(String sdcCertDir) {
234 String certDirLocation = sdcCertDir;
235 if (certDirLocation == null) {
236 certDirLocation = CERTIFICATE_DEFAULT_LOCATION;
238 return new File(certDirLocation);
241 private X509Certificate loadCertificate(File certFile) throws SecurityManagerException, FileNotFoundException {
242 return loadCertificateFactory(new FileInputStream(certFile));
245 private X509Certificate loadCertificate(X509CertificateHolder cert) {
247 return loadCertificateFactory(new ByteArrayInputStream(cert.getEncoded()));
248 } catch (IOException | SecurityManagerException e) {
249 throw new RuntimeException("Error during loading Certificate from bytes!", e);
253 private X509Certificate loadCertificateFactory(InputStream in) throws SecurityManagerException {
255 CertificateFactory factory = CertificateFactory.getInstance("X.509");
256 return (X509Certificate) factory.generateCertificate(in);
257 } catch (CertificateException e) {
258 throw new SecurityManagerException("Error during loading Certificate from bytes!", e);
262 private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
263 Set<X509Certificate> additionalCerts)
264 throws GeneralSecurityException, SecurityManagerException {
266 throw new SecurityManagerException("The certificate is empty!");
269 if (isExpired(cert)) {
270 throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
273 if (isSelfSigned(cert)) {
274 throw new SecurityManagerException("The certificate is self-signed.");
277 Set<X509Certificate> trustedRootCerts = new HashSet<>();
278 Set<X509Certificate> intermediateCerts = new HashSet<>();
279 for (X509Certificate additionalCert : additionalCerts) {
280 if (isSelfSigned(additionalCert)) {
281 trustedRootCerts.add(additionalCert);
283 intermediateCerts.add(additionalCert);
287 return verifyCertificate(cert, trustedRootCerts, intermediateCerts);
290 private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
291 Set<X509Certificate> allTrustedRootCerts,
292 Set<X509Certificate> allIntermediateCerts)
293 throws GeneralSecurityException {
295 // Create the selector that specifies the starting certificate
296 X509CertSelector selector = new X509CertSelector();
297 selector.setCertificate(cert);
299 // Create the trust anchors (set of root CA certificates)
300 Set<TrustAnchor> trustAnchors = new HashSet<>();
301 for (X509Certificate trustedRootCert : allTrustedRootCerts) {
302 trustAnchors.add(new TrustAnchor(trustedRootCert, null));
305 // Configure the PKIX certificate builder algorithm parameters
306 PKIXBuilderParameters pkixParams;
308 pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
309 } catch (InvalidAlgorithmParameterException ex) {
310 throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex);
313 // Not supporting CRL checks for now
314 pkixParams.setRevocationEnabled(false);
316 Set<X509Certificate> certSet = new HashSet<>();
318 pkixParams.addCertStore(createCertStore(certSet));
319 pkixParams.addCertStore(createCertStore(allIntermediateCerts));
320 pkixParams.addCertStore(createCertStore(allTrustedRootCerts));
322 CertPathBuilder builder = CertPathBuilder
323 .getInstance(CertPathBuilder.getDefaultType(), BouncyCastleProvider.PROVIDER_NAME);
324 return (PKIXCertPathBuilderResult) builder.build(pkixParams);
327 private CertStore createCertStore(Set<X509Certificate> certificateSet) throws InvalidAlgorithmParameterException,
328 NoSuchAlgorithmException, NoSuchProviderException {
329 return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet),
330 BouncyCastleProvider.PROVIDER_NAME);
333 private boolean isExpired(X509Certificate cert) {
335 cert.checkValidity();
336 } catch (CertificateExpiredException e) {
337 logger.error(e.getMessage(), e);
339 } catch (CertificateNotYetValidException e) {
340 logger.error(e.getMessage(), e);
346 private boolean isSelfSigned(X509Certificate cert) {
347 return cert.getIssuerDN().equals(cert.getSubjectDN());