1/*
2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.security.sasl.digest;
27
28import java.security.NoSuchAlgorithmException;
29import java.io.ByteArrayOutputStream;
30import java.io.IOException;
31import java.io.UnsupportedEncodingException;
32import java.util.StringTokenizer;
33import java.util.ArrayList;
34import java.util.List;
35import java.util.Map;
36import java.util.Arrays;
37
38import java.util.logging.Level;
39
40import javax.security.sasl.*;
41import javax.security.auth.callback.CallbackHandler;
42import javax.security.auth.callback.PasswordCallback;
43import javax.security.auth.callback.NameCallback;
44import javax.security.auth.callback.Callback;
45import javax.security.auth.callback.UnsupportedCallbackException;
46
47/**
48  * An implementation of the DIGEST-MD5
49  * (<a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>) SASL
50  * (<a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>) mechanism.
51  *
52  * The DIGEST-MD5 SASL mechanism specifies two modes of authentication.
53  * - Initial Authentication
54  * - Subsequent Authentication - optional, (currently unsupported)
55  *
56  * Required callbacks:
57  * - RealmChoiceCallback
58  *    shows user list of realms server has offered; handler must choose one
59  *    from list
60  * - RealmCallback
61  *    shows user the only realm server has offered or none; handler must
62  *    enter realm to use
63  * - NameCallback
64  *    handler must enter username to use for authentication
65  * - PasswordCallback
66  *    handler must enter password for username to use for authentication
67  *
68  * Environment properties that affect behavior of implementation:
69  *
70  * javax.security.sasl.qop
71  *    quality of protection; list of auth, auth-int, auth-conf; default is "auth"
72  * javax.security.sasl.strength
73  *    auth-conf strength; list of high, medium, low; default is highest
74  *    available on platform ["high,medium,low"].
75  *    high means des3 or rc4 (128); medium des or rc4-56; low is rc4-40;
76  *    choice of cipher depends on its availablility on platform
77  * javax.security.sasl.maxbuf
78  *    max receive buffer size; default is 65536
79  * javax.security.sasl.sendmaxbuffer
80  *    max send buffer size; default is 65536; (min with server max recv size)
81  *
82  * com.sun.security.sasl.digest.cipher
83  *    name a specific cipher to use; setting must be compatible with the
84  *    setting of the javax.security.sasl.strength property.
85  *
86  * @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>
87  * - Simple Authentication and Security Layer (SASL)
88  * @see <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>
89  * - Using Digest Authentication as a SASL Mechanism
90  * @see <a href="http://java.sun.com/products/jce">Java(TM)
91  * Cryptography Extension 1.2.1 (JCE)</a>
92  * @see <a href="http://java.sun.com/products/jaas">Java(TM)
93  * Authentication and Authorization Service (JAAS)</a>
94  *
95  * @author Jonathan Bruce
96  * @author Rosanna Lee
97  */
98final class DigestMD5Client extends DigestMD5Base implements SaslClient {
99    private static final String MY_CLASS_NAME = DigestMD5Client.class.getName();
100
101    // Property for specifying cipher explicitly
102    private static final String CIPHER_PROPERTY =
103        "com.sun.security.sasl.digest.cipher";
104
105    /* Directives encountered in challenges sent by the server. */
106    private static final String[] DIRECTIVE_KEY = {
107        "realm",      // >= 0 times
108        "qop",        // atmost once; default is "auth"
109        "algorithm",  // exactly once
110        "nonce",      // exactly once
111        "maxbuf",     // atmost once; default is 65536
112        "charset",    // atmost once; default is ISO 8859-1
113        "cipher",     // exactly once if qop is "auth-conf"
114        "rspauth",    // exactly once in 2nd challenge
115        "stale",      // atmost once for in subsequent auth (not supported)
116    };
117
118    /* Indices into DIRECTIVE_KEY */
119    private static final int REALM = 0;
120    private static final int QOP = 1;
121    private static final int ALGORITHM = 2;
122    private static final int NONCE = 3;
123    private static final int MAXBUF = 4;
124    private static final int CHARSET = 5;
125    private static final int CIPHER = 6;
126    private static final int RESPONSE_AUTH = 7;
127    private static final int STALE = 8;
128
129    private int nonceCount; // number of times nonce has been used/seen
130
131    /* User-supplied/generated information */
132    private String specifiedCipher;  // cipher explicitly requested by user
133    private byte[] cnonce;        // client generated nonce
134    private String username;
135    private char[] passwd;
136    private byte[] authzidBytes;  // byte repr of authzid
137
138    /**
139      * Constructor for DIGEST-MD5 mechanism.
140      *
141      * @param authzid A non-null String representing the principal
142      * for which authorization is being granted..
143      * @param digestURI A non-null String representing detailing the
144      * combined protocol and host being used for authentication.
145      * @param props The possibly null properties to be used by the SASL
146      * mechanism to configure the authentication exchange.
147      * @param cbh The non-null CallbackHanlder object for callbacks
148      * @throws SaslException if no authentication ID or password is supplied
149      */
150    DigestMD5Client(String authzid, String protocol, String serverName,
151        Map<String, ?> props, CallbackHandler cbh) throws SaslException {
152
153        super(props, MY_CLASS_NAME, 2, protocol + "/" + serverName, cbh);
154
155        // authzID can only be encoded in UTF8 - RFC 2222
156        if (authzid != null) {
157            this.authzid = authzid;
158            try {
159                authzidBytes = authzid.getBytes("UTF8");
160
161            } catch (UnsupportedEncodingException e) {
162                throw new SaslException(
163                    "DIGEST-MD5: Error encoding authzid value into UTF-8", e);
164            }
165        }
166
167        if (props != null) {
168            specifiedCipher = (String)props.get(CIPHER_PROPERTY);
169
170            logger.log(Level.FINE, "DIGEST60:Explicitly specified cipher: {0}",
171                specifiedCipher);
172        }
173   }
174
175    /**
176     * DIGEST-MD5 has no initial response
177     *
178     * @return false
179     */
180    public boolean hasInitialResponse() {
181        return false;
182    }
183
184    /**
185     * Process the challenge data.
186     *
187     * The server sends a digest-challenge which the client must reply to
188     * in a digest-response. When the authentication is complete, the
189     * completed field is set to true.
190     *
191     * @param challengeData A non-null byte array containing the challenge
192     * data from the server.
193     * @return A possibly null byte array containing the response to
194     * be sent to the server.
195     *
196     * @throws SaslException If the platform does not have MD5 digest support
197     * or if the server sends an invalid challenge.
198     */
199    public byte[] evaluateChallenge(byte[] challengeData) throws SaslException {
200
201        if (challengeData.length > MAX_CHALLENGE_LENGTH) {
202            throw new SaslException(
203                "DIGEST-MD5: Invalid digest-challenge length. Got:  " +
204                challengeData.length + " Expected < " + MAX_CHALLENGE_LENGTH);
205        }
206
207        /* Extract and process digest-challenge */
208        byte[][] challengeVal;
209
210        switch (step) {
211        case 2:
212            /* Process server's first challenge (from Step 1) */
213            /* Get realm, qop, maxbuf, charset, algorithm, cipher, nonce
214               directives */
215            List<byte[]> realmChoices = new ArrayList<byte[]>(3);
216            challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY,
217                realmChoices, REALM);
218
219            try {
220                processChallenge(challengeVal, realmChoices);
221                checkQopSupport(challengeVal[QOP], challengeVal[CIPHER]);
222                ++step;
223                return generateClientResponse(challengeVal[CHARSET]);
224            } catch (SaslException e) {
225                step = 0;
226                clearPassword();
227                throw e; // rethrow
228            } catch (IOException e) {
229                step = 0;
230                clearPassword();
231                throw new SaslException("DIGEST-MD5: Error generating " +
232                    "digest response-value", e);
233            }
234
235        case 3:
236            try {
237                /* Process server's step 3 (server response to digest response) */
238                /* Get rspauth directive */
239                challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY,
240                    null, REALM);
241                validateResponseValue(challengeVal[RESPONSE_AUTH]);
242
243
244                /* Initialize SecurityCtx implementation */
245                if (integrity && privacy) {
246                    secCtx = new DigestPrivacy(true /* client */);
247                } else if (integrity) {
248                    secCtx = new DigestIntegrity(true /* client */);
249                }
250
251                return null; // Mechanism has completed.
252            } finally {
253                clearPassword();
254                step = 0;  // Set to invalid state
255                completed = true;
256            }
257
258        default:
259            // No other possible state
260            throw new SaslException("DIGEST-MD5: Client at illegal state");
261        }
262    }
263
264
265   /**
266    * Record information from the challengeVal array into variables/fields.
267    * Check directive values that are multi-valued and ensure that mandatory
268    * directives not missing from the digest-challenge.
269    *
270    * @throws SaslException if a sasl is a the mechanism cannot
271    * correcly handle a callbacks or if a violation in the
272    * digest challenge format is detected.
273    */
274    private void processChallenge(byte[][] challengeVal, List<byte[]> realmChoices)
275        throws SaslException, UnsupportedEncodingException {
276
277        /* CHARSET: optional atmost once */
278        if (challengeVal[CHARSET] != null) {
279            if (!"utf-8".equals(new String(challengeVal[CHARSET], encoding))) {
280                throw new SaslException("DIGEST-MD5: digest-challenge format " +
281                    "violation. Unrecognised charset value: " +
282                    new String(challengeVal[CHARSET]));
283            } else {
284                encoding = "UTF8";
285                useUTF8 = true;
286            }
287        }
288
289        /* ALGORITHM: required exactly once */
290        if (challengeVal[ALGORITHM] == null) {
291            throw new SaslException("DIGEST-MD5: Digest-challenge format " +
292                "violation: algorithm directive missing");
293        } else if (!"md5-sess".equals(new String(challengeVal[ALGORITHM], encoding))) {
294            throw new SaslException("DIGEST-MD5: Digest-challenge format " +
295                "violation. Invalid value for 'algorithm' directive: " +
296                challengeVal[ALGORITHM]);
297        }
298
299        /* NONCE: required exactly once */
300        if (challengeVal[NONCE] == null) {
301            throw new SaslException("DIGEST-MD5: Digest-challenge format " +
302                "violation: nonce directive missing");
303        } else {
304            nonce = challengeVal[NONCE];
305        }
306
307        try {
308            /* REALM: optional, if multiple, stored in realmChoices */
309            String[] realmTokens = null;
310
311            if (challengeVal[REALM] != null) {
312                if (realmChoices == null || realmChoices.size() <= 1) {
313                    // Only one realm specified
314                    negotiatedRealm = new String(challengeVal[REALM], encoding);
315                } else {
316                    realmTokens = new String[realmChoices.size()];
317                    for (int i = 0; i < realmTokens.length; i++) {
318                        realmTokens[i] =
319                            new String(realmChoices.get(i), encoding);
320                    }
321                }
322            }
323
324            NameCallback ncb = authzid == null ?
325                new NameCallback("DIGEST-MD5 authentication ID: ") :
326                new NameCallback("DIGEST-MD5 authentication ID: ", authzid);
327            PasswordCallback pcb =
328                new PasswordCallback("DIGEST-MD5 password: ", false);
329
330            if (realmTokens == null) {
331                // Server specified <= 1 realm
332                // If 0, RFC 2831: the client SHOULD solicit a realm from the user.
333                RealmCallback tcb =
334                    (negotiatedRealm == null? new RealmCallback("DIGEST-MD5 realm: ") :
335                        new RealmCallback("DIGEST-MD5 realm: ", negotiatedRealm));
336
337                cbh.handle(new Callback[] {tcb, ncb, pcb});
338
339                /* Acquire realm from RealmCallback */
340                negotiatedRealm = tcb.getText();
341                if (negotiatedRealm == null) {
342                    negotiatedRealm = "";
343                }
344            } else {
345                RealmChoiceCallback ccb = new RealmChoiceCallback(
346                    "DIGEST-MD5 realm: ",
347                    realmTokens,
348                    0, false);
349                cbh.handle(new Callback[] {ccb, ncb, pcb});
350
351                // Acquire realm from RealmChoiceCallback
352                int[] selected = ccb.getSelectedIndexes();
353                if (selected == null
354                        || selected[0] < 0
355                        || selected[0] >= realmTokens.length) {
356                    throw new SaslException("DIGEST-MD5: Invalid realm chosen");
357                }
358                negotiatedRealm = realmTokens[selected[0]];
359            }
360
361            passwd = pcb.getPassword();
362            pcb.clearPassword();
363            username = ncb.getName();
364
365        } catch (SaslException se) {
366            throw se;
367
368        } catch (UnsupportedCallbackException e) {
369            throw new SaslException("DIGEST-MD5: Cannot perform callback to " +
370                "acquire realm, authentication ID or password", e);
371
372        } catch (IOException e) {
373            throw new SaslException(
374                "DIGEST-MD5: Error acquiring realm, authentication ID or password", e);
375        }
376
377        if (username == null || passwd == null) {
378            throw new SaslException(
379                "DIGEST-MD5: authentication ID and password must be specified");
380        }
381
382        /* MAXBUF: optional atmost once */
383        int srvMaxBufSize =
384            (challengeVal[MAXBUF] == null) ? DEFAULT_MAXBUF
385            : Integer.parseInt(new String(challengeVal[MAXBUF], encoding));
386        sendMaxBufSize =
387            (sendMaxBufSize == 0) ? srvMaxBufSize
388            : Math.min(sendMaxBufSize, srvMaxBufSize);
389    }
390
391    /**
392     * Parses the 'qop' directive. If 'auth-conf' is specified by
393     * the client and offered as a QOP option by the server, then a check
394     * is client-side supported ciphers is performed.
395     *
396     * @throws IOException
397     */
398    private void checkQopSupport(byte[] qopInChallenge, byte[] ciphersInChallenge)
399        throws IOException {
400
401        /* QOP: optional; if multiple, merged earlier */
402        String qopOptions;
403
404        if (qopInChallenge == null) {
405            qopOptions = "auth";
406        } else {
407            qopOptions = new String(qopInChallenge, encoding);
408        }
409
410        // process
411        String[] serverQopTokens = new String[3];
412        byte[] serverQop = parseQop(qopOptions, serverQopTokens,
413            true /* ignore unrecognized tokens */);
414        byte serverAllQop = combineMasks(serverQop);
415
416        switch (findPreferredMask(serverAllQop, qop)) {
417        case 0:
418            throw new SaslException("DIGEST-MD5: No common protection " +
419                "layer between client and server");
420
421        case NO_PROTECTION:
422            negotiatedQop = "auth";
423            // buffer sizes not applicable
424            break;
425
426        case INTEGRITY_ONLY_PROTECTION:
427            negotiatedQop = "auth-int";
428            integrity = true;
429            rawSendSize = sendMaxBufSize - 16;
430            break;
431
432        case PRIVACY_PROTECTION:
433            negotiatedQop = "auth-conf";
434            privacy = integrity = true;
435            rawSendSize = sendMaxBufSize - 26;
436            checkStrengthSupport(ciphersInChallenge);
437            break;
438        }
439
440        if (logger.isLoggable(Level.FINE)) {
441            logger.log(Level.FINE, "DIGEST61:Raw send size: {0}",
442                rawSendSize);
443        }
444     }
445
446    /**
447     * Processes the 'cipher' digest-challenge directive. This allows the
448     * mechanism to check for client-side support against the list of
449     * supported ciphers send by the server. If no match is found,
450     * the mechanism aborts.
451     *
452     * @throws SaslException If an error is encountered in processing
453     * the cipher digest-challenge directive or if no client-side
454     * support is found.
455     */
456    private void checkStrengthSupport(byte[] ciphersInChallenge)
457        throws IOException {
458
459        /* CIPHER: required exactly once if qop=auth-conf */
460        if (ciphersInChallenge == null) {
461            throw new SaslException("DIGEST-MD5: server did not specify " +
462                "cipher to use for 'auth-conf'");
463        }
464
465        // First determine ciphers that server supports
466        String cipherOptions = new String(ciphersInChallenge, encoding);
467        StringTokenizer parser = new StringTokenizer(cipherOptions, ", \t\n");
468        int tokenCount = parser.countTokens();
469        String token = null;
470        byte[] serverCiphers = { UNSET,
471                                 UNSET,
472                                 UNSET,
473                                 UNSET,
474                                 UNSET };
475        String[] serverCipherStrs = new String[serverCiphers.length];
476
477        // Parse ciphers in challenge; mark each that server supports
478        for (int i = 0; i < tokenCount; i++) {
479            token = parser.nextToken();
480            for (int j = 0; j < CIPHER_TOKENS.length; j++) {
481                if (token.equals(CIPHER_TOKENS[j])) {
482                    serverCiphers[j] |= CIPHER_MASKS[j];
483                    serverCipherStrs[j] = token; // keep for replay to server
484                    logger.log(Level.FINE, "DIGEST62:Server supports {0}", token);
485                }
486            }
487        }
488
489        // Determine which ciphers are available on client
490        byte[] clntCiphers = getPlatformCiphers();
491
492        // Take intersection of server and client supported ciphers
493        byte inter = 0;
494        for (int i = 0; i < serverCiphers.length; i++) {
495            serverCiphers[i] &= clntCiphers[i];
496            inter |= serverCiphers[i];
497        }
498
499        if (inter == UNSET) {
500            throw new SaslException(
501                "DIGEST-MD5: Client supports none of these cipher suites: " +
502                cipherOptions);
503        }
504
505        // now have a clear picture of user / client; client / server
506        // cipher options. Leverage strength array against what is
507        // supported to choose a cipher.
508        negotiatedCipher = findCipherAndStrength(serverCiphers, serverCipherStrs);
509
510        if (negotiatedCipher == null) {
511            throw new SaslException("DIGEST-MD5: Unable to negotiate " +
512                "a strength level for 'auth-conf'");
513        }
514        logger.log(Level.FINE, "DIGEST63:Cipher suite: {0}", negotiatedCipher);
515    }
516
517    /**
518     * Steps through the ordered 'strength' array, and compares it with
519     * the 'supportedCiphers' array. The cipher returned represents
520     * the best possible cipher based on the strength preference and the
521     * available ciphers on both the server and client environments.
522     *
523     * @param tokens The array of cipher tokens sent by server
524     * @return The agreed cipher.
525     */
526    private String findCipherAndStrength(byte[] supportedCiphers,
527        String[] tokens) {
528        byte s;
529        for (int i = 0; i < strength.length; i++) {
530            if ((s=strength[i]) != 0) {
531                for (int j = 0; j < supportedCiphers.length; j++) {
532
533                    // If user explicitly requested cipher, then it
534                    // must be the one we choose
535
536                    if (s == supportedCiphers[j] &&
537                        (specifiedCipher == null ||
538                            specifiedCipher.equals(tokens[j]))) {
539                        switch (s) {
540                        case HIGH_STRENGTH:
541                            negotiatedStrength = "high";
542                            break;
543                        case MEDIUM_STRENGTH:
544                            negotiatedStrength = "medium";
545                            break;
546                        case LOW_STRENGTH:
547                            negotiatedStrength = "low";
548                            break;
549                        }
550
551                        return tokens[j];
552                    }
553                }
554            }
555        }
556
557        return null;  // none found
558    }
559
560    /**
561     * Returns digest-response suitable for an initial authentication.
562     *
563     * The following are qdstr-val (quoted string values) as per RFC 2831,
564     * which means that any embedded quotes must be escaped.
565     *    realm-value
566     *    nonce-value
567     *    username-value
568     *    cnonce-value
569     *    authzid-value
570     * @return {@code digest-response} in a byte array
571     * @throws SaslException if there is an error generating the
572     * response value or the cnonce value.
573     */
574    private byte[] generateClientResponse(byte[] charset) throws IOException {
575
576        ByteArrayOutputStream digestResp = new ByteArrayOutputStream();
577
578        if (useUTF8) {
579            digestResp.write("charset=".getBytes(encoding));
580            digestResp.write(charset);
581            digestResp.write(',');
582        }
583
584        digestResp.write(("username=\"" +
585            quotedStringValue(username) + "\",").getBytes(encoding));
586
587        if (negotiatedRealm.length() > 0) {
588            digestResp.write(("realm=\"" +
589                quotedStringValue(negotiatedRealm) + "\",").getBytes(encoding));
590        }
591
592        digestResp.write("nonce=\"".getBytes(encoding));
593        writeQuotedStringValue(digestResp, nonce);
594        digestResp.write('"');
595        digestResp.write(',');
596
597        nonceCount = getNonceCount(nonce);
598        digestResp.write(("nc=" +
599            nonceCountToHex(nonceCount) + ",").getBytes(encoding));
600
601        cnonce = generateNonce();
602        digestResp.write("cnonce=\"".getBytes(encoding));
603        writeQuotedStringValue(digestResp, cnonce);
604        digestResp.write("\",".getBytes(encoding));
605        digestResp.write(("digest-uri=\"" + digestUri + "\",").getBytes(encoding));
606
607        digestResp.write("maxbuf=".getBytes(encoding));
608        digestResp.write(String.valueOf(recvMaxBufSize).getBytes(encoding));
609        digestResp.write(',');
610
611        try {
612            digestResp.write("response=".getBytes(encoding));
613            digestResp.write(generateResponseValue("AUTHENTICATE",
614                digestUri, negotiatedQop, username,
615                negotiatedRealm, passwd, nonce, cnonce,
616                nonceCount, authzidBytes));
617            digestResp.write(',');
618        } catch (Exception e) {
619            throw new SaslException(
620                "DIGEST-MD5: Error generating response value", e);
621        }
622
623        digestResp.write(("qop=" + negotiatedQop).getBytes(encoding));
624
625        if (negotiatedCipher != null) {
626            digestResp.write((",cipher=\"" + negotiatedCipher + "\"").getBytes(encoding));
627        }
628
629        if (authzidBytes != null) {
630            digestResp.write(",authzid=\"".getBytes(encoding));
631            writeQuotedStringValue(digestResp, authzidBytes);
632            digestResp.write("\"".getBytes(encoding));
633        }
634
635        if (digestResp.size() > MAX_RESPONSE_LENGTH) {
636            throw new SaslException ("DIGEST-MD5: digest-response size too " +
637                "large. Length: "  + digestResp.size());
638        }
639        return digestResp.toByteArray();
640     }
641
642
643    /**
644     * From RFC 2831, Section 2.1.3: Step Three
645     * [Server] sends a message formatted as follows:
646     *     response-auth = "rspauth" "=" response-value
647     * where response-value is calculated as above, using the values sent in
648     * step two, except that if qop is "auth", then A2 is
649     *
650     *  A2 = { ":", digest-uri-value }
651     *
652     * And if qop is "auth-int" or "auth-conf" then A2 is
653     *
654     *  A2 = { ":", digest-uri-value, ":00000000000000000000000000000000" }
655     */
656    private void validateResponseValue(byte[] fromServer) throws SaslException {
657        if (fromServer == null) {
658            throw new SaslException("DIGEST-MD5: Authenication failed. " +
659                "Expecting 'rspauth' authentication success message");
660        }
661
662        try {
663            byte[] expected = generateResponseValue("",
664                digestUri, negotiatedQop, username, negotiatedRealm,
665                passwd, nonce, cnonce,  nonceCount, authzidBytes);
666            if (!Arrays.equals(expected, fromServer)) {
667                /* Server's rspauth value does not match */
668                throw new SaslException(
669                    "Server's rspauth value does not match what client expects");
670            }
671        } catch (NoSuchAlgorithmException e) {
672            throw new SaslException(
673                "Problem generating response value for verification", e);
674        } catch (IOException e) {
675            throw new SaslException(
676                "Problem generating response value for verification", e);
677        }
678    }
679
680    /**
681     * Returns the number of requests (including current request)
682     * that the client has sent in response to nonceValue.
683     * This is 1 the first time nonceValue is seen.
684     *
685     * We don't cache nonce values seen, and we don't support subsequent
686     * authentication, so the value is always 1.
687     */
688    private static int getNonceCount(byte[] nonceValue) {
689        return 1;
690    }
691
692    private void clearPassword() {
693        if (passwd != null) {
694            for (int i = 0; i < passwd.length; i++) {
695                passwd[i] = 0;
696            }
697            passwd = null;
698        }
699    }
700}
701