feat(jdk8): move files to new folder to avoid resources compiled.

This commit is contained in:
2025-09-07 15:25:52 +08:00
parent 3f0047bf6f
commit 8c35cfb1c0
17415 changed files with 217 additions and 213 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,700 @@
/*
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.security.sasl.digest;
import java.security.NoSuchAlgorithmException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.StringTokenizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.logging.Level;
import javax.security.sasl.*;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
/**
* An implementation of the DIGEST-MD5
* (<a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>) SASL
* (<a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>) mechanism.
*
* The DIGEST-MD5 SASL mechanism specifies two modes of authentication.
* - Initial Authentication
* - Subsequent Authentication - optional, (currently unsupported)
*
* Required callbacks:
* - RealmChoiceCallback
* shows user list of realms server has offered; handler must choose one
* from list
* - RealmCallback
* shows user the only realm server has offered or none; handler must
* enter realm to use
* - NameCallback
* handler must enter username to use for authentication
* - PasswordCallback
* handler must enter password for username to use for authentication
*
* Environment properties that affect behavior of implementation:
*
* javax.security.sasl.qop
* quality of protection; list of auth, auth-int, auth-conf; default is "auth"
* javax.security.sasl.strength
* auth-conf strength; list of high, medium, low; default is highest
* available on platform ["high,medium,low"].
* high means des3 or rc4 (128); medium des or rc4-56; low is rc4-40;
* choice of cipher depends on its availablility on platform
* javax.security.sasl.maxbuf
* max receive buffer size; default is 65536
* javax.security.sasl.sendmaxbuffer
* max send buffer size; default is 65536; (min with server max recv size)
*
* com.sun.security.sasl.digest.cipher
* name a specific cipher to use; setting must be compatible with the
* setting of the javax.security.sasl.strength property.
*
* @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>
* - Simple Authentication and Security Layer (SASL)
* @see <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>
* - Using Digest Authentication as a SASL Mechanism
* @see <a href="http://java.sun.com/products/jce">Java(TM)
* Cryptography Extension 1.2.1 (JCE)</a>
* @see <a href="http://java.sun.com/products/jaas">Java(TM)
* Authentication and Authorization Service (JAAS)</a>
*
* @author Jonathan Bruce
* @author Rosanna Lee
*/
final class DigestMD5Client extends DigestMD5Base implements SaslClient {
private static final String MY_CLASS_NAME = DigestMD5Client.class.getName();
// Property for specifying cipher explicitly
private static final String CIPHER_PROPERTY =
"com.sun.security.sasl.digest.cipher";
/* Directives encountered in challenges sent by the server. */
private static final String[] DIRECTIVE_KEY = {
"realm", // >= 0 times
"qop", // atmost once; default is "auth"
"algorithm", // exactly once
"nonce", // exactly once
"maxbuf", // atmost once; default is 65536
"charset", // atmost once; default is ISO 8859-1
"cipher", // exactly once if qop is "auth-conf"
"rspauth", // exactly once in 2nd challenge
"stale", // atmost once for in subsequent auth (not supported)
};
/* Indices into DIRECTIVE_KEY */
private static final int REALM = 0;
private static final int QOP = 1;
private static final int ALGORITHM = 2;
private static final int NONCE = 3;
private static final int MAXBUF = 4;
private static final int CHARSET = 5;
private static final int CIPHER = 6;
private static final int RESPONSE_AUTH = 7;
private static final int STALE = 8;
private int nonceCount; // number of times nonce has been used/seen
/* User-supplied/generated information */
private String specifiedCipher; // cipher explicitly requested by user
private byte[] cnonce; // client generated nonce
private String username;
private char[] passwd;
private byte[] authzidBytes; // byte repr of authzid
/**
* Constructor for DIGEST-MD5 mechanism.
*
* @param authzid A non-null String representing the principal
* for which authorization is being granted..
* @param digestURI A non-null String representing detailing the
* combined protocol and host being used for authentication.
* @param props The possibly null properties to be used by the SASL
* mechanism to configure the authentication exchange.
* @param cbh The non-null CallbackHanlder object for callbacks
* @throws SaslException if no authentication ID or password is supplied
*/
DigestMD5Client(String authzid, String protocol, String serverName,
Map<String, ?> props, CallbackHandler cbh) throws SaslException {
super(props, MY_CLASS_NAME, 2, protocol + "/" + serverName, cbh);
// authzID can only be encoded in UTF8 - RFC 2222
if (authzid != null) {
this.authzid = authzid;
try {
authzidBytes = authzid.getBytes("UTF8");
} catch (UnsupportedEncodingException e) {
throw new SaslException(
"DIGEST-MD5: Error encoding authzid value into UTF-8", e);
}
}
if (props != null) {
specifiedCipher = (String)props.get(CIPHER_PROPERTY);
logger.log(Level.FINE, "DIGEST60:Explicitly specified cipher: {0}",
specifiedCipher);
}
}
/**
* DIGEST-MD5 has no initial response
*
* @return false
*/
public boolean hasInitialResponse() {
return false;
}
/**
* Process the challenge data.
*
* The server sends a digest-challenge which the client must reply to
* in a digest-response. When the authentication is complete, the
* completed field is set to true.
*
* @param challengeData A non-null byte array containing the challenge
* data from the server.
* @return A possibly null byte array containing the response to
* be sent to the server.
*
* @throws SaslException If the platform does not have MD5 digest support
* or if the server sends an invalid challenge.
*/
public byte[] evaluateChallenge(byte[] challengeData) throws SaslException {
if (challengeData.length > MAX_CHALLENGE_LENGTH) {
throw new SaslException(
"DIGEST-MD5: Invalid digest-challenge length. Got: " +
challengeData.length + " Expected < " + MAX_CHALLENGE_LENGTH);
}
/* Extract and process digest-challenge */
byte[][] challengeVal;
switch (step) {
case 2:
/* Process server's first challenge (from Step 1) */
/* Get realm, qop, maxbuf, charset, algorithm, cipher, nonce
directives */
List<byte[]> realmChoices = new ArrayList<byte[]>(3);
challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY,
realmChoices, REALM);
try {
processChallenge(challengeVal, realmChoices);
checkQopSupport(challengeVal[QOP], challengeVal[CIPHER]);
++step;
return generateClientResponse(challengeVal[CHARSET]);
} catch (SaslException e) {
step = 0;
clearPassword();
throw e; // rethrow
} catch (IOException e) {
step = 0;
clearPassword();
throw new SaslException("DIGEST-MD5: Error generating " +
"digest response-value", e);
}
case 3:
try {
/* Process server's step 3 (server response to digest response) */
/* Get rspauth directive */
challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY,
null, REALM);
validateResponseValue(challengeVal[RESPONSE_AUTH]);
/* Initialize SecurityCtx implementation */
if (integrity && privacy) {
secCtx = new DigestPrivacy(true /* client */);
} else if (integrity) {
secCtx = new DigestIntegrity(true /* client */);
}
return null; // Mechanism has completed.
} finally {
clearPassword();
step = 0; // Set to invalid state
completed = true;
}
default:
// No other possible state
throw new SaslException("DIGEST-MD5: Client at illegal state");
}
}
/**
* Record information from the challengeVal array into variables/fields.
* Check directive values that are multi-valued and ensure that mandatory
* directives not missing from the digest-challenge.
*
* @throws SaslException if a sasl is a the mechanism cannot
* correcly handle a callbacks or if a violation in the
* digest challenge format is detected.
*/
private void processChallenge(byte[][] challengeVal, List<byte[]> realmChoices)
throws SaslException, UnsupportedEncodingException {
/* CHARSET: optional atmost once */
if (challengeVal[CHARSET] != null) {
if (!"utf-8".equals(new String(challengeVal[CHARSET], encoding))) {
throw new SaslException("DIGEST-MD5: digest-challenge format " +
"violation. Unrecognised charset value: " +
new String(challengeVal[CHARSET]));
} else {
encoding = "UTF8";
useUTF8 = true;
}
}
/* ALGORITHM: required exactly once */
if (challengeVal[ALGORITHM] == null) {
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation: algorithm directive missing");
} else if (!"md5-sess".equals(new String(challengeVal[ALGORITHM], encoding))) {
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation. Invalid value for 'algorithm' directive: " +
challengeVal[ALGORITHM]);
}
/* NONCE: required exactly once */
if (challengeVal[NONCE] == null) {
throw new SaslException("DIGEST-MD5: Digest-challenge format " +
"violation: nonce directive missing");
} else {
nonce = challengeVal[NONCE];
}
try {
/* REALM: optional, if multiple, stored in realmChoices */
String[] realmTokens = null;
if (challengeVal[REALM] != null) {
if (realmChoices == null || realmChoices.size() <= 1) {
// Only one realm specified
negotiatedRealm = new String(challengeVal[REALM], encoding);
} else {
realmTokens = new String[realmChoices.size()];
for (int i = 0; i < realmTokens.length; i++) {
realmTokens[i] =
new String(realmChoices.get(i), encoding);
}
}
}
NameCallback ncb = authzid == null ?
new NameCallback("DIGEST-MD5 authentication ID: ") :
new NameCallback("DIGEST-MD5 authentication ID: ", authzid);
PasswordCallback pcb =
new PasswordCallback("DIGEST-MD5 password: ", false);
if (realmTokens == null) {
// Server specified <= 1 realm
// If 0, RFC 2831: the client SHOULD solicit a realm from the user.
RealmCallback tcb =
(negotiatedRealm == null? new RealmCallback("DIGEST-MD5 realm: ") :
new RealmCallback("DIGEST-MD5 realm: ", negotiatedRealm));
cbh.handle(new Callback[] {tcb, ncb, pcb});
/* Acquire realm from RealmCallback */
negotiatedRealm = tcb.getText();
if (negotiatedRealm == null) {
negotiatedRealm = "";
}
} else {
RealmChoiceCallback ccb = new RealmChoiceCallback(
"DIGEST-MD5 realm: ",
realmTokens,
0, false);
cbh.handle(new Callback[] {ccb, ncb, pcb});
// Acquire realm from RealmChoiceCallback
int[] selected = ccb.getSelectedIndexes();
if (selected == null
|| selected[0] < 0
|| selected[0] >= realmTokens.length) {
throw new SaslException("DIGEST-MD5: Invalid realm chosen");
}
negotiatedRealm = realmTokens[selected[0]];
}
passwd = pcb.getPassword();
pcb.clearPassword();
username = ncb.getName();
} catch (SaslException se) {
throw se;
} catch (UnsupportedCallbackException e) {
throw new SaslException("DIGEST-MD5: Cannot perform callback to " +
"acquire realm, authentication ID or password", e);
} catch (IOException e) {
throw new SaslException(
"DIGEST-MD5: Error acquiring realm, authentication ID or password", e);
}
if (username == null || passwd == null) {
throw new SaslException(
"DIGEST-MD5: authentication ID and password must be specified");
}
/* MAXBUF: optional atmost once */
int srvMaxBufSize =
(challengeVal[MAXBUF] == null) ? DEFAULT_MAXBUF
: Integer.parseInt(new String(challengeVal[MAXBUF], encoding));
sendMaxBufSize =
(sendMaxBufSize == 0) ? srvMaxBufSize
: Math.min(sendMaxBufSize, srvMaxBufSize);
}
/**
* Parses the 'qop' directive. If 'auth-conf' is specified by
* the client and offered as a QOP option by the server, then a check
* is client-side supported ciphers is performed.
*
* @throws IOException
*/
private void checkQopSupport(byte[] qopInChallenge, byte[] ciphersInChallenge)
throws IOException {
/* QOP: optional; if multiple, merged earlier */
String qopOptions;
if (qopInChallenge == null) {
qopOptions = "auth";
} else {
qopOptions = new String(qopInChallenge, encoding);
}
// process
String[] serverQopTokens = new String[3];
byte[] serverQop = parseQop(qopOptions, serverQopTokens,
true /* ignore unrecognized tokens */);
byte serverAllQop = combineMasks(serverQop);
switch (findPreferredMask(serverAllQop, qop)) {
case 0:
throw new SaslException("DIGEST-MD5: No common protection " +
"layer between client and server");
case NO_PROTECTION:
negotiatedQop = "auth";
// buffer sizes not applicable
break;
case INTEGRITY_ONLY_PROTECTION:
negotiatedQop = "auth-int";
integrity = true;
rawSendSize = sendMaxBufSize - 16;
break;
case PRIVACY_PROTECTION:
negotiatedQop = "auth-conf";
privacy = integrity = true;
rawSendSize = sendMaxBufSize - 26;
checkStrengthSupport(ciphersInChallenge);
break;
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "DIGEST61:Raw send size: {0}",
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.
*/
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
* @returns {@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;
}
}
}

