Java Dynamic Management Kit 5.1 Tutorial

11.4 Advanced JMXMP Security Features

In addition to the simple SASL authentication demonstrated in 11.1.2 JMXMP Connectors With Simple Security above, the JMXMP connector can also implement a more complete security solution based on the DIGEST-MD5 SASL mechanism. This allows you to make communication private. These examples also show how to customize your SASL implementation by adding your own version of the SASL provider. A third implementation allows you to supply a custom configuration of the TLS socket factory.

The security features described in this section all relate exclusively to the JMXMP connector.

11.4.1 SASL Privacy

As stated above, Java DMK enables you to make your JMXMP connections private by using a combination of the SASL/DIGEST-MD5 profile, for user authentication and encryption, and file access control based on the MBeanServerForwarder interface, for user access level authorization.

There is an example of an JMXMP connector implementing SASL privacy in the current/Security/jmxmp/sasl_privacy directory in the main examplesDir.


Example 11–9 Implementing SASL Privacy in a JMXMP Connector Server

[...]
import javax.security.sasl.Sasl;

public class Server {

    public static void main(String[] args) {
        try {
            MBeanServer mbs = MBeanServerFactory.createMBeanServer();
            HashMap env = new HashMap();

            Security.addProvider(new com.sun.security.sasl.Provider());
            env.put("jmx.remote.profiles", "SASL/DIGEST-MD5");
            env.put(Sasl.QOP, "auth-conf");
            env.put("jmx.remote.sasl.callback.handler",
		             new DigestMD5ServerCallbackHandler("config" +
						                                    File.separator +
						                                    "password.properties",
						                                    null,
						                                    null));

            env.put("jmx.remote.x.access.file",
           		     "config" + File.separator + "access.properties");

            JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555);
            JMXConnectorServer cs =
                JMXConnectorServerFactory.newJMXConnectorServer(url, 
                                                                env, 
                                                                mbs);
            cs.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

This example is similar to the simple secure JMXMP example shown in 11.1.2 JMXMP Connectors With Simple Security. It is structured in the same way, with an environment map env being populated with the required security configurations before being passed into a new instance of JMXConnectorServer.

However, as you can see, unlike in the simple secure JMXMP example, which used the PLAIN SASL mechanism, this example uses the DIGEST-MD5 SASL mechanism. The SASL DIGEST-MD5 profile defines the digital signature that must be exchanged between the client and server for a connection to be made, based on the HTTP Digest Authentication mechanism. The HTTP Digest Authentication mechanism was developed by the Internet Engineering Task Force as RFC 2831.

With the protocol profile set to DIGEST-MD5, the level of protection to be provided is defined by the quality of protection property, Sasl.QOP. In this example, the Sasl.QOP is set to auth-conf, denoting that the types of protection to be implemented are authentication and confidentiality, namely encryption. The encryption cypher used is dictated by SASL and can be controlled by properties specific to the DIGEST-MD5 SASL mechanism.

A instance of the callback handler provided with this example, DigestMD5ServerCallbackHandler, is created with the required password file, config/password.properities, passed to it. Similarly, the name of the user allowed to connect to the server, and the level of access granted to that user, are defined when the config/access.properties file is passed into the environment map. These properties must be matched by the client end of the connector, if the connection is to succeed. In this example, the level of access rights granted is readwrite, and the approved user is called username. The password.properties file provides the password for user username that is expected by the SASL/DIGEST-MD5 profile.

The DigestMD5ServerCallbackHandler class, being an implementation of the standard Java callback interface CallbackHandler, allows the server to retrieve the authentication information it needs from the properties files described above. This example does not implement canonical naming or proxy files, so the second and third parameters passed to DigestMD5ServerCallbackHandler when it is instantiated are null. The DIGEST-MD5 server mechanism will then be able to compare them with the user credentials supplied remotely by the client.

The environment map containing all of the above is then used to create the connector server instance, cs.


Example 11–10 Implementing SASL Privacy in a JMXMP Connector Client

public class Client {

    public static void main(String[] args) {
        try {
            HshMap env = new HashMap();

            Security.addProvider(new com.sun.security.sasl.Provider());
            env.put("jmx.remote.profiles", "SASL/DIGEST-MD5");
            env.put(Sasl.QOP, "auth-conf");
            env.put("jmx.remote.sasl.callback.handler",
                    new DigestMD5ClientCallbackHandler("username", 
                                                       "password"));

            JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555);
            JMXConnector jmxc = JMXConnectorFactory.connect(url, env);

            MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();

            String domains[] = mbsc.getDomains();
            for (int i = 0; i < domains.length; i++) {
                System.out.println("\tDomain[" + i + "] = " + domains[i]);
            }

            ObjectName mbeanName = new ObjectName(
                                        "MBeans:type=SimpleStandard");
            System.out.println("\nCreate SimpleStandard MBean...");
            mbsc.createMBean("SimpleStandard", mbeanName, null, null);

            // Perform MBean operations, before unregistering MBean
            //  & closing connection
            [...]
            mbsc.unregisterMBean(mbeanName);

            jmxc.close();
            System.out.println("\nBye! Bye!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

As shown in Example 11–10 above, the client end of the private connection is created using a very similar set of properties as the server end. However, the client end of the connector supplies the user and password information via a different callback handler, DigestMD5ClientCallbackHandler. Like DigestMD5ServerCallbackHandler, DigestMD5ClientCallbackHandler is an implementation of the CallbackHandler interface. DigestMD5ClientCallbackHandler is passed the username and password expected by DigestMD5ServerCallbackHandler when it is instantiated, which it then passes to the DIGEST-MD5 client mechanism. The client DIGEST-MD5 mechanism then makes them available for comparison with the properties held by the server, thus allowing the connection to the MBean server to be established. Operations can then be performed on the MBeans the MBean server contains.

To Run the Secure JMXMP Connector Example with SASL Privacy

Run this example from within the examplesDir/current/Security/jmxmp/sasl_privacy directory.

  1. Compile the Java classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          server/DigestMD5ServerCallbackHandler.java \
          client/Client.java \
          client/ClientListener.java \
          client/DigestMD5ClientCallbackHandler.java 
    
  2. Start the Server.


    $ java -classpath server:mbeans:classpath Server & 
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector and its registration in the MBean server.

  3. Start the Client.


    $ java -classpath client:mbeans:classpath Client 
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.

11.4.2 SASL Provider

In addition to the simple SASL security and the privacy-based security you can apply to JMXMP connectors, you can also define a custom SASL provider mechanism by defining your own SASL Provider.

The SASL mechanism used in this example is a straightforward customized mechanism called SAMPLE SASL. The example is found in the subdirectories of examplesDir/current/Security/jmxmp/sasl_provider. The SAMPLE SASL mechanism is defined in the SampleServer class, shown below.


Example 11–11 Custom SASL Server

import java.io.*;
import javax.security.sasl.*;

public class SampleServer implements SaslServer {

    private String username;
    private boolean completed = false;

    public String getMechanismName() {
        return "SAMPLE";
    }

    public byte[] evaluateResponse(byte[] responseData) 
            throws SaslException {
        if (completed) {
            throw new SaslException("Already completed");
        }
        completed = true;
        username = new String(responseData);
        return null;
    }

    public String getAuthorizationID() {
        return username;
    }

    public boolean isComplete() {
        return completed;
    }

    public byte[] unwrap(byte[] incoming, int offset, int len) 
        throws SaslException {
        throw new SaslException("No negotiated security layer");
    }

    public byte[] wrap(byte[] outgoing, int offset, int len)
        throws SaslException {
        throw new SaslException("No negotiated security layer");
    }

    public Object getNegotiatedProperty(String propName) {
        if (propName.equals(Sasl.QOP)) {
            return "auth";
        }
        return null;
    }

    public void dispose() throws SaslException {
    }
}

The custom SASL SAMPLE mechanism defined by the SampleServer class, shown above, extends the SASL interface SaslServer. It simply calls the standard SaslServer methods one by one, to perform the following security operations:


Example 11–12 Custom SASL Server

public class SampleClient implements SaslClient {

    private byte[] username;
    private boolean completed = false;

    public SampleClient(String authorizationID) throws SaslException {
        if (authorizationID != null) {
            try {
                username = ((String)authorizationID).getBytes("UTF8");
            } catch (UnsupportedEncodingException e) {
                throw new SaslException("Cannot convert " + authorizationID
                                        + " into UTF-8", e);
            }
        } else {
            username = new byte[0];
        }
    }

    public String getMechanismName() {
        return "SAMPLE";
    }

    public boolean hasInitialResponse() {
        return true;
    }

    public byte[] evaluateChallenge(byte[] challengeData) 
            throws SaslException {
        if (completed) {
            throw new SaslException("Already completed");
        }
        completed = true;
        return username;
    }

    public boolean isComplete() {
        return completed;
    }

    public byte[] unwrap(byte[] incoming, int offset, int len) 
        throws SaslException {
        throw new SaslException("No negotiated security layer");
    }

    public byte[] wrap(byte[] outgoing, int offset, int len)
        throws SaslException {
        throw new SaslException("No negotiated security layer");
    }

    public Object getNegotiatedProperty(String propName) {
        if (propName.equals(Sasl.QOP)) {
            return "auth";
        }
        return null;
    }

    public void dispose() throws SaslException {
    }
}

The SampleClient class is an extension of the SaslClient interface, and calls the standard SaslClient methods one by one, to perform the following operations that are expected by the SampleServer:

With these SASL server and client mechanisms defined, they can then be instantiated by their respective factories.


Example 11–13 A Custom SASL Server Factory

public class ServerFactory implements SaslServerFactory {
   
     public SaslServer createSaslServer(String mechs,
				       String protocol,
				       String serverName,
				       Map props,
				       CallbackHandler cbh)
          throws SaslException {
	         if (mechs.equals("SAMPLE")) {
	             return new SampleServer();
	         }
	         return null;
     }

    public String[] getMechanismNames(Map props) {
	        return new String[]{"SAMPLE"};
    }
}

This basic implementation of the SaslServerFactory interface creates SampleServer instances when it is called with the SASL mechanism parameter set to SAMPLE. None of the other parameters are used by this mechanism.

The SampleClientFactory creates SampleClient instances in exactly the same way as the SampleServerFactory creates SampleServer instances.


Example 11–14 SASL SAMPLE Provider Class

public final class Provider extends java.security.Provider {
    public Provider() {
	super("SampleSasl", 1.0, "SAMPLE SASL MECHANISM PROVIDER");
	put("SaslClientFactory.SAMPLE", "ClientFactory");
	put("SaslServerFactory.SAMPLE", "ServerFactory");
    }
}

The SASL SAMPLE Provider constructor shown above specifies the name of the provider (SampleSasl), the version of this provider (in this case, 1.0) and a description of the provider. It then defines the server and client factories that are used to create SASL servers and clients implementing this SASL mechanism.

With the mechanisms above thus defined, all the Server and Client classes used in this example require the correct Provider to be installed in the environment.


Example 11–15 Adding a Provider to a JMX Connector Server

public class Server {

    public static void main(String[] args) {
        try {
            MBeanServer mbs = MBeanServerFactory.createMBeanServer();
            HashMap env = new HashMap();

            Security.addProvider(new Provider());
            env.put("jmx.remote.profiles", "SASL/SAMPLE");
            env.put("jmx.remote.x.access.file",
      		    "config" + File.separator + "access.properties");

            JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555);
            JMXConnectorServer cs =
                JMXConnectorServerFactory.newJMXConnectorServer(url, 
                                                                env, 
                                                                mbs);

            cs.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The SAMPLE SASL mechanism is passed into the Client in the same way.

To Run the Secure JMXMP Connector Example with a SASL Provider

Run this example from within the examplesDir/current/Security/jmxmp/sasl_provider directory.

  1. Compile the Java classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          client/Client.java \
          client/ClientListener.java \
          sample/Provider.java \
          sample/ClientFactory.java \
          sample/ServerFactory.java \
          sample/SampleClient.java \
          sample/SampleServer.java 
    
  2. Start the Server.


    $ java -classpath server:sample:mbeans:classpath Server & 
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector and its registration in the MBean server.

  3. Start the Client.


    $ java -classpath client:sample:mbeans:classpath Client 
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.

11.4.3 TLS Socket Factory

Your JMXMP connections can also be secured using an implementation of Transport Layer Security (TLS) sockets, as shown in the following example. This example is taken from the sub-directories of examplesDir/current/Security/jmxmp/tls_factory. The example shows how to provide a custom configured TLS factory for use by the client and the server.


Example 11–16 Securing a JMXMP Connector Server Using TLS Socket Factories

public class Server { 
 
    public static void main(String[] args) { 
      try { 
           MBeanServer mbs = MBeanServerFactory.createMBeanServer(); 
           HashMap env = new HashMap(); 
           String keystore = "config" + File.separator + "keystore"; 
           char keystorepass[] = "password".toCharArray(); 
           char keypassword[] = "password".toCharArray(); 
           KeyStore ks = KeyStore.getInstance("JKS"); 
           ks.load(new FileInputStream(keystore), keystorepass); 
           KeyManagerFactory kmf = 
              KeyManagerFactory.getInstance("SunX509"); 
           kmf.init(ks, keypassword); 
           SSLContext ctx = SSLContext.getInstance("TLSv1"); 
           ctx.init(kmf.getKeyManagers(), null, null); 
           SSLSocketFactory ssf = ctx.getSocketFactory(); 
           env.put("jmx.remote.profiles", "TLS"); 
           env.put("jmx.remote.tls.socket.factory", ssf); 
           env.put("jmx.remote.tls.enabled.protocols", "TLSv1"); 
           env.put("jmx.remote.tls.enabled.cipher.suites", 
                 "SSL_RSA_WITH_NULL_MD5"); 
 
           JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555); 
           JMXConnectorServer cs = 
              JMXConnectorServerFactory.newJMXConnectorServer(url, 
                                                              env, 
                                                              mbs); 
           cs.start(); 
 
         } catch (Exception e) { 
           e.printStackTrace(); 
         } 
      } 
  } 

Example 11–16 shows the creation of an MBean server mbs, an environment map env, and a key store object ks. The key store object ks is initialized by loading in the input stream keystore and the key store password, keystorepass, that grants access to the key store.

An instance of the SSL class KeyManagerFactory, named kmf, that implements the SunX509 key management algorithm, is then initialized. The kmf instance is passed the key information contained in the key store ks and the key password keypassword that grants access to the keys in the key store.

The SSL context, ctx, is set to use the protocol version TLSv1, and is initialized with kmf as its key manager. Finally, an instance of SSLSocketFactory, named ssf, is created by calling the getSocketFactory() method of the SSL context ctx.

The environment map env is populated with the TLS profiles, the SSL socket factory ssf, the TLSv1 protocol version, and the SSL_RSA_WITH_NULL_MD5 cipher suite.

Finally, with an instance of JMXConnectorServer, named cs, is created. The connector server cs is started by a call to the start() method of JMXConnectorServer.


Example 11–17 Securing a JMXMP Connector Client Using TLS Socket Factories

public class Client { 
 
   public static void main(String[] args) { 
     try { 
          HashMap env = new HashMap(); 
          String truststore = "config" + File.separator + "truststore"; 
          char truststorepass[] = "trustword".toCharArray(); 
          KeyStore ks = KeyStore.getInstance("JKS"); 
          ks.load(new FileInputStream(truststore), truststorepass); 
          TrustManagerFactory tmf =  
             TrustManagerFactory.getInstance("SunX509"); 
          tmf.init(ks); 
          SSLContext ctx = SSLContext.getInstance("TLSv1"); 
          ctx.init(null, tmf.getTrustManagers(), null); 
          SSLSocketFactory ssf = ctx.getSocketFactory(); 
          env.put("jmx.remote.profiles", "TLS"); 
          env.put("jmx.remote.tls.socket.factory", ssf); 
          env.put("jmx.remote.tls.enabled.protocols", "TLSv1"); 
          env.put("jmx.remote.tls.enabled.cipher.suites", 
             "SSL_RSA_WITH_NULL_MD5"); 
 
          JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555); 
          JMXConnector jmxc = JMXConnectorFactory.connect(url, env); 
          MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); 
          String domains[] = mbsc.getDomains(); 
          for (int i = 0; i < domains.length; i++) { 
          System.out.println("Domain[" + i + "] = " + domains[i]); 
          } 
 
          ObjectName mbeanName = 
              new ObjectName("MBeans:type=SimpleStandard"); 
          mbsc.createMBean("SimpleStandard", mbeanName, null, null); 
          // Perform MBean operations 
          // 
          [...] 
          mbsc.removeNotificationListener(mbeanName, listener); 
          mbsc.unregisterMBean(mbeanName); 
          jmxc.close(); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
     } 
} 

As for the Server, Example 11–17 shows the creation of an environment map env, and a key store object ks for the JMXMP connector Client. The key store object ks is initialized by loading in the input stream truststore and the password, truststorepass, that grants access to the trust store.

An instance of the SSL class TrustManagerFactory, named tmf, that implements the SunX509 key management algorithm, is then initialized. The tmf instance is passed the key information contained in the key store ks.

The SSL context, ctx, is set to use the protocol version TLSv1, and is initialized with tmf as its trust manager. Finally, an instance of SSLSocketFactory, named ssf, is created by calling the getSocketFactory() method of the SSL context ctx.

The environment map env is populated with the the SSL socket factory ssf, the TLSv1 protocol version, and the SSL_RSA_WITH_NULL_MD5 cipher suite.

An instance of JMXConnector, named jmxc is then created, and an MBean server connection, mbsc, is made by calling the getMBeanServerConnection method of the JMX Remote API connector jmxc.

In code that is not shown here, when the connection to the MBean server has been established, the Client creates an MBean called SimpleStandard and performs various operations on it. Once these MBean operations have completed, the Client unregisters the MBean, and closes down the connection jmxc.

To Run the Secure JMXMP Connector Example with TLS Socket Factories

Run this example from within the examplesDir/current/Security/jmxmp/tls_factory directory.

  1. Compile the Java classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          client/Client.java \
          client/ClientListener.java 
    
  2. Start the Server.


    $ java -classpath server:mbeans:classpath Server & 
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector and its registration in the MBean server.

  3. Start the Client.


    $ java -classpath client:mbeans:classpath Client 
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.