diff options
Diffstat (limited to 'RC9/qpid/java/client-java14')
9 files changed, 1323 insertions, 0 deletions
diff --git a/RC9/qpid/java/client-java14/README.txt b/RC9/qpid/java/client-java14/README.txt new file mode 100644 index 0000000000..66621c7eb2 --- /dev/null +++ b/RC9/qpid/java/client-java14/README.txt @@ -0,0 +1,33 @@ +An implementation of SASL is provided here, for the PLAIN and CRAM-MD5 mechanisms as well as maven assembly
+instructions for producing a backported Java client for Java 1.4. In order to use the custom SASL implementation
+on JRE 1.4 the following steps must be taken:
+
+ * Install the SASL JSR-28 API jar.
+ * Install the qpid-client-java14 jar or set it up as a dynamically registered SASL provider.
+ * Set up java.security to add the SASL provider to the list of security providers if hte SASL implemenation jar was installed as an extension.
+
+Installing the SASL JSR-28 API jar.
+
+ Download, http://www.worldspot.com/jsr28/v1.1/download/sasl.jar, and copy it to the JAVA_HOME\lib\ext directory.
+
+Install or set up the qpid-client-java14 jar.
+
+ Copy the output jar for this project, qpid-client-java14-1.0-incubating-M2-SNAPSHOT.jar, to JAVA_HOME\lib\ext.
+
+ OR
+
+ Create a properties file and register the SASL implementations in the jar so that Qpids dynamic SASL registry can find them. In a properties file
+ add the lines:
+
+ PLAIN=org.apache.qpid.sasl.ClientFactoryImpl
+ CRAM-MD5=org.apache.qpid.sasl.ClientFactoryImpl
+
+ Place this somewhere on the classpath and put the qpid-client-java14-1.0-incubating-M2-SNAPSHOT.jar on the classpath too. When starting your application
+ pass in the system property amq.dynamicsaslregistrar.properties and set it to point to the location of the properties file.
+
+Set up the SASL provider.
+
+ You only need to do this if the custom SASL jar, qpid-client-java14-1.0-incubating-M2-SNAPSHOT.jar, was copied to JAVA_HOME\lib\ext.
+ Add the following line to JAVA_HOME\lib\security\java.security file (where n is the providers preference order):
+
+ security.provider.n=org.apache.qpid.sasl.Provider
\ No newline at end of file diff --git a/RC9/qpid/java/client-java14/etc/sasl.properties b/RC9/qpid/java/client-java14/etc/sasl.properties new file mode 100644 index 0000000000..04519e2a30 --- /dev/null +++ b/RC9/qpid/java/client-java14/etc/sasl.properties @@ -0,0 +1,20 @@ +#
+# 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.
+#
+PLAIN=org.apache.qpid.sasl.ClientFactoryImpl
+CRAM-MD5=org.apache.qpid.sasl.ClientFactoryImpl
diff --git a/RC9/qpid/java/client-java14/src/main/assembly/client-java14-bin.xml b/RC9/qpid/java/client-java14/src/main/assembly/client-java14-bin.xml new file mode 100644 index 0000000000..91e1f23975 --- /dev/null +++ b/RC9/qpid/java/client-java14/src/main/assembly/client-java14-bin.xml @@ -0,0 +1,74 @@ +<?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>
+
+ <!-- Exclude the retrotranslated integration tests from the distriubtion. -->
+ <exclude>org.apache.qpid:qpid-integrationtests:jar:java14</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/RC9/qpid/java/client-java14/src/main/assembly/jar-with-dependencies.xml b/RC9/qpid/java/client-java14/src/main/assembly/jar-with-dependencies.xml new file mode 100644 index 0000000000..091fd46427 --- /dev/null +++ b/RC9/qpid/java/client-java14/src/main/assembly/jar-with-dependencies.xml @@ -0,0 +1,104 @@ +<!--
+ 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.
+-->
+<!-- This is an assembly descriptor that produces a jar file that contains all the
+ test dependencies, fully expanded into a single jar, required to run the tests
+ of a maven project.
+-->
+<!--
+<assembly>
+ <id>all-test-deps</id>
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <formats>
+ <format>jar</format>
+ </formats>
+
+ <dependencySets>
+ <!## Include all test dependencies. ##>
+ <dependencySet>
+ <outputDirectory></outputDirectory>
+ <outputFileNameMapping></outputFileNameMapping>
+ <unpack>true</unpack>
+ <!##<scope>runtime</scope>##>
+
+ <!##
+ <includes>
+ <include>org.apache.qpid:qpid-client:jar:java14</include>
+ <include>org.apache.qpid:qpid-common:jar:java14</include>
+ </includes>
+ ##>
+
+ <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>
+
+ <fileSets>
+ <!## Include all project classes. ##>
+ <fileSet>
+ <directory>target/classes</directory>
+ <outputDirectory></outputDirectory>
+ </fileSet>
+
+ <!## Include all project test classes. ##>
+ <fileSet>
+ <directory>target/test-classes</directory>
+ <outputDirectory></outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
+-->
+
+<assembly>
+ <id>all-test-deps</id>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <formats>
+ <format>jar</format>
+ </formats>
+
+ <fileSets>
+ </fileSets>
+
+ <dependencySets>
+ <dependencySet>
+ <outputDirectory></outputDirectory>
+ <unpack>true</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>
\ No newline at end of file diff --git a/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java b/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java new file mode 100644 index 0000000000..ed8e4ad80f --- /dev/null +++ b/RC9/qpid/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/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java b/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java new file mode 100644 index 0000000000..4f890ffed5 --- /dev/null +++ b/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java @@ -0,0 +1,347 @@ +/* + * + * 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> + */ +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/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java b/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java new file mode 100644 index 0000000000..f5a9f150bb --- /dev/null +++ b/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java @@ -0,0 +1,275 @@ +/* + * + * 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> + */ +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/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java b/RC9/qpid/java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java new file mode 100644 index 0000000000..ef075a0cad --- /dev/null +++ b/RC9/qpid/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/RC9/qpid/java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java b/RC9/qpid/java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java new file mode 100644 index 0000000000..77cc79bc49 --- /dev/null +++ b/RC9/qpid/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(); + } +} |
