| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
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.*;  | 
 | 
 | 
 | 
/**  | 
 | 
  * An implementation of the DIGEST-MD5 server SASL mechanism.  | 
 | 
  * (<a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>)  | 
 | 
  * <p>  | 
 | 
  * The DIGEST-MD5 SASL mechanism specifies two modes of authentication.  | 
 | 
  * <ul><li>Initial Authentication  | 
 | 
  * <li>Subsequent Authentication - optional, (currently not supported)  | 
 | 
  * </ul>  | 
 | 
  *  | 
 | 
  * Required callbacks:  | 
 | 
  * - RealmCallback  | 
 | 
  *      used as key by handler to fetch password  | 
 | 
  * - NameCallback  | 
 | 
  *      used as key by handler to fetch password  | 
 | 
  * - PasswordCallback  | 
 | 
  *      handler must enter password for username/realm supplied  | 
 | 
  * - AuthorizeCallback  | 
 | 
  *      handler must verify that authid/authzids are allowed and set  | 
 | 
  *      authorized ID to be the canonicalized authzid (if applicable).  | 
 | 
  *  | 
 | 
  * Environment properties that affect the implementation:  | 
 | 
  * javax.security.sasl.qop:  | 
 | 
  *    specifies list of qops; default is "auth"; typically, caller should set  | 
 | 
  *    this to "auth, auth-int, auth-conf".  | 
 | 
  * javax.security.sasl.strength  | 
 | 
  *    specifies low/medium/high strength of encryption; default is all available  | 
 | 
  *    ciphers [high,medium,low]; high means des3 or rc4 (128); medium des or  | 
 | 
  *    rc4-56; low is rc4-40.  | 
 | 
  * javax.security.sasl.maxbuf  | 
 | 
  *    specifies max receive buf size; default is 65536  | 
 | 
  * javax.security.sasl.sendmaxbuffer  | 
 | 
  *    specifies max send buf size; default is 65536 (min of this and client's max  | 
 | 
  *    recv size)  | 
 | 
  *  | 
 | 
  * com.sun.security.sasl.digest.utf8:  | 
 | 
  *    "true" means to use UTF-8 charset; "false" to use ISO-8859-1 encoding;  | 
 | 
  *    default is "true".  | 
 | 
  * com.sun.security.sasl.digest.realm:  | 
 | 
  *    space-separated list of realms; default is server name (fqdn parameter)  | 
 | 
  *  | 
 | 
  * @author Rosanna Lee  | 
 | 
  */  | 
 | 
 | 
 | 
