View Javadoc

1   /*
2    * #%L
3    * Bitrepository Protocol
4    * %%
5    * Copyright (C) 2010 - 2012 The State and University Library, The Royal Library and The State Archives, Denmark
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 2.1 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-2.1.html>.
20   * #L%
21   */
22  package org.bitrepository.protocol.security;
23  
24  import java.io.BufferedReader;
25  import java.io.ByteArrayInputStream;
26  import java.io.File;
27  import java.io.FileReader;
28  import java.io.IOException;
29  import java.io.UnsupportedEncodingException;
30  import java.security.KeyManagementException;
31  import java.security.KeyStore;
32  import java.security.KeyStoreException;
33  import java.security.NoSuchAlgorithmException;
34  import java.security.PrivateKey;
35  import java.security.Security;
36  import java.security.UnrecoverableKeyException;
37  import java.security.KeyStore.PrivateKeyEntry;
38  import java.security.cert.Certificate;
39  import java.security.cert.CertificateException;
40  import java.security.cert.CertificateExpiredException;
41  import java.security.cert.CertificateFactory;
42  import java.security.cert.CertificateNotYetValidException;
43  import java.security.cert.X509Certificate;
44  
45  import javax.net.ssl.KeyManagerFactory;
46  import javax.net.ssl.SSLContext;
47  import javax.net.ssl.TrustManagerFactory;
48  
49  import org.bitrepository.common.ArgumentValidator;
50  import org.bitrepository.protocol.security.exception.CertificateUseException;
51  import org.bitrepository.protocol.security.exception.MessageAuthenticationException;
52  import org.bitrepository.protocol.security.exception.MessageSigningException;
53  import org.bitrepository.protocol.security.exception.OperationAuthorizationException;
54  import org.bitrepository.protocol.security.exception.SecurityException;
55  import org.bitrepository.protocol.security.exception.UnregisteredPermissionException;
56  import org.bitrepository.settings.repositorysettings.RepositorySettings;
57  import org.bitrepository.settings.repositorysettings.InfrastructurePermission;
58  import org.bitrepository.settings.repositorysettings.Permission;
59  import org.bitrepository.settings.repositorysettings.PermissionSet;
60  import org.bouncycastle.cms.CMSException;
61  import org.bouncycastle.cms.CMSProcessableByteArray;
62  import org.bouncycastle.cms.CMSSignedData;
63  import org.bouncycastle.cms.SignerInformation;
64  import org.bouncycastle.jce.provider.BouncyCastleProvider;
65  import org.bouncycastle.openssl.PEMReader;
66  import org.bouncycastle.util.encoders.Base64;
67  import org.slf4j.Logger;
68  import org.slf4j.LoggerFactory;
69  
70  /**
71   * Class to handle:
72   * - loading of certificates
73   * - setup of SSLContext
74   * - Authentication of signatures
75   * - Signature generation 
76   * - Authorization of operations
77   */
78  public class BasicSecurityManager implements SecurityManager {
79      private final Logger log = LoggerFactory.getLogger(BasicSecurityManager.class);
80      /** Default password for the in-memory keystore */
81      private static final String defaultPassword = "123456";
82      /** path to file containing the components private key and certificate */
83      private final String privateKeyFile;
84      /** RepositorySettings */
85      private final RepositorySettings repositorySettings;
86      /** Object to authenticate messages */
87      private final MessageAuthenticator authenticator;
88      /** Object to sign messages */
89      private final MessageSigner signer;
90      /** Object to authorize operations */
91      private final OperationAuthorizor authorizer;
92      /** Object storing permissions and certificates */
93      private final PermissionStore permissionStore;
94      /** int value to keep track of the next keystore alias */
95      private static int aliasID = 0;
96      /** In memory keyStore */
97      private KeyStore keyStore; 
98      /** Member for holding the PrivateKeyEntry containing the from privateKeyFile loaded key and certificate */
99      private PrivateKeyEntry privateKeyEntry;
100     /** The ID of the component where this instance of the BasicSecurityManager is running */
101     private final String componentID;
102     
103     /**
104      * Constructor for the SecurityManager.
105      * @param repositorySettings the collection settings to retrieve settings from
106      * @param privateKeyFile path to the file containing the components private key and certificate, may be null if not using
107      *        certificates and encryption.
108      * @param authenticator MessageAuthenticator for authenticating messages
109      * @param signer MessageSigner for signing messages.
110      * @param authorizer OperationAuthorizer to authorize operations
111      * @param permissionStore the PermissionStore to hold certificates and adjoining permissions
112      */
113     public BasicSecurityManager(RepositorySettings repositorySettings, String privateKeyFile, MessageAuthenticator authenticator,
114             MessageSigner signer, OperationAuthorizor authorizer, PermissionStore permissionStore, String componentID) {
115         ArgumentValidator.checkNotNull(repositorySettings, "repositorySettings");
116         ArgumentValidator.checkNotNull(authenticator, "authenticator");
117         ArgumentValidator.checkNotNull(signer, "signer");
118         ArgumentValidator.checkNotNull(authorizer, "authorizer");
119         ArgumentValidator.checkNotNull(permissionStore, "permissionStore");
120         this.privateKeyFile = privateKeyFile;
121         this.repositorySettings = repositorySettings;
122         this.authenticator = authenticator;
123         this.signer = signer;
124         this.authorizer = authorizer;
125         this.permissionStore = permissionStore;
126         this.componentID = componentID;
127         initialize();
128     }
129     
130     /**
131      * Method to authenticate a message. 
132      * @param message the message that needs to be authenticated.
133      * @param signature the signature belonging to the message.
134      * @throws MessageAuthenticationException in case of failure.
135      */
136     public void authenticateMessage(String message, String signature) throws MessageAuthenticationException {
137         if(repositorySettings.getProtocolSettings().isRequireMessageAuthentication()) {
138             if (signature != null) {
139             try {
140                 byte[] decodedSig = Base64.decode(signature.getBytes(SecurityModuleConstants.defaultEncodingType));
141                 byte[] decodeMessage = message.getBytes(SecurityModuleConstants.defaultEncodingType);
142                 authenticator.authenticateMessage(decodeMessage, decodedSig);
143             } catch (UnsupportedEncodingException e) {
144                 throw new SecurityException(SecurityModuleConstants.defaultEncodingType + " encoding not supported", e);
145             }
146             } else {
147                 throw new MessageAuthenticationException("Received unsigned message, but authentication is required");
148             }
149         }
150     }
151     
152     /**
153      * Method to sign a message
154      * @param message the message to sign
155      * @return the signature for the message, or null if authentication is disabled.
156      * @throws MessageSigningException if signing of the message fails.   
157      */
158     public String signMessage(String message) throws MessageSigningException {
159         if(repositorySettings.getProtocolSettings().isRequireMessageAuthentication()) {
160             try {
161                 byte[] signature = signer.signMessage(message.getBytes(SecurityModuleConstants.defaultEncodingType));
162                 return new String(Base64.encode(signature));   
163             } catch (UnsupportedEncodingException e) {
164                 throw new SecurityException(SecurityModuleConstants.defaultEncodingType + " encoding not supported", e);
165             }           
166         } else { 
167             return null;
168         }
169     }
170     
171     /** 
172      * Method to authorize the use of a certificate
173      * @param certificateUser the user who signed the message
174      * @param messageData the data of the message request.
175      * @param signature the signature belonging to the message request.
176      * @throws CertificateUseException in case the certificate use could not be authorized. 
177      */
178     public void authorizeCertificateUse(String certificateUser, String messageData, String signature) 
179             throws CertificateUseException {
180         if(repositorySettings.getProtocolSettings().isRequireOperationAuthorization()) {
181             byte[] decodeSig = Base64.decode(signature.getBytes());
182             CMSSignedData s;
183             try {
184                 s = new CMSSignedData(new CMSProcessableByteArray(messageData.getBytes()), decodeSig);
185             } catch (CMSException e) {
186                 throw new SecurityException(e.getMessage(), e);
187             }
188     
189             SignerInformation signer = (SignerInformation) s.getSignerInfos().getSigners().iterator().next();
190             authorizer.authorizeCertificateUse(certificateUser, signer.getSID());    
191         }
192     }
193     
194     /**
195      * Method to authorize an operation 
196      * @param operationType the type of operation that is to be authorized.
197      * @param messageData the data of the message request.
198      * @param signature the signature belonging to the message request.
199      * @throws OperationAuthorizationException in case of failure. 
200      */
201     public void authorizeOperation(String operationType, String messageData, String signature) 
202             throws OperationAuthorizationException {
203         if(repositorySettings.getProtocolSettings().isRequireOperationAuthorization()) {
204             byte[] decodeSig = Base64.decode(signature.getBytes());
205             CMSSignedData s;
206             try {
207                 s = new CMSSignedData(new CMSProcessableByteArray(messageData.getBytes()), decodeSig);
208             } catch (CMSException e) {
209                 throw new SecurityException(e.getMessage(), e);
210             }
211     
212             SignerInformation signer = (SignerInformation) s.getSignerInfos().getSigners().iterator().next();
213             try {
214                 authorizer.authorizeOperation(operationType, signer.getSID());    
215             } catch (UnregisteredPermissionException e) {
216                 log.info(e.getMessage());
217             }
218             
219             
220         }
221     }
222     
223     /**
224      * Do initialization work
225      * - Creates keystore
226      * - Loads private key and certificate
227      * - Loads permissions and certificates
228      * - Sets up SSLContext
229      */
230     private void initialize() {
231         Security.addProvider(new BouncyCastleProvider());
232         try {
233             keyStore = KeyStore.getInstance(SecurityModuleConstants.keyStoreType);
234             keyStore.load(null);
235             loadPrivateKey(privateKeyFile);
236             loadInfrastructureCertificates(repositorySettings.getPermissionSet());
237             permissionStore.loadPermissions(repositorySettings.getPermissionSet(), componentID);
238             signer.setPrivateKeyEntry(privateKeyEntry);
239             setupDefaultSSLContext();
240         } catch (Exception e) {
241             throw new SecurityException(e.getMessage(), e);
242         } 
243     }
244     
245     /**
246      * Alias generator for the keystore entries.
247      * @return returns a String containing the alias for the next keystore entry 
248      */
249     private String getNewAlias() {
250         return "" + aliasID++;
251     }
252     
253     /**
254      * Attempts to load the pillars private key and certificate from a PEM formatted file. 
255      * @param privateKeyFile, path to the file containing the components private key and certificate, may be null
256      * @throws IOException if the file cannot be found or read. 
257      * @throws KeyStoreException if there is problems with adding the privateKeyEntry to keyStore
258      * @throws CertificateExpiredException if the certificate has expired 
259      * @throws CertificateNotYetValidException if the certificate is not yet valid
260      */
261     private void loadPrivateKey(String privateKeyFile) throws IOException, KeyStoreException, 
262             CertificateExpiredException, CertificateNotYetValidException {
263         PrivateKey privKey = null;
264         X509Certificate privCert = null;
265         if(!(new File(privateKeyFile)).isFile()) {
266             log.info("Key file with private key and certificate does not exist!");
267             return;
268         }
269         BufferedReader bufferedReader = new BufferedReader(new FileReader(privateKeyFile));
270         PEMReader pemReader =  new PEMReader(bufferedReader);
271         Object pemObj = pemReader.readObject();
272 
273         while(pemObj != null) {
274             if(pemObj instanceof X509Certificate) {
275                 log.debug("Certificate for PrivateKeyEntry found");
276                 privCert = (X509Certificate) pemObj;
277             } else if(pemObj instanceof PrivateKey) {
278                 log.debug("Key for PrivateKeyEntry found");
279                 privKey = (PrivateKey) pemObj;
280             } else {
281                 log.debug("Got something, thats not X509Certificate or PrivateKey. Class: " + pemObj.getClass().getSimpleName());
282             }
283             pemObj = pemReader.readObject();
284         }
285         pemReader.close();
286         if(privKey == null || privCert == null ) {
287             log.info("No material to create private key entry found!");
288         } else {
289             privCert.checkValidity();
290             privateKeyEntry = new PrivateKeyEntry(privKey, new Certificate[] {privCert});
291             keyStore.setEntry(SecurityModuleConstants.privateKeyAlias, privateKeyEntry, 
292                     new KeyStore.PasswordProtection(defaultPassword.toCharArray()));
293         }
294     }
295 
296     /**
297      * Load the appropriate certificates from PermissionSet into trust/keystore 
298      * @throws CertificateException if certificate cannot be created from the data
299      * @throws KeyStoreException if certificate cannot be put into the keyStore
300      */
301     private void loadInfrastructureCertificates(PermissionSet permissions) throws CertificateException, KeyStoreException {
302         ByteArrayInputStream bs;
303         if(permissions == null) {
304             log.info("The provided PermissionSet is empty. Continuing without permissions!");
305             return;
306         }
307         for(Permission permission : permissions.getPermission()) {
308             if(permission.getInfrastructurePermission().contains(InfrastructurePermission.MESSAGE_BUS_SERVER)) {
309                 try {
310                     bs = new ByteArrayInputStream(permission.getCertificate().getCertificateData());
311                     X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance(
312                             SecurityModuleConstants.CertificateType).generateCertificate(bs);
313                     keyStore.setEntry(getNewAlias(), new KeyStore.TrustedCertificateEntry(certificate), 
314                             SecurityModuleConstants.nullProtectionParameter);
315                     
316                     bs.close();
317                 } catch (IOException e) {
318                     
319                 }
320             }
321             if(permission.getInfrastructurePermission().contains(InfrastructurePermission.FILE_EXCHANGE_SERVER)) {
322                 try {
323                     bs = new ByteArrayInputStream(permission.getCertificate().getCertificateData());
324                     X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance(
325                             SecurityModuleConstants.CertificateType).generateCertificate(bs);
326                     keyStore.setEntry(getNewAlias(), new KeyStore.TrustedCertificateEntry(certificate), 
327                             SecurityModuleConstants.nullProtectionParameter);
328                     bs.close();
329                 } catch (IOException e) {
330                     log.debug("Failed closing ByteArrayInputStream", e);
331                 }
332             }
333         }      
334     }
335     
336     /**
337      * Sets up the Default SSL context  
338      * @throws NoSuchAlgorithmException
339      * @throws KeyStoreException
340      * @throws UnrecoverableKeyException
341      * @throws KeyManagementException
342      */
343     private void setupDefaultSSLContext() throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, 
344             KeyManagementException {
345         TrustManagerFactory tmf;
346         KeyManagerFactory kmf;
347         SSLContext context;
348         tmf = TrustManagerFactory.getInstance(SecurityModuleConstants.keyTrustStoreAlgorithm);
349         tmf.init(keyStore);
350         kmf = KeyManagerFactory.getInstance(SecurityModuleConstants.keyTrustStoreAlgorithm);
351         kmf.init(keyStore, defaultPassword.toCharArray());
352         context = SSLContext.getInstance(SecurityModuleConstants.defaultSSLProtocol);
353         context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), SecurityModuleConstants.defaultRandom);
354         SSLContext.setDefault(context);
355     }
356 }