aboutsummaryrefslogtreecommitdiff
path: root/src/com/novell
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/novell')
-rw-r--r--src/com/novell/sasl/client/DigestChallenge.java393
-rw-r--r--src/com/novell/sasl/client/DigestMD5SaslClient.java820
-rw-r--r--src/com/novell/sasl/client/DirectiveList.java363
-rw-r--r--src/com/novell/sasl/client/ParsedDirective.java56
-rw-r--r--src/com/novell/sasl/client/ResponseAuth.java83
-rw-r--r--src/com/novell/sasl/client/TokenParser.java208
6 files changed, 1923 insertions, 0 deletions
diff --git a/src/com/novell/sasl/client/DigestChallenge.java b/src/com/novell/sasl/client/DigestChallenge.java
new file mode 100644
index 0000000..90e6247
--- /dev/null
+++ b/src/com/novell/sasl/client/DigestChallenge.java
@@ -0,0 +1,393 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/DigestChallenge.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import java.util.*;
+import org.apache.harmony.javax.security.sasl.*;
+
+/**
+ * Implements the DigestChallenge class which will be used by the
+ * DigestMD5SaslClient class
+ */
+class DigestChallenge extends Object
+{
+ public static final int QOP_AUTH = 0x01;
+ public static final int QOP_AUTH_INT = 0x02;
+ public static final int QOP_AUTH_CONF = 0x04;
+ public static final int QOP_UNRECOGNIZED = 0x08;
+
+ private static final int CIPHER_3DES = 0x01;
+ private static final int CIPHER_DES = 0x02;
+ private static final int CIPHER_RC4_40 = 0x04;
+ private static final int CIPHER_RC4 = 0x08;
+ private static final int CIPHER_RC4_56 = 0x10;
+ private static final int CIPHER_UNRECOGNIZED = 0x20;
+ private static final int CIPHER_RECOGNIZED_MASK =
+ CIPHER_3DES | CIPHER_DES | CIPHER_RC4_40 | CIPHER_RC4 | CIPHER_RC4_56;
+
+ private ArrayList m_realms;
+ private String m_nonce;
+ private int m_qop;
+ private boolean m_staleFlag;
+ private int m_maxBuf;
+ private String m_characterSet;
+ private String m_algorithm;
+ private int m_cipherOptions;
+
+ DigestChallenge(
+ byte[] challenge)
+ throws SaslException
+ {
+ m_realms = new ArrayList(5);
+ m_nonce = null;
+ m_qop = 0;
+ m_staleFlag = false;
+ m_maxBuf = -1;
+ m_characterSet = null;
+ m_algorithm = null;
+ m_cipherOptions = 0;
+
+ DirectiveList dirList = new DirectiveList(challenge);
+ try
+ {
+ dirList.parseDirectives();
+ checkSemantics(dirList);
+ }
+ catch (SaslException e)
+ {
+ }
+ }
+
+ /**
+ * Checks the semantics of the directives in the directive list as parsed
+ * from the digest challenge byte array.
+ *
+ * @param dirList the list of directives parsed from the digest challenge
+ *
+ * @exception SaslException If a semantic error occurs
+ */
+ void checkSemantics(
+ DirectiveList dirList) throws SaslException
+ {
+ Iterator directives = dirList.getIterator();
+ ParsedDirective directive;
+ String name;
+
+ while (directives.hasNext())
+ {
+ directive = (ParsedDirective)directives.next();
+ name = directive.getName();
+ if (name.equals("realm"))
+ handleRealm(directive);
+ else if (name.equals("nonce"))
+ handleNonce(directive);
+ else if (name.equals("qop"))
+ handleQop(directive);
+ else if (name.equals("maxbuf"))
+ handleMaxbuf(directive);
+ else if (name.equals("charset"))
+ handleCharset(directive);
+ else if (name.equals("algorithm"))
+ handleAlgorithm(directive);
+ else if (name.equals("cipher"))
+ handleCipher(directive);
+ else if (name.equals("stale"))
+ handleStale(directive);
+ }
+
+ /* post semantic check */
+ if (-1 == m_maxBuf)
+ m_maxBuf = 65536;
+
+ if (m_qop == 0)
+ m_qop = QOP_AUTH;
+ else if ( (m_qop & QOP_AUTH) != QOP_AUTH )
+ throw new SaslException("Only qop-auth is supported by client");
+ else if ( ((m_qop & QOP_AUTH_CONF) == QOP_AUTH_CONF) &&
+ (0 == (m_cipherOptions & CIPHER_RECOGNIZED_MASK)) )
+ throw new SaslException("Invalid cipher options");
+ else if (null == m_nonce)
+ throw new SaslException("Missing nonce directive");
+ else if (m_staleFlag)
+ throw new SaslException("Unexpected stale flag");
+ else if ( null == m_algorithm )
+ throw new SaslException("Missing algorithm directive");
+ }
+
+ /**
+ * This function implements the semenatics of the nonce directive.
+ *
+ * @param pd ParsedDirective
+ *
+ * @exception SaslException If an error occurs due to too many nonce
+ * values
+ */
+ void handleNonce(
+ ParsedDirective pd) throws SaslException
+ {
+ if (null != m_nonce)
+ throw new SaslException("Too many nonce values.");
+
+ m_nonce = pd.getValue();
+ }
+
+ /**
+ * This function implements the semenatics of the realm directive.
+ *
+ * @param pd ParsedDirective
+ */
+ void handleRealm(
+ ParsedDirective pd)
+ {
+ m_realms.add(pd.getValue());
+ }
+
+ /**
+ * This function implements the semenatics of the qop (quality of protection)
+ * directive. The value of the qop directive is as defined below:
+ * qop-options = "qop" "=" <"> qop-list <">
+ * qop-list = 1#qop-value
+ * qop-value = "auth" | "auth-int" | "auth-conf" | token
+ *
+ * @param pd ParsedDirective
+ *
+ * @exception SaslException If an error occurs due to too many qop
+ * directives
+ */
+ void handleQop(
+ ParsedDirective pd) throws SaslException
+ {
+ String token;
+ TokenParser parser;
+
+ if (m_qop != 0)
+ throw new SaslException("Too many qop directives.");
+
+ parser = new TokenParser(pd.getValue());
+ for (token = parser.parseToken();
+ token != null;
+ token = parser.parseToken())
+ {
+ if (token.equals("auth"))
+ m_qop |= QOP_AUTH;
+ else if (token.equals("auth-int"))
+ m_qop |= QOP_AUTH_INT;
+ else if (token.equals("auth-conf"))
+ m_qop |= QOP_AUTH_CONF;
+ else
+ m_qop |= QOP_UNRECOGNIZED;
+ }
+ }
+
+ /**
+ * This function implements the semenatics of the Maxbuf directive.
+ * the value is defined as: 1*DIGIT
+ *
+ * @param pd ParsedDirective
+ *
+ * @exception SaslException If an error occur
+ */
+ void handleMaxbuf(
+ ParsedDirective pd) throws SaslException
+ {
+ if (-1 != m_maxBuf) /*it's initialized to -1 */
+ throw new SaslException("Too many maxBuf directives.");
+
+ m_maxBuf = Integer.parseInt(pd.getValue());
+
+ if (0 == m_maxBuf)
+ throw new SaslException("Max buf value must be greater than zero.");
+ }
+
+ /**
+ * This function implements the semenatics of the charset directive.
+ * the value is defined as: 1*DIGIT
+ *
+ * @param pd ParsedDirective
+ *
+ * @exception SaslException If an error occurs dur to too many charset
+ * directives or Invalid character encoding
+ * directive
+ */
+ void handleCharset(
+ ParsedDirective pd) throws SaslException
+ {
+ if (null != m_characterSet)
+ throw new SaslException("Too many charset directives.");
+
+ m_characterSet = pd.getValue();
+
+ if (!m_characterSet.equals("utf-8"))
+ throw new SaslException("Invalid character encoding directive");
+ }
+
+ /**
+ * This function implements the semenatics of the charset directive.
+ * the value is defined as: 1*DIGIT
+ *
+ * @param pd ParsedDirective
+ *
+ * @exception SaslException If an error occurs due to too many algorith
+ * directive or Invalid algorithm directive
+ * value
+ */
+ void handleAlgorithm(
+ ParsedDirective pd) throws SaslException
+ {
+ if (null != m_algorithm)
+ throw new SaslException("Too many algorithm directives.");
+
+ m_algorithm = pd.getValue();
+
+ if (!"md5-sess".equals(m_algorithm))
+ throw new SaslException("Invalid algorithm directive value: " +
+ m_algorithm);
+ }
+
+ /**
+ * This function implements the semenatics of the cipher-opts directive
+ * directive. The value of the qop directive is as defined below:
+ * qop-options = "qop" "=" <"> qop-list <">
+ * qop-list = 1#qop-value
+ * qop-value = "auth" | "auth-int" | "auth-conf" | token
+ *
+ * @param pd ParsedDirective
+ *
+ * @exception SaslException If an error occurs due to Too many cipher
+ * directives
+ */
+ void handleCipher(
+ ParsedDirective pd) throws SaslException
+ {
+ String token;
+ TokenParser parser;
+
+ if (0 != m_cipherOptions)
+ throw new SaslException("Too many cipher directives.");
+
+ parser = new TokenParser(pd.getValue());
+ token = parser.parseToken();
+ for (token = parser.parseToken();
+ token != null;
+ token = parser.parseToken())
+ {
+ if ("3des".equals(token))
+ m_cipherOptions |= CIPHER_3DES;
+ else if ("des".equals(token))
+ m_cipherOptions |= CIPHER_DES;
+ else if ("rc4-40".equals(token))
+ m_cipherOptions |= CIPHER_RC4_40;
+ else if ("rc4".equals(token))
+ m_cipherOptions |= CIPHER_RC4;
+ else if ("rc4-56".equals(token))
+ m_cipherOptions |= CIPHER_RC4_56;
+ else
+ m_cipherOptions |= CIPHER_UNRECOGNIZED;
+ }
+
+ if (m_cipherOptions == 0)
+ m_cipherOptions = CIPHER_UNRECOGNIZED;
+ }
+
+ /**
+ * This function implements the semenatics of the stale directive.
+ *
+ * @param pd ParsedDirective
+ *
+ * @exception SaslException If an error occurs due to Too many stale
+ * directives or Invalid stale directive value
+ */
+ void handleStale(
+ ParsedDirective pd) throws SaslException
+ {
+ if (false != m_staleFlag)
+ throw new SaslException("Too many stale directives.");
+
+ if ("true".equals(pd.getValue()))
+ m_staleFlag = true;
+ else
+ throw new SaslException("Invalid stale directive value: " +
+ pd.getValue());
+ }
+
+ /**
+ * Return the list of the All the Realms
+ *
+ * @return List of all the realms
+ */
+ public ArrayList getRealms()
+ {
+ return m_realms;
+ }
+
+ /**
+ * @return Returns the Nonce
+ */
+ public String getNonce()
+ {
+ return m_nonce;
+ }
+
+ /**
+ * Return the quality-of-protection
+ *
+ * @return The quality-of-protection
+ */
+ public int getQop()
+ {
+ return m_qop;
+ }
+
+ /**
+ * @return The state of the Staleflag
+ */
+ public boolean getStaleFlag()
+ {
+ return m_staleFlag;
+ }
+
+ /**
+ * @return The Maximum Buffer value
+ */
+ public int getMaxBuf()
+ {
+ return m_maxBuf;
+ }
+
+ /**
+ * @return character set values as string
+ */
+ public String getCharacterSet()
+ {
+ return m_characterSet;
+ }
+
+ /**
+ * @return The String value of the algorithm
+ */
+ public String getAlgorithm()
+ {
+ return m_algorithm;
+ }
+
+ /**
+ * @return The cipher options
+ */
+ public int getCipherOptions()
+ {
+ return m_cipherOptions;
+ }
+}
+
diff --git a/src/com/novell/sasl/client/DigestMD5SaslClient.java b/src/com/novell/sasl/client/DigestMD5SaslClient.java
new file mode 100644
index 0000000..141c96b
--- /dev/null
+++ b/src/com/novell/sasl/client/DigestMD5SaslClient.java
@@ -0,0 +1,820 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/DigestMD5SaslClient.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import org.apache.harmony.javax.security.sasl.*;
+import org.apache.harmony.javax.security.auth.callback.*;
+import java.security.SecureRandom;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.io.UnsupportedEncodingException;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Implements the Client portion of DigestMD5 Sasl mechanism.
+ */
+public class DigestMD5SaslClient implements SaslClient
+{
+ private String m_authorizationId = "";
+ private String m_protocol = "";
+ private String m_serverName = "";
+ private Map m_props;
+ private CallbackHandler m_cbh;
+ private int m_state;
+ private String m_qopValue = "";
+ private char[] m_HA1 = null;
+ private String m_digestURI;
+ private DigestChallenge m_dc;
+ private String m_clientNonce = "";
+ private String m_realm = "";
+ private String m_name = "";
+
+ private static final int STATE_INITIAL = 0;
+ private static final int STATE_DIGEST_RESPONSE_SENT = 1;
+ private static final int STATE_VALID_SERVER_RESPONSE = 2;
+ private static final int STATE_INVALID_SERVER_RESPONSE = 3;
+ private static final int STATE_DISPOSED = 4;
+
+ private static final int NONCE_BYTE_COUNT = 32;
+ private static final int NONCE_HEX_COUNT = 2*NONCE_BYTE_COUNT;
+
+ private static final String DIGEST_METHOD = "AUTHENTICATE";
+
+ /**
+ * Creates an DigestMD5SaslClient object using the parameters supplied.
+ * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
+ * contained in props
+ *
+ * @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 Sasl 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 NameCallback. The password is requested by using
+ * a PasswordCallback. The realm is requested by using
+ * a RealmChoiceCallback if there is a list of realms
+ * to choose from, and by using a RealmCallback if the
+ * realm must be entered.
+ *
+ * @return A possibly null SaslClient created using the
+ * parameters supplied. If null, this factory cannot
+ * produce a SaslClient using the parameters supplied.
+ *
+ * @exception SaslException If a SaslClient instance cannot be created
+ * because of an error
+ */
+ public static SaslClient getClient(
+ String authorizationId,
+ String protocol,
+ String serverName,
+ Map props,
+ CallbackHandler cbh)
+ {
+ String desiredQOP = (String)props.get(Sasl.QOP);
+ String desiredStrength = (String)props.get(Sasl.STRENGTH);
+ String serverAuth = (String)props.get(Sasl.SERVER_AUTH);
+
+ //only support qop equal to auth
+ if ((desiredQOP != null) && !"auth".equals(desiredQOP))
+ return null;
+
+ //doesn't support server authentication
+ if ((serverAuth != null) && !"false".equals(serverAuth))
+ return null;
+
+ //need a callback handler to get the password
+ if (cbh == null)
+ return null;
+
+ return new DigestMD5SaslClient(authorizationId, protocol,
+ serverName, props, cbh);
+ }
+
+ /**
+ * Creates an DigestMD5SaslClient object using the parameters supplied.
+ * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
+ * contained in props
+ *
+ * @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 Sasl 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 NameCallback. The password is requested by using
+ * a PasswordCallback. The realm is requested by using
+ * a RealmChoiceCallback if there is a list of realms
+ * to choose from, and by using a RealmCallback if the
+ * realm must be entered.
+ *
+ */
+ private DigestMD5SaslClient(
+ String authorizationId,
+ String protocol,
+ String serverName,
+ Map props,
+ CallbackHandler cbh)
+ {
+ m_authorizationId = authorizationId;
+ m_protocol = protocol;
+ m_serverName = serverName;
+ m_props = props;
+ m_cbh = cbh;
+
+ m_state = STATE_INITIAL;
+ }
+
+ /**
+ * Determines if this mechanism has an optional initial response. If true,
+ * caller should call evaluateChallenge() with an empty array to get the
+ * initial response.
+ *
+ * @return true if this mechanism has an initial response
+ */
+ public boolean hasInitialResponse()
+ {
+ return false;
+ }
+
+ /**
+ * Determines if 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()
+ {
+ if ((m_state == STATE_VALID_SERVER_RESPONSE) ||
+ (m_state == STATE_INVALID_SERVER_RESPONSE) ||
+ (m_state == STATE_DISPOSED))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Unwraps a byte array received from the server. This method can be called
+ * only after the authentication exchange has completed (i.e., when
+ * isComplete() returns true) and only if the authentication exchange has
+ * negotiated integrity and/or privacy as the quality of protection;
+ * otherwise, an IllegalStateException is thrown.
+ *
+ * incoming is the contents of the SASL buffer as defined in RFC 2222
+ * without the leading four octet field that represents the length.
+ * offset and len specify the portion of incoming to use.
+ *
+ * @param incoming A non-null byte array containing the encoded bytes
+ * from the server
+ * @param offset The starting position at incoming of the bytes to use
+ *
+ * @param len The number of bytes from incoming to use
+ *
+ * @return A non-null byte array containing the decoded bytes
+ *
+ */
+ public byte[] unwrap(
+ byte[] incoming,
+ int offset,
+ int len)
+ throws SaslException
+ {
+ throw new IllegalStateException(
+ "unwrap: QOP has neither integrity nor privacy>");
+ }
+
+ /**
+ * 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
+ * isComplete() returns true) and only if the authentication exchange has
+ * negotiated integrity and/or privacy as the quality of protection;
+ * otherwise, an IllegalStateException is thrown.
+ *
+ * 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. offset and len specify the portion of outgoing to use.
+ *
+ * @param outgoing A non-null byte array containing the bytes to encode
+ * @param offset The starting position at outgoing of the bytes to use
+ * @param len The number of bytes from outgoing to use
+ *
+ * @return A non-null byte array containing the encoded bytes
+ *
+ * @exception SaslException if incoming cannot be successfully unwrapped.
+ *
+ * @exception 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 IllegalStateException(
+ "wrap: QOP has neither integrity nor privacy>");
+ }
+
+ /**
+ * Retrieves the negotiated property. This method can be called only after
+ * the authentication exchange has completed (i.e., when isComplete()
+ * returns true); otherwise, an IllegalStateException 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.
+ *
+ * @exception IllegalStateException if this authentication exchange has
+ * not completed
+ */
+ public Object getNegotiatedProperty(
+ String propName)
+ {
+ if (m_state != STATE_VALID_SERVER_RESPONSE)
+ throw new IllegalStateException(
+ "getNegotiatedProperty: authentication exchange not complete.");
+
+ if (Sasl.QOP.equals(propName))
+ return "auth";
+ else
+ return null;
+ }
+
+ /**
+ * 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.
+ *
+ * @exception SaslException if a problem was encountered while disposing
+ * of the resources
+ */
+ public void dispose()
+ throws SaslException
+ {
+ if (m_state != STATE_DISPOSED)
+ {
+ m_state = STATE_DISPOSED;
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @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.
+ *
+ * @exception SaslException If an error occurred while processing the
+ * challenge or generating a response.
+ */
+ public byte[] evaluateChallenge(
+ byte[] challenge)
+ throws SaslException
+ {
+ byte[] response = null;
+
+ //printState();
+ switch (m_state)
+ {
+ case STATE_INITIAL:
+ if (challenge.length == 0)
+ throw new SaslException("response = byte[0]");
+ else
+ try
+ {
+ response = createDigestResponse(challenge).
+ getBytes("UTF-8");
+ m_state = STATE_DIGEST_RESPONSE_SENT;
+ }
+ catch (java.io.UnsupportedEncodingException e)
+ {
+ throw new SaslException(
+ "UTF-8 encoding not suppported by platform", e);
+ }
+ break;
+ case STATE_DIGEST_RESPONSE_SENT:
+ if (checkServerResponseAuth(challenge))
+ m_state = STATE_VALID_SERVER_RESPONSE;
+ else
+ {
+ m_state = STATE_INVALID_SERVER_RESPONSE;
+ throw new SaslException("Could not validate response-auth " +
+ "value from server");
+ }
+ break;
+ case STATE_VALID_SERVER_RESPONSE:
+ case STATE_INVALID_SERVER_RESPONSE:
+ throw new SaslException("Authentication sequence is complete");
+ case STATE_DISPOSED:
+ throw new SaslException("Client has been disposed");
+ default:
+ throw new SaslException("Unknown client state.");
+ }
+
+ return response;
+ }
+
+ /**
+ * This function takes a 16 byte binary md5-hash value and creates a 32
+ * character (plus a terminating null character) hex-digit
+ * representation of binary data.
+ *
+ * @param hash 16 byte binary md5-hash value in bytes
+ *
+ * @return 32 character (plus a terminating null character) hex-digit
+ * representation of binary data.
+ */
+ char[] convertToHex(
+ byte[] hash)
+ {
+ int i;
+ byte j;
+ byte fifteen = 15;
+ char[] hex = new char[32];
+
+ for (i = 0; i < 16; i++)
+ {
+ //convert value of top 4 bits to hex char
+ hex[i*2] = getHexChar((byte)((hash[i] & 0xf0) >> 4));
+ //convert value of bottom 4 bits to hex char
+ hex[(i*2)+1] = getHexChar((byte)(hash[i] & 0x0f));
+ }
+
+ return hex;
+ }
+
+ /**
+ * Calculates the HA1 portion of the response
+ *
+ * @param algorithm Algorith to use.
+ * @param userName User being authenticated
+ * @param realm realm information
+ * @param password password of teh user
+ * @param nonce nonce value
+ * @param clientNonce Clients Nonce value
+ *
+ * @return HA1 portion of the response in a character array
+ *
+ * @exception SaslException If an error occurs
+ */
+ char[] DigestCalcHA1(
+ String algorithm,
+ String userName,
+ String realm,
+ String password,
+ String nonce,
+ String clientNonce) throws SaslException
+ {
+ byte[] hash;
+
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ md.update(userName.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(realm.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(password.getBytes("UTF-8"));
+ hash = md.digest();
+
+ if ("md5-sess".equals(algorithm))
+ {
+ md.update(hash);
+ md.update(":".getBytes("UTF-8"));
+ md.update(nonce.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(clientNonce.getBytes("UTF-8"));
+ hash = md.digest();
+ }
+ }
+ catch(NoSuchAlgorithmException e)
+ {
+ throw new SaslException("No provider found for MD5 hash", e);
+ }
+ catch(UnsupportedEncodingException e)
+ {
+ throw new SaslException(
+ "UTF-8 encoding not supported by platform.", e);
+ }
+
+ return convertToHex(hash);
+ }
+
+
+ /**
+ * This function calculates the response-value of the response directive of
+ * the digest-response as documented in RFC 2831
+ *
+ * @param HA1 H(A1)
+ * @param serverNonce nonce from server
+ * @param nonceCount 8 hex digits
+ * @param clientNonce client nonce
+ * @param qop qop-value: "", "auth", "auth-int"
+ * @param method method from the request
+ * @param digestUri requested URL
+ * @param clientResponseFlag request-digest or response-digest
+ *
+ * @return Response-value of the response directive of the digest-response
+ *
+ * @exception SaslException If an error occurs
+ */
+ char[] DigestCalcResponse(
+ char[] HA1, /* H(A1) */
+ String serverNonce, /* nonce from server */
+ String nonceCount, /* 8 hex digits */
+ String clientNonce, /* client nonce */
+ String qop, /* qop-value: "", "auth", "auth-int" */
+ String method, /* method from the request */
+ String digestUri, /* requested URL */
+ boolean clientResponseFlag) /* request-digest or response-digest */
+ throws SaslException
+ {
+ byte[] HA2;
+ byte[] respHash;
+ char[] HA2Hex;
+
+ // calculate H(A2)
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ if (clientResponseFlag)
+ md.update(method.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(digestUri.getBytes("UTF-8"));
+ if ("auth-int".equals(qop))
+ {
+ md.update(":".getBytes("UTF-8"));
+ md.update("00000000000000000000000000000000".getBytes("UTF-8"));
+ }
+ HA2 = md.digest();
+ HA2Hex = convertToHex(HA2);
+
+ // calculate response
+ md.update(new String(HA1).getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(serverNonce.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ if (qop.length() > 0)
+ {
+ md.update(nonceCount.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(clientNonce.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(qop.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ }
+ md.update(new String(HA2Hex).getBytes("UTF-8"));
+ respHash = md.digest();
+ }
+ catch(NoSuchAlgorithmException e)
+ {
+ throw new SaslException("No provider found for MD5 hash", e);
+ }
+ catch(UnsupportedEncodingException e)
+ {
+ throw new SaslException(
+ "UTF-8 encoding not supported by platform.", e);
+ }
+
+ return convertToHex(respHash);
+ }
+
+
+ /**
+ * Creates the intial response to be sent to the server.
+ *
+ * @param challenge Challenge in bytes recived form the Server
+ *
+ * @return Initial response to be sent to the server
+ */
+ private String createDigestResponse(
+ byte[] challenge)
+ throws SaslException
+ {
+ char[] response;
+ StringBuffer digestResponse = new StringBuffer(512);
+ int realmSize;
+
+ m_dc = new DigestChallenge(challenge);
+
+ m_digestURI = m_protocol + "/" + m_serverName;
+
+ if ((m_dc.getQop() & DigestChallenge.QOP_AUTH)
+ == DigestChallenge.QOP_AUTH )
+ m_qopValue = "auth";
+ else
+ throw new SaslException("Client only supports qop of 'auth'");
+
+ //get call back information
+ Callback[] callbacks = new Callback[3];
+ ArrayList realms = m_dc.getRealms();
+ realmSize = realms.size();
+ if (realmSize == 0)
+ {
+ callbacks[0] = new RealmCallback("Realm");
+ }
+ else if (realmSize == 1)
+ {
+ callbacks[0] = new RealmCallback("Realm", (String)realms.get(0));
+ }
+ else
+ {
+ callbacks[0] =
+ new RealmChoiceCallback(
+ "Realm",
+ (String[])realms.toArray(new String[realmSize]),
+ 0, //the default choice index
+ false); //no multiple selections
+ }
+
+ callbacks[1] = new PasswordCallback("Password", false);
+ //false = no echo
+
+ if (m_authorizationId == null || m_authorizationId.length() == 0)
+ callbacks[2] = new NameCallback("Name");
+ else
+ callbacks[2] = new NameCallback("Name", m_authorizationId);
+
+ try
+ {
+ m_cbh.handle(callbacks);
+ }
+ catch(UnsupportedCallbackException e)
+ {
+ throw new SaslException("Handler does not support" +
+ " necessary callbacks",e);
+ }
+ catch(IOException e)
+ {
+ throw new SaslException("IO exception in CallbackHandler.", e);
+ }
+
+ if (realmSize > 1)
+ {
+ int[] selections =
+ ((RealmChoiceCallback)callbacks[0]).getSelectedIndexes();
+
+ if (selections.length > 0)
+ m_realm =
+ ((RealmChoiceCallback)callbacks[0]).getChoices()[selections[0]];
+ else
+ m_realm = ((RealmChoiceCallback)callbacks[0]).getChoices()[0];
+ }
+ else
+ m_realm = ((RealmCallback)callbacks[0]).getText();
+
+ m_clientNonce = getClientNonce();
+
+ m_name = ((NameCallback)callbacks[2]).getName();
+ if (m_name == null)
+ m_name = ((NameCallback)callbacks[2]).getDefaultName();
+ if (m_name == null)
+ throw new SaslException("No user name was specified.");
+
+ m_HA1 = DigestCalcHA1(
+ m_dc.getAlgorithm(),
+ m_name,
+ m_realm,
+ new String(((PasswordCallback)callbacks[1]).getPassword()),
+ m_dc.getNonce(),
+ m_clientNonce);
+
+ response = DigestCalcResponse(m_HA1,
+ m_dc.getNonce(),
+ "00000001",
+ m_clientNonce,
+ m_qopValue,
+ "AUTHENTICATE",
+ m_digestURI,
+ true);
+
+ digestResponse.append("username=\"");
+ digestResponse.append(m_authorizationId);
+ if (0 != m_realm.length())
+ {
+ digestResponse.append("\",realm=\"");
+ digestResponse.append(m_realm);
+ }
+ digestResponse.append("\",cnonce=\"");
+ digestResponse.append(m_clientNonce);
+ digestResponse.append("\",nc=");
+ digestResponse.append("00000001"); //nounce count
+ digestResponse.append(",qop=");
+ digestResponse.append(m_qopValue);
+ digestResponse.append(",digest-uri=\"");
+ digestResponse.append(m_digestURI);
+ digestResponse.append("\",response=");
+ digestResponse.append(response);
+ digestResponse.append(",charset=utf-8,nonce=\"");
+ digestResponse.append(m_dc.getNonce());
+ digestResponse.append("\"");
+
+ return digestResponse.toString();
+ }
+
+
+ /**
+ * This function validates the server response. This step performs a
+ * modicum of mutual authentication by verifying that the server knows
+ * the user's password
+ *
+ * @param serverResponse Response recived form Server
+ *
+ * @return true if the mutual authentication succeeds;
+ * else return false
+ *
+ * @exception SaslException If an error occurs
+ */
+ boolean checkServerResponseAuth(
+ byte[] serverResponse) throws SaslException
+ {
+ char[] response;
+ ResponseAuth responseAuth = null;
+ String responseStr;
+
+ responseAuth = new ResponseAuth(serverResponse);
+
+ response = DigestCalcResponse(m_HA1,
+ m_dc.getNonce(),
+ "00000001",
+ m_clientNonce,
+ m_qopValue,
+ DIGEST_METHOD,
+ m_digestURI,
+ false);
+
+ responseStr = new String(response);
+
+ return responseStr.equals(responseAuth.getResponseValue());
+ }
+
+
+ /**
+ * This function returns hex character representing the value of the input
+ *
+ * @param value Input value in byte
+ *
+ * @return Hex value of the Input byte value
+ */
+ private static char getHexChar(
+ byte value)
+ {
+ switch (value)
+ {
+ case 0:
+ return '0';
+ case 1:
+ return '1';
+ case 2:
+ return '2';
+ case 3:
+ return '3';
+ case 4:
+ return '4';
+ case 5:
+ return '5';
+ case 6:
+ return '6';
+ case 7:
+ return '7';
+ case 8:
+ return '8';
+ case 9:
+ return '9';
+ case 10:
+ return 'a';
+ case 11:
+ return 'b';
+ case 12:
+ return 'c';
+ case 13:
+ return 'd';
+ case 14:
+ return 'e';
+ case 15:
+ return 'f';
+ default:
+ return 'Z';
+ }
+ }
+
+ /**
+ * Calculates the Nonce value of the Client
+ *
+ * @return Nonce value of the client
+ *
+ * @exception SaslException If an error Occurs
+ */
+ String getClientNonce() throws SaslException
+ {
+ byte[] nonceBytes = new byte[NONCE_BYTE_COUNT];
+ SecureRandom prng;
+ byte nonceByte;
+ char[] hexNonce = new char[NONCE_HEX_COUNT];
+
+ try
+ {
+ prng = SecureRandom.getInstance("SHA1PRNG");
+ prng.nextBytes(nonceBytes);
+ for(int i=0; i<NONCE_BYTE_COUNT; i++)
+ {
+ //low nibble
+ hexNonce[i*2] = getHexChar((byte)(nonceBytes[i] & 0x0f));
+ //high nibble
+ hexNonce[(i*2)+1] = getHexChar((byte)((nonceBytes[i] & 0xf0)
+ >> 4));
+ }
+ return new String(hexNonce);
+ }
+ catch(NoSuchAlgorithmException e)
+ {
+ throw new SaslException("No random number generator available", e);
+ }
+ }
+
+ /**
+ * Returns the IANA-registered mechanism name of this SASL client.
+ * (e.g. "CRAM-MD5", "GSSAPI")
+ *
+ * @return "DIGEST-MD5"the IANA-registered mechanism name of this SASL
+ * client.
+ */
+ public String getMechanismName()
+ {
+ return "DIGEST-MD5";
+ }
+
+} //end class DigestMD5SaslClient
+
diff --git a/src/com/novell/sasl/client/DirectiveList.java b/src/com/novell/sasl/client/DirectiveList.java
new file mode 100644
index 0000000..fc26a6b
--- /dev/null
+++ b/src/com/novell/sasl/client/DirectiveList.java
@@ -0,0 +1,363 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/DirectiveList.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import java.util.*;
+import org.apache.harmony.javax.security.sasl.*;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Implements the DirectiveList class whihc will be used by the
+ * DigestMD5SaslClient class
+ */
+class DirectiveList extends Object
+{
+ private static final int STATE_LOOKING_FOR_FIRST_DIRECTIVE = 1;
+ private static final int STATE_LOOKING_FOR_DIRECTIVE = 2;
+ private static final int STATE_SCANNING_NAME = 3;
+ private static final int STATE_LOOKING_FOR_EQUALS = 4;
+ private static final int STATE_LOOKING_FOR_VALUE = 5;
+ private static final int STATE_LOOKING_FOR_COMMA = 6;
+ private static final int STATE_SCANNING_QUOTED_STRING_VALUE = 7;
+ private static final int STATE_SCANNING_TOKEN_VALUE = 8;
+ private static final int STATE_NO_UTF8_SUPPORT = 9;
+
+ private int m_curPos;
+ private int m_errorPos;
+ private String m_directives;
+ private int m_state;
+ private ArrayList m_directiveList;
+ private String m_curName;
+ private int m_scanStart;
+
+ /**
+ * Constructs a new DirectiveList.
+ */
+ DirectiveList(
+ byte[] directives)
+ {
+ m_curPos = 0;
+ m_state = STATE_LOOKING_FOR_FIRST_DIRECTIVE;
+ m_directiveList = new ArrayList(10);
+ m_scanStart = 0;
+ m_errorPos = -1;
+ try
+ {
+ m_directives = new String(directives, "UTF-8");
+ }
+ catch(UnsupportedEncodingException e)
+ {
+ m_state = STATE_NO_UTF8_SUPPORT;
+ }
+ }
+
+ /**
+ * This function takes a US-ASCII character string containing a list of comma
+ * separated directives, and parses the string into the individual directives
+ * and their values. A directive consists of a token specifying the directive
+ * name followed by an equal sign (=) and the directive value. The value is
+ * either a token or a quoted string
+ *
+ * @exception SaslException If an error Occurs
+ */
+ void parseDirectives() throws SaslException
+ {
+ char prevChar;
+ char currChar;
+ int rc = 0;
+ boolean haveQuotedPair = false;
+ String currentName = "<no name>";
+
+ if (m_state == STATE_NO_UTF8_SUPPORT)
+ throw new SaslException("No UTF-8 support on platform");
+
+ prevChar = 0;
+
+ while (m_curPos < m_directives.length())
+ {
+ currChar = m_directives.charAt(m_curPos);
+ switch (m_state)
+ {
+ case STATE_LOOKING_FOR_FIRST_DIRECTIVE:
+ case STATE_LOOKING_FOR_DIRECTIVE:
+ if (isWhiteSpace(currChar))
+ {
+ break;
+ }
+ else if (isValidTokenChar(currChar))
+ {
+ m_scanStart = m_curPos;
+ m_state = STATE_SCANNING_NAME;
+ }
+ else
+ {
+ m_errorPos = m_curPos;
+ throw new SaslException("Parse error: Invalid name character");
+ }
+ break;
+
+ case STATE_SCANNING_NAME:
+ if (isValidTokenChar(currChar))
+ {
+ break;
+ }
+ else if (isWhiteSpace(currChar))
+ {
+ currentName = m_directives.substring(m_scanStart, m_curPos);
+ m_state = STATE_LOOKING_FOR_EQUALS;
+ }
+ else if ('=' == currChar)
+ {
+ currentName = m_directives.substring(m_scanStart, m_curPos);
+ m_state = STATE_LOOKING_FOR_VALUE;
+ }
+ else
+ {
+ m_errorPos = m_curPos;
+ throw new SaslException("Parse error: Invalid name character");
+ }
+ break;
+
+ case STATE_LOOKING_FOR_EQUALS:
+ if (isWhiteSpace(currChar))
+ {
+ break;
+ }
+ else if ('=' == currChar)
+ {
+ m_state = STATE_LOOKING_FOR_VALUE;
+ }
+ else
+ {
+ m_errorPos = m_curPos;
+ throw new SaslException("Parse error: Expected equals sign '='.");
+ }
+ break;
+
+ case STATE_LOOKING_FOR_VALUE:
+ if (isWhiteSpace(currChar))
+ {
+ break;
+ }
+ else if ('"' == currChar)
+ {
+ m_scanStart = m_curPos+1; /* don't include the quote */
+ m_state = STATE_SCANNING_QUOTED_STRING_VALUE;
+ }
+ else if (isValidTokenChar(currChar))
+ {
+ m_scanStart = m_curPos;
+ m_state = STATE_SCANNING_TOKEN_VALUE;
+ }
+ else
+ {
+ m_errorPos = m_curPos;
+ throw new SaslException("Parse error: Unexpected character");
+ }
+ break;
+
+ case STATE_SCANNING_TOKEN_VALUE:
+ if (isValidTokenChar(currChar))
+ {
+ break;
+ }
+ else if (isWhiteSpace(currChar))
+ {
+ addDirective(currentName, false);
+ m_state = STATE_LOOKING_FOR_COMMA;
+ }
+ else if (',' == currChar)
+ {
+ addDirective(currentName, false);
+ m_state = STATE_LOOKING_FOR_DIRECTIVE;
+ }
+ else
+ {
+ m_errorPos = m_curPos;
+ throw new SaslException("Parse error: Invalid value character");
+ }
+ break;
+
+ case STATE_SCANNING_QUOTED_STRING_VALUE:
+ if ('\\' == currChar)
+ haveQuotedPair = true;
+ if ( ('"' == currChar) &&
+ ('\\' != prevChar) )
+ {
+ addDirective(currentName, haveQuotedPair);
+ haveQuotedPair = false;
+ m_state = STATE_LOOKING_FOR_COMMA;
+ }
+ break;
+
+ case STATE_LOOKING_FOR_COMMA:
+ if (isWhiteSpace(currChar))
+ break;
+ else if (currChar == ',')
+ m_state = STATE_LOOKING_FOR_DIRECTIVE;
+ else
+ {
+ m_errorPos = m_curPos;
+ throw new SaslException("Parse error: Expected a comma.");
+ }
+ break;
+ }
+ if (0 != rc)
+ break;
+ prevChar = currChar;
+ m_curPos++;
+ } /* end while loop */
+
+
+ if (rc == 0)
+ {
+ /* check the ending state */
+ switch (m_state)
+ {
+ case STATE_SCANNING_TOKEN_VALUE:
+ addDirective(currentName, false);
+ break;
+
+ case STATE_LOOKING_FOR_FIRST_DIRECTIVE:
+ case STATE_LOOKING_FOR_COMMA:
+ break;
+
+ case STATE_LOOKING_FOR_DIRECTIVE:
+ throw new SaslException("Parse error: Trailing comma.");
+
+ case STATE_SCANNING_NAME:
+ case STATE_LOOKING_FOR_EQUALS:
+ case STATE_LOOKING_FOR_VALUE:
+ throw new SaslException("Parse error: Missing value.");
+
+ case STATE_SCANNING_QUOTED_STRING_VALUE:
+ throw new SaslException("Parse error: Missing closing quote.");
+ }
+ }
+
+ }
+
+ /**
+ * This function returns TRUE if the character is a valid token character.
+ *
+ * token = 1*<any CHAR except CTLs or separators>
+ *
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ *
+ * CTL = <any US-ASCII control character
+ * (octets 0 - 31) and DEL (127)>
+ *
+ * CHAR = <any US-ASCII character (octets 0 - 127)>
+ *
+ * @param c character to be tested
+ *
+ * @return Returns TRUE if the character is a valid token character.
+ */
+ boolean isValidTokenChar(
+ char c)
+ {
+ if ( ( (c >= '\u0000') && (c <='\u0020') ) ||
+ ( (c >= '\u003a') && (c <= '\u0040') ) ||
+ ( (c >= '\u005b') && (c <= '\u005d') ) ||
+ ('\u002c' == c) ||
+ ('\u0025' == c) ||
+ ('\u0028' == c) ||
+ ('\u0029' == c) ||
+ ('\u007b' == c) ||
+ ('\u007d' == c) ||
+ ('\u007f' == c) )
+ return false;
+
+ return true;
+ }
+
+ /**
+ * This function returns TRUE if the character is linear white space (LWS).
+ * LWS = [CRLF] 1*( SP | HT )
+ * @param c Input charcter to be tested
+ *
+ * @return Returns TRUE if the character is linear white space (LWS)
+ */
+ boolean isWhiteSpace(
+ char c)
+ {
+ if ( ('\t' == c) || // HORIZONTAL TABULATION.
+ ('\n' == c) || // LINE FEED.
+ ('\r' == c) || // CARRIAGE RETURN.
+ ('\u0020' == c) )
+ return true;
+
+ return false;
+ }
+
+ /**
+ * This function creates a directive record and adds it to the list, the
+ * value will be added later after it is parsed.
+ *
+ * @param name Name
+ * @param haveQuotedPair true if quoted pair is there else false
+ */
+ void addDirective(
+ String name,
+ boolean haveQuotedPair)
+ {
+ String value;
+ int inputIndex;
+ int valueIndex;
+ char valueChar;
+ int type;
+
+ if (!haveQuotedPair)
+ {
+ value = m_directives.substring(m_scanStart, m_curPos);
+ }
+ else
+ { //copy one character at a time skipping backslash excapes.
+ StringBuffer valueBuf = new StringBuffer(m_curPos - m_scanStart);
+ valueIndex = 0;
+ inputIndex = m_scanStart;
+ while (inputIndex < m_curPos)
+ {
+ if ('\\' == (valueChar = m_directives.charAt(inputIndex)))
+ inputIndex++;
+ valueBuf.setCharAt(valueIndex, m_directives.charAt(inputIndex));
+ valueIndex++;
+ inputIndex++;
+ }
+ value = new String(valueBuf);
+ }
+
+ if (m_state == STATE_SCANNING_QUOTED_STRING_VALUE)
+ type = ParsedDirective.QUOTED_STRING_VALUE;
+ else
+ type = ParsedDirective.TOKEN_VALUE;
+ m_directiveList.add(new ParsedDirective(name, value, type));
+ }
+
+
+ /**
+ * Returns the List iterator.
+ *
+ * @return Returns the Iterator Object for the List.
+ */
+ Iterator getIterator()
+ {
+ return m_directiveList.iterator();
+ }
+}
+
diff --git a/src/com/novell/sasl/client/ParsedDirective.java b/src/com/novell/sasl/client/ParsedDirective.java
new file mode 100644
index 0000000..17bf70e
--- /dev/null
+++ b/src/com/novell/sasl/client/ParsedDirective.java
@@ -0,0 +1,56 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/ParsedDirective.java,v 1.1 2003/08/21 10:06:26 kkanil Exp $
+ *
+ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+/**
+ * Implements the ParsedDirective class which will be used in the
+ * DigestMD5SaslClient mechanism.
+ */
+class ParsedDirective
+{
+ public static final int QUOTED_STRING_VALUE = 1;
+ public static final int TOKEN_VALUE = 2;
+
+ private int m_valueType;
+ private String m_name;
+ private String m_value;
+
+ ParsedDirective(
+ String name,
+ String value,
+ int type)
+ {
+ m_name = name;
+ m_value = value;
+ m_valueType = type;
+ }
+
+ String getValue()
+ {
+ return m_value;
+ }
+
+ String getName()
+ {
+ return m_name;
+ }
+
+ int getValueType()
+ {
+ return m_valueType;
+ }
+
+}
+
diff --git a/src/com/novell/sasl/client/ResponseAuth.java b/src/com/novell/sasl/client/ResponseAuth.java
new file mode 100644
index 0000000..0aef955
--- /dev/null
+++ b/src/com/novell/sasl/client/ResponseAuth.java
@@ -0,0 +1,83 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/ResponseAuth.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import java.util.*;
+import org.apache.harmony.javax.security.sasl.*;
+
+/**
+ * Implements the ResponseAuth class used by the DigestMD5SaslClient mechanism
+ */
+class ResponseAuth
+{
+
+ private String m_responseValue;
+
+ ResponseAuth(
+ byte[] responseAuth)
+ throws SaslException
+ {
+ m_responseValue = null;
+
+ DirectiveList dirList = new DirectiveList(responseAuth);
+ try
+ {
+ dirList.parseDirectives();
+ checkSemantics(dirList);
+ }
+ catch (SaslException e)
+ {
+ }
+ }
+
+ /**
+ * Checks the semantics of the directives in the directive list as parsed
+ * from the digest challenge byte array.
+ *
+ * @param dirList the list of directives parsed from the digest challenge
+ *
+ * @exception SaslException If a semantic error occurs
+ */
+ void checkSemantics(
+ DirectiveList dirList) throws SaslException
+ {
+ Iterator directives = dirList.getIterator();
+ ParsedDirective directive;
+ String name;
+
+ while (directives.hasNext())
+ {
+ directive = (ParsedDirective)directives.next();
+ name = directive.getName();
+ if (name.equals("rspauth"))
+ m_responseValue = directive.getValue();
+ }
+
+ /* post semantic check */
+ if (m_responseValue == null)
+ throw new SaslException("Missing response-auth directive.");
+ }
+
+ /**
+ * returns the ResponseValue
+ *
+ * @return the ResponseValue as a String.
+ */
+ public String getResponseValue()
+ {
+ return m_responseValue;
+ }
+}
+
diff --git a/src/com/novell/sasl/client/TokenParser.java b/src/com/novell/sasl/client/TokenParser.java
new file mode 100644
index 0000000..3d3491d
--- /dev/null
+++ b/src/com/novell/sasl/client/TokenParser.java
@@ -0,0 +1,208 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/TokenParser.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import org.apache.harmony.javax.security.sasl.*;
+/**
+ * The TokenParser class will parse individual tokens from a list of tokens that
+ * are a directive value for a DigestMD5 authentication.The tokens are separated
+ * commas.
+ */
+class TokenParser extends Object
+{
+ private static final int STATE_LOOKING_FOR_FIRST_TOKEN = 1;
+ private static final int STATE_LOOKING_FOR_TOKEN = 2;
+ private static final int STATE_SCANNING_TOKEN = 3;
+ private static final int STATE_LOOKING_FOR_COMMA = 4;
+ private static final int STATE_PARSING_ERROR = 5;
+ private static final int STATE_DONE = 6;
+
+ private int m_curPos;
+ private int m_scanStart;
+ private int m_state;
+ private String m_tokens;
+
+
+ TokenParser(
+ String tokens)
+ {
+ m_tokens = tokens;
+ m_curPos = 0;
+ m_scanStart = 0;
+ m_state = STATE_LOOKING_FOR_FIRST_TOKEN;
+ }
+
+ /**
+ * This function parses the next token from the tokens string and returns
+ * it as a string. If there are no more tokens a null reference is returned.
+ *
+ * @return the parsed token or a null reference if there are no more
+ * tokens
+ *
+ * @exception SASLException if an error occurs while parsing
+ */
+ String parseToken() throws SaslException
+ {
+ char currChar;
+ String token = null;
+
+
+ if (m_state == STATE_DONE)
+ return null;
+
+ while (m_curPos < m_tokens.length() && (token == null))
+ {
+ currChar = m_tokens.charAt(m_curPos);
+ switch (m_state)
+ {
+ case STATE_LOOKING_FOR_FIRST_TOKEN:
+ case STATE_LOOKING_FOR_TOKEN:
+ if (isWhiteSpace(currChar))
+ {
+ break;
+ }
+ else if (isValidTokenChar(currChar))
+ {
+ m_scanStart = m_curPos;
+ m_state = STATE_SCANNING_TOKEN;
+ }
+ else
+ {
+ m_state = STATE_PARSING_ERROR;
+ throw new SaslException("Invalid token character at position " + m_curPos);
+ }
+ break;
+
+ case STATE_SCANNING_TOKEN:
+ if (isValidTokenChar(currChar))
+ {
+ break;
+ }
+ else if (isWhiteSpace(currChar))
+ {
+ token = m_tokens.substring(m_scanStart, m_curPos);
+ m_state = STATE_LOOKING_FOR_COMMA;
+ }
+ else if (',' == currChar)
+ {
+ token = m_tokens.substring(m_scanStart, m_curPos);
+ m_state = STATE_LOOKING_FOR_TOKEN;
+ }
+ else
+ {
+ m_state = STATE_PARSING_ERROR;
+ throw new SaslException("Invalid token character at position " + m_curPos);
+ }
+ break;
+
+
+ case STATE_LOOKING_FOR_COMMA:
+ if (isWhiteSpace(currChar))
+ break;
+ else if (currChar == ',')
+ m_state = STATE_LOOKING_FOR_TOKEN;
+ else
+ {
+ m_state = STATE_PARSING_ERROR;
+ throw new SaslException("Expected a comma, found '" +
+ currChar + "' at postion " +
+ m_curPos);
+ }
+ break;
+ }
+ m_curPos++;
+ } /* end while loop */
+
+ if (token == null)
+ { /* check the ending state */
+ switch (m_state)
+ {
+ case STATE_SCANNING_TOKEN:
+ token = m_tokens.substring(m_scanStart);
+ m_state = STATE_DONE;
+ break;
+
+ case STATE_LOOKING_FOR_FIRST_TOKEN:
+ case STATE_LOOKING_FOR_COMMA:
+ break;
+
+ case STATE_LOOKING_FOR_TOKEN:
+ throw new SaslException("Trialing comma");
+ }
+ }
+
+ return token;
+ }
+
+ /**
+ * This function returns TRUE if the character is a valid token character.
+ *
+ * token = 1*<any CHAR except CTLs or separators>
+ *
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ *
+ * CTL = <any US-ASCII control character
+ * (octets 0 - 31) and DEL (127)>
+ *
+ * CHAR = <any US-ASCII character (octets 0 - 127)>
+ *
+ * @param c character to be validated
+ *
+ * @return True if character is valid Token character else it returns
+ * false
+ */
+ boolean isValidTokenChar(
+ char c)
+ {
+ if ( ( (c >= '\u0000') && (c <='\u0020') ) ||
+ ( (c >= '\u003a') && (c <= '\u0040') ) ||
+ ( (c >= '\u005b') && (c <= '\u005d') ) ||
+ ('\u002c' == c) ||
+ ('\u0025' == c) ||
+ ('\u0028' == c) ||
+ ('\u0029' == c) ||
+ ('\u007b' == c) ||
+ ('\u007d' == c) ||
+ ('\u007f' == c) )
+ return false;
+
+ return true;
+ }
+
+ /**
+ * This function returns TRUE if the character is linear white space (LWS).
+ * LWS = [CRLF] 1*( SP | HT )
+ *
+ * @param c character to be validated
+ *
+ * @return True if character is liner whitespace else it returns false
+ */
+ boolean isWhiteSpace(
+ char c)
+ {
+ if ( ('\t' == c) || // HORIZONTAL TABULATION.
+ ('\n' == c) || // LINE FEED.
+ ('\r' == c) || // CARRIAGE RETURN.
+ ('\u0020' == c) )
+ return true;
+
+ return false;
+ }
+
+}
+