/*
 * 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:
/** * 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; } } }