summaryrefslogtreecommitdiff
path: root/java/client-java14/src
diff options
context:
space:
mode:
authorRobert Greig <rgreig@apache.org>2007-02-20 16:51:32 +0000
committerRobert Greig <rgreig@apache.org>2007-02-20 16:51:32 +0000
commit53dfa7e61494fc38de8b527a91dfdb6051260e2a (patch)
tree9c36100358f74974f5c5b544d8c09b5053aa6d6d /java/client-java14/src
parent797b61fa56e3576e4a85780e7e8bdc20e6833d75 (diff)
downloadqpid-python-53dfa7e61494fc38de8b527a91dfdb6051260e2a.tar.gz
(Path submitted by Rupert Smith) Qpid-338. Custom SASL implementation for Java 1.4 retrotranslation of the Java client.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@509642 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'java/client-java14/src')
-rw-r--r--java/client-java14/src/main/assembly/client-java14-bin.xml69
-rw-r--r--java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java343
-rw-r--r--java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java349
-rw-r--r--java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java277
-rw-r--r--java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java61
-rw-r--r--java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java66
6 files changed, 1165 insertions, 0 deletions
diff --git a/java/client-java14/src/main/assembly/client-java14-bin.xml b/java/client-java14/src/main/assembly/client-java14-bin.xml
new file mode 100644
index 0000000000..87c7f498f9
--- /dev/null
+++ b/java/client-java14/src/main/assembly/client-java14-bin.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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.
+-->
+<!-- Assembly instructions for a client that runs on Java 1.4 -->
+<assembly>
+ <id>java-client-java14-bin</id>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+
+ <fileSets>
+ <fileSet>
+ <!-- Apache license files -->
+ <directory>../resources</directory>
+ <outputDirectory>qpid-${qpid.version}</outputDirectory>
+ <includes>
+ <include>DISCLAIMER</include>
+ <include>LICENSE.txt</include>
+ <include>NOTICE.txt</include>
+ <include>README.txt</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>../release-docs</directory>
+ <outputDirectory>qpid-${qpid.version}/docs</outputDirectory>
+ <includes>
+ <include>RELEASE_NOTES.txt</include>
+ </includes>
+ </fileSet>
+
+ </fileSets>
+
+ <dependencySets>
+ <dependencySet>
+ <outputDirectory>qpid-${qpid.version}/lib</outputDirectory>
+ <unpack>false</unpack>
+ <excludes>
+ <!-- Exclude the Java 5 built client and common. The java 1.4 retrotranslated versions are used instead. -->
+ <exclude>org.apache.qpid:qpid-client:jar</exclude>
+ <exclude>org.apache.qpid:qpid-common:jar</exclude>
+
+ <!-- Mina SSL support only available in Java 5. No SSL on 1.4. -->
+ <exclude>org.apache.mina:mina-java5</exclude>
+ <exclude>org.apache.mina:mina-filter-ssl</exclude>
+ </excludes>
+ </dependencySet>
+ </dependencySets>
+</assembly>
+
+
diff --git a/java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java b/java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java
new file mode 100644
index 0000000000..691020307a
--- /dev/null
+++ b/java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java
@@ -0,0 +1,343 @@
+/*
+ *
+ * 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.sasl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.security.auth.callback.*;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslClientFactory;
+import javax.security.sasl.SaslException;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.util.PrettyPrintingUtils;
+
+/**
+ * Implements a factory for generating Sasl client implementations.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a list of supported encryption mechansims that meet a defined set of Sasl properties.
+ * <tr><td> Provide the best matching supported Sasl mechanism to a preference ordered list of mechanisms and Sasl
+ * properties.
+ * <tr><td> Perform username and password request call backs. <td> CallBackHandler
+ * </table>
+ */
+public class ClientFactoryImpl implements SaslClientFactory
+{
+ //private static final Logger log = Logger.getLogger(ClientFactoryImpl.class);
+
+ /** Holds the names of the supported encryption mechanisms. */
+ private static final String[] SUPPORTED_MECHANISMS = { "CRAM-MD5", "PLAIN" };
+
+ /** Defines index of the CRAM-MD5 mechanism within the supported mechanisms. */
+ private static final int CRAM_MD5 = 0;
+
+ /** Defines index of the PLAIN mechanism within the supported mechanisms. */
+ private static final int PLAIN = 1;
+
+ /** Bit mapping of the no plain text policy. */
+ private static final int NOPLAINTEXT = 0x0001;
+
+ /** Bit mapping of the no susceptible active attacks policy. */
+ private static final int NOACTIVE = 0x0002;
+
+ /** Bit mapping of the no susceptible to dictionary attacks policy. */
+ private static final int NODICTIONARY = 0x0004;
+
+ /** Bit mapping of the must use forward secrecy between sessions policy. */
+ private static final int FORWARD_SECRECY = 0x0008;
+
+ /** Bit mapping of the no anonymous logins policy. */
+ private static final int NOANONYMOUS = 0x0010;
+
+ /** Bit mapping of the must pass credentials policy. */
+ private static final int PASS_CREDENTIALS = 0x0020;
+
+ /** Defines a mapping from supported mechanisms to supported policy flags. */
+ private static final int[] SUPPPORTED_MECHANISMS_POLICIES =
+ {
+ NOPLAINTEXT | NOANONYMOUS, // CRAM-MD5
+ NOANONYMOUS // PLAIN
+ };
+
+ /**
+ * Creates a SaslClient using the parameters supplied.
+ *
+ * @param mechanisms The non-null list of mechanism names to try. Each is the IANA-registered name of a SASL
+ * mechanism. (e.g. "GSSAPI", "CRAM-MD5").
+ * @param authorizationId The possibly null protocol-dependent identification to be used for authorization.
+ * If null or empty, the server derives an authorization ID from the client's authentication
+ * credentials. When the SASL authentication completes successfully, the specified entity is
+ * granted access.
+ * @param protocol The non-null string name of the protocol for which the authentication is being performed
+ * (e.g., "ldap").
+ * @param serverName The non-null fully qualified host name of the server to authenticate to.
+ * @param props The possibly null set of properties used to select the SASL mechanism and to configure the
+ * authentication exchange of the selected mechanism. See the <tt>Sasl</tt> class for a list
+ * of standard properties. Other, possibly mechanism-specific, properties can be included.
+ * Properties not relevant to the selected mechanism are ignored.
+ * @param cbh The possibly null callback handler to used by the SASL mechanisms to get further
+ * information from the application/library to complete the authentication. For example, a
+ * SASL mechanism might require the authentication ID, password and realm from the caller.
+ * The authentication ID is requested by using a <tt>NameCallback</tt>.
+ * The password is requested by using a <tt>PasswordCallback</tt>.
+ * The realm is requested by using a <tt>RealmChoiceCallback</tt> if there is a list
+ * of realms to choose from, and by using a <tt>RealmCallback</tt> if
+ * the realm must be entered.
+ *
+ * @return A possibly null <tt>SaslClient</tt> created using the parameters supplied. If null, this factory cannot
+ * produce a <tt>SaslClient</tt> using the parameters supplied.
+ *
+ * @throws javax.security.sasl.SaslException If cannot create a <tt>SaslClient</tt> because of an error.
+ */
+ public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, String serverName,
+ Map props, CallbackHandler cbh) throws SaslException
+ {
+ /*log.debug("public SaslClient createSaslClient(String[] mechanisms = " + PrettyPrintingUtils.printArray(mechanisms)
+ + ", String authorizationId = " + authorizationId + ", String protocol = " + protocol
+ + ", String serverName = " + serverName + ", Map props = " + props + ", CallbackHandler cbh): called");*/
+
+ // Get a list of all supported mechanisms that matched the required properties.
+ String[] matchingMechanisms = getMechanismNames(props);
+ //log.debug("matchingMechanisms = " + PrettyPrintingUtils.printArray(matchingMechanisms));
+
+ // Scan down the list of mechanisms until the first one that matches one of the matching supported mechanisms
+ // is found.
+ String chosenMechanism = null;
+
+ for (int i = 0; i < mechanisms.length; i++)
+ {
+ String mechanism = mechanisms[i];
+
+ for (int j = 0; j < matchingMechanisms.length; j++)
+ {
+ String matchingMechanism = matchingMechanisms[j];
+
+ if (mechanism.equals(matchingMechanism))
+ {
+ chosenMechanism = mechanism;
+
+ break;
+ }
+ }
+
+ // Stop scanning if a match has been found.
+ if (chosenMechanism != null)
+ {
+ break;
+ }
+ }
+
+ // Check that a matching mechanism was found or return null otherwise.
+ if (chosenMechanism == null)
+ {
+ //log.debug("No matching mechanism could be found.");
+
+ return null;
+ }
+
+ // Instantiate an appropriate client type for the chosen mechanism.
+ if (chosenMechanism.equals(SUPPORTED_MECHANISMS[CRAM_MD5]))
+ {
+ Object[] uinfo = getUserInfo("CRAM-MD5", authorizationId, cbh);
+
+ //log.debug("Using CRAM-MD5 mechanism.");
+
+ return new CramMD5Client((String) uinfo[0], (byte[]) uinfo[1]);
+ }
+ else
+ {
+ Object[] uinfo = getUserInfo("PLAIN", authorizationId, cbh);
+
+ //log.debug("Using PLAIN mechanism.");
+
+ return new PlainClient(authorizationId, (String) uinfo[0], (byte[]) uinfo[1]);
+ }
+ }
+
+ /**
+ * Returns an array of names of mechanisms that match the specified
+ * mechanism selection policies.
+ *
+ * @param props The possibly null set of properties used to specify the
+ * security policy of the SASL mechanisms. For example, if <tt>props</tt>
+ * contains the <tt>Sasl.POLICY_NOPLAINTEXT</tt> property with the value
+ * <tt>"true"</tt>, then the factory must not return any SASL mechanisms
+ * that are susceptible to simple plain passive attacks.
+ * See the <tt>Sasl</tt> class for a complete list of policy properties.
+ * Non-policy related properties, if present in <tt>props</tt>, are ignored.
+ *
+ * @return A non-null array containing a IANA-registered SASL mechanism names.
+ */
+ public String[] getMechanismNames(Map props)
+ {
+ //log.debug("public String[] getMechanismNames(Map props = " + props + "): called");
+
+ // Used to build up the valid mechanisms in.
+ List validMechanisms = new ArrayList();
+
+ // Transform the Sasl properties into a set of bit mapped flags indicating the required properties of the
+ // encryption mechanism employed.
+ int requiredFlags = bitMapSaslProperties(props);
+ //log.debug("requiredFlags = " + requiredFlags);
+
+ // Scan down the list of supported mechanisms filtering in only those that satisfy all of the desired
+ // encryption properties.
+ for (int i = 0; i < SUPPORTED_MECHANISMS.length; i++)
+ {
+ int mechanismFlags = SUPPPORTED_MECHANISMS_POLICIES[i];
+ //log.debug("mechanismFlags = " + mechanismFlags);
+
+ // Check if the current mechanism contains all of the required flags.
+ if ((requiredFlags & ~mechanismFlags) == 0)
+ {
+ //log.debug("Mechanism " + SUPPORTED_MECHANISMS[i] + " meets the required properties.");
+ validMechanisms.add(SUPPORTED_MECHANISMS[i]);
+ }
+ }
+
+ String[] result = (String[]) validMechanisms.toArray(new String[validMechanisms.size()]);
+
+ //log.debug("result = " + PrettyPrintingUtils.printArray(result));
+
+ return result;
+ }
+
+ /**
+ * Transforms a set of Sasl properties, defined using the property names in javax.security.sasl.Sasl, into
+ * a bit mapped set of property flags encoded using the bit mapping constants defined in this class.
+ *
+ * @param properties The Sasl properties to bit map.
+ *
+ * @return A set of bit mapped properties encoded in an integer.
+ */
+ private int bitMapSaslProperties(Map properties)
+ {
+ //log.debug("private int bitMapSaslProperties(Map properties = " + properties + "): called");
+
+ int result = 0;
+
+ // No flags set if no properties are set.
+ if (properties == null)
+ {
+ return result;
+ }
+
+ if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_NOPLAINTEXT)))
+ {
+ result |= NOPLAINTEXT;
+ }
+
+ if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_NOACTIVE)))
+ {
+ result |= NOACTIVE;
+ }
+
+ if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_NODICTIONARY)))
+ {
+ result |= NODICTIONARY;
+ }
+
+ if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_NOANONYMOUS)))
+ {
+ result |= NOANONYMOUS;
+ }
+
+ if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_FORWARD_SECRECY)))
+ {
+ result |= FORWARD_SECRECY;
+ }
+
+ if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_PASS_CREDENTIALS)))
+ {
+ result |= PASS_CREDENTIALS;
+ }
+
+ return result;
+ }
+
+ /**
+ * Uses the specified call back handler to query for the users log in name and password.
+ *
+ * @param prefix A prefix to prepend onto the username and password queries.
+ * @param authorizationId The default autorhization name.
+ * @param cbh The call back handler.
+ *
+ * @return The username and password from the callback.
+ *
+ * @throws SaslException If the callback fails for any reason.
+ */
+ private Object[] getUserInfo(String prefix, String authorizationId, CallbackHandler cbh) throws SaslException
+ {
+ // Check that the callback handler is defined.
+ if (cbh == null)
+ {
+ throw new SaslException("Callback handler to get username/password required.");
+ }
+
+ try
+ {
+ String userPrompt = prefix + " authentication id: ";
+ String passwdPrompt = prefix + " password: ";
+
+ NameCallback ncb =
+ (authorizationId == null) ? new NameCallback(userPrompt) : new NameCallback(userPrompt, authorizationId);
+ PasswordCallback pcb = new PasswordCallback(passwdPrompt, false);
+
+ // Ask the call back handler to get the users name and password.
+ cbh.handle(new Callback[] { ncb, pcb });
+
+ char[] pw = pcb.getPassword();
+
+ byte[] bytepw;
+ String authId;
+
+ if (pw != null)
+ {
+ bytepw = new String(pw).getBytes("UTF8");
+ pcb.clearPassword();
+ }
+ else
+ {
+ bytepw = null;
+ }
+
+ authId = ncb.getName();
+
+ return new Object[] { authId, bytepw };
+ }
+ catch (IOException e)
+ {
+ throw new SaslException("Cannot get password.", e);
+ }
+ catch (UnsupportedCallbackException e)
+ {
+ throw new SaslException("Cannot get userid/password.", e);
+ }
+ }
+}
diff --git a/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java b/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java
new file mode 100644
index 0000000000..c771df458e
--- /dev/null
+++ b/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java
@@ -0,0 +1,349 @@
+/*
+ *
+ * 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.sasl;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+/**
+ * Implements the CRAM-MD5 SASL mechanism.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Concatenate the user id and password hashed by a challenge string as the challenge response.
+ * <tr><td> Ensure password is wiped once a challenge has been processed.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public class CramMD5Client implements SaslClient
+{
+ /** Defines the HMAC block size. */
+ private static final int MD5_BLOCKSIZE = 64;
+
+ /** Flag used to indicate that the authentication has completed. */
+ private boolean completed = false;
+
+ private String authenticationId;
+ private byte[] password;
+
+ /**
+ * Creates a PLAIN SASL client for an authorization id, authentication id and password.
+ *
+ * @param authenticationId The authentication id.
+ * @param password The password.
+ *
+ * @throws SaslException If the authentication id or password is null.
+ */
+ CramMD5Client(String authenticationId, byte[] password) throws SaslException
+ {
+ // Check that a username and password are specified.
+ if ((authenticationId == null) || (password == null))
+ {
+ throw new SaslException("CRAM: authentication id and password must be specified");
+ }
+
+ // Keep the log in credentials.
+ this.authenticationId = authenticationId;
+ this.password = password;
+ }
+
+ /**
+ * Returns the IANA-registered mechanism name of this SASL client. (e.g. "CRAM-MD5", "GSSAPI").
+ *
+ * @return A non-null string representing the IANA-registered mechanism name.
+ */
+ public String getMechanismName()
+ {
+ return "CRAM-MD5";
+ }
+
+ /**
+ * Determines whether this mechanism has an optional initial response. If true, caller should call
+ * <tt>evaluateChallenge()</tt> with an empty array to get the initial response.
+ *
+ * <p/>CRAM-MD5 has no intial response.
+ *
+ * @return true if this mechanism has an initial response.
+ */
+ public boolean hasInitialResponse()
+ {
+ return false;
+ }
+
+ /**
+ * Evaluates the challenge data and generates a response. If a challenge is received from the server during the
+ * authentication process, this method is called to prepare an appropriate next response to submit to the server.
+ *
+ * <p/>The initial response for the SASL command, for the CRAM-MD5 mechanism is the concatenation of authentication
+ * ID and password HMAC-MD5 hashed by the server challenge.
+ *
+ * @param challenge The non-null challenge sent from the server. The challenge array may have zero length.
+ *
+ * @return The possibly null reponse to send to the server. It is null if the challenge accompanied a "SUCCESS"
+ * status and the challenge only contains data for the client to update its state and no response
+ * needs to be sent to the server. The response is a zero-length byte array if the client is to send a
+ * response with no data.
+ *
+ * @throws javax.security.sasl.SaslException If an error occurred while processing the challenge or generating a
+ * response.
+ */
+ public byte[] evaluateChallenge(byte[] challenge) throws SaslException
+ {
+ // Check that the authentication has not already been performed.
+ if (completed)
+ {
+ throw new IllegalStateException("CRAM-MD5 authentication already completed.");
+ }
+
+ // Check if the password is null, this will be the case if a previous attempt to authenticated failed.
+ if (password == null)
+ {
+ throw new IllegalStateException("CRAM-MD5 authentication previously aborted due to error.");
+ }
+
+ // Generate a keyed-MD5 digest from the user's password keyed by the challenge bytes.
+ try
+ {
+ String digest = hmac_md5(password, challenge);
+ String result = authenticationId + " " + digest;
+
+ completed = true;
+
+ return result.getBytes("UTF8");
+ }
+ catch (java.security.NoSuchAlgorithmException e)
+ {
+ throw new SaslException("MD5 algorithm not available on platform", e);
+ }
+ catch (java.io.UnsupportedEncodingException e)
+ {
+ throw new SaslException("UTF8 not available on platform", e);
+ }
+ finally
+ {
+ clearPassword();
+ }
+ }
+
+ /**
+ * Determines whether the authentication exchange has completed. This method may be called at any time, but
+ * typically, it will not be called until the caller has received indication from the server (in a protocol-specific
+ * manner) that the exchange has completed.
+ *
+ * @return true if the authentication exchange has completed; false otherwise.
+ */
+ public boolean isComplete()
+ {
+ return completed;
+ }
+
+ /**
+ * Unwraps a byte array received from the server. This method can be called only after the authentication exchange
+ * has completed (i.e., when <tt>isComplete()</tt> returns true) and only if the authentication exchange has
+ * negotiated integrity and/or privacy as the quality of protection; otherwise, an <tt>IllegalStateException</tt> is
+ * thrown.
+ *
+ * <p/><tt>incoming</tt> is the contents of the SASL buffer as defined in RFC 2222 without the leading four octet
+ * field that represents the length. <tt>offset</tt> and <tt>len</tt> specify the portion of <tt>incoming</tt>
+ * to use.
+ *
+ * @param incoming A non-null byte array containing the encoded bytes from the server.
+ * @param offset The starting position at <tt>incoming</tt> of the bytes to use.
+ * @param len The number of bytes from <tt>incoming</tt> to use.
+ *
+ * @return A non-null byte array containing the decoded bytes.
+ *
+ * @throws javax.security.sasl.SaslException If <tt>incoming</tt> cannot be successfully unwrapped.
+ * @throws IllegalStateException If the authentication exchange has not completed, or if the negotiated quality of
+ * protection has neither integrity nor privacy.
+ */
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException("CRAM-MD5 does not support quality of protection.");
+ }
+
+ /**
+ * Wraps a byte array to be sent to the server. This method can be called only after the authentication exchange has
+ * completed (i.e., when <tt>isComplete()</tt> returns true) and only if the authentication exchange has negotiated
+ * integrity and/or privacy as the quality of protection; otherwise, an <tt>IllegalStateException</tt> is thrown.
+ *
+ * <p/>The result of this method will make up the contents of the SASL buffer as defined in RFC 2222 without the
+ * leading four octet field that represents the length. <tt>offset</tt> and <tt>len</tt> specify the portion of
+ * <tt>outgoing</tt> to use.
+ *
+ * @param outgoing A non-null byte array containing the bytes to encode.
+ * @param offset The starting position at <tt>outgoing</tt> of the bytes to use.
+ * @param len The number of bytes from <tt>outgoing</tt> to use.
+ *
+ * @return A non-null byte array containing the encoded bytes.
+ *
+ * @throws javax.security.sasl.SaslException If <tt>outgoing</tt> cannot be successfully wrapped.
+ * @throws IllegalStateException If the authentication exchange has not completed, or if the negotiated quality of
+ * protection has neither integrity nor privacy.
+ */
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException("CRAM-MD5 does not support quality of protection.");
+ }
+
+ /**
+ * Retrieves the negotiated property. This method can be called only after the authentication exchange has
+ * completed (i.e., when <tt>isComplete()</tt> returns true); otherwise, an <tt>IllegalStateException</tt> is thrown.
+ *
+ * @param propName The non-null property name.
+ *
+ * @return The value of the negotiated property. If null, the property was not negotiated or is not applicable to
+ * this mechanism.
+ *
+ * @throws IllegalStateException If this authentication exchange has not completed.
+ */
+ public Object getNegotiatedProperty(String propName)
+ {
+ if (completed)
+ {
+ if (propName.equals(Sasl.QOP))
+ {
+ return "auth";
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else
+ {
+ throw new IllegalStateException("CRAM-MD5 authentication not completed");
+ }
+ }
+
+ /**
+ * Disposes of any system resources or security-sensitive information the SaslClient might be using. Invoking this
+ * method invalidates the SaslClient instance. This method is idempotent.
+ *
+ * @throws javax.security.sasl.SaslException If a problem was encountered while disposing the resources.
+ */
+ public void dispose() throws SaslException
+ { }
+
+ /*
+ * Hashes its input arguments according to HMAC-MD5 (RFC 2104) and returns the resulting digest in its ASCII
+ * representation.
+ *
+ * <p/> The HMAC-MD5 function is described as follows:
+ * <p/><pre>
+ * MD5(key XOR opad, MD5(key XOR ipad, text))
+ * </pre>
+ *
+ * <p/>Where key is an n byte key, ipad is the byte 0x36 repeated 64 times, opad is the byte 0x5c repeated 64 times
+ * and text is the data to be protected.
+ *
+ * @param key The key to hash by.
+ * @param text The plain text to hash.
+ *
+ * @return The hashed text.
+ *
+ * @throws NoSuchAlgorithmException If the Java platform does not supply an MD5 implementation.
+ */
+ private static final String hmac_md5(byte[] key, byte[] text) throws NoSuchAlgorithmException
+ {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+
+ /* digest the key if longer than 64 bytes */
+ if (key.length > 64)
+ {
+ key = md5.digest(key);
+ }
+
+ byte[] ipad = new byte[MD5_BLOCKSIZE]; /* inner padding */
+ byte[] opad = new byte[MD5_BLOCKSIZE]; /* outer padding */
+ byte[] digest;
+ int i;
+
+ /* store key in pads */
+ for (i = 0; i < MD5_BLOCKSIZE; i++)
+ {
+ for (; i < key.length; i++)
+ {
+ ipad[i] = key[i];
+ opad[i] = key[i];
+ }
+
+ ipad[i] = 0x00;
+ opad[i] = 0x00;
+ }
+
+ /* XOR key with pads */
+ for (i = 0; i < MD5_BLOCKSIZE; i++)
+ {
+ ipad[i] ^= 0x36;
+ opad[i] ^= 0x5c;
+ }
+
+ /* inner MD5 */
+ md5.update(ipad);
+ md5.update(text);
+ digest = md5.digest();
+
+ /* outer MD5 */
+ md5.update(opad);
+ md5.update(digest);
+ digest = md5.digest();
+
+ // Get character representation of digest
+ StringBuffer digestString = new StringBuffer();
+
+ for (i = 0; i < digest.length; i++)
+ {
+ if ((digest[i] & 0x000000ff) < 0x10)
+ {
+ digestString.append("0" + Integer.toHexString(digest[i] & 0x000000ff));
+ }
+ else
+ {
+ digestString.append(Integer.toHexString(digest[i] & 0x000000ff));
+ }
+ }
+
+ return (digestString.toString());
+ }
+
+ /**
+ * Overwrites the password with zeros.
+ */
+ private void clearPassword()
+ {
+ if (password != null)
+ {
+ // Zero out password.
+ for (int i = 0; i < password.length; i++)
+ {
+ password[i] = (byte) 0;
+ }
+
+ password = null;
+ }
+ }
+}
diff --git a/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java b/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java
new file mode 100644
index 0000000000..67d00fd7d4
--- /dev/null
+++ b/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java
@@ -0,0 +1,277 @@
+/*
+ *
+ * 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.sasl;
+
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+/**
+ * Implements the PLAIN SASL mechanism.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Concatenate the user id and password in plain text as the challenge respose.
+ * <tr><td> Ensure password is wiped once a challenge has been processed.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public class PlainClient implements SaslClient
+{
+ /** Flag used to indicate that the authentication has completed. */
+ private boolean completed = false;
+
+ private String authorizationId;
+ private String authenticationId;
+ private byte[] password;
+
+ private static byte SEPERATOR = 0; // US-ASCII <NUL>
+
+ /**
+ * Creates a PLAIN SASL client for an authorization id, authentication id and password.
+ *
+ * @param authorizationId The authorization id. May be null.
+ * @param authenticationId The authentication id.
+ * @param password The password.
+ *
+ * @throws SaslException If the authentication id or password is null.
+ */
+ PlainClient(String authorizationId, String authenticationId, byte[] password) throws SaslException
+ {
+ // Check that a username and password are specified.
+ if ((authenticationId == null) || (password == null))
+ {
+ throw new SaslException("PLAIN: authentication ID and password must be specified");
+ }
+
+ // Keep the log in credentials.
+ this.authorizationId = authorizationId;
+ this.authenticationId = authenticationId;
+ this.password = password;
+ }
+
+ /**
+ * Returns the IANA-registered mechanism name of this SASL client. (e.g. "CRAM-MD5", "GSSAPI").
+ *
+ * @return A non-null string representing the IANA-registered mechanism name.
+ */
+ public String getMechanismName()
+ {
+ return "PLAIN";
+ }
+
+ /**
+ * Determines whether this mechanism has an optional initial response. If true, caller should call
+ * <tt>evaluateChallenge()</tt> with an empty array to get the initial response.
+ *
+ * @return true if this mechanism has an initial response.
+ */
+ public boolean hasInitialResponse()
+ {
+ return true;
+ }
+
+ /**
+ * Evaluates the challenge data and generates a response. If a challenge is received from the server during the
+ * authentication process, this method is called to prepare an appropriate next response to submit to the server.
+ *
+ * <p/>The initial response for the SASL command, for the PLAIN mechanism is the concatenation of authorization ID,
+ * authentication ID and password, with each component separated by the US-ASCII <NUL> byte.
+ *
+ * @param challenge The non-null challenge sent from the server. The challenge array may have zero length.
+ *
+ * @return The possibly null reponse to send to the server. It is null if the challenge accompanied a "SUCCESS"
+ * status and the challenge only contains data for the client to update its state and no response
+ * needs to be sent to the server. The response is a zero-length byte array if the client is to send a
+ * response with no data.
+ *
+ * @throws javax.security.sasl.SaslException If an error occurred while processing the challenge or generating a
+ * response.
+ */
+ public byte[] evaluateChallenge(byte[] challenge) throws SaslException
+ {
+ // Check that the authentication has not already been performed.
+ if (completed)
+ {
+ throw new IllegalStateException("PLAIN authentication already completed");
+ }
+
+ try
+ {
+ // Get the authorization and authentication ids in bytes.
+ byte[] authorizationBytes = (authorizationId != null) ? authorizationId.getBytes("UTF8") : null;
+ byte[] authenticationBytes = authenticationId.getBytes("UTF8");
+
+ // Create an array big enough to hold the results.
+ byte[] result =
+ new byte[password.length + authenticationBytes.length + 2
+ + ((authorizationBytes == null) ? 0 : authorizationBytes.length)];
+
+ // Copy the authorization id, authentication id and password into the results.
+ int pos = 0;
+ if (authorizationBytes != null)
+ {
+ System.arraycopy(authorizationBytes, 0, result, 0, authorizationBytes.length);
+ pos = authorizationBytes.length;
+ }
+
+ result[pos++] = SEPERATOR;
+ System.arraycopy(authenticationBytes, 0, result, pos, authenticationBytes.length);
+
+ pos += authenticationBytes.length;
+ result[pos++] = SEPERATOR;
+
+ System.arraycopy(password, 0, result, pos, password.length);
+
+ completed = true;
+
+ return result;
+ }
+ catch (java.io.UnsupportedEncodingException e)
+ {
+ throw new SaslException("Cannot get UTF-8 encoding of ids", e);
+ }
+ finally
+ {
+ clearPassword();
+ }
+ }
+
+ /**
+ * Determines whether the authentication exchange has completed. This method may be called at any time, but
+ * typically, it will not be called until the caller has received indication from the server (in a protocol-specific
+ * manner) that the exchange has completed.
+ *
+ * @return true if the authentication exchange has completed; false otherwise.
+ */
+ public boolean isComplete()
+ {
+ return completed;
+ }
+
+ /**
+ * Unwraps a byte array received from the server. This method can be called only after the authentication exchange has
+ * completed (i.e., when <tt>isComplete()</tt> returns true) and only if the authentication exchange has negotiated
+ * integrity and/or privacy as the quality of protection; otherwise, an <tt>IllegalStateException</tt> is thrown.
+ *
+ * <p/><tt>incoming</tt> is the contents of the SASL buffer as defined in RFC 2222 without the leading four octet
+ * field that represents the length. <tt>offset</tt> and <tt>len</tt> specify the portion of <tt>incoming</tt>
+ * to use.
+ *
+ * @param incoming A non-null byte array containing the encoded bytes
+ * from the server.
+ * @param offset The starting position at <tt>incoming</tt> of the bytes to use.
+ * @param len The number of bytes from <tt>incoming</tt> to use.
+ *
+ * @return A non-null byte array containing the decoded bytes.
+ *
+ * @throws javax.security.sasl.SaslException If <tt>incoming</tt> cannot be successfully unwrapped.
+ * @throws IllegalStateException If the authentication exchange has not completed, or if the negotiated quality of
+ * protection has neither integrity nor privacy.
+ */
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException("PLAIN does not support quality of protection.");
+ }
+
+ /**
+ * Wraps a byte array to be sent to the server. This method can be called only after the authentication exchange has
+ * completed (i.e., when <tt>isComplete()</tt> returns true) and only if the authentication exchange has negotiated
+ * integrity and/or privacy as the quality of protection; otherwise, an <tt>IllegalStateException</tt> is thrown.
+ *
+ * <p/>The result of this method will make up the contents of the SASL buffer as defined in RFC 2222 without the
+ * leading four octet field that represents the length. <tt>offset</tt> and <tt>len</tt> specify the portion of
+ * <tt>outgoing</tt> to use.
+ *
+ * @param outgoing A non-null byte array containing the bytes to encode.
+ * @param offset The starting position at <tt>outgoing</tt> of the bytes to use.
+ * @param len The number of bytes from <tt>outgoing</tt> to use.
+ *
+ * @return A non-null byte array containing the encoded bytes.
+ *
+ * @throws javax.security.sasl.SaslException If <tt>outgoing</tt> cannot be successfully wrapped.
+ * @throws IllegalStateException If the authentication exchange has not completed, or if the negotiated quality of
+ * protection has neither integrity nor privacy.
+ */
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException("PLAIN does not support quality of protection.");
+ }
+
+ /**
+ * Retrieves the negotiated property. This method can be called only after the authentication exchange has
+ * completed (i.e., when <tt>isComplete()</tt> returns true); otherwise, an <tt>IllegalStateException</tt> is thrown.
+ *
+ * @param propName The non-null property name.
+ *
+ * @return The value of the negotiated property. If null, the property was not negotiated or is not applicable to
+ * this mechanism.
+ *
+ * @throws IllegalStateException If this authentication exchange has not completed.
+ */
+ public Object getNegotiatedProperty(String propName)
+ {
+ if (completed)
+ {
+ if (propName.equals(Sasl.QOP))
+ {
+ return "auth";
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else
+ {
+ throw new IllegalStateException("PLAIN authentication not completed");
+ }
+ }
+
+ /**
+ * Disposes of any system resources or security-sensitive information the SaslClient might be using. Invoking this
+ * method invalidates the SaslClient instance. This method is idempotent.
+ *
+ * @throws javax.security.sasl.SaslException If a problem was encountered while disposing the resources.
+ */
+ public void dispose() throws SaslException
+ {
+ clearPassword();
+ }
+
+ /**
+ * Overwrites the password with zeros.
+ */
+ private void clearPassword()
+ {
+ if (password != null)
+ {
+ // Zero out password.
+ for (int i = 0; i < password.length; i++)
+ {
+ password[i] = (byte) 0;
+ }
+
+ password = null;
+ }
+ }
+}
diff --git a/java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java b/java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java
new file mode 100644
index 0000000000..f9a5c42c3d
--- /dev/null
+++ b/java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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.sasl;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A SASL provider for Java 1.4. Declares the capabilities of this implementation, which supports PLAIN and CRAM-MD5
+ * client implementations only.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Declare PLAIN SASL support.
+ * <tr><td> Declare CRAM-MD5 SASL support.
+ * </table>
+ */
+public class Provider extends java.security.Provider
+{
+ //Logger log = Logger.getLogger(Provider.class);
+
+ private static final String info = "Qpid SASL provider" + "(implements client mechanisms for: PLAIN, CRAM-MD5)";
+
+ public Provider()
+ {
+ super("QpidSASL", 1.4, info);
+
+ //log.debug("public Provider(): called");
+
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ put("SaslClientFactory.PLAIN", "org.apache.qpid.sasl.ClientFactoryImpl");
+ put("SaslClientFactory.CRAM-MD5", "org.apache.qpid.sasl.ClientFactoryImpl");
+
+ return null;
+ }
+ });
+ }
+}
diff --git a/java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java b/java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java
new file mode 100644
index 0000000000..468beda5b5
--- /dev/null
+++ b/java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * 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.test.integration.client;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.NDC;
+
+import org.apache.qpid.client.AMQConnection;
+
+/**
+ * Implements a trivial broker connection test, to a broker on the default port on localhost, to check that SASL
+ * authentication works.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Check that a connection to a broker can be established.
+ * </table>
+ */
+public class ConnectionTest extends TestCase
+{
+ private String BROKER_URL = "tcp://localhost:5672";
+
+ public ConnectionTest(String name)
+ {
+ super(name);
+ }
+
+ /** Check that a connection to a broker can be established. */
+ public void testConnection() throws Exception
+ {
+ // Open a connection to the broker and close it again.
+ AMQConnection conn = new AMQConnection(BROKER_URL, "guest", "guest", "clientid", "test");
+ conn.close();
+ }
+
+ protected void setUp()
+ {
+ NDC.push(getName());
+ }
+
+ protected void tearDown()
+ {
+ NDC.pop();
+ }
+}