001: /*
002: * Copyright (C) The MX4J Contributors.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the MX4J License version 1.0.
006: * See the terms of the MX4J License in the documentation provided with this software.
007: */
008:
009: package mx4j.tools.remote;
010:
011: import java.io.File;
012: import java.io.FileInputStream;
013: import java.io.IOException;
014: import java.io.InputStream;
015: import java.security.MessageDigest;
016: import java.security.NoSuchAlgorithmException;
017: import java.util.Collections;
018: import java.util.HashMap;
019: import java.util.HashSet;
020: import java.util.Map;
021: import java.util.Properties;
022: import java.util.Set;
023: import javax.management.remote.JMXAuthenticator;
024: import javax.management.remote.JMXPrincipal;
025: import javax.security.auth.Subject;
026:
027: import mx4j.util.Base64Codec;
028:
029: /**
030: * Implementation of the JMXAuthenticator interface to be used on server side
031: * to secure access to {@link javax.management.remote.JMXConnectorServer JMXConnectorServer}s. <br/>
032: * Usage:
033: * <pre>
034: * JMXAuthenticator authenticator = new PasswordAuthenticator(new File("users.properties"));
035: * Map environment = new HashMap();
036: * environment.put(JMXConnectorServer.AUTHENTICATOR, authenticator);
037: * JMXServiceURL address = new JMXServiceURL("rmi", "localhost", 0);
038: * MBeanServer server = ...;
039: * JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, environment, server);
040: * </pre>
041: * The format of the users.properties file is that of a standard properties file: <br/>
042: * <user>=<password><br/>
043: * where <password> can be stored in 2 ways:
044: * <ul>
045: * <li>Clear text: the password is written in clear text</li>
046: * <li>Obfuscated text: the password is obfuscated</li>
047: * </ul>
048: * The obfuscated form can be obtained running this class as a main class:
049: * <pre>
050: * java -cp mx4j-remote.jar mx4j.tools.remote.PasswordAuthenticator
051: * </pre>
052: * and following the instructions printed on the console. The output will be a string that should be
053: * copy/pasted as the password into the properties file.<br/>
054: * The obfuscated password is obtained by digesting the clear text password using a
055: * {@link java.security.MessageDigest} algorithm, and then by Base64-encoding the resulting bytes.<br/>
056: * <br/>
057: * On client side, you are allowed to connect to a server side secured with the PasswordAuthenticator
058: * only if you provide the correct credentials:
059: * <pre>
060: * String[] credentials = new String[2];
061: * // The user will travel as clear text
062: * credentials[0] = "user";
063: * // You may send the password in clear text, but it's better to obfuscate it
064: * credentials[1] = PasswordAuthenticator.obfuscatePassword("password");
065: * Map environment = new HashMap();
066: * environment.put(JMXConnector.CREDENTIALS, credentials);
067: * JMXServiceURL address = ...;
068: * JMXConnector cntor = JMXConnectorFactory.connect(address, environment);
069: * </pre>
070: * Note that {@link #obfuscatePassword(java.lang.String,java.lang.String) obfuscating} the passwords only works if the server side has been
071: * setup with the PasswordAuthenticator.
072: * However, the PasswordAuthenticator can be used with other JSR 160 implementations, such as Sun's reference
073: * implementation.
074: *
075: * @version $Revision: 1.3 $
076: */
077: public class PasswordAuthenticator implements JMXAuthenticator {
078: private static final String LEFT_DELIMITER = "OBF(";
079: private static final String RIGHT_DELIMITER = "):";
080:
081: /**
082: * Runs this class as main class to obfuscate passwords.
083: * When no arguments are provided, it prints out the usage.
084: *
085: * @see #obfuscatePassword(java.lang.String,java.lang.String)
086: */
087: public static void main(String[] args) throws Exception {
088: if (args.length == 1) {
089: if (!"-help".equals(args[0])) {
090: printPassword("MD5", args[0]);
091: return;
092: }
093: } else if (args.length == 3) {
094: if ("-alg".equals(args[0])) {
095: printPassword(args[1], args[2]);
096: return;
097: }
098: }
099: printUsage();
100: }
101:
102: private static void printPassword(String algorithm, String input) {
103: String password = obfuscatePassword(input, algorithm);
104: System.out.println(password);
105: }
106:
107: private static void printUsage() {
108: System.out.println();
109: System.out
110: .println("Usage: java -cp <lib>/mx4j-tools.jar mx4j.tools.remote.PasswordAuthenticator <options> <password>");
111: System.out.println("Where <options> is one of the following:");
112: System.out
113: .println(" -help Prints this message");
114: System.out
115: .println(" -alg <digest algorithm> Specifies the digest algorithm (default is MD5)");
116: System.out.println();
117: }
118:
119: /**
120: * Obfuscates the given password using MD5 as digest algorithm
121: *
122: * @see #obfuscatePassword(java.lang.String,java.lang.String)
123: */
124: public static String obfuscatePassword(String password) {
125: return obfuscatePassword(password, "MD5");
126: }
127:
128: /**
129: * Obfuscates the given password using the given digest algorithm.<br/>
130: * Obfuscation consists of 2 steps: first the clear text password is {@link java.security.MessageDigest#digest digested}
131: * using the specified algorithm, then the resulting bytes are Base64-encoded.<br/>
132: * For example, the obfuscated version of the password "password" is "OBF(MD5):X03MO1qnZdYdgyfeuILPmQ=="
133: * or "OBF(SHA-1):W6ph5Mm5Pz8GgiULbPgzG37mj9g=". <br/>
134: * OBF stands for "obfuscated", in parenthesis the algorithm used to digest the password.
135: */
136: public static String obfuscatePassword(String password,
137: String algorithm) {
138: try {
139: MessageDigest digest = MessageDigest.getInstance(algorithm);
140: byte[] digestedBytes = digest.digest(password.getBytes());
141: byte[] obfuscatedBytes = Base64Codec
142: .encodeBase64(digestedBytes);
143: return LEFT_DELIMITER + algorithm + RIGHT_DELIMITER
144: + new String(obfuscatedBytes);
145: } catch (NoSuchAlgorithmException x) {
146: throw new SecurityException(
147: "Could not find digest algorithm " + algorithm);
148: }
149: }
150:
151: private Map passwords;
152:
153: /**
154: * Creates a new PasswordAuthenticator that reads user/password pairs from the specified properties file.
155: * The file format is described in the javadoc of this class.
156: *
157: * @see #obfuscatePassword
158: */
159: public PasswordAuthenticator(File passwordFile) throws IOException {
160: this (new FileInputStream(passwordFile));
161: }
162:
163: /**
164: * Creates a new PasswordAuthenticator that reads user/password pairs from the specified InputStream.
165: * The file format is described in the javadoc of this class.
166: *
167: * @see #obfuscatePassword
168: */
169: public PasswordAuthenticator(InputStream is) throws IOException {
170: passwords = readPasswords(is);
171: }
172:
173: private Map readPasswords(InputStream is) throws IOException {
174: Properties properties = new Properties();
175: try {
176: properties.load(is);
177: } finally {
178: is.close();
179: }
180: return new HashMap(properties);
181: }
182:
183: public Subject authenticate(Object credentials)
184: throws SecurityException {
185: if (!(credentials instanceof String[]))
186: throw new SecurityException("Bad credentials");
187: String[] creds = (String[]) credentials;
188: if (creds.length != 2)
189: throw new SecurityException("Bad credentials");
190:
191: String user = creds[0];
192: String password = creds[1];
193:
194: if (password == null)
195: throw new SecurityException("Bad password");
196:
197: if (!passwords.containsKey(user))
198: throw new SecurityException("Unknown user " + user);
199:
200: String storedPassword = (String) passwords.get(user);
201: if (!isPasswordCorrect(password, storedPassword))
202: throw new SecurityException("Bad password");
203:
204: Set principals = new HashSet();
205: principals.add(new JMXPrincipal(user));
206: return new Subject(true, principals, Collections.EMPTY_SET,
207: Collections.EMPTY_SET);
208: }
209:
210: private boolean isPasswordCorrect(String password,
211: String storedPassword) {
212: if (password.startsWith(LEFT_DELIMITER)) {
213: if (storedPassword.startsWith(LEFT_DELIMITER)) {
214: return password.equals(storedPassword);
215: } else {
216: String algorithm = getAlgorithm(password);
217: String obfuscated = obfuscatePassword(storedPassword,
218: algorithm);
219: return password.equals(obfuscated);
220: }
221: } else {
222: if (storedPassword.startsWith(LEFT_DELIMITER)) {
223: // Password was sent in clear, bad practice
224: String algorithm = getAlgorithm(storedPassword);
225: String obfuscated = obfuscatePassword(password,
226: algorithm);
227: return obfuscated.equals(storedPassword);
228: } else {
229: return password.equals(storedPassword);
230: }
231: }
232: }
233:
234: private String getAlgorithm(String obfuscatedPassword) {
235: try {
236: return obfuscatedPassword.substring(
237: LEFT_DELIMITER.length(), obfuscatedPassword
238: .indexOf(RIGHT_DELIMITER));
239: } catch (IndexOutOfBoundsException x) {
240: throw new SecurityException("Bad password");
241: }
242: }
243: }
|