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 }