/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* 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.
*/
/**
* 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
*/
// Property for specifying cipher explicitly
"com.sun.security.sasl.digest.cipher";
/* Directives encountered in challenges sent by the server. */
"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 */
/* User-supplied/generated information */
private char[] passwd;
/**
* 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
*/
// authzID can only be encoded in UTF8 - RFC 2222
try {
} catch (UnsupportedEncodingException e) {
throw new SaslException(
"DIGEST-MD5: Error encoding authzid value into UTF-8", e);
}
}
}
}
/**
* 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.
*/
throw new SaslException(
"DIGEST-MD5: Invalid digest-challenge length. Got: " +
}
/* 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 */
try {
++step;
} catch (SaslException e) {
step = 0;
throw e; // rethrow
} catch (IOException e) {
step = 0;
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 */
/* Initialize SecurityCtx implementation */
} else if (integrity) {
}
return null; // Mechanism has completed.
} finally {
completed = true;
}
default:
// No other possible state
throw new SaslException("DIGEST-MD5: Client at illegal state");
}
}
/**
* 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.
*/
throws SaslException, UnsupportedEncodingException {
/* CHARSET: optional atmost once */
throw new SaslException("DIGEST-MD5: digest-challenge format " +
"violation. Unrecognised charset value: " +
} else {
encoding = "UTF8";
useUTF8 = true;
}
}
/* ALGORITHM: required exactly once */
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation: algorithm directive missing");
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation. Invalid value for 'algorithm' directive: " +
}
/* NONCE: required exactly once */
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation: nonce directive missing");
} else {
}
try {
/* REALM: optional, if multiple, stored in realmChoices */
// Only one realm specified
} else {
realmTokens[i] =
}
}
}
new NameCallback("DIGEST-MD5 authentication ID: ") :
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.
/* Acquire realm from RealmCallback */
if (negotiatedRealm == null) {
negotiatedRealm = "";
}
} else {
"DIGEST-MD5 realm: ",
0, false);
/* Acquire realm from RealmChoiceCallback*/
}
pcb.clearPassword();
} 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);
}
throw new SaslException(
"DIGEST-MD5: authentication ID and password must be specified");
}
/* MAXBUF: optional atmost once */
int 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
*/
throws IOException {
/* QOP: optional; if multiple, merged earlier */
if (qopInChallenge == null) {
qopOptions = "auth";
} else {
}
// process
true /* ignore unrecognized tokens */);
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;
negotiatedQop = "auth-int";
integrity = true;
break;
case PRIVACY_PROTECTION:
negotiatedQop = "auth-conf";
break;
}
new Integer(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.
*/
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
byte[] serverCiphers = { UNSET,
UNSET };
// Parse ciphers in challenge; mark each that server supports
for (int i = 0; i < tokenCount; i++) {
serverCiphers[j] |= CIPHER_MASKS[j];
}
}
}
// Determine which ciphers are available on client
byte[] clntCiphers = getPlatformCiphers();
// Take intersection of server and client supported ciphers
byte inter = 0;
serverCiphers[i] &= clntCiphers[i];
inter |= serverCiphers[i];
}
throw new SaslException(
"DIGEST-MD5: Client supports none of these cipher suites: " +
}
// now have a clear picture of user / client; client / server
// cipher options. Leverage strength array against what is
// supported to choose a cipher.
if (negotiatedCipher == null) {
throw new SaslException("DIGEST-MD5: Unable to negotiate " +
"a strength level for 'auth-conf'");
}
}
/**
* 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.
*/
byte s;
if ((s=strength[i]) != 0) {
// If user explicitly requested cipher, then it
// must be the one we choose
if (s == supportedCiphers[j] &&
(specifiedCipher == null ||
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
* @returns <tt>digest-response</tt> in a byte array
* @throws SaslException if there is an error generating the
* response value or the cnonce value.
*/
if (useUTF8) {
}
}
cnonce = generateNonce();
try {
} catch (Exception e) {
throw new SaslException(
"DIGEST-MD5: Error generating response value", e);
}
if (negotiatedCipher != null) {
}
if (authzidBytes != null) {
}
throw new SaslException ("DIGEST-MD5: digest-response size too " +
}
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" }
*/
if (fromServer == null) {
throw new SaslException("DIGEST-MD5: Authenication failed. " +
"Expecting 'rspauth' authentication success message");
}
try {
/* 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.
*/
return 1;
}
private void clearPassword() {
passwd[i] = 0;
}
}
}
}