001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.ksb.security.admin.service.impl;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.bouncycastle.jce.X509Principal;
020    import org.bouncycastle.x509.X509V3CertificateGenerator;
021    import org.kuali.rice.core.api.config.property.Config;
022    import org.kuali.rice.core.api.config.property.ConfigContext;
023    import org.kuali.rice.ksb.security.admin.KeyStoreEntryDataContainer;
024    import org.kuali.rice.ksb.security.admin.service.JavaSecurityManagementService;
025    import org.springframework.beans.factory.InitializingBean;
026    
027    import java.io.File;
028    import java.io.FileInputStream;
029    import java.io.IOException;
030    import java.security.GeneralSecurityException;
031    import java.security.KeyPair;
032    import java.security.KeyPairGenerator;
033    import java.security.KeyStore;
034    import java.security.KeyStoreException;
035    import java.security.NoSuchAlgorithmException;
036    import java.security.PrivateKey;
037    import java.security.Security;
038    import java.security.UnrecoverableEntryException;
039    import java.security.cert.Certificate;
040    import java.util.ArrayList;
041    import java.util.Calendar;
042    import java.util.Date;
043    import java.util.Enumeration;
044    import java.util.List;
045    
046    /**
047     * This is an implementation of the {@link JavaSecurityManagementService} interface used by the KSB module 
048     * 
049     * @author Kuali Rice Team (rice.collab@kuali.org)
050     *
051     */
052    public class JavaSecurityManagementServiceImpl implements JavaSecurityManagementService, InitializingBean {
053    
054        protected final String CLIENT_KEY_GENERATOR_ALGORITHM = "RSA";
055        protected final String CLIENT_SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
056        protected final int CLIENT_KEY_PAIR_KEY_SIZE = 512;
057        private final int CLIENT_CERT_EXPIRATION_DAYS = 9999;
058    
059        private static final String MODULE_SHA_RSA_ALGORITHM = "SHA1withRSA";
060        private static final String MODULE_JKS_TYPE = "JKS";
061    
062        private String moduleKeyStoreLocation;
063        private String moduleKeyStoreAlias;
064        private String moduleKeyStorePassword;
065    
066        private KeyStore moduleKeyStore;
067        private PrivateKey modulePrivateKey;
068    
069        /**
070         * Load the module's keystore and private key for this "application"
071         */
072        public void afterPropertiesSet() throws Exception {
073            if (StringUtils.isEmpty(getModuleKeyStoreLocation())) {
074                setModuleKeyStoreLocation(ConfigContext.getCurrentContextConfig().getKeystoreFile());
075            }
076            if (StringUtils.isEmpty(getModuleKeyStoreAlias())) {
077                setModuleKeyStoreAlias(ConfigContext.getCurrentContextConfig().getKeystoreAlias());
078            }
079            if (StringUtils.isEmpty(getModuleKeyStorePassword())) {
080                setModuleKeyStorePassword(ConfigContext.getCurrentContextConfig().getKeystorePassword());
081            }
082            verifyConfiguration();
083            this.moduleKeyStore = loadKeyStore();
084            this.modulePrivateKey = loadPrivateKey();
085        }
086    
087        /**
088         * Verifies the configuration of this service and throws an exception if it is not configured properly.
089         */
090        protected void verifyConfiguration() {
091            if (StringUtils.isEmpty(getModuleKeyStoreLocation())) {
092                throw new RuntimeException("Value for configuration parameter '" + Config.KEYSTORE_FILE + "' could not be found.  Please ensure that the keystore is configured properly.");
093            }
094            if (StringUtils.isEmpty(getModuleKeyStoreAlias())) {
095                throw new RuntimeException("Value for configuration parameter '" + Config.KEYSTORE_ALIAS + "' could not be found.  Please ensure that the keystore is configured properly.");
096            }
097            if (StringUtils.isEmpty(getModuleKeyStorePassword())) {
098                throw new RuntimeException("Value for configuration parameter '" + Config.KEYSTORE_PASSWORD + "' could not be found.  Please ensure that the keystore is configured properly.");
099            }
100            File keystoreFile = new File(getModuleKeyStoreLocation());
101            if (!keystoreFile.exists()) {
102                throw new RuntimeException("Value for configuration parameter '" + Config.KEYSTORE_FILE + "' is invalid.  The file does not exist on the filesystem, location was: '" + getModuleKeyStoreLocation() + "'");
103            }
104            if (!keystoreFile.canRead()) {
105                throw new RuntimeException("Value for configuration parameter '" + Config.KEYSTORE_FILE + "' is invalid.  The file exists but is not readable (please check permissions), location was: '" + getModuleKeyStoreLocation() + "'");
106            }
107        }
108    
109        protected KeyStore loadKeyStore() throws GeneralSecurityException, IOException {
110            KeyStore keyStore = KeyStore.getInstance(getModuleKeyStoreType());
111            FileInputStream stream = null;
112            try {
113                stream = new FileInputStream(getModuleKeyStoreLocation());
114                keyStore.load(stream, getModuleKeyStorePassword().toCharArray());
115                stream.close();
116            } catch (Exception e) {
117                if (stream != null) {
118                    try {
119                        stream.close();
120                    } catch (Exception ignored) {
121                    }
122                }
123            }
124            return keyStore;
125        }
126    
127        protected PrivateKey loadPrivateKey() throws GeneralSecurityException {
128            return (PrivateKey)getModuleKeyStore().getKey(getModuleKeyStoreAlias(), getModuleKeyStorePassword().toCharArray());
129        }
130    
131        public void removeClientCertificate(String alias) throws KeyStoreException {
132            KeyStore moduleKeyStore = getModuleKeyStore();
133            if (!moduleKeyStore.entryInstanceOf(alias, KeyStore.TrustedCertificateEntry.class)) {
134                throw new RuntimeException("Only entries of type " + KeyStoreEntryDataContainer.DISPLAYABLE_ENTRY_TYPES.get(KeyStore.TrustedCertificateEntry.class) + " can be removed");
135            }
136            getModuleKeyStore().deleteEntry(alias);
137        }
138        
139        protected void addClientCertificateToModuleKeyStore(String alias, Certificate clientCertificate) throws KeyStoreException {
140            getModuleKeyStore().setEntry(alias, new KeyStore.TrustedCertificateEntry(clientCertificate), null);
141        }
142        
143        public boolean isAliasInKeystore(String alias) throws KeyStoreException {
144            return getModuleKeyStore().containsAlias(alias);
145        }
146        
147        public String getCertificateAlias(Certificate certificate) throws KeyStoreException {
148            return getModuleKeyStore().getCertificateAlias(certificate);
149        }
150        
151        public KeyStore generateClientKeystore(String alias, String clientPassphrase) throws GeneralSecurityException {
152            if (isAliasInKeystore(alias)) {
153                throw new KeyStoreException("Alias '" + alias + "' already exists in module keystore");
154            }
155    //        Certificate[] clientCertificateChain = {};
156    //        PrivateKey clientPrivateKey = null;
157            KeyStore ks = null;
158            try {
159                // generate a key pair for the client
160                KeyPairGenerator keyGen = KeyPairGenerator.getInstance(CLIENT_KEY_GENERATOR_ALGORITHM);
161    //            SecureRandom random = SecureRandom.getInstance(CLIENT_SECURE_RANDOM_ALGORITHM);
162                keyGen.initialize(CLIENT_KEY_PAIR_KEY_SIZE);
163    //            keyGen.initialize(new RSAKeyGenParameterSpec(512,RSAKeyGenParameterSpec.F0));
164                KeyPair pair = keyGen.generateKeyPair();
165    
166    //            PublicKey clientPublicKey = pair.getPublic();
167    //            clientPrivateKey = pair.getPrivate();
168    //            // generate the Certificate
169    //            X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
170    ////            X509Name nameInfo = new X509Name(false,"CN=" + alias);
171    //            certificateGenerator.setSignatureAlgorithm("MD5WithRSA");
172    //            certificateGenerator.setSerialNumber(new java.math.BigInteger("1"));
173    //            X509Principal nameInfo = new X509Principal("CN=" + alias);
174    //            certificateGenerator.setIssuerDN(nameInfo);
175    //            certificateGenerator.setSubjectDN(nameInfo);                       // note: same as issuer
176    //            certificateGenerator.setNotBefore(new Date());
177    //            Calendar c = Calendar.getInstance();
178    //            c.add(Calendar.DATE, CLIENT_CERT_EXPIRATION_DAYS);
179    //            certificateGenerator.setNotAfter(c.getTime());
180    //            certificateGenerator.setPublicKey(clientPublicKey);
181    //            X509Certificate cert = certificateGenerator.generateX509Certificate(clientPrivateKey);
182    //            clientCertificateChain = new Certificate[]{cert};
183    //
184    //            // generate client keyStore file
185    //            ks = KeyStore.getInstance(getModuleKeyStoreType());
186    //            ks.load(null, clientPassphrase.toCharArray());
187    //            // set client private key on keyStore file
188    //            ks.setEntry(alias, new KeyStore.PrivateKeyEntry(clientPrivateKey, clientCertificateChain), new KeyStore.PasswordProtection(clientPassphrase.toCharArray()));
189                Certificate cert = generateCertificate(pair, alias);
190                ks = generateKeyStore(cert, pair.getPrivate(), alias, clientPassphrase);
191                
192                // set the module certificate on the client keyStore file
193                ks.setEntry(getModuleKeyStoreAlias(), new KeyStore.TrustedCertificateEntry(getCertificate(getModuleKeyStoreAlias())),  null);
194    
195                // add the client certificate to the module keyStore
196                addClientCertificateToModuleKeyStore(alias, cert);
197                
198                return ks;
199            } catch (IOException e) {
200                throw new RuntimeException("Could not create new KeyStore",e);
201            }
202        }
203        
204        protected Certificate generateCertificate(KeyPair keyPair, String alias) throws GeneralSecurityException {
205          
206          //test that Bouncy Castle provider is present and add it if it's not
207          if( Security.getProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME) == null) {
208              Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
209          }
210          X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
211    //      X509Name nameInfo = new X509Name(false,"CN=" + alias);
212          certificateGenerator.setSignatureAlgorithm("MD5WithRSA");
213          certificateGenerator.setSerialNumber(new java.math.BigInteger("1"));
214          X509Principal nameInfo = new X509Principal("CN=" + alias);
215          certificateGenerator.setIssuerDN(nameInfo);
216          certificateGenerator.setSubjectDN(nameInfo);  // note: same as issuer for self signed
217          certificateGenerator.setNotBefore(new Date());
218          Calendar c = Calendar.getInstance();
219          c.add(Calendar.DATE, CLIENT_CERT_EXPIRATION_DAYS);
220          certificateGenerator.setNotAfter(c.getTime());
221          certificateGenerator.setPublicKey(keyPair.getPublic());
222          return certificateGenerator.generate(keyPair.getPrivate(), org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
223        }
224        
225        protected KeyStore generateKeyStore(Certificate cert, PrivateKey privateKey, String alias, String keyStorePassword) throws GeneralSecurityException, IOException {
226            KeyStore ks = KeyStore.getInstance(getModuleKeyStoreType());
227            ks.load(null, keyStorePassword.toCharArray());
228            // set client private key on keyStore file
229            ks.setEntry(alias, new KeyStore.PrivateKeyEntry(privateKey, new Certificate[]{cert}), new KeyStore.PasswordProtection(keyStorePassword.toCharArray()));
230            return ks;
231        }
232    
233        public List<KeyStoreEntryDataContainer> getListOfModuleKeyStoreEntries() {
234            List<KeyStoreEntryDataContainer> keyStoreEntries = new ArrayList<KeyStoreEntryDataContainer>();
235            try {
236                KeyStore moduleKeyStore = getModuleKeyStore();
237    
238                // List the aliases
239                for (Enumeration<String> enumer = moduleKeyStore.aliases(); enumer.hasMoreElements();) {
240                    String alias = (String) enumer.nextElement();
241                    KeyStoreEntryDataContainer dataContainer = new KeyStoreEntryDataContainer(alias,moduleKeyStore.getCreationDate(alias));
242                    KeyStore.PasswordProtection passwordProtection = null;
243                    if (moduleKeyStore.isKeyEntry(alias)) {
244                        passwordProtection = new KeyStore.PasswordProtection(getModuleKeyStorePassword().toCharArray());
245                    }
246                    KeyStore.Entry entry = moduleKeyStore.getEntry(alias, passwordProtection);
247                    dataContainer.setType(entry.getClass());
248                    keyStoreEntries.add(dataContainer);
249                }
250            } catch (KeyStoreException e) {
251                e.printStackTrace();
252                throw new RuntimeException(e);
253            } catch (NoSuchAlgorithmException e) {
254                e.printStackTrace();
255                throw new RuntimeException(e);
256            } catch (UnrecoverableEntryException e) {
257                e.printStackTrace();
258                throw new RuntimeException(e);
259            }
260            return keyStoreEntries;
261        }
262    
263        public String getModuleSignatureAlgorithm() {
264            return getModuleAlgorithm();
265        }
266    
267        /**
268         * @see java.security.KeyStore#getCertificate(java.lang.String)
269         */
270        public Certificate getCertificate(String alias) throws KeyStoreException {
271            return getModuleKeyStore().getCertificate(alias);
272        }
273        
274        protected String getModuleKeyStoreType() {
275            return MODULE_JKS_TYPE;
276        }
277    
278        protected String getModuleAlgorithm() {
279            return MODULE_SHA_RSA_ALGORITHM;
280        }
281    
282        public String getModuleKeyStoreLocation() {
283            return this.moduleKeyStoreLocation;
284        }
285    
286        public void setModuleKeyStoreLocation(String moduleKeyStoreLocation) {
287            this.moduleKeyStoreLocation = moduleKeyStoreLocation;
288        }
289    
290        public String getModuleKeyStoreAlias() {
291            return this.moduleKeyStoreAlias;
292        }
293    
294        public void setModuleKeyStoreAlias(String moduleKeyStoreAlias) {
295            this.moduleKeyStoreAlias = moduleKeyStoreAlias;
296        }
297    
298        public String getModuleKeyStorePassword() {
299            return this.moduleKeyStorePassword;
300        }
301    
302        public void setModuleKeyStorePassword(String moduleKeyStorePassword) {
303            this.moduleKeyStorePassword = moduleKeyStorePassword;
304        }
305    
306        public KeyStore getModuleKeyStore() {
307            return this.moduleKeyStore;
308        }
309        
310        public PrivateKey getModulePrivateKey() {
311            return this.modulePrivateKey;
312        }
313        
314    }