How to encrypt passwords for JConsole's password file

EclipseGuru picture EclipseGuru · Nov 4, 2009 · Viewed 8.3k times · Source

I am using the JConsole to access my application MBeans and i use the the password.properties file. But as per the Sun's specification this file contains passwords in clear text formats only.

com.sun.management.jmxremote.password.file=<someLocation>/password.properties

Now i would want to encrypt the password and use it for the JMX user authentication from JConsole (the username and password fields in Remote section). I could use any pre-defined encryption logic or my own encryption algorithms.

Does anyone know of any such interception to change the plain text password to encrypted one so that the JMX Framework too knows about the encrypted password?

My Current password file:

guest  guest
admin  admin

With Encryption it should look like:

guest  ENC(RjqpRYbAOwbAfAEDBdHJ7Q4l/GO5IoJidZctNT5oG64=)
admin  ENC(psg3EnDei6fVRuqHeLwOqNTgIWkwQTjI2+u2O7MXXWc=)

Answer

mhaller picture mhaller · Nov 5, 2009

You can use the configuration parameter com.sun.management.jmxremote.login.config in the management.properties file (see %JAVA_HOME%/lib/management/management.properties) to configure which Authenticator and LoginModule to use.

The default is the following:

JMXPluggableAuthenticator {
    com.sun.jmx.remote.security.FileLoginModule required;
};

which reads plain text password file jmxremote.password. Since the com.sun.jmx.remote.security.JMXPluggableAuthenticator can be reconfigured to use any LoginModule implementation, you are free to either choose an existing LoginModule or to implement your own which uses encrypted password files.

To reimplement FileLoginModule, you should have a look at the attemptAuthentication(boolean) method, which actually performs the authentication and which you probably are going to replace. Implement the javax.security.auth.spi.LoginModule interface and use the given CallbackHandler (you will get it from the init() method) to ask for a username and password. Encrypt/hash the received password and compare it against the one read from your encrypted password file. Pseudo code:

public class EncryptedFileLoginModule implements LoginModule {

@Override
public void initialize(Subject subject, CallbackHandler callbackHandler,
        Map<String, ?> sharedState, Map<String, ?> options) {
    this.subject = subject;
    this.callbackHandler = callbackHandler;
}

public boolean login() throws LoginException {
    attemptLogin();
    if (username == null || password == null) {
        throw new LoginException("Either no username or no password specified");
    }
    MessageDigest instance = MessageDigest.getInstance("SHA-1");
    byte[] raw = new String(password).getBytes();
    byte[] crypted = instance.digest(raw);
    // TODO: Compare to the one stored locally
    if (!authenticated) throw new LoginException();
    return true;
}

private void attemptLogin() throws LoginException {
    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback("username");
    callbacks[1] = new PasswordCallback("password", false);
        callbackHandler.handle(callbacks);
        username = ((NameCallback) callbacks[0]).getName();
        user = new JMXPrincipal(username);
        char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
        password = new char[tmpPassword.length];
        System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
        ((PasswordCallback) callbacks[1]).clearPassword();
}

However, as this is already the server-side, the password would afaik be still transferred in plain text if you don't enforce JMX over SSL. So, either enforce SSL or use another transport protocol mechanism which encodes the credentials before transmitting them over the wire.

To conclude, it's perhaps much better to rely on existing authentication mechanisms provided by JAAS. If, for example, you're running in a local Windows environment, you can easily use the NTLoginModule for auto-login. But it only works on local machine.

Create a file c:/temp/mysecurity.cfg:

MyLoginModule {
 com.sun.security.auth.module.NTLoginModule REQUIRED  debug=true debugNative=true;
};

Next, configure the jmxremote.access file to contain the usernames or roles you wish to grant access to your JMX server:

monitorRole readonly
controlRole readwrite ...
mhaller readonly

(I recommend to enable debug mode until it works. You will see all the user names, domain names and group names when a user tries to log in) Set the following JVM arguments for your server:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8686
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=true
-Djava.net.preferIPv4Stack=true
-Djava.security.auth.login.config=c:/temp/mysecurity.cfg
-Dcom.sun.management.jmxremote.login.config=MyLoginModule

Start up your application and try to connect using JConsole or VisualVM.

Note that JConsole, you will need to specify a username and a password, although it's not going to be used. Any password and any username will work. The reason is because jconsole will try to authenticate with null username and null password, which is blocked explicitly. VisualVM does a better job by using empty strings for username and password when none are entered by the user.

Also note that the NTLoginModule does not work when connecting remotely, i think you would have to use a more sophisticated login module, but Sun already provides enough of them:

  • com.sun.security.auth.module.Krb5LoginModule: Authenticates users using the Kerberos protocols
  • com.sun.security.auth.module.LdapLoginModule: (new in Java 6): Performs authentication against an LDAP server by specifying technical connection user
  • com.sun.security.auth.module.JndiLoginModule: Performs authentication against an LDAP server registered in the JNDI context
  • com.sun.security.auth.module.KeyStoreLoginModule: Authenticates users by using a Java Keystore. Supports PIN or smart card authentication.

You will want to have a look at the LdapLoginModule