From d7955ce24d294fb2014c59d11fca184471056f44 Mon Sep 17 00:00:00 2001 From: Shuyi Chen Date: Wed, 22 May 2013 14:51:55 -0700 Subject: Add android smack source. Change-Id: I49ce97136c17173c4ae3965c694af6e7bc49897d --- src/com/novell/sasl/client/DigestChallenge.java | 393 ++++++++++ .../novell/sasl/client/DigestMD5SaslClient.java | 820 +++++++++++++++++++++ src/com/novell/sasl/client/DirectiveList.java | 363 +++++++++ src/com/novell/sasl/client/ParsedDirective.java | 56 ++ src/com/novell/sasl/client/ResponseAuth.java | 83 +++ src/com/novell/sasl/client/TokenParser.java | 208 ++++++ 6 files changed, 1923 insertions(+) create mode 100644 src/com/novell/sasl/client/DigestChallenge.java create mode 100644 src/com/novell/sasl/client/DigestMD5SaslClient.java create mode 100644 src/com/novell/sasl/client/DirectiveList.java create mode 100644 src/com/novell/sasl/client/ParsedDirective.java create mode 100644 src/com/novell/sasl/client/ResponseAuth.java create mode 100644 src/com/novell/sasl/client/TokenParser.java (limited to 'src/com/novell/sasl/client') 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> 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 = ""; + + 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* + * + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + * + * CTL = + * + * CHAR = + * + * @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* + * + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + * + * CTL = + * + * CHAR = + * + * @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; + } + +} + -- cgit v1.2.3