001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.security.plugins;
023:
024: import java.io.File;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.net.MalformedURLException;
028: import java.net.URL;
029: import java.security.KeyStore;
030: import java.util.Arrays;
031: import javax.crypto.Cipher;
032: import javax.crypto.SecretKey;
033: import javax.crypto.SecretKeyFactory;
034: import javax.crypto.spec.PBEKeySpec;
035: import javax.crypto.spec.PBEParameterSpec;
036: import javax.management.MBeanServer;
037: import javax.management.ObjectName;
038: import javax.net.ssl.KeyManagerFactory;
039: import javax.net.ssl.TrustManagerFactory;
040: import javax.security.auth.callback.CallbackHandler;
041:
042: import org.jboss.mx.util.MBeanServerLocator;
043: import org.jboss.security.SecurityDomain;
044: import org.jboss.security.Util;
045: import org.jboss.security.auth.callback.SecurityAssociationHandler;
046:
047: /** The JaasSecurityDomain is an extension of JaasSecurityManager that addes
048: the notion of a KeyStore, and JSSE KeyManagerFactory and TrustManagerFactory
049: for supporting SSL and other cryptographic use cases.
050:
051: Attributes:
052: <ul>
053: <li>KeyStoreType: The implementation type name being used, defaults to 'JKS'.
054: </li>
055:
056: <li>KeyStoreURL: Set the KeyStore database URL string. This is used to obtain
057: an InputStream to initialize the KeyStore. If the string is not a value
058: URL, its treated as a file.
059: </li>
060:
061: <li>KeyStorePass: the password used to load the KeyStore. Its format is one of:
062: <ul>
063: <li>The plaintext password for the KeyStore(or whatever format is used
064: by the KeyStore). The toCharArray() value of the string is used without any
065: manipulation.
066: </li>
067: <li>A command to execute to obtain the plaintext password. The format
068: is '{EXT}...' where the '...' is the exact command line that will be passed
069: to the Runtime.exec(String) method to execute a platform command. The first
070: line of the command output is used as the password.
071: </li>
072: <li>A class to create to obtain the plaintext password. The format
073: is '{CLASS}classname[:ctorarg]' where the '[:ctorarg]' is an optional
074: string delimited by the ':' from the classname that will be passed to the
075: classname ctor. The password is obtained from classname by invoking a 'char[]
076: toCharArray()' method if found, otherwise, the 'String toString()' method is
077: used.
078: </li>
079: </ul>
080: The KeyStorePass is also used in combination with the Salt and IterationCount
081: attributes to create a PBE secret key used with the encode/decode operations.
082: </li>
083:
084: <li>ManagerServiceName: The JMX object name string of the security manager service
085: that the domain registers with to function as a security manager for the
086: security domain name passed to the ctor. The makes the JaasSecurityDomain
087: available under the standard JNDI java:/jaas/(domain) binding.
088: </li>
089:
090: <li>LoadSunJSSEProvider: A flag indicating if the Sun com.sun.net.ssl.internal.ssl.Provider
091: security provider should be loaded on startup. This is needed when using
092: the Sun JSSE jars without them installed as an extension with JDK 1.3. This
093: should be set to false with JDK 1.4 or when using an alternate JSSE provider
094: </li>
095:
096: <li>Salt:
097: </li>
098:
099: <li>IterationCount:
100: </li>
101: </ul>
102:
103: @todo add support for encode/decode based on a SecretKey in the keystore.
104:
105: @author Scott.Stark@jboss.org
106: @author <a href="mailto:jasone@greenrivercomputing.com">Jason Essington</a>
107:
108: @version $Revision: 59905 $
109: */
110: public class JaasSecurityDomain extends JaasSecurityManager implements
111: SecurityDomain, JaasSecurityDomainMBean {
112: /** The permission required to access encode, encode64 */
113: private static final RuntimePermission encodePermission = new RuntimePermission(
114: "org.jboss.security.plugins.JaasSecurityDomain.encode");
115: /** The permission required to access decode, decode64 */
116: private static final RuntimePermission decodePermission = new RuntimePermission(
117: "org.jboss.security.plugins.JaasSecurityDomain.decode");
118:
119: /** The KeyStore associated with the security domain */
120: private KeyStore keyStore;
121: private KeyManagerFactory keyMgr;
122: /** The KeyStore implementation type which defaults to 'JKS' */
123: private String keyStoreType = "JKS";
124: /** The resource for the keystore location */
125: private URL keyStoreURL;
126: /** The keystore password for loading */
127: private char[] keyStorePassword;
128: /** The secret key that corresponds to the keystore password */
129: private SecretKey cipherKey;
130: /** The encode/decode cipher algorigthm */
131: private String cipherAlgorithm = "PBEwithMD5andDES";
132: private byte[] salt = { 1, 2, 3, 4, 5, 6, 7, 8 };
133: private int iterationCount = 103;
134: private PBEParameterSpec cipherSpec;
135: /** The JMX object name of the security manager service */
136: private ObjectName managerServiceName = JaasSecurityManagerServiceMBean.OBJECT_NAME;
137:
138: private KeyStore trustStore;
139: private String trustStoreType = "JKS";
140: private char[] trustStorePassword;
141: private URL trustStoreURL;
142: private TrustManagerFactory trustMgr;
143:
144: /** Creates a default JaasSecurityDomain for with a securityDomain
145: name of 'other'.
146: */
147: public JaasSecurityDomain() {
148: super ();
149: }
150:
151: /** Creates a JaasSecurityDomain for with a securityDomain
152: name of that given by the 'securityDomain' argument.
153: @param securityDomain , the name of the security domain
154: */
155: public JaasSecurityDomain(String securityDomain) {
156: this (securityDomain, new SecurityAssociationHandler());
157: }
158:
159: /** Creates a JaasSecurityDomain for with a securityDomain
160: name of that given by the 'securityDomain' argument.
161: @param securityDomain , the name of the security domain
162: @param handler , the CallbackHandler to use to obtain login module info
163: */
164: public JaasSecurityDomain(String securityDomain,
165: CallbackHandler handler) {
166: super (securityDomain, handler);
167: }
168:
169: public KeyStore getKeyStore() throws SecurityException {
170: return keyStore;
171: }
172:
173: public KeyManagerFactory getKeyManagerFactory()
174: throws SecurityException {
175: return keyMgr;
176: }
177:
178: public KeyStore getTrustStore() throws SecurityException {
179: return trustStore;
180: }
181:
182: public TrustManagerFactory getTrustManagerFactory()
183: throws SecurityException {
184: return trustMgr;
185: }
186:
187: /** The JMX object name string of the security manager service.
188: @return The JMX object name string of the security manager service.
189: */
190: public ObjectName getManagerServiceName() {
191: return this .managerServiceName;
192: }
193:
194: /** Set the JMX object name string of the security manager service.
195: */
196: public void setManagerServiceName(ObjectName managerServiceName) {
197: this .managerServiceName = managerServiceName;
198: }
199:
200: public String getKeyStoreType() {
201: return this .keyStoreType;
202: }
203:
204: public void setKeyStoreType(String type) {
205: this .keyStoreType = type;
206: }
207:
208: public String getKeyStoreURL() {
209: String url = null;
210: if (keyStoreURL != null)
211: url = keyStoreURL.toExternalForm();
212: return url;
213: }
214:
215: public void setKeyStoreURL(String storeURL) throws IOException {
216: this .keyStoreURL = this .validateStoreURL(storeURL);
217: log.debug("Using KeyStore=" + keyStoreURL.toExternalForm());
218: }
219:
220: public void setKeyStorePass(String password) throws Exception {
221: this .keyStorePassword = Util.loadPassword(password);
222: }
223:
224: public String getTrustStoreType() {
225: return this .trustStoreType;
226: }
227:
228: public void setTrustStoreType(String type) {
229: this .trustStoreType = type;
230: }
231:
232: public void setTrustStorePass(String password) {
233: this .trustStorePassword = password.toCharArray();
234: }
235:
236: public String getTrustStoreURL() {
237: String url = null;
238: if (trustStoreURL != null)
239: url = trustStoreURL.toExternalForm();
240: return url;
241: }
242:
243: public void setTrustStoreURL(String storeURL) throws IOException {
244: this .trustStoreURL = validateStoreURL(storeURL);
245: }
246:
247: public void setSalt(String salt) {
248: this .salt = salt.getBytes();
249: }
250:
251: public void setIterationCount(int iterationCount) {
252: this .iterationCount = iterationCount;
253: }
254:
255: public String getCipherAlgorithm() {
256: return cipherAlgorithm;
257: }
258:
259: public void setCipherAlgorithm(String cipherAlgorithm) {
260: this .cipherAlgorithm = cipherAlgorithm;
261: }
262:
263: public String getName() {
264: return "JaasSecurityDomain(" + getSecurityDomain() + ")";
265: }
266:
267: /** Encrypt the secret using the cipherKey.
268: * @param secret - the plaintext secret to encrypt
269: * @return the encrypted secret
270: * @throws Exception
271: */
272: public byte[] encode(byte[] secret) throws Exception {
273: SecurityManager sm = System.getSecurityManager();
274: if (sm != null) {
275: System.out.println("Checking: " + encodePermission);
276: sm.checkPermission(encodePermission);
277: }
278:
279: Cipher cipher = Cipher.getInstance(cipherAlgorithm);
280: cipher.init(Cipher.ENCRYPT_MODE, cipherKey, cipherSpec);
281: byte[] encoding = cipher.doFinal(secret);
282: return encoding;
283: }
284:
285: /** Decrypt the secret using the cipherKey.
286: *
287: * @param secret - the encrypted secret to decrypt.
288: * @return the decrypted secret
289: * @throws Exception
290: */
291: public byte[] decode(byte[] secret) throws Exception {
292: SecurityManager sm = System.getSecurityManager();
293: if (sm != null)
294: sm.checkPermission(decodePermission);
295:
296: Cipher cipher = Cipher.getInstance(cipherAlgorithm);
297: cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherSpec);
298: byte[] decode = cipher.doFinal(secret);
299: return decode;
300: }
301:
302: /** Encrypt the secret using the cipherKey and return a base64 encoding.
303: * @param secret - the plaintext secret to encrypt
304: * @return the encrypted secret as a base64 string
305: * @throws Exception
306: */
307: public String encode64(byte[] secret) throws Exception {
308: byte[] encoding = encode(secret);
309: String b64 = Util.tob64(encoding);
310: return b64;
311: }
312:
313: /** Decrypt the base64 encoded secret using the cipherKey.
314: *
315: * @param secret - the base64 encoded encrypted secret to decrypt.
316: * @return the decrypted secret
317: * @throws Exception
318: */
319: public byte[] decode64(String secret) throws Exception {
320: byte[] encoding = Util.fromb64(secret);
321: byte[] decode = decode(encoding);
322: return decode;
323: }
324:
325: /**
326: Reload the key- and truststore
327: */
328: public void reloadKeyAndTrustStore() throws Exception {
329: loadKeyAndTrustStore();
330: }
331:
332: protected void startService() throws Exception {
333: // Load the keystore password if it was
334: loadKeystorePassword();
335:
336: // Load the key and/or truststore into memory
337: loadKeyAndTrustStore();
338:
339: // Only register with the JaasSecurityManagerService if its defined
340: if (managerServiceName != null) {
341: /* Register with the JaasSecurityManagerServiceMBean. This allows this
342: JaasSecurityDomain to function as the security manager for security-domain
343: elements that declare java:/jaas/xxx for our security domain name.
344: */
345: MBeanServer server = MBeanServerLocator.locateJBoss();
346: Object[] params = { getSecurityDomain(), this };
347: String[] signature = new String[] { "java.lang.String",
348: "org.jboss.security.SecurityDomain" };
349: server.invoke(managerServiceName, "registerSecurityDomain",
350: params, signature);
351: }
352: }
353:
354: protected void stopService() {
355: if (keyStorePassword != null) {
356: Arrays.fill(keyStorePassword, '\0');
357: keyStorePassword = null;
358: }
359: cipherKey = null;
360: }
361:
362: /** If keyStorePassword is null and keyStorePasswordCmd exists,
363: * execute it to obtain the password.
364: */
365: private void loadKeystorePassword() throws Exception {
366: // Create the PBE secret key
367: cipherSpec = new PBEParameterSpec(salt, iterationCount);
368: PBEKeySpec keySpec = new PBEKeySpec(keyStorePassword);
369: SecretKeyFactory factory = SecretKeyFactory
370: .getInstance("PBEwithMD5andDES");
371: cipherKey = factory.generateSecret(keySpec);
372: }
373:
374: private void loadKeyAndTrustStore() throws Exception {
375: if (keyStoreURL != null) {
376: keyStore = KeyStore.getInstance(keyStoreType);
377: InputStream is = keyStoreURL.openStream();
378: keyStore.load(is, keyStorePassword);
379: String algorithm = KeyManagerFactory.getDefaultAlgorithm();
380: keyMgr = KeyManagerFactory.getInstance(algorithm);
381: keyMgr.init(keyStore, keyStorePassword);
382: }
383: if (trustStoreURL != null) {
384: trustStore = KeyStore.getInstance(trustStoreType);
385: InputStream is = trustStoreURL.openStream();
386: trustStore.load(is, trustStorePassword);
387: String algorithm = TrustManagerFactory
388: .getDefaultAlgorithm();
389: trustMgr = TrustManagerFactory.getInstance(algorithm);
390: trustMgr.init(trustStore);
391: } else if (keyStore != null) {
392: trustStore = keyStore;
393: String algorithm = TrustManagerFactory
394: .getDefaultAlgorithm();
395: trustMgr = TrustManagerFactory.getInstance(algorithm);
396: trustMgr.init(trustStore);
397: }
398: }
399:
400: private URL validateStoreURL(String storeURL) throws IOException {
401: URL url = null;
402: // First see if this is a URL
403: try {
404: url = new URL(storeURL);
405: } catch (MalformedURLException e) {
406: // Not a URL or a protocol without a handler
407: }
408:
409: // Next try to locate this as file path
410: if (url == null) {
411: File tst = new File(storeURL);
412: if (tst.exists() == true)
413: url = tst.toURL();
414: }
415:
416: // Last try to locate this as a classpath resource
417: if (url == null) {
418: ClassLoader loader = SubjectActions.getContextClassLoader();
419: url = loader.getResource(storeURL);
420: }
421:
422: // Fail if no valid key store was located
423: if (url == null) {
424: String msg = "Failed to find url=" + storeURL
425: + " as a URL, file or resource";
426: throw new MalformedURLException(msg);
427: }
428: return url;
429: }
430: }
|