Author: aidan Date: Fri Feb 13 14:00:10 2009 New Revision: 744113 URL: http://svn.apache.org/viewvc?rev=744113&view=rev Log: QPID-1511 : Adds authentication and ssl encryption capabilities to the RMI based JMXConnectorServer in use, enforces use of the custom MBeanInvocationhandlerImp when using the RMI based JMX, and implements a customised RMI registry to prevent external changes being possible. Updated Management console accordingly.
Patch from Robbert Gemmell <gemme...@dcs.gla.ac.uk> Added: qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/ qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java qpid/trunk/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/ qpid/trunk/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java Modified: qpid/trunk/qpid/java/broker/etc/config.xml qpid/trunk/qpid/java/broker/etc/persistent_config.xml qpid/trunk/qpid/java/broker/etc/transient_config.xml qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java qpid/trunk/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/JMXConnnectionFactory.java qpid/trunk/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/linux-gtk-x86/qpidmc.ini qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/macosx/Contents/MacOS/qpidmc.ini qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini Modified: qpid/trunk/qpid/java/broker/etc/config.xml URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker/etc/config.xml?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker/etc/config.xml (original) +++ qpid/trunk/qpid/java/broker/etc/config.xml Fri Feb 13 14:00:10 2009 @@ -43,9 +43,15 @@ <socketSendBuffer>32768</socketSendBuffer> </connector> <management> - <enabled>false</enabled> + <enabled>true</enabled> <jmxport>8999</jmxport> <security-enabled>false</security-enabled> + <ssl> + <enabled>true</enabled> + <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore --> + <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath> + <keyStorePassword>password</keyStorePassword> + </ssl> </management> <advanced> <filterchain enableExecutorPool="true"/> Modified: qpid/trunk/qpid/java/broker/etc/persistent_config.xml URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker/etc/persistent_config.xml?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker/etc/persistent_config.xml (original) +++ qpid/trunk/qpid/java/broker/etc/persistent_config.xml Fri Feb 13 14:00:10 2009 @@ -35,8 +35,15 @@ <socketSendBuffer>32768</socketSendBuffer> </connector> <management> - <enabled>false</enabled> + <enabled>true</enabled> <jmxport>8999</jmxport> + <security-enabled>false</security-enabled> + <ssl> + <enabled>true</enabled> + <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore --> + <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath> + <keyStorePassword>password</keyStorePassword> + </ssl> </management> <advanced> <filterchain enableExecutorPool="true"/> Modified: qpid/trunk/qpid/java/broker/etc/transient_config.xml URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker/etc/transient_config.xml?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker/etc/transient_config.xml (original) +++ qpid/trunk/qpid/java/broker/etc/transient_config.xml Fri Feb 13 14:00:10 2009 @@ -35,8 +35,15 @@ <socketSendBuffer>32768</socketSendBuffer> </connector> <management> - <enabled>false</enabled> + <enabled>true</enabled> <jmxport>8999</jmxport> + <security-enabled>false</security-enabled> + <ssl> + <enabled>true</enabled> + <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore --> + <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath> + <keyStorePassword>password</keyStorePassword> + </ssl> </management> <advanced> <filterchain enableExecutorPool="true"/> Modified: qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java (original) +++ qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java Fri Feb 13 14:00:10 2009 @@ -20,6 +20,7 @@ */ package org.apache.qpid.server.management; +import org.apache.commons.configuration.ConfigurationException; import org.apache.log4j.Logger; import org.apache.qpid.AMQException; import org.apache.qpid.server.registry.ApplicationRegistry; @@ -27,6 +28,7 @@ import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.rmi.RMIPasswordAuthenticator; import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser; import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; @@ -37,31 +39,45 @@ import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; import javax.management.remote.MBeanServerForwarder; +import javax.management.remote.rmi.RMIConnectorServer; +import javax.management.remote.rmi.RMIJRMPServerImpl; +import javax.management.remote.rmi.RMIServerImpl; +import javax.rmi.ssl.SslRMIClientSocketFactory; +import javax.rmi.ssl.SslRMIServerSocketFactory; + +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import java.util.Map; /** - * This class starts up an MBeanserver. If out of the box agent is being used then there are no security features - * implemented. To use the security features like user authentication, turn off the jmx options in the "QPID_OPTS" env - * variable and use JMXMP connector server. If JMXMP connector is not available, then the standard JMXConnector will be - * used, which again doesn't have user authentication. + * This class starts up an MBeanserver. If out of the box agent has been enabled then there are no + * security features implemented like user authentication and authorisation. */ public class JMXManagedObjectRegistry implements ManagedObjectRegistry { private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class); + private static final Logger _startupLog = Logger.getLogger("Qpid.Broker"); + + public static final String MANAGEMENT_PORT_CONFIG_PATH = "management.jmxport"; + public static final int MANAGEMENT_PORT_DEFAULT = 8999; + public static final int PORT_EXPORT_OFFSET = 100; private final MBeanServer _mbeanServer; private Registry _rmiRegistry; - private JMXServiceURL _jmxURL; - public static final String MANAGEMENT_PORT_CONFIG_PATH = "management.jmxport"; - public static final int MANAGEMENT_PORT_DEFAULT = 8999; public JMXManagedObjectRegistry() throws AMQException { @@ -77,44 +93,38 @@ } - public void start() throws IOException + public void start() throws IOException, ConfigurationException { - // Check if the "QPID_OPTS" is set to use Out of the Box JMXAgent + //check if system properties are set to use the JVM's out-of-the-box JMXAgent if (areOutOfTheBoxJMXOptionsSet()) { - _log.info("JMX: Using the out of the box JMX Agent"); + _log.warn("JMX: Using the out of the box JMX Agent"); + _startupLog.warn("JMX: Using the out of the box JMX Agent"); return; } IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); - boolean security = appRegistry.getConfiguration().getBoolean("management.security-enabled", false); + boolean jmxmpSecurity = appRegistry.getConfiguration().getBoolean("management.security-enabled", false); int port = appRegistry.getConfiguration().getInt(MANAGEMENT_PORT_CONFIG_PATH, MANAGEMENT_PORT_DEFAULT); - if (security) - { - // For SASL using JMXMP - _jmxURL = new JMXServiceURL("jmxmp", null, port); + //retrieve the Principal Database assigned to JMX authentication duties + String jmxDatabaseName = appRegistry.getConfiguration().getString("security.jmx.principal-database"); + Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases(); + PrincipalDatabase db = map.get(jmxDatabaseName); - Map env = new HashMap(); - Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases(); - PrincipalDatabase db = null; + final JMXConnectorServer cs; + HashMap<String,Object> env = new HashMap<String,Object>(); - for (Map.Entry<String, PrincipalDatabase> entry : map.entrySet()) - { - if (entry.getValue() instanceof Base64MD5PasswordFilePrincipalDatabase) - { - db = entry.getValue(); - break; - } - else if (entry.getValue() instanceof PlainPasswordFilePrincipalDatabase) - { - db = entry.getValue(); - } - } + if (jmxmpSecurity) + { + // For SASL using JMXMP + JMXServiceURL jmxURL = new JMXServiceURL("jmxmp", null, port); + String saslType = null; if (db instanceof Base64MD5PasswordFilePrincipalDatabase) { + saslType = "SASL/CRAM-MD5"; env.put("jmx.remote.profiles", "SASL/CRAM-MD5"); CRAMMD5HashedInitialiser initialiser = new CRAMMD5HashedInitialiser(); initialiser.initialise(db); @@ -122,6 +132,7 @@ } else if (db instanceof PlainPasswordFilePrincipalDatabase) { + saslType = "SASL/PLAIN"; PlainInitialiser initialiser = new PlainInitialiser(); initialiser.initialise(db); env.put("jmx.remote.sasl.callback.handler", initialiser.getCallbackHandler()); @@ -131,43 +142,209 @@ //workaround NPE generated from env map classloader issue when using Eclipse 3.4 to launch env.put("jmx.remote.profile.provider.class.loader", this.getClass().getClassLoader()); - // Enable the SSL security and server authentication - /* - SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); - SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(); - env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); - env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf); - */ - - JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, env, _mbeanServer); - MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(); - cs.setMBeanServerForwarder(mbsf); - cs.start(); - _log.warn("JMX: Started JMXConnector server on port '" + port + "' with SASL"); - + _log.warn("Starting JMXMP based JMX ConnectorServer on port '" + port + "' with " + saslType); + _startupLog.warn("Starting JMXMP based JMX ConnectorServer on port '" + port + "' with " + saslType); + + cs = JMXConnectorServerFactory.newJMXConnectorServer(jmxURL, env, _mbeanServer); } else - { - startJMXConnectorServer(port); - _log.warn("JMX: Started JMXConnector server on port '" + port + "' with security disabled"); + { + //Socket factories for the RMIConnectorServer, either default or SLL depending on configuration + RMIClientSocketFactory csf; + RMIServerSocketFactory ssf; + + //check ssl enabled option in config, default to true if option is not set + boolean sslEnabled = appRegistry.getConfiguration().getBoolean("management.ssl.enabled", true); + + if (sslEnabled) + { + //set the SSL related system properties used by the SSL RMI socket factories to the values + //given in the configuration file, unless command line settings have already been specified + String keyStorePath; + + if(System.getProperty("javax.net.ssl.keyStore") != null) + { + keyStorePath = System.getProperty("javax.net.ssl.keyStore"); + } + else{ + keyStorePath = appRegistry.getConfiguration().getString("management.ssl.keyStorePath", null); + } + + //check the keystore path value is valid + if (keyStorePath == null) + { + throw new ConfigurationException("JMX management SSL keystore path not defined, " + + "unable to start SSL protected JMX ConnectorServer"); + } + else + { + //ensure the system property is set + System.setProperty("javax.net.ssl.keyStore", keyStorePath); + + //check the file is usable + File ksf = new File(keyStorePath); + + if (!ksf.exists()) + { + throw new FileNotFoundException("Cannot find JMX management SSL keystore file " + ksf); + } + if (!ksf.canRead()) + { + throw new FileNotFoundException("Cannot read JMX management SSL keystore file: " + + ksf + ". Check permissions."); + } + + _log.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath()); + _startupLog.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath()); + } + + //check the key store password is set + if (System.getProperty("javax.net.ssl.keyStorePassword") == null) + { + + if (appRegistry.getConfiguration().getString("management.ssl.keyStorePassword") == null) + { + throw new ConfigurationException("JMX management SSL keystore password not defined, " + + "unable to start requested SSL protected JMX server"); + } + else + { + System.setProperty("javax.net.ssl.keyStorePassword", + appRegistry.getConfiguration().getString("management.ssl.keyStorePassword")); + } + } + + //create the SSL RMI socket factories + csf = new SslRMIClientSocketFactory(); + ssf = new SslRMIServerSocketFactory(); + + _log.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" + + (port +PORT_EXPORT_OFFSET) + ") with SSL"); + _startupLog.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" + + (port +PORT_EXPORT_OFFSET) + ") with SSL"); + } + else + { + //Do not specify any specific RMI socket factories, resulting in use of the defaults. + csf = null; + ssf = null; + + _log.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")"); + _startupLog.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")"); + } + + //add a JMXAuthenticator implementation the env map to authenticate the RMI based JMX connector server + RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator(); + rmipa.setPrincipalDatabase(db); + env.put(JMXConnectorServer.AUTHENTICATOR, rmipa); + + /* + * Start a RMI registry on the management port, to hold the JMX RMI ConnectorServer stub. + * Using custom socket factory to prevent anyone (including us unfortunately) binding to the registry using RMI. + * As a result, only binds made using the object reference will succeed, thus securing it from external change. + */ + System.setProperty("java.rmi.server.randomIDs", "true"); + _rmiRegistry = LocateRegistry.createRegistry(port, null, new CustomRMIServerSocketFactory()); + + /* + * We must now create the RMI ConnectorServer manually, as the JMX Factory methods use RMI calls + * to bind the ConnectorServer to the registry, which will now fail as for security we have + * locked it from any RMI based modifications, including our own. Instead, we will manually bind + * the RMIConnectorServer stub to the registry using its object reference, which will still succeed. + * + * The registry is exported on the defined management port 'port'. We will export the RMIConnectorServer + * on 'port +1'. Use of these two well-defined ports will ease any navigation through firewall's. + */ + final RMIServerImpl rmiConnectorServerStub = new RMIJRMPServerImpl(port+PORT_EXPORT_OFFSET, csf, ssf, env); + final String hostname = InetAddress.getLocalHost().getHostName(); + final JMXServiceURL externalUrl = new JMXServiceURL( + "service:jmx:rmi://"+hostname+":"+(port+PORT_EXPORT_OFFSET)+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi"); + + final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, port+PORT_EXPORT_OFFSET); + cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer) + { + @Override + public synchronized void start() throws IOException + { + try + { + //manually bind the connector server to the registry at key 'jmxrmi', like the out-of-the-box agent + _rmiRegistry.bind("jmxrmi", rmiConnectorServerStub); + } + catch (AlreadyBoundException abe) + { + //key was already in use. shouldnt happen here as its a new registry, unbindable by normal means. + + //IOExceptions are the only checked type throwable by the method, wrap and rethrow + IOException ioe = new IOException(abe.getMessage()); + ioe.initCause(abe); + throw ioe; + } + + //now do the normal tasks + super.start(); + } + + @Override + public JMXServiceURL getAddress() + { + //must return our pre-crafted url that includes the full details, inc JNDI details + return externalUrl; + } + + }; } + + //Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer. + MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(); + cs.setMBeanServerForwarder(mbsf); + cs.start(); } - /** - * Starts up an RMIRegistry at configured port and attaches a JMXConnectorServer to it. - * - * @param port - * - * @throws IOException + /* + * Custom RMIServerSocketFactory class, used to prevent updates to the RMI registry. + * Supplied to the registry at creation, this will prevent RMI-based operations on the + * registry such as attempting to bind a new object, thereby securing it from tampering. + * This is accomplished by always returning null when attempting to determine the address + * of the caller, thus ensuring the registry will refuse the attempt. Calls to bind etc + * made using the object reference will not be affected and continue to operate normally. */ - private void startJMXConnectorServer(int port) throws IOException + + private class CustomRMIServerSocketFactory implements RMIServerSocketFactory { - startRMIRegistry(port); - _jmxURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi"); - JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, null, _mbeanServer); - cs.start(); + + public ServerSocket createServerSocket(int port) throws IOException + { + return new NoLocalAddressServerSocket(port); + } + + private class NoLocalAddressServerSocket extends ServerSocket + { + NoLocalAddressServerSocket(int port) throws IOException + { + super(port); + } + + @Override + public Socket accept() throws IOException + { + Socket s = new NoLocalAddressSocket(); + super.implAccept(s); + return s; + } + } + + private class NoLocalAddressSocket extends Socket + { + @Override + public InetAddress getInetAddress() + { + return null; + } + } } + public void registerObject(ManagedObject managedObject) throws JMException { _mbeanServer.registerMBean(managedObject, managedObject.getObjectName()); @@ -178,11 +355,7 @@ _mbeanServer.unregisterMBean(managedObject.getObjectName()); } - /** - * Checks is the "QPID_OPTS" env variable is set to use the out of the box JMXAgent. - * - * @return - */ + // checks if the system properties are set which enable the JVM's out-of-the-box JMXAgent. private boolean areOutOfTheBoxJMXOptionsSet() { if (System.getProperty("com.sun.management.jmxremote") != null) @@ -198,19 +371,6 @@ return false; } - /** - * Starts the rmi registry at given port - * - * @param port - * - * @throws RemoteException - */ - private void startRMIRegistry(int port) throws RemoteException - { - System.setProperty("java.rmi.server.randomIDs", "true"); - _rmiRegistry = LocateRegistry.createRegistry(port); - } - // stops the RMIRegistry, if it was running and bound to a port public void close() throws RemoteException { Modified: qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java (original) +++ qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java Fri Feb 13 14:00:10 2009 @@ -21,6 +21,9 @@ package org.apache.qpid.server.management; import javax.management.JMException; + +import org.apache.commons.configuration.ConfigurationException; + import java.rmi.RemoteException; import java.io.IOException; @@ -38,7 +41,7 @@ */ public interface ManagedObjectRegistry { - void start() throws IOException; + void start() throws IOException, ConfigurationException; void registerObject(ManagedObject managedObject) throws JMException; Added: qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java?rev=744113&view=auto ============================================================================== --- qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java (added) +++ qpid/trunk/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java Fri Feb 13 14:00:10 2009 @@ -0,0 +1,182 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.rmi; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collections; + +import javax.management.remote.JMXAuthenticator; +import javax.management.remote.JMXPrincipal; +import javax.security.auth.Subject; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +public class RMIPasswordAuthenticator implements JMXAuthenticator +{ + static final String UNABLE_TO_LOOKUP = "The broker was unable to lookup the user details"; + static final String SHOULD_BE_STRING_ARRAY = "User details should be String[]"; + static final String SHOULD_HAVE_2_ELEMENTS = "User details should have 2 elements, username, password"; + static final String SHOULD_BE_NON_NULL = "Supplied username and password should be non-null"; + static final String INVALID_CREDENTIALS = "Invalid user details supplied"; + static final String CREDENTIALS_REQUIRED = "User details are required. " + + "Please ensure you are using an up to date management console to connect."; + + public static final String DEFAULT_ENCODING = "utf-8"; + private PrincipalDatabase _db = null; + + public RMIPasswordAuthenticator() + { + } + + public void setPrincipalDatabase(PrincipalDatabase pd) + { + this._db = pd; + } + + public Subject authenticate(Object credentials) throws SecurityException + { + // Verify that credential's are of type String[]. + if (!(credentials instanceof String[])) + { + if (credentials == null) + { + throw new SecurityException(CREDENTIALS_REQUIRED); + } + else + { + throw new SecurityException(SHOULD_BE_STRING_ARRAY); + } + } + + // Verify that required number of credential's. + final String[] userCredentials = (String[]) credentials; + if (userCredentials.length != 2) + { + throw new SecurityException(SHOULD_HAVE_2_ELEMENTS); + } + + String username = (String) userCredentials[0]; + String password = (String) userCredentials[1]; + + // Verify that all required credential's are actually present. + if (username == null || password == null) + { + throw new SecurityException(SHOULD_BE_NON_NULL); + } + + boolean authenticated = false; + + // Perform authentication + try + { + PasswordCallback pwCallback = new PasswordCallback("prompt",false); + UsernamePrincipal uname = new UsernamePrincipal(username); + + if (_db instanceof Base64MD5PasswordFilePrincipalDatabase) + { + //retrieve the stored password for the given user + _db.setPassword(uname, pwCallback); + + //compare the MD5Hash of the given password with the stored value + if (Arrays.equals(getMD5Hash(password), pwCallback.getPassword())) + { + authenticated = true; + } + } + else if (_db instanceof PlainPasswordFilePrincipalDatabase) + { + //retrieve the users stored password and compare with given value + _db.setPassword(uname, pwCallback); + + if (password.equals(new String(pwCallback.getPassword()))) + { + authenticated = true; + } + } + else + { + throw new SecurityException(UNABLE_TO_LOOKUP); + } + } + catch (AccountNotFoundException e) + { + throw new SecurityException(INVALID_CREDENTIALS); + } + catch (UnsupportedEncodingException e) + { + throw new SecurityException(UNABLE_TO_LOOKUP); + } + catch (NoSuchAlgorithmException e) + { + throw new SecurityException(UNABLE_TO_LOOKUP); + } + catch (IOException e) + { + throw new SecurityException(UNABLE_TO_LOOKUP); + } + + if (authenticated) + { + //credential's check out, return the appropriate JAAS Subject + return new Subject(true, + Collections.singleton(new JMXPrincipal(username)), + Collections.EMPTY_SET, + Collections.EMPTY_SET); + } + else + { + throw new SecurityException(INVALID_CREDENTIALS); + } + } + + public static char[] getMD5Hash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException + { + byte[] data = text.getBytes(DEFAULT_ENCODING); + + MessageDigest md = MessageDigest.getInstance("MD5"); + + for (byte b : data) + { + md.update(b); + } + + byte[] digest = md.digest(); + + char[] hash = new char[digest.length ]; + + int index = 0; + for (byte b : digest) + { + hash[index++] = (char) b; + } + + return hash; + } +} \ No newline at end of file Added: qpid/trunk/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java?rev=744113&view=auto ============================================================================== --- qpid/trunk/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java (added) +++ qpid/trunk/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java Fri Feb 13 14:00:10 2009 @@ -0,0 +1,267 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.rmi; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collections; + +import javax.management.remote.JMXPrincipal; +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; + +import junit.framework.TestCase; + +public class RMIPasswordAuthenticatorTest extends TestCase +{ + private final String USERNAME = "guest"; + private final String PASSWORD = "guest"; + private final String B64_MD5HASHED_PASSWORD = "CE4DQ6BIb/BVMN9scFyLtA=="; + private RMIPasswordAuthenticator _rmipa; + + private Base64MD5PasswordFilePrincipalDatabase _md5Pd; + private File _md5PwdFile; + + private PlainPasswordFilePrincipalDatabase _plainPd; + private File _plainPwdFile; + + private Subject testSubject; + + protected void setUp() throws Exception + { + _rmipa = new RMIPasswordAuthenticator(); + + _md5Pd = new Base64MD5PasswordFilePrincipalDatabase(); + _md5PwdFile = createTempPasswordFile(this.getClass().getName()+"md5pwd", USERNAME, B64_MD5HASHED_PASSWORD); + _md5Pd.setPasswordFile(_md5PwdFile.getAbsolutePath()); + + _plainPd = new PlainPasswordFilePrincipalDatabase(); + _plainPwdFile = createTempPasswordFile(this.getClass().getName()+"plainpwd", USERNAME, PASSWORD); + _plainPd.setPasswordFile(_plainPwdFile.getAbsolutePath()); + + testSubject = new Subject(true, + Collections.singleton(new JMXPrincipal(USERNAME)), + Collections.EMPTY_SET, + Collections.EMPTY_SET); + } + + private File createTempPasswordFile(String filenamePrefix, String user, String password) + { + try + { + File testFile = File.createTempFile(filenamePrefix,"tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + writer.write(user + ":" + password); + writer.newLine(); + + writer.flush(); + writer.close(); + + return testFile; + } + catch (IOException e) + { + fail("Unable to create temporary test password file." + e.getMessage()); + } + + return null; + } + + + //********** Test Methods *********// + + + public void testAuthenticate() + { + String[] credentials; + Subject newSubject; + + // Test when no PD has been set + try + { + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to lack of principal database"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.UNABLE_TO_LOOKUP, se.getMessage()); + } + + //The PrincipalDatabase's are tested primarily by their own tests, but + //minimal tests are done here to exercise their usage in this area. + + // Test correct passwords are verified with an MD5 PD + try + { + _rmipa.setPrincipalDatabase(_md5Pd); + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + assertTrue("Returned subject does not equal expected value", + newSubject.equals(testSubject)); + } + catch (Exception e) + { + fail("Unexpected Exception:" + e.getMessage()); + } + + // Test incorrect passwords are not verified with an MD5 PD + try + { + credentials = new String[]{USERNAME, PASSWORD+"incorrect"}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to incorrect password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test non-existent accounts are not verified with an MD5 PD + try + { + credentials = new String[]{USERNAME+"invalid", PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to non-existant account"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test correct passwords are verified with a Plain PD + try + { + _rmipa.setPrincipalDatabase(_plainPd); + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + assertTrue("Returned subject does not equal expected value", + newSubject.equals(testSubject)); + } + catch (Exception e) + { + fail("Unexpected Exception"); + } + + // Test incorrect passwords are not verified with a Plain PD + try + { + credentials = new String[]{USERNAME, PASSWORD+"incorrect"}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to incorrect password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test non-existent accounts are not verified with an Plain PD + try + { + credentials = new String[]{USERNAME+"invalid", PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to non existant account"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test handling of non-string credential's + try + { + Object[] objCredentials = new Object[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(objCredentials); + fail("SecurityException expected due to non string[] credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_STRING_ARRAY, se.getMessage()); + } + + // Test handling of incorrect number of credential's + try + { + credentials = new String[]{USERNAME, PASSWORD, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to supplying wrong number of credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_HAVE_2_ELEMENTS, se.getMessage()); + } + + // Test handling of null credential's + try + { + //send a null array + credentials = null; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to not supplying an array of credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.CREDENTIALS_REQUIRED, se.getMessage()); + } + + try + { + //send a null password + credentials = new String[]{USERNAME, null}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to sending a null password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage()); + } + + try + { + //send a null username + credentials = new String[]{null, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to sending a null username"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage()); + } + } + +} Modified: qpid/trunk/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/JMXConnnectionFactory.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/JMXConnnectionFactory.java?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/JMXConnnectionFactory.java (original) +++ qpid/trunk/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/JMXConnnectionFactory.java Fri Feb 13 14:00:10 2009 @@ -29,6 +29,7 @@ import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; +import javax.net.ssl.SSLException; import javax.security.auth.callback.CallbackHandler; import javax.security.sasl.SaslClientFactory; @@ -40,8 +41,13 @@ import org.apache.qpid.management.common.sasl.UsernameHashedPasswordCallbackHandler; public class JMXConnnectionFactory { - - public static JMXConnector getJMXConnection(long timeout, String host, int port, String username, String password) throws Exception + + private static final String NON_JRMP_SERVER = "non-JRMP server at remote endpoint"; + private static final String SERVER_SUPPORTED_PROFILES = "The server supported profiles"; + private static final String CLIENT_REQUIRED_PROFILES = "do not match the client required profiles"; + + public static JMXConnector getJMXConnection(long timeout, String host, int port, String username, String password) + throws SSLException, IOException, Exception { //auto-negotiate an RMI or JMXMP (SASL/CRAM-MD5 or SASL/PLAIN) JMX connection to broker try @@ -51,11 +57,30 @@ catch (IOException rmiIOE) { // check if the ioe was raised because we tried connecting to a non RMI-JRMP based JMX server - boolean jrmpServer = !rmiIOE.getMessage().contains("non-JRMP server at remote endpoint"); + boolean jrmpServer = !rmiIOE.getMessage().contains(NON_JRMP_SERVER); if (jrmpServer) { - throw rmiIOE; + //it was an RMI-JRMP based JMX server, so something else went wrong. Check for SSL issues. + Throwable rmiIOECause = rmiIOE.getCause(); + boolean isSSLException = false; + if (rmiIOECause != null) + { + isSSLException = rmiIOECause instanceof SSLException; + } + + //if it was an SSLException based cause, throw it + if (isSSLException) + { + throw (SSLException) rmiIOECause; + } + else + { + //can't determine cause, throw new IOE citing the original as cause + IOException nioe = new IOException(); + nioe.initCause(rmiIOE); + throw nioe; + } } else { @@ -67,8 +92,8 @@ catch (IOException cramIOE) { // check if the IOE was raised because we tried connecting to a SASL/PLAIN server using SASL/CRAM-MD5 - boolean plainProfileServer = cramIOE.getMessage().contains("The server supported profiles [SASL/PLAIN]" + - " do not match the client required profiles [SASL/CRAM-MD5]"); + boolean plainProfileServer = cramIOE.getMessage().contains(SERVER_SUPPORTED_PROFILES + + " [" + Constants.SASL_PLAIN + "] " + CLIENT_REQUIRED_PROFILES + " [" + Constants.SASL_CRAMMD5 + "]"); if (!plainProfileServer) { @@ -87,7 +112,7 @@ { /* Out of options now. Check that the IOE was raised because we tried connecting to a server * which didnt support SASL/PLAIN. If so, signal an unknown profile type. If not, raise the exception. */ - boolean unknownProfile = cramIOE.getMessage().contains("do not match the client required profiles [SASL/PLAIN]"); + boolean unknownProfile = plainIOE.getMessage().contains(CLIENT_REQUIRED_PROFILES + " [" + Constants.SASL_PLAIN + "]"); if (unknownProfile) { @@ -106,18 +131,19 @@ } } - private static JMXConnector createJMXconnector(String connectionType, long timeout, String host, int port, String userName, String password) throws IOException, Exception + private static JMXConnector createJMXconnector(String connectionType, long timeout, String host, int port, + String userName, String password) throws IOException, Exception { Map<String, Object> env = new HashMap<String, Object>(); - String securityMechanism = null; JMXServiceURL jmxUrl = null; if (connectionType == "RMI") { - securityMechanism = Constants.MECH_PLAIN; - jmxUrl = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/jmxrmi"); - env = null; + + //Add user credential's to environment map for RMIConnector startup. + //These will be used for authentication by the remote RMIConnectorServer if supported, or ignored otherwise. + env.put(JMXConnector.CREDENTIALS, new String[] {userName,password}); } else if (connectionType.contains("JMXMP")) { @@ -143,8 +169,6 @@ if (connectionType == "JMXMP_CRAM-MD5") { - securityMechanism = Constants.MECH_CRAMMD5; - Map<String, Class<? extends SaslClientFactory>> map = new HashMap<String, Class<? extends SaslClientFactory>>(); map.put("CRAM-MD5-HASHED", CRAMMD5HashedSaslClientFactory.class); Security.addProvider(new JCAProvider(map)); @@ -156,8 +180,6 @@ } else if (connectionType == "JMXMP_PLAIN") { - securityMechanism = Constants.MECH_PLAIN; - Security.addProvider(new SaslProvider()); CallbackHandler handler = new UserPasswordCallbackHandler(userName, password); env.put("jmx.remote.profiles", Constants.SASL_PLAIN); @@ -165,7 +187,7 @@ } else { - throw new Exception("Unknown authentication mechanism"); + throw new Exception("Unknown JMXMP authentication mechanism"); } } else Modified: qpid/trunk/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java (original) +++ qpid/trunk/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java Fri Feb 13 14:00:10 2009 @@ -24,6 +24,11 @@ import java.io.IOException; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; +import javax.net.ssl.SSLPeerUnverifiedException; + import org.apache.qpid.management.ui.ApplicationRegistry; import org.apache.qpid.management.ui.ApplicationWorkbenchAdvisor; import org.apache.qpid.management.ui.Constants; @@ -47,6 +52,10 @@ public static final String SERVER_UNAVAILABLE = "Unable to connect to the specified Qpid JMX server"; public static final String INVALID_PERSPECTIVE = "Invalid Perspective"; public static final String CHANGE_PERSPECTIVE = "Please use the Qpid Management Perspective"; + + private static final String SSL_EMPTY_TRUSTANCHORS = "the trustAnchors parameter must be non-empty"; + private static final String SSL_UNABLE_TO_FIND_CERTPATH = "sun.security.provider.certpath.SunCertPathBuilderException: " + + "unable to find valid certification path to requested target"; /** * We will cache window object in order to @@ -93,9 +102,59 @@ //determine the error message to display if (msg == null) { - if (ex instanceof IOException) + if (ex instanceof SSLException) + { + if (ex instanceof SSLKeyException) + { + msg = "SSL key was invalid, please check the certificate configuration."; + //Display error dialogue and return + displayErrorDialogue(msg, title); + return; + } + else if (ex instanceof SSLPeerUnverifiedException) + { + msg = "SSL peer identity could not be verified, please ensure valid certificate configuration."; + //Display error dialogue and return + displayErrorDialogue(msg, title); + return; + } + else if (ex instanceof SSLHandshakeException) + { + if (ex.getMessage().contains(SSL_UNABLE_TO_FIND_CERTPATH)) + { + msg = "Unable to certify the provided SSL certificate using the current SSL trust store."; + } + else + { + //cause unknown, provide a trace too + MBeanUtility.printStackTrace(ex); + msg = "SSL handhshake error."; + } + //Display error dialogue and return + displayErrorDialogue(msg, title); + return; + } + else + { + //general SSL Exception. + if (ex.getMessage().contains(SSL_EMPTY_TRUSTANCHORS)) + { + msg = "Unable to locate the specified SSL certificate trust store, please check the configuration."; + } + else + { + //cause unknown, print stack trace + MBeanUtility.printStackTrace(ex); + msg = "SSL connection error."; + } + //Display error dialogue and return + displayErrorDialogue(msg, title); + return; + } + } + else if (ex instanceof IOException) { - //IOException, eg when trying to connect to a server/port with no JMX server running + //uncaught IOException, eg when trying to connect to a server/port with no JMX server running msg = SERVER_UNAVAILABLE; //Display error dialogue and return displayErrorDialogue(msg, title); Modified: qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/linux-gtk-x86/qpidmc.ini URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/linux-gtk-x86/qpidmc.ini?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/linux-gtk-x86/qpidmc.ini (original) +++ qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/linux-gtk-x86/qpidmc.ini Fri Feb 13 14:00:10 2009 @@ -23,3 +23,15 @@ -XX:MaxPermSize=256m -Dosgi.requiredJavaVersion=1.5 -Declipse.consoleLog=true + +#=============================================== +# SSL trust store configuration options. +#=============================================== + +# Uncomment lines below to specify custom truststore for server SSL +# certificate verification, eg when using self-signed server certs. +# +#-Djavax.net.ssl.trustStore=<path.to.truststore> +#-Djavax.net.ssl.trustStorePassword=<truststore.password> + + Modified: qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/macosx/Contents/MacOS/qpidmc.ini URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/macosx/Contents/MacOS/qpidmc.ini?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/macosx/Contents/MacOS/qpidmc.ini (original) +++ qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/macosx/Contents/MacOS/qpidmc.ini Fri Feb 13 14:00:10 2009 @@ -29,3 +29,14 @@ -Dosgi.requiredJavaVersion=1.5 -Declipse.consoleLog=true -Dorg.eclipse.swt.internal.carbon.smallFonts + +#=============================================== +# SSL trust store configuration options. +#=============================================== + +# Uncomment lines below to specify custom truststore for server SSL +# certificate verification, eg when using self-signed server certs. +# +#-Djavax.net.ssl.trustStore=<path.to.truststore> +#-Djavax.net.ssl.trustStorePassword=<truststore.password> + Modified: qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini?rev=744113&r1=744112&r2=744113&view=diff ============================================================================== --- qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini (original) +++ qpid/trunk/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini Fri Feb 13 14:00:10 2009 @@ -23,3 +23,14 @@ -XX:MaxPermSize=256m -Dosgi.requiredJavaVersion=1.5 -Declipse.consoleLog=true + +#=============================================== +# SSL trust store configuration options. +#=============================================== + +# Uncomment lines below to specify custom truststore for server SSL +# certificate verification, eg when using self-signed server certs. +# +#-Djavax.net.ssl.trustStore=<path.to.truststore> +#-Djavax.net.ssl.trustStorePassword=<truststore.password> + --------------------------------------------------------------------- Apache Qpid - AMQP Messaging Implementation Project: http://qpid.apache.org Use/Interact: mailto:commits-subscr...@qpid.apache.org