JDK 11 java.security.sasl.jmod - Security SASL Module

JDK 11 java.security.sasl.jmod is the JMOD file for JDK 11 Security SASL (Simple Authentication and Security Layer) module.

JDK 11 Security SASL module compiled class files are stored in \fyicenter\jdk-11.0.1\jmods\java.security.sasl.jmod.

JDK 11 Security SASL module compiled class files are also linked and stored in the \fyicenter\jdk-11.0.1\lib\modules JImage file.

JDK 11 Security SASL module source code files are stored in \fyicenter\jdk-11.0.1\lib\src.zip\java.security.sasl.

You can click and view the content of each source code file in the list below.

✍: FYIcenter

com/sun/security/sasl/digest/DigestMD5Client.java

/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

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;
        }
    }
}

com/sun/security/sasl/digest/DigestMD5Client.java

 

JDK 11 java.smartcardio.jmod - Smart Card IO Module

JDK 11 java.security.jgss.jmod - Security JGSS Module

Download and Use JDK 11

⇑⇑ FAQ for JDK (Java Development Kit)

2018-11-10, 2607👍, 0💬