/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.security.sasl.digest;
import java.security.NoSuchAlgorithmException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.StringTokenizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.logging.Level;
import javax.security.sasl.*;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
An implementation of the DIGEST-MD5
(RFC 2831) SASL
(RFC 2222) mechanism.
The DIGEST-MD5 SASL mechanism specifies two modes of authentication.
- Initial Authentication
- Subsequent Authentication - optional, (currently unsupported)
Required callbacks:
- RealmChoiceCallback
shows user list of realms server has offered; handler must choose one
from list
- RealmCallback
shows user the only realm server has offered or none; handler must
enter realm to use
- NameCallback
handler must enter username to use for authentication
- PasswordCallback
handler must enter password for username to use for authentication
Environment properties that affect behavior of implementation:
javax.security.sasl.qop
quality of protection; list of auth, auth-int, auth-conf; default is "auth"
javax.security.sasl.strength
auth-conf strength; list of high, medium, low; default is highest
available on platform ["high,medium,low"].
high means des3 or rc4 (128); medium des or rc4-56; low is rc4-40;
choice of cipher depends on its availablility on platform
javax.security.sasl.maxbuf
max receive buffer size; default is 65536
javax.security.sasl.sendmaxbuffer
max send buffer size; default is 65536; (min with server max recv size)
com.sun.security.sasl.digest.cipher
name a specific cipher to use; setting must be compatible with the
setting of the javax.security.sasl.strength property.
Author: Jonathan Bruce, Rosanna Lee See Also: - RFC 2222
- Simple Authentication and Security Layer (SASL)
- RFC 2831
- Using Digest Authentication as a SASL Mechanism
- Java(TM)
* Cryptography Extension 1.2.1 (JCE)
- Java(TM)
* Authentication and Authorization Service (JAAS)
/**
* An implementation of the DIGEST-MD5
* (<a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>) SASL
* (<a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>) mechanism.
*
* The DIGEST-MD5 SASL mechanism specifies two modes of authentication.
* - Initial Authentication
* - Subsequent Authentication - optional, (currently unsupported)
*
* Required callbacks:
* - RealmChoiceCallback
* shows user list of realms server has offered; handler must choose one
* from list
* - RealmCallback
* shows user the only realm server has offered or none; handler must
* enter realm to use
* - NameCallback
* handler must enter username to use for authentication
* - PasswordCallback
* handler must enter password for username to use for authentication
*
* Environment properties that affect behavior of implementation:
*
* javax.security.sasl.qop
* quality of protection; list of auth, auth-int, auth-conf; default is "auth"
* javax.security.sasl.strength
* auth-conf strength; list of high, medium, low; default is highest
* available on platform ["high,medium,low"].
* high means des3 or rc4 (128); medium des or rc4-56; low is rc4-40;
* choice of cipher depends on its availablility on platform
* javax.security.sasl.maxbuf
* max receive buffer size; default is 65536
* javax.security.sasl.sendmaxbuffer
* max send buffer size; default is 65536; (min with server max recv size)
*
* com.sun.security.sasl.digest.cipher
* name a specific cipher to use; setting must be compatible with the
* setting of the javax.security.sasl.strength property.
*
* @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>
* - Simple Authentication and Security Layer (SASL)
* @see <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>
* - Using Digest Authentication as a SASL Mechanism
* @see <a href="http://java.sun.com/products/jce">Java(TM)
* Cryptography Extension 1.2.1 (JCE)</a>
* @see <a href="http://java.sun.com/products/jaas">Java(TM)
* Authentication and Authorization Service (JAAS)</a>
*
* @author Jonathan Bruce
* @author Rosanna Lee
*/
final class DigestMD5Client extends DigestMD5Base implements SaslClient {
private static final String MY_CLASS_NAME = DigestMD5Client.class.getName();
// Property for specifying cipher explicitly
private static final String CIPHER_PROPERTY =
"com.sun.security.sasl.digest.cipher";
/* Directives encountered in challenges sent by the server. */
private static final String[] DIRECTIVE_KEY = {
"realm", // >= 0 times
"qop", // atmost once; default is "auth"
"algorithm", // exactly once
"nonce", // exactly once
"maxbuf", // atmost once; default is 65536
"charset", // atmost once; default is ISO 8859-1
"cipher", // exactly once if qop is "auth-conf"
"rspauth", // exactly once in 2nd challenge
"stale", // atmost once for in subsequent auth (not supported)
};
/* Indices into DIRECTIVE_KEY */
private static final int REALM = 0;
private static final int QOP = 1;
private static final int ALGORITHM = 2;
private static final int NONCE = 3;
private static final int MAXBUF = 4;
private static final int CHARSET = 5;
private static final int CIPHER = 6;
private static final int RESPONSE_AUTH = 7;
private static final int STALE = 8;
private int nonceCount; // number of times nonce has been used/seen
/* User-supplied/generated information */
private String specifiedCipher; // cipher explicitly requested by user
private byte[] cnonce; // client generated nonce
private String username;
private char[] passwd;
private byte[] authzidBytes; // byte repr of authzid
Constructor for DIGEST-MD5 mechanism.
Params: - authzid – A non-null String representing the principal
for which authorization is being granted..
- digestURI – A non-null String representing detailing the
combined protocol and host being used for authentication.
- props – The possibly null properties to be used by the SASL
mechanism to configure the authentication exchange.
- cbh – The non-null CallbackHanlder object for callbacks
Throws: - SaslException – if no authentication ID or password is supplied
/**
* Constructor for DIGEST-MD5 mechanism.
*
* @param authzid A non-null String representing the principal
* for which authorization is being granted..
* @param digestURI A non-null String representing detailing the
* combined protocol and host being used for authentication.
* @param props The possibly null properties to be used by the SASL
* mechanism to configure the authentication exchange.
* @param cbh The non-null CallbackHanlder object for callbacks
* @throws SaslException if no authentication ID or password is supplied
*/
DigestMD5Client(String authzid, String protocol, String serverName,
Map<String, ?> props, CallbackHandler cbh) throws SaslException {
super(props, MY_CLASS_NAME, 2, protocol + "/" + serverName, cbh);
// authzID can only be encoded in UTF8 - RFC 2222
if (authzid != null) {
this.authzid = authzid;
try {
authzidBytes = authzid.getBytes("UTF8");
} catch (UnsupportedEncodingException e) {
throw new SaslException(
"DIGEST-MD5: Error encoding authzid value into UTF-8", e);
}
}
if (props != null) {
specifiedCipher = (String)props.get(CIPHER_PROPERTY);
logger.log(Level.FINE, "DIGEST60:Explicitly specified cipher: {0}",
specifiedCipher);
}
}
DIGEST-MD5 has no initial response
Returns: false
/**
* DIGEST-MD5 has no initial response
*
* @return false
*/
public boolean hasInitialResponse() {
return false;
}
Process the challenge data.
The server sends a digest-challenge which the client must reply to
in a digest-response. When the authentication is complete, the
completed field is set to true.
Params: - challengeData – A non-null byte array containing the challenge
data from the server.
Throws: - SaslException – If the platform does not have MD5 digest support
or if the server sends an invalid challenge.
Returns: A possibly null byte array containing the response to
be sent to the server.
/**
* Process the challenge data.
*
* The server sends a digest-challenge which the client must reply to
* in a digest-response. When the authentication is complete, the
* completed field is set to true.
*
* @param challengeData A non-null byte array containing the challenge
* data from the server.
* @return A possibly null byte array containing the response to
* be sent to the server.
*
* @throws SaslException If the platform does not have MD5 digest support
* or if the server sends an invalid challenge.
*/
public byte[] evaluateChallenge(byte[] challengeData) throws SaslException {
if (challengeData.length > MAX_CHALLENGE_LENGTH) {
throw new SaslException(
"DIGEST-MD5: Invalid digest-challenge length. Got: " +
challengeData.length + " Expected < " + MAX_CHALLENGE_LENGTH);
}
/* Extract and process digest-challenge */
byte[][] challengeVal;
switch (step) {
case 2:
/* Process server's first challenge (from Step 1) */
/* Get realm, qop, maxbuf, charset, algorithm, cipher, nonce
directives */
List<byte[]> realmChoices = new ArrayList<byte[]>(3);
challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY,
realmChoices, REALM);
try {
processChallenge(challengeVal, realmChoices);
checkQopSupport(challengeVal[QOP], challengeVal[CIPHER]);
++step;
return generateClientResponse(challengeVal[CHARSET]);
} catch (SaslException e) {
step = 0;
clearPassword();
throw e; // rethrow
} catch (IOException e) {
step = 0;
clearPassword();
throw new SaslException("DIGEST-MD5: Error generating " +
"digest response-value", e);
}
case 3:
try {
/* Process server's step 3 (server response to digest response) */
/* Get rspauth directive */
challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY,
null, REALM);
validateResponseValue(challengeVal[RESPONSE_AUTH]);
/* Initialize SecurityCtx implementation */
if (integrity && privacy) {
secCtx = new DigestPrivacy(true /* client */);
} else if (integrity) {
secCtx = new DigestIntegrity(true /* client */);
}
return null; // Mechanism has completed.
} finally {
clearPassword();
step = 0; // Set to invalid state
completed = true;
}
default:
// No other possible state
throw new SaslException("DIGEST-MD5: Client at illegal state");
}
}
Record information from the challengeVal array into variables/fields.
Check directive values that are multi-valued and ensure that mandatory
directives not missing from the digest-challenge.
Throws: - SaslException – if a sasl is a the mechanism cannot
correcly handle a callbacks or if a violation in the
digest challenge format is detected.
/**
* Record information from the challengeVal array into variables/fields.
* Check directive values that are multi-valued and ensure that mandatory
* directives not missing from the digest-challenge.
*
* @throws SaslException if a sasl is a the mechanism cannot
* correcly handle a callbacks or if a violation in the
* digest challenge format is detected.
*/
private void processChallenge(byte[][] challengeVal, List<byte[]> realmChoices)
throws SaslException, UnsupportedEncodingException {
/* CHARSET: optional atmost once */
if (challengeVal[CHARSET] != null) {
if (!"utf-8".equals(new String(challengeVal[CHARSET], encoding))) {
throw new SaslException("DIGEST-MD5: digest-challenge format " +
"violation. Unrecognised charset value: " +
new String(challengeVal[CHARSET]));
} else {
encoding = "UTF8";
useUTF8 = true;
}
}
/* ALGORITHM: required exactly once */
if (challengeVal[ALGORITHM] == null) {
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation: algorithm directive missing");
} else if (!"md5-sess".equals(new String(challengeVal[ALGORITHM], encoding))) {
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation. Invalid value for 'algorithm' directive: " +
challengeVal[ALGORITHM]);
}
/* NONCE: required exactly once */
if (challengeVal[NONCE] == null) {
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation: nonce directive missing");
} else {
nonce = challengeVal[NONCE];
}
try {
/* REALM: optional, if multiple, stored in realmChoices */
String[] realmTokens = null;
if (challengeVal[REALM] != null) {
if (realmChoices == null || realmChoices.size() <= 1) {
// Only one realm specified
negotiatedRealm = new String(challengeVal[REALM], encoding);
} else {
realmTokens = new String[realmChoices.size()];
for (int i = 0; i < realmTokens.length; i++) {
realmTokens[i] =
new String(realmChoices.get(i), encoding);
}
}
}
NameCallback ncb = authzid == null ?
new NameCallback("DIGEST-MD5 authentication ID: ") :
new NameCallback("DIGEST-MD5 authentication ID: ", authzid);
PasswordCallback pcb =
new PasswordCallback("DIGEST-MD5 password: ", false);
if (realmTokens == null) {
// Server specified <= 1 realm
// If 0, RFC 2831: the client SHOULD solicit a realm from the user.
RealmCallback tcb =
(negotiatedRealm == null? new RealmCallback("DIGEST-MD5 realm: ") :
new RealmCallback("DIGEST-MD5 realm: ", negotiatedRealm));
cbh.handle(new Callback[] {tcb, ncb, pcb});
/* Acquire realm from RealmCallback */
negotiatedRealm = tcb.getText();
if (negotiatedRealm == null) {
negotiatedRealm = "";
}
} else {
RealmChoiceCallback ccb = new RealmChoiceCallback(
"DIGEST-MD5 realm: ",
realmTokens,
0, false);
cbh.handle(new Callback[] {ccb, ncb, pcb});
// Acquire realm from RealmChoiceCallback
int[] selected = ccb.getSelectedIndexes();
if (selected == null
|| selected[0] < 0
|| selected[0] >= realmTokens.length) {
throw new SaslException("DIGEST-MD5: Invalid realm chosen");
}
negotiatedRealm = realmTokens[selected[0]];
}
passwd = pcb.getPassword();
pcb.clearPassword();
username = ncb.getName();
} catch (SaslException se) {
throw se;
} catch (UnsupportedCallbackException e) {
throw new SaslException("DIGEST-MD5: Cannot perform callback to " +
"acquire realm, authentication ID or password", e);
} catch (IOException e) {
throw new SaslException(
"DIGEST-MD5: Error acquiring realm, authentication ID or password", e);
}
if (username == null || passwd == null) {
throw new SaslException(
"DIGEST-MD5: authentication ID and password must be specified");
}
/* MAXBUF: optional atmost once */
int srvMaxBufSize =
(challengeVal[MAXBUF] == null) ? DEFAULT_MAXBUF
: Integer.parseInt(new String(challengeVal[MAXBUF], encoding));
sendMaxBufSize =
(sendMaxBufSize == 0) ? srvMaxBufSize
: Math.min(sendMaxBufSize, srvMaxBufSize);
}
Parses the 'qop' directive. If 'auth-conf' is specified by
the client and offered as a QOP option by the server, then a check
is client-side supported ciphers is performed.
Throws: - IOException –
/**
* Parses the 'qop' directive. If 'auth-conf' is specified by
* the client and offered as a QOP option by the server, then a check
* is client-side supported ciphers is performed.
*
* @throws IOException
*/
private void checkQopSupport(byte[] qopInChallenge, byte[] ciphersInChallenge)
throws IOException {
/* QOP: optional; if multiple, merged earlier */
String qopOptions;
if (qopInChallenge == null) {
qopOptions = "auth";
} else {
qopOptions = new String(qopInChallenge, encoding);
}
// process
String[] serverQopTokens = new String[3];
byte[] serverQop = parseQop(qopOptions, serverQopTokens,
true /* ignore unrecognized tokens */);
byte serverAllQop = combineMasks(serverQop);
switch (findPreferredMask(serverAllQop, qop)) {
case 0:
throw new SaslException("DIGEST-MD5: No common protection " +
"layer between client and server");
case NO_PROTECTION:
negotiatedQop = "auth";
// buffer sizes not applicable
break;
case INTEGRITY_ONLY_PROTECTION:
negotiatedQop = "auth-int";
integrity = true;
rawSendSize = sendMaxBufSize - 16;
break;
case PRIVACY_PROTECTION:
negotiatedQop = "auth-conf";
privacy = integrity = true;
rawSendSize = sendMaxBufSize - 26;
checkStrengthSupport(ciphersInChallenge);
break;
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "DIGEST61:Raw send size: {0}",
rawSendSize);
}
}
Processes the 'cipher' digest-challenge directive. This allows the
mechanism to check for client-side support against the list of
supported ciphers send by the server. If no match is found,
the mechanism aborts.
Throws: - SaslException – If an error is encountered in processing
the cipher digest-challenge directive or if no client-side
support is found.
/**
* Processes the 'cipher' digest-challenge directive. This allows the
* mechanism to check for client-side support against the list of
* supported ciphers send by the server. If no match is found,
* the mechanism aborts.
*
* @throws SaslException If an error is encountered in processing
* the cipher digest-challenge directive or if no client-side
* support is found.
*/
private void checkStrengthSupport(byte[] ciphersInChallenge)
throws IOException {
/* CIPHER: required exactly once if qop=auth-conf */
if (ciphersInChallenge == null) {
throw new SaslException("DIGEST-MD5: server did not specify " +
"cipher to use for 'auth-conf'");
}
// First determine ciphers that server supports
String cipherOptions = new String(ciphersInChallenge, encoding);
StringTokenizer parser = new StringTokenizer(cipherOptions, ", \t\n");
int tokenCount = parser.countTokens();
String token = null;
byte[] serverCiphers = { UNSET,
UNSET,
UNSET,
UNSET,
UNSET };
String[] serverCipherStrs = new String[serverCiphers.length];
// Parse ciphers in challenge; mark each that server supports
for (int i = 0; i < tokenCount; i++) {
token = parser.nextToken();
for (int j = 0; j < CIPHER_TOKENS.length; j++) {
if (token.equals(CIPHER_TOKENS[j])) {
serverCiphers[j] |= CIPHER_MASKS[j];
serverCipherStrs[j] = token; // keep for replay to server
logger.log(Level.FINE, "DIGEST62:Server supports {0}", token);
}
}
}
// Determine which ciphers are available on client
byte[] clntCiphers = getPlatformCiphers();
// Take intersection of server and client supported ciphers
byte inter = 0;
for (int i = 0; i < serverCiphers.length; i++) {
serverCiphers[i] &= clntCiphers[i];
inter |= serverCiphers[i];
}
if (inter == UNSET) {
throw new SaslException(
"DIGEST-MD5: Client supports none of these cipher suites: " +
cipherOptions);
}
// now have a clear picture of user / client; client / server
// cipher options. Leverage strength array against what is
// supported to choose a cipher.
negotiatedCipher = findCipherAndStrength(serverCiphers, serverCipherStrs);
if (negotiatedCipher == null) {
throw new SaslException("DIGEST-MD5: Unable to negotiate " +
"a strength level for 'auth-conf'");
}
logger.log(Level.FINE, "DIGEST63:Cipher suite: {0}", negotiatedCipher);
}
Steps through the ordered 'strength' array, and compares it with
the 'supportedCiphers' array. The cipher returned represents
the best possible cipher based on the strength preference and the
available ciphers on both the server and client environments.
Params: - tokens – The array of cipher tokens sent by server
Returns: The agreed cipher.
/**
* Steps through the ordered 'strength' array, and compares it with
* the 'supportedCiphers' array. The cipher returned represents
* the best possible cipher based on the strength preference and the
* available ciphers on both the server and client environments.
*
* @param tokens The array of cipher tokens sent by server
* @return The agreed cipher.
*/
private String findCipherAndStrength(byte[] supportedCiphers,
String[] tokens) {
byte s;
for (int i = 0; i < strength.length; i++) {
if ((s=strength[i]) != 0) {
for (int j = 0; j < supportedCiphers.length; j++) {
// If user explicitly requested cipher, then it
// must be the one we choose
if (s == supportedCiphers[j] &&
(specifiedCipher == null ||
specifiedCipher.equals(tokens[j]))) {
switch (s) {
case HIGH_STRENGTH:
negotiatedStrength = "high";
break;
case MEDIUM_STRENGTH:
negotiatedStrength = "medium";
break;
case LOW_STRENGTH:
negotiatedStrength = "low";
break;
}
return tokens[j];
}
}
}
}
return null; // none found
}
Returns digest-response suitable for an initial authentication.
The following are qdstr-val (quoted string values) as per RFC 2831,
which means that any embedded quotes must be escaped.
realm-value
nonce-value
username-value
cnonce-value
authzid-value
Throws: - SaslException – if there is an error generating the
response value or the cnonce value.
Returns: digest-response
in a byte array
/**
* Returns digest-response suitable for an initial authentication.
*
* The following are qdstr-val (quoted string values) as per RFC 2831,
* which means that any embedded quotes must be escaped.
* realm-value
* nonce-value
* username-value
* cnonce-value
* authzid-value
* @return {@code digest-response} in a byte array
* @throws SaslException if there is an error generating the
* response value or the cnonce value.
*/
private byte[] generateClientResponse(byte[] charset) throws IOException {
ByteArrayOutputStream digestResp = new ByteArrayOutputStream();
if (useUTF8) {
digestResp.write("charset=".getBytes(encoding));
digestResp.write(charset);
digestResp.write(',');
}
digestResp.write(("username=\"" +
quotedStringValue(username) + "\",").getBytes(encoding));
if (negotiatedRealm.length() > 0) {
digestResp.write(("realm=\"" +
quotedStringValue(negotiatedRealm) + "\",").getBytes(encoding));
}
digestResp.write("nonce=\"".getBytes(encoding));
writeQuotedStringValue(digestResp, nonce);
digestResp.write('"');
digestResp.write(',');
nonceCount = getNonceCount(nonce);
digestResp.write(("nc=" +
nonceCountToHex(nonceCount) + ",").getBytes(encoding));
cnonce = generateNonce();
digestResp.write("cnonce=\"".getBytes(encoding));
writeQuotedStringValue(digestResp, cnonce);
digestResp.write("\",".getBytes(encoding));
digestResp.write(("digest-uri=\"" + digestUri + "\",").getBytes(encoding));
digestResp.write("maxbuf=".getBytes(encoding));
digestResp.write(String.valueOf(recvMaxBufSize).getBytes(encoding));
digestResp.write(',');
try {
digestResp.write("response=".getBytes(encoding));
digestResp.write(generateResponseValue("AUTHENTICATE",
digestUri, negotiatedQop, username,
negotiatedRealm, passwd, nonce, cnonce,
nonceCount, authzidBytes));
digestResp.write(',');
} catch (Exception e) {
throw new SaslException(
"DIGEST-MD5: Error generating response value", e);
}
digestResp.write(("qop=" + negotiatedQop).getBytes(encoding));
if (negotiatedCipher != null) {
digestResp.write((",cipher=\"" + negotiatedCipher + "\"").getBytes(encoding));
}
if (authzidBytes != null) {
digestResp.write(",authzid=\"".getBytes(encoding));
writeQuotedStringValue(digestResp, authzidBytes);
digestResp.write("\"".getBytes(encoding));
}
if (digestResp.size() > MAX_RESPONSE_LENGTH) {
throw new SaslException ("DIGEST-MD5: digest-response size too " +
"large. Length: " + digestResp.size());
}
return digestResp.toByteArray();
}
From RFC 2831, Section 2.1.3: Step Three
[Server] sends a message formatted as follows:
response-auth = "rspauth" "=" response-value
where response-value is calculated as above, using the values sent in
step two, except that if qop is "auth", then A2 is
A2 = { ":", digest-uri-value }
And if qop is "auth-int" or "auth-conf" then A2 is
A2 = { ":", digest-uri-value, ":00000000000000000000000000000000" }
/**
* From RFC 2831, Section 2.1.3: Step Three
* [Server] sends a message formatted as follows:
* response-auth = "rspauth" "=" response-value
* where response-value is calculated as above, using the values sent in
* step two, except that if qop is "auth", then A2 is
*
* A2 = { ":", digest-uri-value }
*
* And if qop is "auth-int" or "auth-conf" then A2 is
*
* A2 = { ":", digest-uri-value, ":00000000000000000000000000000000" }
*/
private void validateResponseValue(byte[] fromServer) throws SaslException {
if (fromServer == null) {
throw new SaslException("DIGEST-MD5: Authenication failed. " +
"Expecting 'rspauth' authentication success message");
}
try {
byte[] expected = generateResponseValue("",
digestUri, negotiatedQop, username, negotiatedRealm,
passwd, nonce, cnonce, nonceCount, authzidBytes);
if (!Arrays.equals(expected, fromServer)) {
/* Server's rspauth value does not match */
throw new SaslException(
"Server's rspauth value does not match what client expects");
}
} catch (NoSuchAlgorithmException e) {
throw new SaslException(
"Problem generating response value for verification", e);
} catch (IOException e) {
throw new SaslException(
"Problem generating response value for verification", e);
}
}
Returns the number of requests (including current request)
that the client has sent in response to nonceValue.
This is 1 the first time nonceValue is seen.
We don't cache nonce values seen, and we don't support subsequent
authentication, so the value is always 1.
/**
* Returns the number of requests (including current request)
* that the client has sent in response to nonceValue.
* This is 1 the first time nonceValue is seen.
*
* We don't cache nonce values seen, and we don't support subsequent
* authentication, so the value is always 1.
*/
private static int getNonceCount(byte[] nonceValue) {
return 1;
}
private void clearPassword() {
if (passwd != null) {
for (int i = 0; i < passwd.length; i++) {
passwd[i] = 0;
}
passwd = null;
}
}
}