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 org.bouncycastle.asn1.cms.ContentInfo;
24 import org.bouncycastle.cert.X509CertificateHolder;
25 import org.bouncycastle.cms.CMSException;
26 import org.bouncycastle.cms.CMSProcessableByteArray;
27 import org.bouncycastle.cms.CMSSignedData;
28 import org.bouncycastle.cms.CMSTypedData;
29 import org.bouncycastle.cms.SignerInformation;
30 import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
31 import org.bouncycastle.jce.provider.BouncyCastleProvider;
32 import org.bouncycastle.openssl.PEMParser;
33 import org.bouncycastle.operator.OperatorCreationException;
34 import org.bouncycastle.util.Store;
35 import org.openecomp.sdc.logging.api.Logger;
36 import org.openecomp.sdc.logging.api.LoggerFactory;
38 import java.io.ByteArrayInputStream;
40 import java.io.FileInputStream;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.InputStreamReader;
44 import java.security.GeneralSecurityException;
45 import java.security.InvalidAlgorithmParameterException;
46 import java.security.InvalidKeyException;
47 import java.security.NoSuchAlgorithmException;
48 import java.security.NoSuchProviderException;
49 import java.security.PublicKey;
50 import java.security.Security;
51 import java.security.SignatureException;
52 import java.security.cert.CertPathBuilder;
53 import java.security.cert.CertStore;
54 import java.security.cert.Certificate;
55 import java.security.cert.CertificateException;
56 import java.security.cert.CertificateExpiredException;
57 import java.security.cert.CertificateFactory;
58 import java.security.cert.CertificateNotYetValidException;
59 import java.security.cert.CollectionCertStoreParameters;
60 import java.security.cert.PKIXBuilderParameters;
61 import java.security.cert.PKIXCertPathBuilderResult;
62 import java.security.cert.TrustAnchor;
63 import java.security.cert.X509CertSelector;
64 import java.security.cert.X509Certificate;
65 import java.util.Collection;
66 import java.util.HashSet;
70 * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be reviewed
71 * Class is responsible for providing root trustedCertificates from configured location in onboarding container.
73 public class SecurityManager {
74 private static final String CERTIFICATE_DEFAULT_LOCATION = "cert";
75 private static final SecurityManager INSTANCE = new SecurityManager();
77 private Logger logger = LoggerFactory.getLogger(SecurityManager.class);
78 private Set<X509Certificate> trustedCertificates = new HashSet<>();
79 private File certificateDirectory;
82 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
83 Security.addProvider(new BouncyCastleProvider());
87 private SecurityManager() {
88 certificateDirectory = this.getcertDirectory();
91 public static SecurityManager getInstance(){
97 * Checks the configured location for available trustedCertificates
99 * @return set of trustedCertificates
100 * @throws SecurityManagerException
102 public Set<X509Certificate> getTrustedCertificates() throws SecurityManagerException {
103 //if file number in certificate directory changed reload certs
104 String[] certFiles = certificateDirectory.list();
105 if (certFiles == null) {
106 logger.error("Certificate directory is empty!");
107 return ImmutableSet.copyOf(new HashSet<>());
109 if (trustedCertificates.size() != certFiles.length) {
110 trustedCertificates = new HashSet<>();
111 processCertificateDir();
113 return ImmutableSet.copyOf(trustedCertificates);
117 * Cleans certificate collection
119 public void cleanTrustedCertificates(){
120 trustedCertificates.clear();
125 * Verifies if packaged signed with trusted certificate
127 * @param messageSyntaxSignature - signature data in cms format
128 * @param packageCert - package certificate if not part of cms signature, can be null
129 * @param innerPackageFile data package signed with cms signature
130 * @return true if signature verified
131 * @throws SecurityManagerException
133 public boolean verifySignedData(final byte[] messageSyntaxSignature, final byte[] packageCert,
134 final byte[] innerPackageFile) throws SecurityManagerException{
135 try (ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature)) {
136 Object parsedObject = new PEMParser(new InputStreamReader(signatureStream)).readObject();
137 if (!(parsedObject instanceof ContentInfo)) {
138 throw new SecurityManagerException("Signature is not recognized");
140 ContentInfo signature = ContentInfo.getInstance(parsedObject);
141 CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile);
142 CMSSignedData signedData = new CMSSignedData(signedContent, signature);
144 Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners();
145 SignerInformation firstSigner = signers.iterator().next();
146 Store certificates = signedData.getCertificates();
147 X509Certificate cert;
148 if (packageCert == null) {
149 Collection<X509CertificateHolder> firstSignerCertificates = certificates.getMatches(firstSigner.getSID());
150 if(!firstSignerCertificates.iterator().hasNext()){
151 throw new SecurityManagerException("No certificate found in cms signature that should contain one!");
153 X509CertificateHolder firstSignerFirstCertificate = firstSignerCertificates.iterator().next();
154 cert = loadCertificate(firstSignerFirstCertificate.getEncoded());
156 cert = loadCertificate(packageCert);
159 PKIXCertPathBuilderResult result = verifyCertificate(cert, getTrustedCertificates());
161 if (result == null) {
165 return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
166 } catch (OperatorCreationException | IOException | CMSException e) {
167 logger.error(e.getMessage(), e);
168 throw new SecurityManagerException("Unexpected error occurred during signature validation!", e);
169 } catch (GeneralSecurityException e){
170 throw new SecurityManagerException("Could not verify signature!", e);
174 private void processCertificateDir() throws SecurityManagerException {
175 if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) {
176 logger.error("Issue with certificate directory, check if exists!");
180 File[] files = certificateDirectory.listFiles();
182 logger.error("Certificate directory is empty!");
185 for (File f : files) {
186 trustedCertificates.add(loadCertificate(f));
190 private File getcertDirectory() {
191 String certDirLocation = System.getenv("SDC_CERT_DIR");
192 if (certDirLocation == null) {
193 certDirLocation = CERTIFICATE_DEFAULT_LOCATION;
195 return new File(certDirLocation);
198 private X509Certificate loadCertificate(File certFile) throws SecurityManagerException {
199 try (InputStream fileInputStream = new FileInputStream(certFile)) {
200 CertificateFactory factory = CertificateFactory.getInstance("X.509");
201 return (X509Certificate) factory.generateCertificate(fileInputStream);
202 } catch (CertificateException | IOException e) {
203 throw new SecurityManagerException("Error during loading Certificate file!", e);
207 private X509Certificate loadCertificate(byte[] certFile) throws SecurityManagerException {
208 try (InputStream in = new ByteArrayInputStream(certFile)) {
209 CertificateFactory factory = CertificateFactory.getInstance("X.509");
210 return (X509Certificate) factory.generateCertificate(in);
211 } catch (CertificateException | IOException e) {
212 throw new SecurityManagerException("Error during loading Certificate from bytes!", e);
216 private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
217 Set<X509Certificate> additionalCerts) throws GeneralSecurityException, SecurityManagerException {
219 throw new SecurityManagerException("The certificate is empty!");
222 if (isExpired(cert)) {
223 throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
226 if (isSelfSigned(cert)) {
227 throw new SecurityManagerException("The certificate is self-signed.");
230 Set<X509Certificate> trustedRootCerts = new HashSet<>();
231 Set<X509Certificate> intermediateCerts = new HashSet<>();
232 for (X509Certificate additionalCert : additionalCerts) {
233 if (isSelfSigned(additionalCert)) {
234 trustedRootCerts.add(additionalCert);
236 intermediateCerts.add(additionalCert);
240 return verifyCertificate(cert, trustedRootCerts, intermediateCerts);
243 private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
244 Set<X509Certificate> allTrustedRootCerts,
245 Set<X509Certificate> allIntermediateCerts)
246 throws GeneralSecurityException {
248 // Create the selector that specifies the starting certificate
249 X509CertSelector selector = new X509CertSelector();
250 selector.setCertificate(cert);
252 // Create the trust anchors (set of root CA certificates)
253 Set<TrustAnchor> trustAnchors = new HashSet<>();
254 for (X509Certificate trustedRootCert : allTrustedRootCerts) {
255 trustAnchors.add(new TrustAnchor(trustedRootCert, null));
258 // Configure the PKIX certificate builder algorithm parameters
259 PKIXBuilderParameters pkixParams;
261 pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
262 } catch (InvalidAlgorithmParameterException ex) {
263 throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex);
266 // Not supporting CRL checks for now
267 pkixParams.setRevocationEnabled(false);
269 Set<X509Certificate> certSet = new HashSet<>();
271 pkixParams.addCertStore(createCertStore(certSet));
272 pkixParams.addCertStore(createCertStore(allIntermediateCerts));
273 pkixParams.addCertStore(createCertStore(allTrustedRootCerts));
275 CertPathBuilder builder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType(), BouncyCastleProvider.PROVIDER_NAME);
276 return (PKIXCertPathBuilderResult) builder.build(pkixParams);
279 private CertStore createCertStore(Set<X509Certificate> certificateSet) throws InvalidAlgorithmParameterException,
280 NoSuchAlgorithmException, NoSuchProviderException {
281 return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet), BouncyCastleProvider.PROVIDER_NAME);
284 private boolean isExpired(X509Certificate cert) {
286 cert.checkValidity();
287 } catch (CertificateExpiredException e) {
288 logger.error(e.getMessage(), e);
290 } catch (CertificateNotYetValidException e) {
291 logger.error(e.getMessage(), e);
297 private boolean isSelfSigned(Certificate cert)
298 throws CertificateException, NoSuchAlgorithmException,
299 NoSuchProviderException {
301 // Try to verify certificate signature with its own public key
302 PublicKey key = cert.getPublicKey();
305 } catch (SignatureException | InvalidKeyException e) {
306 logger.error(e.getMessage(), e);