View File

@@ -0,0 +1,724 @@
/*
* Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.security.sasl.digest;
import java.security.NoSuchAlgorithmException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.StringTokenizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.logging.Level;
import javax.security.sasl.*;
import javax.security.auth.callback.*;
/**
* 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";
/*
* Always expect nonce count value to be 1 because we support only
* initial authentication.
*/
private static final int NONCE_COUNT_VALUE = 1;
/* "true" means use UTF8; "false" ISO 8859-1; default is "true" */
private static final String UTF8_PROPERTY =
"com.sun.security.sasl.digest.utf8";
/* List of space-separated realms used for authentication */
private static final String REALM_PROPERTY =
"com.sun.security.sasl.digest.realm";
/* Directives encountered in responses sent by the client. */
private static final String[] DIRECTIVE_KEY = {
"username", // exactly once
"realm", // exactly once if sent by server
"nonce", // exactly once
"cnonce", // exactly once
"nonce-count", // atmost once; default is 00000001
"qop", // atmost once; default is "auth"
"digest-uri", // atmost once; (default?)
"response", // exactly once
"maxbuf", // atmost once; default is 65536
"charset", // atmost once; default is ISO-8859-1
"cipher", // exactly once if qop is "auth-conf"
"authzid", // atmost once; default is none
"auth-param", // >= 0 times (ignored)
};
/* Indices into DIRECTIVE_KEY */
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;
/* Server-generated/supplied information */
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; // default
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");
// By default, use server name as realm
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");
}
/* Generate first challenge */
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]
// is supported
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:
/* Validates client's response and generate challenge:
* response-auth = "rspauth" "=" response-value
*/
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; // Set to invalid state
}
completed = true;
/* Initialize SecurityCtx implementation */
if (integrity && privacy) {
secCtx = new DigestPrivacy(false /* not client */);
} else if (integrity) {
secCtx = new DigestIntegrity(false /* not client */);
}
return challenge;
default:
// No other possible state
throw new SaslException("DIGEST-MD5: Server at illegal state");
}
}
/**
* Generates challenge to be sent to client.
* digest-challenge =
* 1#( realm | nonce | qop-options | stale | maxbuf | charset
* algorithm | cipher-opts | auth-param )
*
* realm = "realm" "=" <"> realm-value <">
* realm-value = qdstr-val
* nonce = "nonce" "=" <"> nonce-value <">
* nonce-value = qdstr-val
* qop-options = "qop" "=" <"> qop-list <">
* qop-list = 1#qop-value
* qop-value = "auth" | "auth-int" | "auth-conf" |
* token
* stale = "stale" "=" "true"
* maxbuf = "maxbuf" "=" maxbuf-value
* maxbuf-value = 1*DIGIT
* charset = "charset" "=" "utf-8"
* algorithm = "algorithm" "=" "md5-sess"
* cipher-opts = "cipher" "=" <"> 1#cipher-value <">
* cipher-value = "3des" | "des" | "rc4-40" | "rc4" |
* "rc4-56" | token
* auth-param = token "=" ( token | quoted-string )
*/
private byte[] generateChallenge(List<String> realms, String qopStr,
String cipherStr) throws UnsupportedEncodingException, IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Realms (>= 0)
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(',');
}
// Nonce - required (1)
out.write(("nonce=\"").getBytes(encoding));
nonce = generateNonce();
writeQuotedStringValue(out, nonce);
out.write('"');
out.write(',');
// QOP - optional (1) [default: auth]
// qop="auth,auth-conf,auth-int"
if (qopStr != null) {
out.write(("qop=\"").getBytes(encoding));
// Check for quotes in case of non-standard qop options
writeQuotedStringValue(out, qopStr.getBytes(encoding));
out.write('"');
out.write(',');
}
// maxbuf - optional (1) [default: 65536]
if (recvMaxBufSize != DEFAULT_MAXBUF) {
out.write(("maxbuf=\"" + recvMaxBufSize + "\",").getBytes(encoding));
}
// charset - optional (1) [default: ISO 8859_1]
if (useUTF8) {
out.write(UTF8_DIRECTIVE.getBytes(encoding));
}
if (cipherStr != null) {
out.write("cipher=\"".getBytes(encoding));
// Check for quotes in case of custom ciphers
writeQuotedStringValue(out, cipherStr.getBytes(encoding));
out.write('"');
out.write(',');
}
// algorithm - required (1)
out.write(ALGORITHM_DIRECTIVE.getBytes(encoding));
return out.toByteArray();
}
/**
* Validates client's response.
* digest-response = 1#( username | realm | nonce | cnonce |
* nonce-count | qop | digest-uri | response |
* maxbuf | charset | cipher | authzid |
* auth-param )
*
* username = "username" "=" <"> username-value <">
* username-value = qdstr-val
* cnonce = "cnonce" "=" <"> cnonce-value <">
* cnonce-value = qdstr-val
* nonce-count = "nc" "=" nc-value
* nc-value = 8LHEX
* qop = "qop" "=" qop-value
* digest-uri = "digest-uri" "=" <"> digest-uri-value <">
* digest-uri-value = serv-type "/" host [ "/" serv-name ]
* serv-type = 1*ALPHA
* host = 1*( ALPHA | DIGIT | "-" | "." )
* serv-name = host
* response = "response" "=" response-value
* response-value = 32LHEX
* LHEX = "0" | "1" | "2" | "3" |
* "4" | "5" | "6" | "7" |
* "8" | "9" | "a" | "b" |
* "c" | "d" | "e" | "f"
* cipher = "cipher" "=" cipher-value
* authzid = "authzid" "=" <"> authzid-value <">
* authzid-value = qdstr-val
* sets:
* negotiatedQop
* negotiatedCipher
* negotiatedRealm
* negotiatedStrength
* digestUri (checked and set to clients to account for case diffs)
* sendMaxBufSize
* authzid (gotten from callback)
* @return response-value ('rspauth') for client to validate
*/
private byte[] validateClientResponse(byte[][] responseVal)
throws SaslException, UnsupportedEncodingException {
/* CHARSET: optional atmost once */
if (responseVal[CHARSET] != null) {
// The client should send this directive only if the server has
// indicated it supports UTF-8.
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]));
}
}
// maxbuf: atmost once
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
// server's max send buf size
sendMaxBufSize = ((sendMaxBufSize == 0) ? clntMaxBufSize :
Math.min(sendMaxBufSize, clntMaxBufSize));
/* username: exactly once */
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.");
}
/* realm: exactly once if sent by server */
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
// Check that response is one of these
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
/* nonce: exactly once */
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.");
}
/* cnonce: exactly once */
if (responseVal[CNONCE] == null) {
throw new SaslException("DIGEST-MD5: digest response format " +
"violation. Missing cnonce.");
}
byte[] cnonce = responseVal[CNONCE];
/* nonce-count: atmost once */
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]));
}
/* qop: atmost once; default is "auth" */
negotiatedQop = ((responseVal[QOP] != null) ?
new String(responseVal[QOP], encoding) : "auth");
logger.log(Level.FINE, "DIGEST84:Client negotiated qop: {0}",
negotiatedQop);
// Check that QOP is one sent by server
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);
// Check that cipher is one that we offered
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);
}
// Set negotiatedStrength
if ((CIPHER_MASKS[foundCipher]&HIGH_STRENGTH) != 0) {
negotiatedStrength = "high";
} else if ((CIPHER_MASKS[foundCipher]&MEDIUM_STRENGTH) != 0) {
negotiatedStrength = "medium";
} else {
// assume default low
negotiatedStrength = "low";
}
logger.log(Level.FINE, "DIGEST86:Negotiated strength: {0}",
negotiatedStrength);
}
// atmost once
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; // account for case-sensitive diffs
} else {
throw new SaslException("DIGEST-MD5: digest response format " +
"violation. Mismatched URI: " + digestUriFromResponse +
"; expecting: " + digestUri);
}
// response: exactly once
byte[] responseFromClient = responseVal[RESPONSE];
if (responseFromClient == null) {
throw new SaslException("DIGEST-MD5: digest response format " +
" violation. Missing response.");
}
// authzid: atmost once
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
// Get password need to generate verifying response
char[] passwd;
try {
// Realm and Name callbacks are used to provide info
RealmCallback rcb = new RealmCallback("DIGEST-MD5 realm: ",
negotiatedRealm);
NameCallback ncb = new NameCallback("DIGEST-MD5 authentication ID: ",
username);
// PasswordCallback is used to collect info
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 {
// Validate response value sent by client
byte[] expectedResponse;
try {
expectedResponse = generateResponseValue("AUTHENTICATE",
digestUri, negotiatedQop, username, negotiatedRealm,
passwd, nonce /* use own 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.");
}
// Ensure that authzid mapping is OK
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 {
// Clear password
for (int i = 0; i < passwd.length; i++) {
passwd[i] = 0;
}
}
}
private static boolean uriMatches(String thisUri, String incomingUri) {
// Full match
if (thisUri.equalsIgnoreCase(incomingUri)) {
return true;
}
// Unbound match
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;
}
/**
* 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" }
*
* Clears password afterwards.
*/
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");
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.security.sasl.digest;
import java.util.Map;
import javax.security.sasl.*;
import javax.security.auth.callback.CallbackHandler;
import com.sun.security.sasl.util.PolicyUtils;
/**
* Client and server factory for DIGEST-MD5 SASL client/server mechanisms.
* See DigestMD5Client and DigestMD5Server for input requirements.
*
* @author Jonathan Bruce
* @author Rosanna Lee
*/
public final class FactoryImpl implements SaslClientFactory,
SaslServerFactory{
private static final String myMechs[] = { "DIGEST-MD5" };
private static final int DIGEST_MD5 = 0;
private static final int mechPolicies[] = {
PolicyUtils.NOPLAINTEXT|PolicyUtils.NOANONYMOUS};
/**
* Empty constructor.
*/
public FactoryImpl() {
}
/**
* Returns a new instance of the DIGEST-MD5 SASL client mechanism.
*
* @throws SaslException If there is an error creating the DigestMD5
* SASL client.
* @return a new SaslClient; otherwise null if unsuccessful.
*/
public SaslClient createSaslClient(String[] mechs,
String authorizationId, String protocol, String serverName,
Map<String,?> props, CallbackHandler cbh)
throws SaslException {
for (int i=0; i<mechs.length; i++) {
if (mechs[i].equals(myMechs[DIGEST_MD5]) &&
PolicyUtils.checkPolicy(mechPolicies[DIGEST_MD5], props)) {
if (cbh == null) {
throw new SaslException(
"Callback handler with support for RealmChoiceCallback, " +
"RealmCallback, NameCallback, and PasswordCallback " +
"required");
}
return new DigestMD5Client(authorizationId,
protocol, serverName, props, cbh);
}
}
return null;
}
/**
* Returns a new instance of the DIGEST-MD5 SASL server mechanism.
*
* @throws SaslException If there is an error creating the DigestMD5
* SASL server.
* @return a new SaslServer; otherwise null if unsuccessful.
*/
public SaslServer createSaslServer(String mech,
String protocol, String serverName, Map<String,?> props, CallbackHandler cbh)
throws SaslException {
if (mech.equals(myMechs[DIGEST_MD5]) &&
PolicyUtils.checkPolicy(mechPolicies[DIGEST_MD5], props)) {
if (cbh == null) {
throw new SaslException(
"Callback handler with support for AuthorizeCallback, "+
"RealmCallback, NameCallback, and PasswordCallback " +
"required");
}
return new DigestMD5Server(protocol, serverName, props, cbh);
}
return null;
}
/**
* Returns the authentication mechanisms that this factory can produce.
*
* @return String[] {"DigestMD5"} if policies in env match those of this
* factory.
*/
public String[] getMechanismNames(Map<String,?> env) {
return PolicyUtils.filterMechs(myMechs, mechPolicies, env);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.security.sasl.digest;
import javax.security.sasl.SaslException;
/**
* Interface used for classes implementing integrity checking and privacy
* for DIGEST-MD5 SASL mechanism implementation.
*
* @see <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>
* - Using Digest Authentication as a SASL Mechanism
*
* @author Jonathan Bruce
*/
interface SecurityCtx {
/**
* Wrap out-going message and return wrapped message
*
* @throws SaslException
*/
byte[] wrap(byte[] dest, int start, int len)
throws SaslException;
/**
* Unwrap incoming message and return original message
*
* @throws SaslException
*/
byte[] unwrap(byte[] outgoing, int start, int len)
throws SaslException;
}