7b1890dcaa55422744d520d79caf647805e0bad4
[sdc.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20 package org.openecomp.sdc.vendorsoftwareproduct.security;
21
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;
37
38 import java.io.ByteArrayInputStream;
39 import java.io.File;
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;
67 import java.util.Set;
68
69 /**
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.
72  */
73 public class SecurityManager {
74     private static final String CERTIFICATE_DEFAULT_LOCATION = "cert";
75     private static final SecurityManager INSTANCE = new SecurityManager();
76
77     private Logger logger = LoggerFactory.getLogger(SecurityManager.class);
78     private Set<X509Certificate> trustedCertificates = new HashSet<>();
79     private File certificateDirectory;
80
81     static {
82         if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
83             Security.addProvider(new BouncyCastleProvider());
84         }
85     }
86
87     private SecurityManager() {
88         certificateDirectory = this.getcertDirectory();
89     }
90
91     public static SecurityManager getInstance(){
92         return INSTANCE;
93     }
94
95     /**
96      *
97      * Checks the configured location for available trustedCertificates
98      *
99      * @return set of trustedCertificates
100      * @throws SecurityManagerException
101      */
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<>());
108         }
109         if (trustedCertificates.size() != certFiles.length) {
110             trustedCertificates = new HashSet<>();
111             processCertificateDir();
112         }
113         return ImmutableSet.copyOf(trustedCertificates);
114     }
115
116     /**
117      * Cleans certificate collection
118      */
119     public void cleanTrustedCertificates(){
120         trustedCertificates.clear();
121     }
122
123     /**
124      *
125      * Verifies if packaged signed with trusted certificate
126      *
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
132      */
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");
139             }
140             ContentInfo signature = ContentInfo.getInstance(parsedObject);
141             CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile);
142             CMSSignedData signedData = new CMSSignedData(signedContent, signature);
143
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!");
152                 }
153                 X509CertificateHolder firstSignerFirstCertificate = firstSignerCertificates.iterator().next();
154                 cert = loadCertificate(firstSignerFirstCertificate.getEncoded());
155             } else {
156                 cert = loadCertificate(packageCert);
157             }
158
159             PKIXCertPathBuilderResult result = verifyCertificate(cert, getTrustedCertificates());
160
161             if (result == null) {
162                 return false;
163             }
164
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);
171         }
172     }
173
174     private void processCertificateDir() throws SecurityManagerException {
175         if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) {
176             logger.error("Issue with certificate directory, check if exists!");
177             return;
178         }
179
180         File[] files = certificateDirectory.listFiles();
181         if (files == null) {
182             logger.error("Certificate directory is empty!");
183             return;
184         }
185         for (File f : files) {
186             trustedCertificates.add(loadCertificate(f));
187         }
188     }
189
190     private File getcertDirectory() {
191         String certDirLocation = System.getenv("SDC_CERT_DIR");
192         if (certDirLocation == null) {
193             certDirLocation = CERTIFICATE_DEFAULT_LOCATION;
194         }
195         return new File(certDirLocation);
196     }
197
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);
204         }
205     }
206
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);
213         }
214     }
215
216     private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
217                                                         Set<X509Certificate> additionalCerts) throws GeneralSecurityException, SecurityManagerException {
218             if (null == cert) {
219                 throw new SecurityManagerException("The certificate is empty!");
220             }
221
222             if (isExpired(cert)) {
223                 throw new SecurityManagerException("The certificate expired on: " + cert.getNotAfter());
224             }
225
226             if (isSelfSigned(cert)) {
227                 throw new SecurityManagerException("The certificate is self-signed.");
228             }
229
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);
235                 } else {
236                     intermediateCerts.add(additionalCert);
237                 }
238             }
239
240             return verifyCertificate(cert, trustedRootCerts, intermediateCerts);
241     }
242
243     private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert,
244                                                         Set<X509Certificate> allTrustedRootCerts,
245                                                         Set<X509Certificate> allIntermediateCerts)
246             throws GeneralSecurityException {
247
248         // Create the selector that specifies the starting certificate
249         X509CertSelector selector = new X509CertSelector();
250         selector.setCertificate(cert);
251
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));
256         }
257
258         // Configure the PKIX certificate builder algorithm parameters
259         PKIXBuilderParameters pkixParams;
260         try {
261             pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
262         } catch (InvalidAlgorithmParameterException ex) {
263             throw new InvalidAlgorithmParameterException("No root CA has been found for this certificate", ex);
264         }
265
266         // Not supporting CRL checks for now
267         pkixParams.setRevocationEnabled(false);
268
269         Set<X509Certificate> certSet = new HashSet<>();
270         certSet.add(cert);
271         pkixParams.addCertStore(createCertStore(certSet));
272         pkixParams.addCertStore(createCertStore(allIntermediateCerts));
273         pkixParams.addCertStore(createCertStore(allTrustedRootCerts));
274
275         CertPathBuilder builder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType(), BouncyCastleProvider.PROVIDER_NAME);
276         return (PKIXCertPathBuilderResult) builder.build(pkixParams);
277     }
278
279     private CertStore createCertStore(Set<X509Certificate> certificateSet) throws InvalidAlgorithmParameterException,
280             NoSuchAlgorithmException, NoSuchProviderException {
281         return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificateSet), BouncyCastleProvider.PROVIDER_NAME);
282     }
283
284     private boolean isExpired(X509Certificate cert) {
285         try {
286             cert.checkValidity();
287         } catch (CertificateExpiredException e) {
288             logger.error(e.getMessage(), e);
289             return true;
290         } catch (CertificateNotYetValidException e) {
291             logger.error(e.getMessage(), e);
292             return false;
293         }
294         return false;
295     }
296
297     private boolean isSelfSigned(Certificate cert)
298             throws CertificateException, NoSuchAlgorithmException,
299             NoSuchProviderException {
300         try {
301             // Try to verify certificate signature with its own public key
302             PublicKey key = cert.getPublicKey();
303             cert.verify(key);
304             return true;
305         } catch (SignatureException | InvalidKeyException e) {
306             logger.error(e.getMessage(), e);
307             //not self-signed
308             return false;
309         }
310     }
311 }