final class DigestMD5Server extends DigestMD5Base implements SaslServer { | 
 | 
    private static final String MY_CLASS_NAME = DigestMD5Server.class.getName();  | 
 | 
 | 
 | 
    private static final String UTF8_DIRECTIVE = "charset=utf-8,";  | 
 | 
    private static final String ALGORITHM_DIRECTIVE = "algorithm=md5-sess";  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final int NONCE_COUNT_VALUE = 1;  | 
 | 
 | 
 | 
      | 
 | 
    private static final String UTF8_PROPERTY =  | 
 | 
        "com.sun.security.sasl.digest.utf8";  | 
 | 
 | 
 | 
      | 
 | 
    private static final String REALM_PROPERTY =  | 
 | 
        "com.sun.security.sasl.digest.realm";  | 
 | 
 | 
 | 
      | 
 | 
    private static final String[] DIRECTIVE_KEY = { | 
 | 
        "username",      | 
 | 
        "realm",         | 
 | 
        "nonce",         | 
 | 
        "cnonce",        | 
 | 
        "nonce-count",   | 
 | 
        "qop",           | 
 | 
        "digest-uri",    | 
 | 
        "response",      | 
 | 
        "maxbuf",        | 
 | 
        "charset",       | 
 | 
        "cipher",        | 
 | 
        "authzid",       | 
 | 
        "auth-param",    | 
 | 
    };  | 
 | 
 | 
 | 
      | 
 | 
    private static final int USERNAME = 0;  | 
 | 
    private static final int REALM = 1;  | 
 | 
    private static final int NONCE = 2;  | 
 | 
    private static final int CNONCE = 3;  | 
 | 
    private static final int NONCE_COUNT = 4;  | 
 | 
    private static final int QOP = 5;  | 
 | 
    private static final int DIGEST_URI = 6;  | 
 | 
    private static final int RESPONSE = 7;  | 
 | 
    private static final int MAXBUF = 8;  | 
 | 
    private static final int CHARSET = 9;  | 
 | 
    private static final int CIPHER = 10;  | 
 | 
    private static final int AUTHZID = 11;  | 
 | 
    private static final int AUTH_PARAM = 12;  | 
 | 
 | 
 | 
      | 
 | 
    private String specifiedQops;  | 
 | 
    private byte[] myCiphers;  | 
 | 
    private List<String> serverRealms;  | 
 | 
 | 
 | 
    DigestMD5Server(String protocol, String serverName, Map<String, ?> props,  | 
 | 
            CallbackHandler cbh) throws SaslException { | 
 | 
        super(props, MY_CLASS_NAME, 1,  | 
 | 
                protocol + "/" + (serverName==null?"*":serverName),  | 
 | 
                cbh);  | 
 | 
 | 
 | 
        serverRealms = new ArrayList<String>();  | 
 | 
 | 
 | 
        useUTF8 = true;    | 
 | 
 | 
 | 
        if (props != null) { | 
 | 
            specifiedQops = (String) props.get(Sasl.QOP);  | 
 | 
            if ("false".equals((String) props.get(UTF8_PROPERTY))) { | 
 | 
                useUTF8 = false;  | 
 | 
                logger.log(Level.FINE, "DIGEST80:Server supports ISO-Latin-1");  | 
 | 
            }  | 
 | 
 | 
 | 
            String realms = (String) props.get(REALM_PROPERTY);  | 
 | 
            if (realms != null) { | 
 | 
                StringTokenizer parser = new StringTokenizer(realms, ", \t\n");  | 
 | 
                int tokenCount = parser.countTokens();  | 
 | 
                String token = null;  | 
 | 
                for (int i = 0; i < tokenCount; i++) { | 
 | 
                    token = parser.nextToken();  | 
 | 
                    logger.log(Level.FINE, "DIGEST81:Server supports realm {0}", | 
 | 
                        token);  | 
 | 
                    serverRealms.add(token);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        encoding = (useUTF8 ? "UTF8" : "8859_1");  | 
 | 
 | 
 | 
          | 
 | 
        if (serverRealms.isEmpty()) { | 
 | 
            if (serverName == null) { | 
 | 
                throw new SaslException(  | 
 | 
                        "A realm must be provided in props or serverName");  | 
 | 
            } else { | 
 | 
                serverRealms.add(serverName);  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    public  byte[] evaluateResponse(byte[] response) throws SaslException { | 
 | 
        if (response.length > MAX_RESPONSE_LENGTH) { | 
 | 
            throw new SaslException(  | 
 | 
                "DIGEST-MD5: Invalid digest response length. Got:  " +  | 
 | 
                response.length + " Expected < " + MAX_RESPONSE_LENGTH);  | 
 | 
        }  | 
 | 
 | 
 | 
        byte[] challenge;  | 
 | 
        switch (step) { | 
 | 
        case 1:  | 
 | 
            if (response.length != 0) { | 
 | 
                throw new SaslException(  | 
 | 
                    "DIGEST-MD5 must not have an initial response");  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            String supportedCiphers = null;  | 
 | 
            if ((allQop&PRIVACY_PROTECTION) != 0) { | 
 | 
                myCiphers = getPlatformCiphers();  | 
 | 
                StringBuffer buf = new StringBuffer();  | 
 | 
 | 
 | 
                // myCipher[i] is a byte that indicates whether CIPHER_TOKENS[i]  | 
 | 
                  | 
 | 
                for (int i = 0; i < CIPHER_TOKENS.length; i++) { | 
 | 
                    if (myCiphers[i] != 0) { | 
 | 
                        if (buf.length() > 0) { | 
 | 
                            buf.append(','); | 
 | 
                        }  | 
 | 
                        buf.append(CIPHER_TOKENS[i]);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                supportedCiphers = buf.toString();  | 
 | 
            }  | 
 | 
 | 
 | 
            try { | 
 | 
                challenge = generateChallenge(serverRealms, specifiedQops,  | 
 | 
                    supportedCiphers);  | 
 | 
 | 
 | 
                step = 3;  | 
 | 
                return challenge;  | 
 | 
            } catch (UnsupportedEncodingException e) { | 
 | 
                throw new SaslException(  | 
 | 
                    "DIGEST-MD5: Error encoding challenge", e);  | 
 | 
            } catch (IOException e) { | 
 | 
                throw new SaslException(  | 
 | 
                    "DIGEST-MD5: Error generating challenge", e);  | 
 | 
            }  | 
 | 
 | 
 | 
            // Step 2 is performed by client  | 
 | 
 | 
 | 
        case 3:  | 
 | 
              | 
 | 
 | 
 | 
             */  | 
 | 
            try { | 
 | 
                byte[][] responseVal = parseDirectives(response, DIRECTIVE_KEY,  | 
 | 
                    null, REALM);  | 
 | 
                challenge = validateClientResponse(responseVal);  | 
 | 
            } catch (SaslException e) { | 
 | 
                throw e;  | 
 | 
            } catch (UnsupportedEncodingException e) { | 
 | 
                throw new SaslException(  | 
 | 
                    "DIGEST-MD5: Error validating client response", e);  | 
 | 
            } finally { | 
 | 
                step = 0;    | 
 | 
            }  | 
 | 
 | 
 | 
            completed = true;  | 
 | 
 | 
 | 
              | 
 | 
            if (integrity && privacy) { | 
 | 
                secCtx = new DigestPrivacy(false /* not client */);  | 
 | 
            } else if (integrity) { | 
 | 
                secCtx = new DigestIntegrity(false /* not client */);  | 
 | 
            }  | 
 | 
 | 
 | 
            return challenge;  | 
 | 
 | 
 | 
        default:  | 
 | 
              | 
 | 
            throw new SaslException("DIGEST-MD5: Server at illegal state"); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private byte[] generateChallenge(List<String> realms, String qopStr,  | 
 | 
        String cipherStr) throws UnsupportedEncodingException, IOException { | 
 | 
        ByteArrayOutputStream out = new ByteArrayOutputStream();  | 
 | 
 | 
 | 
          | 
 | 
        for (int i = 0; realms != null && i < realms.size(); i++) { | 
 | 
            out.write("realm=\"".getBytes(encoding)); | 
 | 
            writeQuotedStringValue(out, realms.get(i).getBytes(encoding));  | 
 | 
            out.write('"'); | 
 | 
            out.write(','); | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        out.write(("nonce=\"").getBytes(encoding)); | 
 | 
        nonce = generateNonce();  | 
 | 
        writeQuotedStringValue(out, nonce);  | 
 | 
        out.write('"'); | 
 | 
        out.write(','); | 
 | 
 | 
 | 
        // QOP - optional (1) [default: auth]  | 
 | 
          | 
 | 
        if (qopStr != null) { | 
 | 
            out.write(("qop=\"").getBytes(encoding)); | 
 | 
              | 
 | 
            writeQuotedStringValue(out, qopStr.getBytes(encoding));  | 
 | 
            out.write('"'); | 
 | 
            out.write(','); | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (recvMaxBufSize != DEFAULT_MAXBUF) { | 
 | 
            out.write(("maxbuf=\"" + recvMaxBufSize + "\",").getBytes(encoding)); | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (useUTF8) { | 
 | 
            out.write(UTF8_DIRECTIVE.getBytes(encoding));  | 
 | 
        }  | 
 | 
 | 
 | 
        if (cipherStr != null) { | 
 | 
            out.write("cipher=\"".getBytes(encoding)); | 
 | 
              | 
 | 
            writeQuotedStringValue(out, cipherStr.getBytes(encoding));  | 
 | 
            out.write('"'); | 
 | 
            out.write(','); | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        out.write(ALGORITHM_DIRECTIVE.getBytes(encoding));  | 
 | 
 | 
 | 
        return out.toByteArray();  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private byte[] validateClientResponse(byte[][] responseVal)  | 
 | 
        throws SaslException, UnsupportedEncodingException { | 
 | 
 | 
 | 
          | 
 | 
        if (responseVal[CHARSET] != null) { | 
 | 
            // The client should send this directive only if the server has  | 
 | 
              | 
 | 
            if (!useUTF8 ||  | 
 | 
                !"utf-8".equals(new String(responseVal[CHARSET], encoding))) { | 
 | 
                throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                    "violation. Incompatible charset value: " +  | 
 | 
                    new String(responseVal[CHARSET]));  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        int clntMaxBufSize =  | 
 | 
            (responseVal[MAXBUF] == null) ? DEFAULT_MAXBUF  | 
 | 
            : Integer.parseInt(new String(responseVal[MAXBUF], encoding));  | 
 | 
 | 
 | 
        // Max send buf size is min of client's max recv buf size and  | 
 | 
          | 
 | 
        sendMaxBufSize = ((sendMaxBufSize == 0) ? clntMaxBufSize :  | 
 | 
            Math.min(sendMaxBufSize, clntMaxBufSize));  | 
 | 
 | 
 | 
          | 
 | 
        String username;  | 
 | 
        if (responseVal[USERNAME] != null) { | 
 | 
            username = new String(responseVal[USERNAME], encoding);  | 
 | 
            logger.log(Level.FINE, "DIGEST82:Username: {0}", username); | 
 | 
        } else { | 
 | 
            throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                "violation. Missing username.");  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        negotiatedRealm = ((responseVal[REALM] != null) ?  | 
 | 
            new String(responseVal[REALM], encoding) : "");  | 
 | 
        logger.log(Level.FINE, "DIGEST83:Client negotiated realm: {0}", | 
 | 
            negotiatedRealm);  | 
 | 
 | 
 | 
        if (!serverRealms.contains(negotiatedRealm)) { | 
 | 
            // Server had sent at least one realm  | 
 | 
              | 
 | 
            throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                "violation. Nonexistent realm: " + negotiatedRealm);  | 
 | 
        }  | 
 | 
        // Else, client specified realm was one of server's or server had none  | 
 | 
 | 
 | 
          | 
 | 
        if (responseVal[NONCE] == null) { | 
 | 
            throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                "violation. Missing nonce.");  | 
 | 
        }  | 
 | 
        byte[] nonceFromClient = responseVal[NONCE];  | 
 | 
        if (!Arrays.equals(nonceFromClient, nonce)) { | 
 | 
            throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                "violation. Mismatched nonce.");  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (responseVal[CNONCE] == null) { | 
 | 
            throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                "violation. Missing cnonce.");  | 
 | 
        }  | 
 | 
        byte[] cnonce = responseVal[CNONCE];  | 
 | 
 | 
 | 
          | 
 | 
        if (responseVal[NONCE_COUNT] != null &&  | 
 | 
            NONCE_COUNT_VALUE != Integer.parseInt(  | 
 | 
                new String(responseVal[NONCE_COUNT], encoding), 16)) { | 
 | 
            throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                "violation. Nonce count does not match: " +  | 
 | 
                new String(responseVal[NONCE_COUNT]));  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        negotiatedQop = ((responseVal[QOP] != null) ?  | 
 | 
            new String(responseVal[QOP], encoding) : "auth");  | 
 | 
 | 
 | 
        logger.log(Level.FINE, "DIGEST84:Client negotiated qop: {0}", | 
 | 
            negotiatedQop);  | 
 | 
 | 
 | 
          | 
 | 
        byte cQop;  | 
 | 
        switch (negotiatedQop) { | 
 | 
            case "auth":  | 
 | 
                cQop = NO_PROTECTION;  | 
 | 
                break;  | 
 | 
            case "auth-int":  | 
 | 
                cQop = INTEGRITY_ONLY_PROTECTION;  | 
 | 
                integrity = true;  | 
 | 
                rawSendSize = sendMaxBufSize - 16;  | 
 | 
                break;  | 
 | 
            case "auth-conf":  | 
 | 
                cQop = PRIVACY_PROTECTION;  | 
 | 
                integrity = privacy = true;  | 
 | 
                rawSendSize = sendMaxBufSize - 26;  | 
 | 
                break;  | 
 | 
            default:  | 
 | 
                throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                    "violation. Invalid QOP: " + negotiatedQop);  | 
 | 
        }  | 
 | 
        if ((cQop&allQop) == 0) { | 
 | 
            throw new SaslException("DIGEST-MD5: server does not support " + | 
 | 
                " qop: " + negotiatedQop);  | 
 | 
        }  | 
 | 
 | 
 | 
        if (privacy) { | 
 | 
            negotiatedCipher = ((responseVal[CIPHER] != null) ?  | 
 | 
                new String(responseVal[CIPHER], encoding) : null);  | 
 | 
            if (negotiatedCipher == null) { | 
 | 
                throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                    "violation. No cipher specified.");  | 
 | 
            }  | 
 | 
 | 
 | 
            int foundCipher = -1;  | 
 | 
            logger.log(Level.FINE, "DIGEST85:Client negotiated cipher: {0}", | 
 | 
                negotiatedCipher);  | 
 | 
 | 
 | 
              | 
 | 
            for (int j = 0; j < CIPHER_TOKENS.length; j++) { | 
 | 
                if (negotiatedCipher.equals(CIPHER_TOKENS[j]) &&  | 
 | 
                    myCiphers[j] != 0) { | 
 | 
                    foundCipher = j;  | 
 | 
                    break;  | 
 | 
                }  | 
 | 
            }  | 
 | 
            if (foundCipher == -1) { | 
 | 
                throw new SaslException("DIGEST-MD5: server does not " + | 
 | 
                    "support cipher: " + negotiatedCipher);  | 
 | 
            }  | 
 | 
              | 
 | 
            if ((CIPHER_MASKS[foundCipher]&HIGH_STRENGTH) != 0) { | 
 | 
                negotiatedStrength = "high";  | 
 | 
            } else if ((CIPHER_MASKS[foundCipher]&MEDIUM_STRENGTH) != 0) { | 
 | 
                negotiatedStrength = "medium";  | 
 | 
            } else { | 
 | 
                  | 
 | 
                negotiatedStrength = "low";  | 
 | 
            }  | 
 | 
 | 
 | 
            logger.log(Level.FINE, "DIGEST86:Negotiated strength: {0}", | 
 | 
                negotiatedStrength);  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        String digestUriFromResponse = ((responseVal[DIGEST_URI]) != null ?  | 
 | 
            new String(responseVal[DIGEST_URI], encoding) : null);  | 
 | 
 | 
 | 
        if (digestUriFromResponse != null) { | 
 | 
            logger.log(Level.FINE, "DIGEST87:digest URI: {0}", | 
 | 
                digestUriFromResponse);  | 
 | 
        }  | 
 | 
 | 
 | 
        // serv-type "/" host [ "/" serv-name ]  | 
 | 
        // e.g.: smtp/mail3.example.com/example.com  | 
 | 
        // e.g.: ftp/ftp.example.com  | 
 | 
        // e.g.: ldap/ldapserver.example.com  | 
 | 
 | 
 | 
        // host should match one of service's configured service names  | 
 | 
        // Check against digest URI that mech was created with  | 
 | 
 | 
 | 
        if (uriMatches(digestUri, digestUriFromResponse)) { | 
 | 
            digestUri = digestUriFromResponse;   | 
 | 
        } else { | 
 | 
            throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                "violation. Mismatched URI: " + digestUriFromResponse +  | 
 | 
                "; expecting: " + digestUri);  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        byte[] responseFromClient = responseVal[RESPONSE];  | 
 | 
        if (responseFromClient == null) { | 
 | 
            throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                " violation. Missing response.");  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        byte[] authzidBytes;  | 
 | 
        String authzidFromClient = ((authzidBytes=responseVal[AUTHZID]) != null?  | 
 | 
            new String(authzidBytes, encoding) : username);  | 
 | 
 | 
 | 
        if (authzidBytes != null) { | 
 | 
            logger.log(Level.FINE, "DIGEST88:Authzid: {0}", | 
 | 
                new String(authzidBytes));  | 
 | 
        }  | 
 | 
 | 
 | 
        // Ignore auth-param  | 
 | 
 | 
 | 
          | 
 | 
        char[] passwd;  | 
 | 
        try { | 
 | 
              | 
 | 
            RealmCallback rcb = new RealmCallback("DIGEST-MD5 realm: ", | 
 | 
                negotiatedRealm);  | 
 | 
            NameCallback ncb = new NameCallback("DIGEST-MD5 authentication ID: ", | 
 | 
                username);  | 
 | 
 | 
 | 
              | 
 | 
            PasswordCallback pcb =  | 
 | 
                new PasswordCallback("DIGEST-MD5 password: ", false); | 
 | 
 | 
 | 
            cbh.handle(new Callback[] {rcb, ncb, pcb}); | 
 | 
            passwd = pcb.getPassword();  | 
 | 
            pcb.clearPassword();  | 
 | 
 | 
 | 
        } catch (UnsupportedCallbackException e) { | 
 | 
            throw new SaslException(  | 
 | 
                "DIGEST-MD5: Cannot perform callback to acquire password", e);  | 
 | 
 | 
 | 
        } catch (IOException e) { | 
 | 
            throw new SaslException(  | 
 | 
                "DIGEST-MD5: IO error acquiring password", e);  | 
 | 
        }  | 
 | 
 | 
 | 
        if (passwd == null) { | 
 | 
            throw new SaslException(  | 
 | 
                "DIGEST-MD5: cannot acquire password for " + username +  | 
 | 
                " in realm : " + negotiatedRealm);  | 
 | 
        }  | 
 | 
 | 
 | 
        try { | 
 | 
              | 
 | 
            byte[] expectedResponse;  | 
 | 
 | 
 | 
            try { | 
 | 
                expectedResponse = generateResponseValue("AUTHENTICATE", | 
 | 
                    digestUri, negotiatedQop, username, negotiatedRealm,  | 
 | 
                    passwd, nonce ,  | 
 | 
                    cnonce, NONCE_COUNT_VALUE, authzidBytes);  | 
 | 
 | 
 | 
            } catch (NoSuchAlgorithmException e) { | 
 | 
                throw new SaslException(  | 
 | 
                    "DIGEST-MD5: problem duplicating client response", e);  | 
 | 
            } catch (IOException e) { | 
 | 
                throw new SaslException(  | 
 | 
                    "DIGEST-MD5: problem duplicating client response", e);  | 
 | 
            }  | 
 | 
 | 
 | 
            if (!Arrays.equals(responseFromClient, expectedResponse)) { | 
 | 
                throw new SaslException("DIGEST-MD5: digest response format " + | 
 | 
                    "violation. Mismatched response.");  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            try { | 
 | 
                AuthorizeCallback acb =  | 
 | 
                    new AuthorizeCallback(username, authzidFromClient);  | 
 | 
                cbh.handle(new Callback[]{acb}); | 
 | 
 | 
 | 
                if (acb.isAuthorized()) { | 
 | 
                    authzid = acb.getAuthorizedID();  | 
 | 
                } else { | 
 | 
                    throw new SaslException("DIGEST-MD5: " + username + | 
 | 
                        " is not authorized to act as " + authzidFromClient);  | 
 | 
                }  | 
 | 
            } catch (SaslException e) { | 
 | 
                throw e;  | 
 | 
            } catch (UnsupportedCallbackException e) { | 
 | 
                throw new SaslException(  | 
 | 
                    "DIGEST-MD5: Cannot perform callback to check authzid", e);  | 
 | 
            } catch (IOException e) { | 
 | 
                throw new SaslException(  | 
 | 
                    "DIGEST-MD5: IO error checking authzid", e);  | 
 | 
            }  | 
 | 
 | 
 | 
            return generateResponseAuth(username, passwd, cnonce,  | 
 | 
                NONCE_COUNT_VALUE, authzidBytes);  | 
 | 
        } finally { | 
 | 
              | 
 | 
            for (int i = 0; i < passwd.length; i++) { | 
 | 
                passwd[i] = 0;  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private static boolean uriMatches(String thisUri, String incomingUri) { | 
 | 
          | 
 | 
        if (thisUri.equalsIgnoreCase(incomingUri)) { | 
 | 
            return true;  | 
 | 
        }  | 
 | 
          | 
 | 
        if (thisUri.endsWith("/*")) { | 
 | 
            int protoAndSlash = thisUri.length() - 1;  | 
 | 
            String thisProtoAndSlash = thisUri.substring(0, protoAndSlash);  | 
 | 
            String incomingProtoAndSlash = incomingUri.substring(0, protoAndSlash);  | 
 | 
            return thisProtoAndSlash.equalsIgnoreCase(incomingProtoAndSlash);  | 
 | 
        }  | 
 | 
        return false;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private byte[] generateResponseAuth(String username, char[] passwd,  | 
 | 
        byte[] cnonce, int nonceCount, byte[] authzidBytes) throws SaslException { | 
 | 
 | 
 | 
        // Construct response value  | 
 | 
 | 
 | 
        try { | 
 | 
            byte[] responseValue = generateResponseValue("", | 
 | 
                digestUri, negotiatedQop, username, negotiatedRealm,  | 
 | 
                passwd, nonce, cnonce, nonceCount, authzidBytes);  | 
 | 
 | 
 | 
            byte[] challenge = new byte[responseValue.length + 8];  | 
 | 
            System.arraycopy("rspauth=".getBytes(encoding), 0, challenge, 0, 8); | 
 | 
            System.arraycopy(responseValue, 0, challenge, 8,  | 
 | 
                responseValue.length );  | 
 | 
 | 
 | 
            return challenge;  | 
 | 
 | 
 | 
        } catch (NoSuchAlgorithmException e) { | 
 | 
            throw new SaslException("DIGEST-MD5: problem generating response", e); | 
 | 
        } catch (IOException e) { | 
 | 
            throw new SaslException("DIGEST-MD5: problem generating response", e); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    public String getAuthorizationID() { | 
 | 
        if (completed) { | 
 | 
            return authzid;  | 
 | 
        } else { | 
 | 
            throw new IllegalStateException(  | 
 | 
                "DIGEST-MD5 server negotiation not complete");  | 
 | 
        }  | 
 | 
    }  | 
 | 
}  |