feat(jdk8): move files to new folder to avoid resources compiled.
This commit is contained in:
1626
jdkSrc/jdk8/com/sun/security/sasl/digest/DigestMD5Base.java
Normal file
1626
jdkSrc/jdk8/com/sun/security/sasl/digest/DigestMD5Base.java
Normal file
File diff suppressed because it is too large
Load Diff
700
jdkSrc/jdk8/com/sun/security/sasl/digest/DigestMD5Client.java
Normal file
700
jdkSrc/jdk8/com/sun/security/sasl/digest/DigestMD5Client.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
724
jdkSrc/jdk8/com/sun/security/sasl/digest/DigestMD5Server.java
Normal file
724
jdkSrc/jdk8/com/sun/security/sasl/digest/DigestMD5Server.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
123
jdkSrc/jdk8/com/sun/security/sasl/digest/FactoryImpl.java
Normal file
123
jdkSrc/jdk8/com/sun/security/sasl/digest/FactoryImpl.java
Normal 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);
|
||||
}
|
||||
}
|
||||
57
jdkSrc/jdk8/com/sun/security/sasl/digest/SecurityCtx.java
Normal file
57
jdkSrc/jdk8/com/sun/security/sasl/digest/SecurityCtx.java
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user