1 /*
2 * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.jmx.remote.security;
27
28 import com.sun.jmx.mbeanserver.GetPropertyAction;
29 import com.sun.jmx.mbeanserver.Util;
30 import java.io.BufferedInputStream;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.FilePermission;
34 import java.io.IOException;
35 import java.security.AccessControlException;
36 import java.security.AccessController;
37 import java.util.Arrays;
38 import java.util.Hashtable;
39 import java.util.Map;
40 import java.util.Properties;
41
42 import javax.security.auth.*;
43 import javax.security.auth.callback.*;
44 import javax.security.auth.login.*;
45 import javax.security.auth.spi.*;
46 import javax.management.remote.JMXPrincipal;
47
48 import com.sun.jmx.remote.util.ClassLogger;
49 import com.sun.jmx.remote.util.EnvHelp;
50
51 /**
52 * This {@link LoginModule} performs file-based authentication.
53 *
54 * <p> A supplied username and password is verified against the
55 * corresponding user credentials stored in a designated password file.
56 * If successful then a new {@link JMXPrincipal} is created with the
57 * user's name and it is associated with the current {@link Subject}.
58 * Such principals may be identified and granted management privileges in
59 * the access control file for JMX remote management or in a Java security
60 * policy.
61 *
62 * <p> The password file comprises a list of key-value pairs as specified in
63 * {@link Properties}. The key represents a user's name and the value is its
64 * associated cleartext password. By default, the following password file is
65 * used:
66 * <pre>
67 * ${java.home}/conf/management/jmxremote.password
68 * </pre>
69 * A different password file can be specified via the <code>passwordFile</code>
70 * configuration option.
71 *
72 * <p> This module recognizes the following <code>Configuration</code> options:
73 * <dl>
74 * <dt> <code>passwordFile</code> </dt>
75 * <dd> the path to an alternative password file. It is used instead of
76 * the default password file.</dd>
77 *
78 * <dt> <code>useFirstPass</code> </dt>
79 * <dd> if <code>true</code>, this module retrieves the username and password
80 * from the module's shared state, using "javax.security.auth.login.name"
81 * and "javax.security.auth.login.password" as the respective keys. The
82 * retrieved values are used for authentication. If authentication fails,
83 * no attempt for a retry is made, and the failure is reported back to
84 * the calling application.</dd>
85 *
88 * from the module's shared state, using "javax.security.auth.login.name"
89 * and "javax.security.auth.login.password" as the respective keys. The
90 * retrieved values are used for authentication. If authentication fails,
91 * the module uses the CallbackHandler to retrieve a new username and
92 * password, and another attempt to authenticate is made. If the
93 * authentication fails, the failure is reported back to the calling
94 * application.</dd>
95 *
96 * <dt> <code>storePass</code> </dt>
97 * <dd> if <code>true</code>, this module stores the username and password
98 * obtained from the CallbackHandler in the module's shared state, using
99 * "javax.security.auth.login.name" and
100 * "javax.security.auth.login.password" as the respective keys. This is
101 * not performed if existing values already exist for the username and
102 * password in the shared state, or if authentication fails.</dd>
103 *
104 * <dt> <code>clearPass</code> </dt>
105 * <dd> if <code>true</code>, this module clears the username and password
106 * stored in the module's shared state after both phases of authentication
107 * (login and commit) have completed.</dd>
108 * </dl>
109 */
110 public class FileLoginModule implements LoginModule {
111
112 private static final String PASSWORD_FILE_NAME = "jmxremote.password";
113
114 // Location of the default password file
115 private static final String DEFAULT_PASSWORD_FILE_NAME =
116 AccessController.doPrivileged(new GetPropertyAction("java.home")) +
117 File.separatorChar + "conf" +
118 File.separatorChar + "management" + File.separatorChar +
119 PASSWORD_FILE_NAME;
120
121 // Key to retrieve the stored username
122 private static final String USERNAME_KEY =
123 "javax.security.auth.login.name";
124
125 // Key to retrieve the stored password
126 private static final String PASSWORD_KEY =
127 "javax.security.auth.login.password";
128
129 // Log messages
130 private static final ClassLogger logger =
131 new ClassLogger("javax.management.remote.misc", "FileLoginModule");
132
133 // Configurable options
134 private boolean useFirstPass = false;
135 private boolean tryFirstPass = false;
136 private boolean storePass = false;
137 private boolean clearPass = false;
138
139 // Authentication status
140 private boolean succeeded = false;
141 private boolean commitSucceeded = false;
142
143 // Supplied username and password
144 private String username;
145 private char[] password;
146 private JMXPrincipal user;
147
148 // Initial state
149 private Subject subject;
150 private CallbackHandler callbackHandler;
151 private Map<String, Object> sharedState;
152 private Map<String, ?> options;
153 private String passwordFile;
154 private String passwordFileDisplayName;
155 private boolean userSuppliedPasswordFile;
156 private boolean hasJavaHomePermission;
157 private Properties userCredentials;
158
159 /**
160 * Initialize this <code>LoginModule</code>.
161 *
162 * @param subject the <code>Subject</code> to be authenticated.
163 * @param callbackHandler a <code>CallbackHandler</code> to acquire the
164 * user's name and password.
165 * @param sharedState shared <code>LoginModule</code> state.
166 * @param options options specified in the login
167 * <code>Configuration</code> for this particular
168 * <code>LoginModule</code>.
169 */
170 public void initialize(Subject subject, CallbackHandler callbackHandler,
171 Map<String,?> sharedState,
172 Map<String,?> options)
173 {
174
175 this.subject = subject;
176 this.callbackHandler = callbackHandler;
177 this.sharedState = Util.cast(sharedState);
178 this.options = options;
179
180 // initialize any configured options
181 tryFirstPass =
182 "true".equalsIgnoreCase((String)options.get("tryFirstPass"));
183 useFirstPass =
184 "true".equalsIgnoreCase((String)options.get("useFirstPass"));
185 storePass =
186 "true".equalsIgnoreCase((String)options.get("storePass"));
187 clearPass =
188 "true".equalsIgnoreCase((String)options.get("clearPass"));
189
190 passwordFile = (String)options.get("passwordFile");
191 passwordFileDisplayName = passwordFile;
192 userSuppliedPasswordFile = true;
193
194 // set the location of the password file
195 if (passwordFile == null) {
196 passwordFile = DEFAULT_PASSWORD_FILE_NAME;
197 userSuppliedPasswordFile = false;
198 try {
199 System.getProperty("java.home");
200 hasJavaHomePermission = true;
201 passwordFileDisplayName = passwordFile;
202 } catch (SecurityException e) {
203 hasJavaHomePermission = false;
204 passwordFileDisplayName = PASSWORD_FILE_NAME;
205 }
206 }
207 }
208
209 /**
210 * Begin user authentication (Authentication Phase 1).
211 *
212 * <p> Acquire the user's name and password and verify them against
213 * the corresponding credentials from the password file.
214 *
215 * @return true always, since this <code>LoginModule</code>
216 * should not be ignored.
217 * @exception FailedLoginException if the authentication fails.
218 * @exception LoginException if this <code>LoginModule</code>
219 * is unable to perform the authentication.
220 */
221 public boolean login() throws LoginException {
222
223 try {
224 loadPasswordFile();
225 } catch (IOException ioe) {
226 LoginException le = new LoginException(
227 "Error: unable to load the password file: " +
228 passwordFileDisplayName);
229 throw EnvHelp.initCause(le, ioe);
230 }
231
232 if (userCredentials == null) {
233 throw new LoginException
234 ("Error: unable to locate the users' credentials.");
235 }
236
237 if (logger.debugOn()) {
238 logger.debug("login",
239 "Using password file: " + passwordFileDisplayName);
240 }
241
242 // attempt the authentication
243 if (tryFirstPass) {
244
245 try {
246 // attempt the authentication by getting the
247 // username and password from shared state
248 attemptAuthentication(true);
249
250 // authentication succeeded
251 succeeded = true;
252 if (logger.debugOn()) {
253 logger.debug("login",
254 "Authentication using cached password has succeeded");
420 if (logger.debugOn()) {
421 logger.debug("logout", "Subject is being logged out");
422 }
423
424 return true;
425 }
426
427 /**
428 * Attempt authentication
429 *
430 * @param usePasswdFromSharedState a flag to tell this method whether
431 * to retrieve the password from the sharedState.
432 */
433 @SuppressWarnings("unchecked") // sharedState used as Map<String,Object>
434 private void attemptAuthentication(boolean usePasswdFromSharedState)
435 throws LoginException {
436
437 // get the username and password
438 getUsernamePassword(usePasswdFromSharedState);
439
440 String localPassword;
441
442 // userCredentials is initialized in login()
443 if (((localPassword = userCredentials.getProperty(username)) == null) ||
444 (! localPassword.equals(new String(password)))) {
445
446 // username not found or passwords do not match
447 if (logger.debugOn()) {
448 logger.debug("login", "Invalid username or password");
449 }
450 throw new FailedLoginException("Invalid username or password");
451 }
452
453 // Save the username and password in the shared state
454 // only if authentication succeeded
455 if (storePass &&
456 !sharedState.containsKey(USERNAME_KEY) &&
457 !sharedState.containsKey(PASSWORD_KEY)) {
458 sharedState.put(USERNAME_KEY, username);
459 sharedState.put(PASSWORD_KEY, password);
460 }
461
462 // Create a new user principal
463 user = new JMXPrincipal(username);
464
465 if (logger.debugOn()) {
466 logger.debug("login",
467 "User '" + username + "' successfully validated");
468 }
469 }
470
471 /*
472 * Read the password file.
473 */
474 private void loadPasswordFile() throws IOException {
475 FileInputStream fis;
476 try {
477 fis = new FileInputStream(passwordFile);
478 } catch (SecurityException e) {
479 if (userSuppliedPasswordFile || hasJavaHomePermission) {
480 throw e;
481 } else {
482 final FilePermission fp =
483 new FilePermission(passwordFileDisplayName, "read");
484 AccessControlException ace = new AccessControlException(
485 "access denied " + fp.toString());
486 ace.setStackTrace(e.getStackTrace());
487 throw ace;
488 }
489 }
490 try {
491 final BufferedInputStream bis = new BufferedInputStream(fis);
492 try {
493 userCredentials = new Properties();
494 userCredentials.load(bis);
495 } finally {
496 bis.close();
497 }
498 } finally {
499 fis.close();
500 }
501 }
502
503 /**
504 * Get the username and password.
505 * This method does not return any value.
506 * Instead, it sets global name and password variables.
507 *
508 * <p> Also note that this method will set the username and password
509 * values in the shared state in case subsequent LoginModules
510 * want to use them via use/tryFirstPass.
511 *
512 * @param usePasswdFromSharedState boolean that tells this method whether
513 * to retrieve the password from the sharedState.
514 */
515 private void getUsernamePassword(boolean usePasswdFromSharedState)
516 throws LoginException {
517
518 if (usePasswdFromSharedState) {
519 // use the password saved by the first module in the stack
|
1 /*
2 * Copyright (c) 2004, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.jmx.remote.security;
27
28 import com.sun.jmx.mbeanserver.GetPropertyAction;
29 import com.sun.jmx.mbeanserver.Util;
30 import java.io.File;
31 import java.io.FilePermission;
32 import java.io.IOException;
33 import java.security.AccessControlException;
34 import java.security.AccessController;
35 import java.util.Arrays;
36 import java.util.Map;
37
38 import javax.security.auth.*;
39 import javax.security.auth.callback.*;
40 import javax.security.auth.login.*;
41 import javax.security.auth.spi.*;
42 import javax.management.remote.JMXPrincipal;
43
44 import com.sun.jmx.remote.util.ClassLogger;
45 import com.sun.jmx.remote.util.EnvHelp;
46
47 /**
48 * This {@link LoginModule} performs file-based authentication.
49 *
50 * <p> A supplied username and password is verified against the
51 * corresponding user credentials stored in a designated password file.
52 * If successful then a new {@link JMXPrincipal} is created with the
53 * user's name and it is associated with the current {@link Subject}.
54 * Such principals may be identified and granted management privileges in
55 * the access control file for JMX remote management or in a Java security
56 * policy.
57 *
58 * By default, the following password file is used:
59 * <pre>
60 * ${java.home}/conf/management/jmxremote.password
61 * </pre>
62 * A different password file can be specified via the <code>passwordFile</code>
63 * configuration option.
64 *
65 * <p> This module recognizes the following <code>Configuration</code> options:
66 * <dl>
67 * <dt> <code>passwordFile</code> </dt>
68 * <dd> the path to an alternative password file. It is used instead of
69 * the default password file.</dd>
70 *
71 * <dt> <code>useFirstPass</code> </dt>
72 * <dd> if <code>true</code>, this module retrieves the username and password
73 * from the module's shared state, using "javax.security.auth.login.name"
74 * and "javax.security.auth.login.password" as the respective keys. The
75 * retrieved values are used for authentication. If authentication fails,
76 * no attempt for a retry is made, and the failure is reported back to
77 * the calling application.</dd>
78 *
81 * from the module's shared state, using "javax.security.auth.login.name"
82 * and "javax.security.auth.login.password" as the respective keys. The
83 * retrieved values are used for authentication. If authentication fails,
84 * the module uses the CallbackHandler to retrieve a new username and
85 * password, and another attempt to authenticate is made. If the
86 * authentication fails, the failure is reported back to the calling
87 * application.</dd>
88 *
89 * <dt> <code>storePass</code> </dt>
90 * <dd> if <code>true</code>, this module stores the username and password
91 * obtained from the CallbackHandler in the module's shared state, using
92 * "javax.security.auth.login.name" and
93 * "javax.security.auth.login.password" as the respective keys. This is
94 * not performed if existing values already exist for the username and
95 * password in the shared state, or if authentication fails.</dd>
96 *
97 * <dt> <code>clearPass</code> </dt>
98 * <dd> if <code>true</code>, this module clears the username and password
99 * stored in the module's shared state after both phases of authentication
100 * (login and commit) have completed.</dd>
101 *
102 * <dt> <code>hashPasswords</code> </dt>
103 * <dd> if <code>true</code>, this module replaces each clear text passwords
104 * with its hash, if present. </dd>
105 *
106 * </dl>
107 */
108 public class FileLoginModule implements LoginModule {
109
110 private static final String PASSWORD_FILE_NAME = "jmxremote.password";
111
112 // Location of the default password file
113 private static final String DEFAULT_PASSWORD_FILE_NAME =
114 AccessController.doPrivileged(new GetPropertyAction("java.home")) +
115 File.separatorChar + "conf" +
116 File.separatorChar + "management" + File.separatorChar +
117 PASSWORD_FILE_NAME;
118
119 // Key to retrieve the stored username
120 private static final String USERNAME_KEY =
121 "javax.security.auth.login.name";
122
123 // Key to retrieve the stored password
124 private static final String PASSWORD_KEY =
125 "javax.security.auth.login.password";
126
127 // Log messages
128 private static final ClassLogger logger =
129 new ClassLogger("javax.management.remote.misc", "FileLoginModule");
130
131 // Configurable options
132 private boolean useFirstPass = false;
133 private boolean tryFirstPass = false;
134 private boolean storePass = false;
135 private boolean clearPass = false;
136 private boolean hashPasswords = false;
137
138 // Authentication status
139 private boolean succeeded = false;
140 private boolean commitSucceeded = false;
141
142 // Supplied username and password
143 private String username;
144 private char[] password;
145 private JMXPrincipal user;
146
147 // Initial state
148 private Subject subject;
149 private CallbackHandler callbackHandler;
150 private Map<String, Object> sharedState;
151 private Map<String, ?> options;
152 private String passwordFile;
153 private String passwordFileDisplayName;
154 private boolean userSuppliedPasswordFile;
155 private boolean hasJavaHomePermission;
156 private HashedPasswordManager hashPwdMgr;
157
158 /**
159 * Initialize this <code>LoginModule</code>.
160 *
161 * @param subject the <code>Subject</code> to be authenticated.
162 * @param callbackHandler a <code>CallbackHandler</code> to acquire the
163 * user's name and password.
164 * @param sharedState shared <code>LoginModule</code> state.
165 * @param options options specified in the login
166 * <code>Configuration</code> for this particular
167 * <code>LoginModule</code>.
168 */
169 public void initialize(Subject subject, CallbackHandler callbackHandler,
170 Map<String,?> sharedState,
171 Map<String,?> options)
172 {
173
174 this.subject = subject;
175 this.callbackHandler = callbackHandler;
176 this.sharedState = Util.cast(sharedState);
177 this.options = options;
178
179 // initialize any configured options
180 tryFirstPass =
181 "true".equalsIgnoreCase((String)options.get("tryFirstPass"));
182 useFirstPass =
183 "true".equalsIgnoreCase((String)options.get("useFirstPass"));
184 storePass =
185 "true".equalsIgnoreCase((String)options.get("storePass"));
186 clearPass =
187 "true".equalsIgnoreCase((String)options.get("clearPass"));
188 hashPasswords =
189 "true".equalsIgnoreCase((String)options.get("hashPasswords"));
190
191 passwordFile = (String)options.get("passwordFile");
192 passwordFileDisplayName = passwordFile;
193 userSuppliedPasswordFile = true;
194
195 // set the location of the password file
196 if (passwordFile == null) {
197 passwordFile = DEFAULT_PASSWORD_FILE_NAME;
198 userSuppliedPasswordFile = false;
199 try {
200 System.getProperty("java.home");
201 hasJavaHomePermission = true;
202 passwordFileDisplayName = passwordFile;
203 } catch (SecurityException e) {
204 hasJavaHomePermission = false;
205 passwordFileDisplayName = PASSWORD_FILE_NAME;
206 }
207 }
208 }
209
210 /**
211 * Begin user authentication (Authentication Phase 1).
212 *
213 * <p> Acquire the user's name and password and verify them against
214 * the corresponding credentials from the password file.
215 *
216 * @return true always, since this <code>LoginModule</code>
217 * should not be ignored.
218 * @exception FailedLoginException if the authentication fails.
219 * @exception LoginException if this <code>LoginModule</code>
220 * is unable to perform the authentication.
221 */
222 public boolean login() throws LoginException {
223
224 try {
225 if(hashPwdMgr == null) {
226 hashPwdMgr = new HashedPasswordManager(passwordFile, hashPasswords);
227 } else {
228 hashPwdMgr.loadPasswords();
229 }
230 } catch (IOException ioe) {
231 LoginException le = new LoginException(
232 "Error: unable to load the password file: " +
233 passwordFileDisplayName);
234 throw EnvHelp.initCause(le, ioe);
235 } catch (SecurityException e) {
236 if (userSuppliedPasswordFile || hasJavaHomePermission) {
237 throw e;
238 } else {
239 final FilePermission fp =
240 new FilePermission(passwordFileDisplayName, "read");
241 AccessControlException ace = new AccessControlException(
242 "access denied " + fp.toString());
243 ace.setStackTrace(e.getStackTrace());
244 throw ace;
245 }
246 }
247
248 if (logger.debugOn()) {
249 logger.debug("login",
250 "Using password file: " + passwordFileDisplayName);
251 }
252
253 // attempt the authentication
254 if (tryFirstPass) {
255
256 try {
257 // attempt the authentication by getting the
258 // username and password from shared state
259 attemptAuthentication(true);
260
261 // authentication succeeded
262 succeeded = true;
263 if (logger.debugOn()) {
264 logger.debug("login",
265 "Authentication using cached password has succeeded");
431 if (logger.debugOn()) {
432 logger.debug("logout", "Subject is being logged out");
433 }
434
435 return true;
436 }
437
438 /**
439 * Attempt authentication
440 *
441 * @param usePasswdFromSharedState a flag to tell this method whether
442 * to retrieve the password from the sharedState.
443 */
444 @SuppressWarnings("unchecked") // sharedState used as Map<String,Object>
445 private void attemptAuthentication(boolean usePasswdFromSharedState)
446 throws LoginException {
447
448 // get the username and password
449 getUsernamePassword(usePasswdFromSharedState);
450
451 if (!hashPwdMgr.authenticate(username, new String(password))) {
452 // username not found or passwords do not match
453 if (logger.debugOn()) {
454 logger.debug("login", "Invalid username or password");
455 }
456 throw new FailedLoginException("Invalid username or password");
457 }
458
459 // Save the username and password in the shared state
460 // only if authentication succeeded
461 if (storePass &&
462 !sharedState.containsKey(USERNAME_KEY) &&
463 !sharedState.containsKey(PASSWORD_KEY)) {
464 sharedState.put(USERNAME_KEY, username);
465 sharedState.put(PASSWORD_KEY, password);
466 }
467
468 // Create a new user principal
469 user = new JMXPrincipal(username);
470
471 if (logger.debugOn()) {
472 logger.debug("login",
473 "User '" + username + "' successfully validated");
474 }
475 }
476
477 /**
478 * Get the username and password.
479 * This method does not return any value.
480 * Instead, it sets global name and password variables.
481 *
482 * <p> Also note that this method will set the username and password
483 * values in the shared state in case subsequent LoginModules
484 * want to use them via use/tryFirstPass.
485 *
486 * @param usePasswdFromSharedState boolean that tells this method whether
487 * to retrieve the password from the sharedState.
488 */
489 private void getUsernamePassword(boolean usePasswdFromSharedState)
490 throws LoginException {
491
492 if (usePasswdFromSharedState) {
493 // use the password saved by the first module in the stack
|