| /* |
| * 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 |
| * (<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. |
| * |
| * @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 |
| * |
| * @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. |
| * |
| * @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. |
| */ |
| 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 |
| */ |
| 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. |
| */ |
| 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. |
| * |
| * @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 |
| * @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" } |
| */ |
| 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. |
| */ |
| 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; |
| } |
| } |
| } |