001/**
002 * Copyright 2005-2014 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 */
016package org.kuali.rice.ksb.security.admin.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.bouncycastle.jce.X509Principal;
020import org.bouncycastle.x509.X509V3CertificateGenerator;
021import org.kuali.rice.core.api.config.property.Config;
022import org.kuali.rice.core.api.config.property.ConfigContext;
023import org.kuali.rice.ksb.security.admin.KeyStoreEntryDataContainer;
024import org.kuali.rice.ksb.security.admin.service.JavaSecurityManagementService;
025import org.springframework.beans.factory.InitializingBean;
026
027import java.io.File;
028import java.io.FileInputStream;
029import java.io.IOException;
030import java.security.GeneralSecurityException;
031import java.security.KeyPair;
032import java.security.KeyPairGenerator;
033import java.security.KeyStore;
034import java.security.KeyStoreException;
035import java.security.NoSuchAlgorithmException;
036import java.security.PrivateKey;
037import java.security.Security;
038import java.security.UnrecoverableEntryException;
039import java.security.cert.Certificate;
040import java.util.ArrayList;
041import java.util.Calendar;
042import java.util.Date;
043import java.util.Enumeration;
044import 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 */
052public 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}