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 }