8281561: Disable http DIGEST mechanism with MD5 and SHA-1 by default
Reviewed-by: weijun, dfuchs
This commit is contained in:
parent
0c472c8a4f
commit
7f2a3ca289
src/java.base/share
classes
conf/security
test/jdk
java/net
Authenticator
HttpURLConnection/SetAuthenticator
sun/net/www
@ -224,6 +224,14 @@ of proxies.</P>
|
||||
property is defined, then its value will be used as the domain
|
||||
name.</P>
|
||||
</OL>
|
||||
<LI><P><B>{@systemProperty http.auth.digest.reEnabledAlgorithms}</B> (default: <none>)<BR>
|
||||
By default, certain message digest algorithms are disabled for use in HTTP Digest
|
||||
authentication due to their proven security limitations. This only applies to proxy
|
||||
authentication and plain-text HTTP server authentication. Disabled algorithms are still
|
||||
usable for HTTPS server authentication. The default list of disabled algorithms is specified
|
||||
in the {@code java.security} properties file and currently comprises {@code MD5} and
|
||||
{@code SHA-1}. If it is still required to use one of these algorithms, then they can be
|
||||
re-enabled by setting this property to a comma separated list of the algorithm names.</P>
|
||||
<LI><P><B>{@systemProperty jdk.https.negotiate.cbt}</B> (default: <never>)<BR>
|
||||
This controls the generation and sending of TLS channel binding tokens (CBT) when Kerberos
|
||||
or the Negotiate authentication scheme using Kerberos are employed over HTTPS with
|
||||
|
@ -29,17 +29,32 @@ import java.io.*;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.AccessController;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.Security;
|
||||
import java.text.Normalizer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import sun.net.NetProperties;
|
||||
import sun.net.www.HeaderParser;
|
||||
import sun.nio.cs.ISO_8859_1;
|
||||
import sun.security.util.KnownOIDs;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
import static sun.net.www.protocol.http.HttpURLConnection.HTTP_CONNECT;
|
||||
|
||||
@ -57,22 +72,67 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
|
||||
private String authMethod;
|
||||
|
||||
private static final String compatPropName = "http.auth.digest." +
|
||||
private static final String propPrefix = "http.auth.digest.";
|
||||
|
||||
private static final String compatPropName = propPrefix +
|
||||
"quoteParameters";
|
||||
|
||||
// Takes a set and input string containing comma separated values. converts to upper
|
||||
// case, and trims each value, then applies given function to set and value
|
||||
// (either add or delete element from set)
|
||||
private static void processPropValue(String input,
|
||||
Set<String> theSet,
|
||||
BiConsumer<Set<String>,String> consumer)
|
||||
{
|
||||
if (input == null) {
|
||||
return;
|
||||
}
|
||||
String[] values = input.toUpperCase(Locale.ROOT).split(",");
|
||||
for (String v : values) {
|
||||
consumer.accept(theSet, v.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private static final String secPropName =
|
||||
propPrefix + "disabledAlgorithms";
|
||||
|
||||
// A net property which overrides the disabled set above.
|
||||
private static final String enabledAlgPropName =
|
||||
propPrefix + "reEnabledAlgorithms";
|
||||
|
||||
// Set of disabled message digest algorithms
|
||||
private static final Set<String> disabledDigests;
|
||||
|
||||
// true if http.auth.digest.quoteParameters Net property is true
|
||||
private static final boolean delimCompatFlag;
|
||||
|
||||
private static final PlatformLogger logger =
|
||||
HttpURLConnection.getHttpLogger();
|
||||
|
||||
static {
|
||||
@SuppressWarnings("removal")
|
||||
Boolean b = AccessController.doPrivileged(
|
||||
new PrivilegedAction<>() {
|
||||
public Boolean run() {
|
||||
return NetProperties.getBoolean(compatPropName);
|
||||
}
|
||||
}
|
||||
(PrivilegedAction<Boolean>) () -> NetProperties.getBoolean(compatPropName)
|
||||
);
|
||||
delimCompatFlag = (b == null) ? false : b.booleanValue();
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
String secprops = AccessController.doPrivileged(
|
||||
(PrivilegedAction<String>) () -> Security.getProperty(secPropName)
|
||||
);
|
||||
|
||||
Set<String> algs = new HashSet<>();
|
||||
|
||||
// add the default insecure algorithms to set
|
||||
processPropValue(secprops, algs, (set, elem) -> set.add(elem));
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
String netprops = AccessController.doPrivileged(
|
||||
(PrivilegedAction<String>) () -> NetProperties.get(enabledAlgPropName)
|
||||
);
|
||||
// remove any algorithms from disabled set that were opted-in by user
|
||||
processPropValue(netprops, algs, (set, elem) -> set.remove(elem));
|
||||
disabledDigests = Set.copyOf(algs);
|
||||
}
|
||||
|
||||
// Authentication parameters defined in RFC2617.
|
||||
@ -90,9 +150,18 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
private String cnonce;
|
||||
private String nonce;
|
||||
private String algorithm;
|
||||
// Normally same as algorithm, but excludes the -SESS suffix if present
|
||||
private String digestName;
|
||||
private String charset;
|
||||
private int NCcount=0;
|
||||
|
||||
// The H(A1) string used for MD5-sess
|
||||
// true if the server supports user hashing
|
||||
// in which case the username returned to server
|
||||
// will be H(unq(username) ":" unq(realm))
|
||||
// meaning the username doesn't appear in the clear
|
||||
private boolean userhash;
|
||||
|
||||
// The H(A1) string used for XXX-sess
|
||||
private String cachedHA1;
|
||||
|
||||
// Force the HA1 value to be recalculated because the nonce has changed
|
||||
@ -112,8 +181,10 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
serverQop = false;
|
||||
opaque = null;
|
||||
algorithm = null;
|
||||
digestName = null;
|
||||
cachedHA1 = null;
|
||||
nonce = null;
|
||||
charset = null;
|
||||
setNewCnonce();
|
||||
}
|
||||
|
||||
@ -151,6 +222,24 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
redoCachedHA1 = true;
|
||||
}
|
||||
|
||||
synchronized boolean getUserhash() {
|
||||
return userhash;
|
||||
}
|
||||
|
||||
synchronized void setUserhash(boolean userhash) {
|
||||
this.userhash = userhash;
|
||||
}
|
||||
|
||||
synchronized Charset getCharset() {
|
||||
return "UTF-8".equals(charset)
|
||||
? StandardCharsets.UTF_8
|
||||
: StandardCharsets.ISO_8859_1;
|
||||
}
|
||||
|
||||
synchronized void setCharset(String charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
synchronized void setQop (String qop) {
|
||||
if (qop != null) {
|
||||
String items[] = qop.split(",");
|
||||
@ -191,7 +280,13 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
}
|
||||
|
||||
synchronized String getAlgorithm () { return algorithm;}
|
||||
synchronized String getDigestName () {
|
||||
return digestName;
|
||||
}
|
||||
synchronized void setAlgorithm (String s) { algorithm=s;}
|
||||
synchronized void setDigestName (String s) {
|
||||
this.digestName = s;
|
||||
}
|
||||
}
|
||||
|
||||
Parameters params;
|
||||
@ -309,6 +404,16 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
params.setNonce (p.findValue("nonce"));
|
||||
params.setOpaque (p.findValue("opaque"));
|
||||
params.setQop (p.findValue("qop"));
|
||||
params.setUserhash (Boolean.valueOf(p.findValue("userhash")));
|
||||
String charset = p.findValue("charset");
|
||||
if (charset == null) {
|
||||
charset = "ISO_8859_1";
|
||||
} else if (!charset.equalsIgnoreCase("UTF-8")) {
|
||||
// UTF-8 is only valid value. ISO_8859_1 represents default behavior
|
||||
// when the parameter is not set.
|
||||
return false;
|
||||
}
|
||||
params.setCharset(charset.toUpperCase(Locale.ROOT));
|
||||
|
||||
String uri="";
|
||||
String method;
|
||||
@ -333,11 +438,9 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
authMethod = Character.toUpperCase(authMethod.charAt(0))
|
||||
+ authMethod.substring(1).toLowerCase();
|
||||
}
|
||||
String algorithm = p.findValue("algorithm");
|
||||
if (algorithm == null || algorithm.isEmpty()) {
|
||||
algorithm = "MD5"; // The default, accoriding to rfc2069
|
||||
}
|
||||
params.setAlgorithm (algorithm);
|
||||
|
||||
if (!setAlgorithmNames(p, params))
|
||||
return false;
|
||||
|
||||
// If authQop is true, then the server is doing RFC2617 and
|
||||
// has offered qop=auth. We do not support any other modes
|
||||
@ -356,6 +459,38 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// Algorithm name is stored in two separate fields (of Paramaeters)
|
||||
// This allows for variations in digest algorithm name (aliases)
|
||||
// and also allow for the -sess variant defined in HTTP Digest protocol
|
||||
// returns false if algorithm not supported
|
||||
private static boolean setAlgorithmNames(HeaderParser p, Parameters params) {
|
||||
String algorithm = p.findValue("algorithm");
|
||||
String digestName = algorithm;
|
||||
if (algorithm == null || algorithm.isEmpty()) {
|
||||
algorithm = "MD5"; // The default, accoriding to rfc2069
|
||||
digestName = "MD5";
|
||||
} else {
|
||||
algorithm = algorithm.toUpperCase(Locale.ROOT);
|
||||
digestName = algorithm;
|
||||
}
|
||||
if (algorithm.endsWith("-SESS")) {
|
||||
digestName = algorithm.substring(0, algorithm.length() - 5);
|
||||
algorithm = digestName + "-sess"; // suffix lower case
|
||||
}
|
||||
if (digestName.equals("SHA-512-256")) {
|
||||
digestName = "SHA-512/256";
|
||||
}
|
||||
var oid = KnownOIDs.findMatch(digestName);
|
||||
if (oid == null) {
|
||||
log("unknown algorithm: " + algorithm);
|
||||
return false;
|
||||
}
|
||||
digestName = oid.stdName();
|
||||
params.setAlgorithm (algorithm);
|
||||
params.setDigestName (digestName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Calculate the Authorization header field given the request URI
|
||||
* and based on the authorization information in params
|
||||
*/
|
||||
@ -367,6 +502,14 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
String cnonce = params.getCnonce ();
|
||||
String nonce = params.getNonce ();
|
||||
String algorithm = params.getAlgorithm ();
|
||||
String digest = params.getDigestName ();
|
||||
try {
|
||||
validateDigest(digest);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
Charset charset = params.getCharset();
|
||||
boolean userhash = params.getUserhash ();
|
||||
params.incrementNC ();
|
||||
int nccount = params.getNCCount ();
|
||||
String ncstring=null;
|
||||
@ -378,10 +521,14 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
ncstring = zeroPad [len] + ncstring;
|
||||
}
|
||||
|
||||
boolean session = algorithm.endsWith ("-sess");
|
||||
|
||||
try {
|
||||
response = computeDigest(true, pw.getUserName(),passwd,realm,
|
||||
method, uri, nonce, cnonce, ncstring);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
method, uri, nonce, cnonce, ncstring,
|
||||
digest, session, charset);
|
||||
} catch (CharacterCodingException | NoSuchAlgorithmException ex) {
|
||||
log(ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -402,11 +549,24 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
qopS = ", qop=auth";
|
||||
}
|
||||
|
||||
String user = pw.getUserName();
|
||||
String userhashField = "";
|
||||
try {
|
||||
if (userhash) {
|
||||
user = computeUserhash(digest, user, realm, charset);
|
||||
userhashField = ", userhash=true";
|
||||
}
|
||||
} catch (CharacterCodingException | NoSuchAlgorithmException ex) {
|
||||
log(ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
String value = authMethod
|
||||
+ " username=\"" + pw.getUserName()
|
||||
+ " username=\"" + user
|
||||
+ "\", realm=\"" + realm
|
||||
+ "\", nonce=\"" + nonce
|
||||
+ ncfield
|
||||
+ userhashField
|
||||
+ ", uri=\"" + uri
|
||||
+ "\", response=\"" + response + "\""
|
||||
+ algoS;
|
||||
@ -427,6 +587,27 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
checkResponse (header, method, url.getFile());
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
if (logger.isLoggable(PlatformLogger.Level.INFO)) {
|
||||
logger.info(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDigest(String name) throws IOException {
|
||||
if (getAuthType() == AuthCacheValue.Type.Server &&
|
||||
getProtocolScheme().equals("https")) {
|
||||
// HTTPS server authentication can use any algorithm
|
||||
return;
|
||||
}
|
||||
if (disabledDigests.contains(name)) {
|
||||
String msg = "Rejecting digest authentication with insecure algorithm: "
|
||||
+ name;
|
||||
log(msg + " This constraint may be relaxed by setting " +
|
||||
"the \"http.auth.digest.reEnabledAlgorithms\" system property.");
|
||||
throw new IOException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkResponse (String header, String method, String uri)
|
||||
throws IOException {
|
||||
char[] passwd = pw.getPassword();
|
||||
@ -436,6 +617,9 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
String cnonce = params.cnonce;
|
||||
String nonce = params.getNonce ();
|
||||
String algorithm = params.getAlgorithm ();
|
||||
String digest = params.getDigestName ();
|
||||
Charset charset = params.getCharset();
|
||||
validateDigest(digest);
|
||||
int nccount = params.getNCCount ();
|
||||
String ncstring=null;
|
||||
|
||||
@ -443,15 +627,21 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
throw new ProtocolException ("No authentication information in response");
|
||||
}
|
||||
|
||||
boolean session = algorithm.endsWith ("-SESS");
|
||||
if (session) {
|
||||
algorithm = algorithm.substring(0, algorithm.length() - 5);
|
||||
}
|
||||
|
||||
if (nccount != -1) {
|
||||
ncstring = Integer.toHexString (nccount).toUpperCase();
|
||||
ncstring = Integer.toHexString (nccount).toUpperCase(Locale.ROOT);
|
||||
int len = ncstring.length();
|
||||
if (len < 8)
|
||||
ncstring = zeroPad [len] + ncstring;
|
||||
}
|
||||
try {
|
||||
String expected = computeDigest(false, username,passwd,realm,
|
||||
method, uri, nonce, cnonce, ncstring);
|
||||
String expected = computeDigest(false, username,passwd,realm, method, uri,
|
||||
nonce, cnonce, ncstring, digest,
|
||||
session, charset);
|
||||
HeaderParser p = new HeaderParser (header);
|
||||
String rspauth = p.findValue ("rspauth");
|
||||
if (rspauth == null) {
|
||||
@ -468,34 +658,45 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new ProtocolException ("Unsupported algorithm in response");
|
||||
} catch (CharacterCodingException ex) {
|
||||
throw new ProtocolException ("Invalid characters in username or password");
|
||||
}
|
||||
}
|
||||
|
||||
private String computeUserhash(String digest, String user,
|
||||
String realm, Charset charset)
|
||||
throws NoSuchAlgorithmException, CharacterCodingException
|
||||
{
|
||||
MessageDigest md = MessageDigest.getInstance(digest);
|
||||
String s = user + ":" + realm;
|
||||
return encode(s, null, md, charset);
|
||||
}
|
||||
|
||||
private String computeDigest(
|
||||
boolean isRequest, String userName, char[] password,
|
||||
String realm, String connMethod,
|
||||
String requestURI, String nonceString,
|
||||
String cnonce, String ncValue
|
||||
) throws NoSuchAlgorithmException
|
||||
String cnonce, String ncValue,
|
||||
String algorithm, boolean session,
|
||||
Charset charset
|
||||
) throws NoSuchAlgorithmException, CharacterCodingException
|
||||
{
|
||||
|
||||
String A1, HashA1;
|
||||
String algorithm = params.getAlgorithm ();
|
||||
boolean md5sess = algorithm.equalsIgnoreCase ("MD5-sess");
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance(md5sess?"MD5":algorithm);
|
||||
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||||
|
||||
if (md5sess) {
|
||||
if (session) {
|
||||
if ((HashA1 = params.getCachedHA1 ()) == null) {
|
||||
String s = userName + ":" + realm + ":";
|
||||
String s1 = encode (s, password, md);
|
||||
String s1 = encode (s, password, md, charset);
|
||||
A1 = s1 + ":" + nonceString + ":" + cnonce;
|
||||
HashA1 = encode(A1, null, md);
|
||||
HashA1 = encode(A1, null, md, charset);
|
||||
params.setCachedHA1 (HashA1);
|
||||
}
|
||||
} else {
|
||||
A1 = userName + ":" + realm + ":";
|
||||
HashA1 = encode(A1, password, md);
|
||||
HashA1 = encode(A1, password, md, charset);
|
||||
}
|
||||
|
||||
String A2;
|
||||
@ -504,7 +705,7 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
} else {
|
||||
A2 = ":" + requestURI;
|
||||
}
|
||||
String HashA2 = encode(A2, null, md);
|
||||
String HashA2 = encode(A2, null, md, ISO_8859_1.INSTANCE);
|
||||
String combo, finalHash;
|
||||
|
||||
if (params.authQop()) { /* RRC2617 when qop=auth */
|
||||
@ -516,7 +717,7 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
nonceString + ":" +
|
||||
HashA2;
|
||||
}
|
||||
finalHash = encode(combo, null, md);
|
||||
finalHash = encode(combo, null, md, ISO_8859_1.INSTANCE);
|
||||
return finalHash;
|
||||
}
|
||||
|
||||
@ -530,17 +731,28 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
"00000000", "0000000", "000000", "00000", "0000", "000", "00", "0"
|
||||
};
|
||||
|
||||
private String encode(String src, char[] passwd, MessageDigest md) {
|
||||
md.update(src.getBytes(ISO_8859_1.INSTANCE));
|
||||
private String encode(String src, char[] passwd, MessageDigest md, Charset charset)
|
||||
throws CharacterCodingException
|
||||
{
|
||||
boolean isUtf8 = charset.equals(StandardCharsets.UTF_8);
|
||||
|
||||
if (isUtf8) {
|
||||
src = Normalizer.normalize(src, Normalizer.Form.NFC);
|
||||
}
|
||||
md.update(src.getBytes(charset));
|
||||
if (passwd != null) {
|
||||
byte[] passwdBytes = new byte[passwd.length];
|
||||
for (int i=0; i<passwd.length; i++)
|
||||
passwdBytes[i] = (byte)passwd[i];
|
||||
byte[] passwdBytes;
|
||||
if (isUtf8) {
|
||||
passwdBytes = getUtf8Bytes(passwd);
|
||||
} else {
|
||||
passwdBytes = new byte[passwd.length];
|
||||
for (int i=0; i<passwd.length; i++)
|
||||
passwdBytes[i] = (byte)passwd[i];
|
||||
}
|
||||
md.update(passwdBytes);
|
||||
Arrays.fill(passwdBytes, (byte)0x00);
|
||||
}
|
||||
byte[] digest = md.digest();
|
||||
|
||||
StringBuilder res = new StringBuilder(digest.length * 2);
|
||||
for (int i = 0; i < digest.length; i++) {
|
||||
int hashchar = ((digest[i] >>> 4) & 0xf);
|
||||
@ -550,4 +762,15 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||
}
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
private static byte[] getUtf8Bytes(char[] passwd) throws CharacterCodingException {
|
||||
CharBuffer cb = CharBuffer.wrap(passwd);
|
||||
CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
|
||||
ByteBuffer bb = encoder.encode(cb);
|
||||
byte[] buf = new byte[bb.remaining()];
|
||||
bb.get(buf);
|
||||
if (bb.hasArray())
|
||||
Arrays.fill(bb.array(), bb.arrayOffset(), bb.capacity(), (byte)0);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
@ -701,6 +701,16 @@ jdk.security.legacyAlgorithms=SHA1, \
|
||||
jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \
|
||||
DSA keySize < 1024, SHA1 denyAfter 2019-01-01
|
||||
|
||||
#
|
||||
# Disabled message digest algorithms for use with plaintext
|
||||
# HTTP Digest authentication (java.net.HttpURLConnection).
|
||||
# This includes HTTPS Digest authentication to proxies.
|
||||
# This may be overridden by setting the networking (or system)
|
||||
# property "http.auth.digest.reEnabledAlgorithms" to a comma
|
||||
# separated list of algorithms to be allowed.
|
||||
#
|
||||
http.auth.digest.disabledAlgorithms = MD5, SHA-1
|
||||
|
||||
#
|
||||
# Algorithm restrictions for Secure Socket Layer/Transport Layer Security
|
||||
# (SSL/TLS/DTLS) processing
|
||||
|
@ -25,7 +25,7 @@
|
||||
* @test
|
||||
* @bug 4722333
|
||||
* @library /test/lib
|
||||
* @run main/othervm B4722333
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 B4722333
|
||||
* @summary JRE Proxy Authentication Not Working with ISA2000
|
||||
*/
|
||||
|
||||
|
@ -25,8 +25,8 @@
|
||||
* @test
|
||||
* @bug 4759514
|
||||
* @library /test/lib
|
||||
* @run main/othervm B4759514
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true B4759514
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 B4759514
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 -Djava.net.preferIPv6Addresses=true B4759514
|
||||
* @summary Digest Authentication is erroniously quoting the nc value, contrary to RFC 2617
|
||||
*/
|
||||
|
||||
|
@ -25,8 +25,10 @@
|
||||
* @test
|
||||
* @bug 6870935
|
||||
* @modules java.base/sun.net.www
|
||||
* @run main/othervm -Dhttp.nonProxyHosts="" -Dhttp.auth.digest.validateProxy=true B6870935
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Dhttp.nonProxyHosts="" -Dhttp.auth.digest.validateProxy=true B6870935
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Djava.net.preferIPv6Addresses=true
|
||||
* -Dhttp.nonProxyHosts="" -Dhttp.auth.digest.validateProxy=true B6870935
|
||||
*/
|
||||
|
||||
|
@ -31,9 +31,11 @@ import jdk.test.lib.net.URIBuilder;
|
||||
* @bug 8034170
|
||||
* @summary Digest authentication interop issue
|
||||
* @library /test/lib
|
||||
* @run main/othervm B8034170 unquoted
|
||||
* @run main/othervm -Dhttp.auth.digest.quoteParameters=true B8034170 quoted
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true B8034170 unquoted
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 B8034170 unquoted
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Dhttp.auth.digest.quoteParameters=true B8034170 quoted
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Djava.net.preferIPv6Addresses=true B8034170 unquoted
|
||||
*/
|
||||
|
||||
public class B8034170 {
|
||||
|
@ -63,10 +63,10 @@ import java.util.stream.Stream;
|
||||
* no real difference between BASICSERVER and BASIC - it should
|
||||
* be transparent on the client side.
|
||||
* @run main/othervm HTTPSetAuthenticatorTest NONE SERVER PROXY SERVER307 PROXY305
|
||||
* @run main/othervm HTTPSetAuthenticatorTest DIGEST SERVER
|
||||
* @run main/othervm HTTPSetAuthenticatorTest DIGEST PROXY
|
||||
* @run main/othervm HTTPSetAuthenticatorTest DIGEST PROXY305
|
||||
* @run main/othervm HTTPSetAuthenticatorTest DIGEST SERVER307
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPSetAuthenticatorTest DIGEST SERVER
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPSetAuthenticatorTest DIGEST PROXY
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPSetAuthenticatorTest DIGEST PROXY305
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPSetAuthenticatorTest DIGEST SERVER307
|
||||
* @run main/othervm HTTPSetAuthenticatorTest BASIC SERVER
|
||||
* @run main/othervm HTTPSetAuthenticatorTest BASIC PROXY
|
||||
* @run main/othervm HTTPSetAuthenticatorTest BASIC PROXY305
|
||||
|
@ -63,15 +63,16 @@ import static java.net.Proxy.NO_PROXY;
|
||||
* server that perform Digest authentication;
|
||||
* PROXY305: The server attempts to redirect
|
||||
* the client to a proxy using 305 code;
|
||||
* @run main/othervm HTTPTest SERVER
|
||||
* @run main/othervm HTTPTest PROXY
|
||||
* @run main/othervm HTTPTest SERVER307
|
||||
* @run main/othervm HTTPTest PROXY305
|
||||
* @run main/othervm -Dtest.debug=true -Dtest.digest.algorithm=SHA-512 HTTPTest SERVER
|
||||
* @run main/othervm -Dtest.debug=true -Dtest.digest.algorithm=SHA-256 HTTPTest SERVER
|
||||
* @run main/othervm -Dtest.debug=true -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPTest SERVER
|
||||
* @run main/othervm -Dtest.debug=true -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPTest PROXY
|
||||
* @run main/othervm -Dtest.debug=true -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPTest SERVER307
|
||||
* @run main/othervm -Dtest.debug=true -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPTest PROXY305
|
||||
*
|
||||
* @author danielfuchs
|
||||
*/
|
||||
public class HTTPTest {
|
||||
|
||||
public static final boolean DEBUG =
|
||||
Boolean.parseBoolean(System.getProperty("test.debug", "false"));
|
||||
public static enum HttpAuthType { SERVER, PROXY, SERVER307, PROXY305 };
|
||||
@ -194,6 +195,10 @@ public class HTTPTest {
|
||||
// silently skip unsupported test combination
|
||||
return;
|
||||
}
|
||||
String digestalg = System.getProperty("test.digest.algorithm");
|
||||
if (digestalg == null || "".equals(digestalg))
|
||||
digestalg = "MD5";
|
||||
|
||||
System.out.println("\n**** Testing " + protocol + " "
|
||||
+ mode + " mode ****\n");
|
||||
int authCount = AUTHENTICATOR.count.get();
|
||||
@ -205,7 +210,9 @@ public class HTTPTest {
|
||||
HTTPTestServer.create(protocol,
|
||||
mode,
|
||||
AUTHENTICATOR,
|
||||
getHttpSchemeType());
|
||||
getHttpSchemeType(),
|
||||
null,
|
||||
digestalg);
|
||||
try {
|
||||
expectedIncrement += run(server, protocol, mode);
|
||||
} finally {
|
||||
|
@ -117,12 +117,22 @@ public class HTTPTestServer extends HTTPTest {
|
||||
HttpSchemeType schemeType,
|
||||
HttpHandler delegate)
|
||||
throws IOException {
|
||||
return create(protocol, authType, auth, schemeType, null, "MD5");
|
||||
}
|
||||
|
||||
public static HTTPTestServer create(HttpProtocolType protocol,
|
||||
HttpAuthType authType,
|
||||
HttpTestAuthenticator auth,
|
||||
HttpSchemeType schemeType,
|
||||
HttpHandler delegate,
|
||||
String algorithm)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(authType);
|
||||
Objects.requireNonNull(auth);
|
||||
switch(authType) {
|
||||
// A server that performs Server Digest authentication.
|
||||
case SERVER: return createServer(protocol, authType, auth,
|
||||
schemeType, delegate, "/");
|
||||
schemeType, delegate, algorithm, "/");
|
||||
// A server that pretends to be a Proxy and performs
|
||||
// Proxy Digest authentication. If protocol is HTTPS,
|
||||
// then this will create a HttpsProxyTunnel that will
|
||||
@ -327,6 +337,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||
HttpTestAuthenticator auth,
|
||||
HttpSchemeType schemeType,
|
||||
HttpHandler delegate,
|
||||
String algorithm,
|
||||
String path)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(authType);
|
||||
@ -336,7 +347,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||
final HTTPTestServer server = new HTTPTestServer(impl, null, delegate);
|
||||
final HttpHandler hh = server.createHandler(schemeType, auth, authType);
|
||||
HttpContext ctxt = impl.createContext(path, hh);
|
||||
server.configureAuthentication(ctxt, schemeType, auth, authType);
|
||||
server.configureAuthentication(ctxt, schemeType, auth, authType, algorithm);
|
||||
impl.start();
|
||||
return server;
|
||||
}
|
||||
@ -357,7 +368,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||
: new HTTPTestServer(impl, null, delegate);
|
||||
final HttpHandler hh = server.createHandler(schemeType, auth, authType);
|
||||
HttpContext ctxt = impl.createContext(path, hh);
|
||||
server.configureAuthentication(ctxt, schemeType, auth, authType);
|
||||
server.configureAuthentication(ctxt, schemeType, auth, authType, null);
|
||||
impl.start();
|
||||
|
||||
return server;
|
||||
@ -385,7 +396,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||
? createProxy(protocol, targetAuthType,
|
||||
auth, schemeType, targetDelegate, "/")
|
||||
: createServer(targetProtocol, targetAuthType,
|
||||
auth, schemeType, targetDelegate, "/");
|
||||
auth, schemeType, targetDelegate, "MD5", "/");
|
||||
HttpServer impl = createHttpServer(protocol);
|
||||
final HTTPTestServer redirectingServer =
|
||||
new HTTPTestServer(impl, redirectTarget, null);
|
||||
@ -431,11 +442,11 @@ public class HTTPTestServer extends HTTPTest {
|
||||
private void configureAuthentication(HttpContext ctxt,
|
||||
HttpSchemeType schemeType,
|
||||
HttpTestAuthenticator auth,
|
||||
HttpAuthType authType) {
|
||||
HttpAuthType authType, String algorithm) {
|
||||
switch(schemeType) {
|
||||
case DIGEST:
|
||||
// DIGEST authentication is handled by the handler.
|
||||
ctxt.getFilters().add(new HttpDigestFilter(auth, authType));
|
||||
ctxt.getFilters().add(new HttpDigestFilter(auth, authType, algorithm));
|
||||
break;
|
||||
case BASIC:
|
||||
// BASIC authentication is handled by the filter.
|
||||
@ -603,15 +614,21 @@ public class HTTPTestServer extends HTTPTest {
|
||||
public static String computeDigest(boolean isRequest,
|
||||
String reqMethod,
|
||||
char[] password,
|
||||
String expectedAlgorithm,
|
||||
DigestResponse params)
|
||||
throws NoSuchAlgorithmException
|
||||
{
|
||||
|
||||
String A1, HashA1;
|
||||
String algorithm = params.getAlgorithm("MD5");
|
||||
boolean md5sess = algorithm.equalsIgnoreCase ("MD5-sess");
|
||||
if (algorithm.endsWith("-sess")) {
|
||||
algorithm = algorithm.substring(0, algorithm.length() - 5);
|
||||
}
|
||||
if (!algorithm.equalsIgnoreCase(expectedAlgorithm)) {
|
||||
throw new IllegalArgumentException("unexpected algorithm");
|
||||
}
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance(md5sess?"MD5":algorithm);
|
||||
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||||
|
||||
if (params.username == null) {
|
||||
throw new IllegalArgumentException("missing username");
|
||||
@ -776,13 +793,15 @@ public class HTTPTestServer extends HTTPTest {
|
||||
private final HttpTestAuthenticator auth;
|
||||
private final byte[] nonce;
|
||||
private final String ns;
|
||||
public HttpDigestFilter(HttpTestAuthenticator auth, HttpAuthType authType) {
|
||||
private final String algorithm;
|
||||
public HttpDigestFilter(HttpTestAuthenticator auth, HttpAuthType authType, String algorithm) {
|
||||
super(authType, authType == HttpAuthType.SERVER
|
||||
? "Digest Server" : "Digest Proxy");
|
||||
this.auth = auth;
|
||||
nonce = new byte[16];
|
||||
new Random(Instant.now().toEpochMilli()).nextBytes(nonce);
|
||||
ns = new BigInteger(1, nonce).toString(16);
|
||||
this.algorithm = (algorithm == null) ? "MD5" : algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -790,7 +809,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||
throws IOException {
|
||||
he.getResponseHeaders().add(getAuthenticate(),
|
||||
"Digest realm=\"" + auth.getRealm() + "\","
|
||||
+ "\r\n qop=\"auth\","
|
||||
+ "\r\n qop=\"auth\", " + "algorithm=\"" + algorithm + "\", "
|
||||
+ "\r\n nonce=\"" + ns +"\"");
|
||||
System.out.println(type + ": Requesting Digest Authentication "
|
||||
+ he.getResponseHeaders().getFirst(getAuthenticate()));
|
||||
@ -823,7 +842,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||
}
|
||||
|
||||
boolean validate(String reqMethod, DigestResponse dg) {
|
||||
if (!"MD5".equalsIgnoreCase(dg.getAlgorithm("MD5"))) {
|
||||
if (!this.algorithm.equalsIgnoreCase(dg.getAlgorithm("MD5"))) {
|
||||
System.out.println(type + ": Unsupported algorithm "
|
||||
+ dg.algorithm);
|
||||
return false;
|
||||
@ -854,7 +873,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||
|
||||
boolean verify(String reqMethod, DigestResponse dg, char[] pw)
|
||||
throws NoSuchAlgorithmException {
|
||||
String response = DigestResponse.computeDigest(true, reqMethod, pw, dg);
|
||||
String response = DigestResponse.computeDigest(true, reqMethod, pw, algorithm, dg);
|
||||
if (!dg.response.equals(response)) {
|
||||
System.out.println(type + ": bad response returned by client: "
|
||||
+ dg.response + " expected " + response);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, 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
|
||||
@ -24,6 +24,7 @@
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
@ -34,24 +35,51 @@ import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8138990
|
||||
* @bug 8138990 8281561
|
||||
* @summary Tests for HTTP Digest auth
|
||||
* The impl maintains a cache for auth info,
|
||||
* the testcases run in a separate JVM to avoid cache hits
|
||||
* @modules jdk.httpserver
|
||||
* @run main/othervm DigestAuth good
|
||||
* @run main/othervm DigestAuth only_nonce
|
||||
* @run main/othervm DigestAuth sha1
|
||||
* @run main/othervm DigestAuth no_header
|
||||
* @run main/othervm DigestAuth no_nonce
|
||||
* @run main/othervm DigestAuth no_qop
|
||||
* @run main/othervm DigestAuth invalid_alg
|
||||
* @run main/othervm DigestAuth validate_server
|
||||
* @run main/othervm DigestAuth validate_server_no_qop
|
||||
* @run main/othervm DigestAuth bad
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth good
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth only_nonce
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=SHA-1 DigestAuth sha1-good
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth sha1-bad
|
||||
* @run main/othervm DigestAuth sha256
|
||||
* @run main/othervm DigestAuth sha512
|
||||
* @run main/othervm DigestAuth sha256-userhash
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth sha256
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth no_header
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth no_nonce
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth no_qop
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth invalid_alg
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth validate_server
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth validate_server_no_qop
|
||||
*/
|
||||
|
||||
/*
|
||||
* The sha512-256-userhash case must be run manually. It needs to run with sudo as the
|
||||
* test must bind to port 80. You also need a modified JDK where
|
||||
* sun.net.www.protocol.http.DigestAuthentication.getCnonce
|
||||
* returns the hardcoded cnonce value below (normally it is chosen at random)
|
||||
* "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v"
|
||||
* It can be run from the command line directly as follows:
|
||||
* sudo java -Djdk.net.hosts.file=hosts DigestAuth sha512-256-userhash port80
|
||||
* assuming you are running in the test source directory
|
||||
*/
|
||||
public class DigestAuth {
|
||||
|
||||
@ -88,6 +116,28 @@ public class DigestAuth {
|
||||
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
|
||||
+ "algorithm=\"SHA1\"";
|
||||
|
||||
static final String WWW_AUTH_HEADER_SHA256 = "Digest "
|
||||
+ "nonce=\"a69ae8a2e17c219bc6c118b673e93601616a6a"
|
||||
+ "4d8fde3a19996748d77ad0464b\", qop=\"auth\", "
|
||||
+ "opaque=\"efc62777cff802cb29252f626b041f381cd360"
|
||||
+ "7187115871ca25e7b51a3757e9\", algorithm=SHA-256";
|
||||
|
||||
static final String WWW_AUTH_HEADER_SHA512 = "Digest "
|
||||
+ "nonce=\"9aaa8d3ae53b54ce653a5d52d895afcd9c0e430"
|
||||
+ "a17bdf98bb34235af84fba268d31376a63e0c39079b519"
|
||||
+ "c14baa0429754266f35b62a47b9c8b5d3d36c638282\","
|
||||
+ " qop=\"auth\", opaque=\"28cdc6bae6c5dd7ec89dbf"
|
||||
+ "af4d4f26b70f41ebbb83dc7af0950d6de016c40f412224"
|
||||
+ "676cd45ebcf889a70e65a2b055a8b5232e50281272ba7c"
|
||||
+ "67628cc3bb3492\", algorithm=SHA-512";
|
||||
|
||||
static final String WWW_AUTH_HEADER_SHA_256_UHASH = "Digest "
|
||||
+ "realm=\"testrealm@host.com\", "
|
||||
+ "qop=\"auth\", algorithm=SHA-256,"
|
||||
+ "nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC"
|
||||
+ "/RVvkK\", opaque=\"HRPCssKJSGjCrkzDg8OhwpzCiGP"
|
||||
+ "ChXYjwrI2QmXDnsOS\", charset=UTF-8, userhash=true";
|
||||
|
||||
static final String WWW_AUTH_HEADER_INVALID_ALGORITHM = "Digest "
|
||||
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
|
||||
+ "algorithm=\"SHA123\"";
|
||||
@ -106,23 +156,77 @@ public class DigestAuth {
|
||||
+ "nc=00000001, "
|
||||
+ "qop=auth";
|
||||
|
||||
// These two must be run manually with a modified JDK
|
||||
// that generates the exact cnonce given below.
|
||||
static final String SHA_512_256_FIRST = "Digest "
|
||||
+ "realm=\"api@example.org\", "
|
||||
+ "qop=\"auth\", "
|
||||
+ "algorithm=SHA-512-256, "
|
||||
+ "nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", "
|
||||
+ "opaque=\"HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS\", "
|
||||
+ "charset=UTF-8, "
|
||||
+ "userhash=true ";
|
||||
|
||||
// Below taken from corrected version of RFC 7616
|
||||
static final Map<String,String> SHA_512_256_EXPECTED =
|
||||
Map.ofEntries(
|
||||
entry("username", "793263caabb707a56211940d90411ea4a575adeccb"
|
||||
+ "7e360aeb624ed06ece9b0b"),
|
||||
entry("realm", "api@example.org"),
|
||||
entry("uri", "/doe.json"),
|
||||
entry("algorithm", "SHA-512-256"),
|
||||
entry("nonce", "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK"),
|
||||
entry("nc", "00000001"),
|
||||
entry("cnonce", "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v"),
|
||||
entry("qop", "auth"),
|
||||
entry("response", "3798d4131c277846293534c3edc11bd8a5e4cdcbff78"
|
||||
+ "b05db9d95eeb1cec68a5"),
|
||||
entry("opaque", "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS"),
|
||||
entry("userhash", "true"));
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length == 0) {
|
||||
throw new RuntimeException("No testcase specified");
|
||||
}
|
||||
String testcase = args[0];
|
||||
System.out.println("Running test: " + testcase);
|
||||
boolean usePort80 = args.length > 1 && args[1].equals("port80");
|
||||
|
||||
// start a local HTTP server
|
||||
try (LocalHttpServer server = LocalHttpServer.startServer()) {
|
||||
try (LocalHttpServer server = LocalHttpServer.startServer(usePort80)) {
|
||||
|
||||
// set authenticator
|
||||
AuthenticatorImpl auth = new AuthenticatorImpl();
|
||||
Authenticator.setDefault(auth);
|
||||
|
||||
String url = String.format("http://%s/test/", server.getAuthority());
|
||||
|
||||
boolean success = true;
|
||||
switch (testcase) {
|
||||
case "sha512-256-userhash":
|
||||
auth = new AuthenticatorImpl("J\u00e4s\u00f8n Doe", "Secret, or not?");
|
||||
// file based name service must be used so domain
|
||||
// below resolves to localhost
|
||||
if (usePort80) {
|
||||
url = "http://api.example.org/doe.json";
|
||||
} else {
|
||||
url = "http://api.example.org:" + server.getPort() + "/doe.json";
|
||||
}
|
||||
server.setWWWAuthHeader(SHA_512_256_FIRST);
|
||||
server.setExpectedRequestParams(SHA_512_256_EXPECTED);
|
||||
success = testAuth(url, auth, EXPECT_DIGEST);
|
||||
break;
|
||||
case "bad":
|
||||
// server returns a good WWW-Authenticate header with MD5
|
||||
// but MD5 is disallowed by default
|
||||
server.setWWWAuthHeader(GOOD_WWW_AUTH_HEADER);
|
||||
success = testAuth(url, auth, EXPECT_FAILURE);
|
||||
if (auth.lastRequestedPrompt == null ||
|
||||
!auth.lastRequestedPrompt.equals(REALM)) {
|
||||
System.out.println("Unexpected realm: "
|
||||
+ auth.lastRequestedPrompt);
|
||||
success = false;
|
||||
}
|
||||
break;
|
||||
case "good":
|
||||
// server returns a good WWW-Authenticate header
|
||||
server.setWWWAuthHeader(GOOD_WWW_AUTH_HEADER);
|
||||
@ -201,11 +305,36 @@ public class DigestAuth {
|
||||
success = false;
|
||||
}
|
||||
break;
|
||||
case "sha1":
|
||||
case "sha1-good":
|
||||
// server returns a good WWW-Authenticate header with SHA-1
|
||||
server.setWWWAuthHeader(WWW_AUTH_HEADER_SHA1);
|
||||
success = testAuth(url, auth, EXPECT_DIGEST);
|
||||
break;
|
||||
case "sha1-bad":
|
||||
// server returns a WWW-Authenticate header with SHA-1
|
||||
// but SHA-1 disabled
|
||||
server.setWWWAuthHeader(WWW_AUTH_HEADER_SHA1);
|
||||
success = testAuth(url, auth, EXPECT_FAILURE);
|
||||
break;
|
||||
case "sha256":
|
||||
// server returns a good WWW-Authenticate header with SHA-256
|
||||
server.setWWWAuthHeader(WWW_AUTH_HEADER_SHA256);
|
||||
success = testAuth(url, auth, EXPECT_DIGEST);
|
||||
break;
|
||||
case "sha512":
|
||||
// server returns a good WWW-Authenticate header with SHA-512
|
||||
server.setWWWAuthHeader(WWW_AUTH_HEADER_SHA512);
|
||||
success = testAuth(url, auth, EXPECT_DIGEST);
|
||||
break;
|
||||
case "sha256-userhash":
|
||||
// server returns a good WWW-Authenticate header with SHA-256
|
||||
// also sets the userhash=true parameter
|
||||
server.setWWWAuthHeader(WWW_AUTH_HEADER_SHA_256_UHASH);
|
||||
success = testAuth(url, auth, EXPECT_DIGEST);
|
||||
// make sure the userhash parameter was set correctly
|
||||
// and the username itself is the correct hash
|
||||
server.checkUserHash(getUserHash("SHA-256", "Mufasa", REALM));
|
||||
break;
|
||||
case "no_header":
|
||||
// server returns no WWW-Authenticate header
|
||||
success = testAuth(url, auth, EXPECT_FAILURE);
|
||||
@ -251,7 +380,7 @@ public class DigestAuth {
|
||||
try {
|
||||
System.out.printf("Connect to %s, expected auth scheme is '%s'%n",
|
||||
url, expectedScheme);
|
||||
load(url);
|
||||
load(url, auth);
|
||||
|
||||
if (expectedScheme == null) {
|
||||
System.out.println("Unexpected successful connection");
|
||||
@ -276,8 +405,9 @@ public class DigestAuth {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void load(String url) throws IOException {
|
||||
URLConnection conn = new URL(url).openConnection();
|
||||
static void load(String url, Authenticator auth) throws IOException {
|
||||
HttpURLConnection conn = (HttpURLConnection)(new URL(url).openConnection());
|
||||
conn.setAuthenticator(auth);
|
||||
conn.setUseCaches(false);
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream()))) {
|
||||
@ -292,20 +422,47 @@ public class DigestAuth {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getUserHash(String alg, String user, String realm) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(alg);
|
||||
String msg = user + ":" + realm;
|
||||
//String msg = "Mufasa:testrealm@host.com";
|
||||
byte[] output = md.digest(msg.getBytes(StandardCharsets.ISO_8859_1));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i=0; i<output.length; i++) {
|
||||
String s1 = Integer.toHexString(output[i] & 0xf);
|
||||
String s2 = Integer.toHexString(Byte.toUnsignedInt(output[i]) >>> 4);
|
||||
sb.append(s2).append(s1);
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AuthenticatorImpl extends Authenticator {
|
||||
|
||||
private String lastRequestedScheme;
|
||||
private String lastRequestedPrompt;
|
||||
|
||||
private final String user, pass;
|
||||
|
||||
AuthenticatorImpl() {
|
||||
this("Mufasa", "Circle Of Life");
|
||||
}
|
||||
|
||||
AuthenticatorImpl(String user, String pass) {
|
||||
this.user = user;
|
||||
this.pass = pass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordAuthentication getPasswordAuthentication() {
|
||||
lastRequestedScheme = getRequestingScheme();
|
||||
lastRequestedPrompt = getRequestingPrompt();
|
||||
System.out.println("AuthenticatorImpl: requested "
|
||||
+ lastRequestedScheme);
|
||||
|
||||
return new PasswordAuthentication("Mufasa",
|
||||
"Circle Of Life".toCharArray());
|
||||
return new PasswordAuthentication(user, pass.toCharArray());
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,6 +473,9 @@ public class DigestAuth {
|
||||
private volatile String wwwAuthHeader = null;
|
||||
private volatile String authInfoHeader = null;
|
||||
private volatile String lastRequestedNonce;
|
||||
private volatile String lastRequestedUser;
|
||||
private volatile String lastRequestedUserhash;
|
||||
private volatile Map<String,String> expectedParams;
|
||||
|
||||
private LocalHttpServer(HttpServer server) {
|
||||
this.server = server;
|
||||
@ -335,14 +495,49 @@ public class DigestAuth {
|
||||
this.wwwAuthHeader = wwwAuthHeader;
|
||||
}
|
||||
|
||||
void setExpectedRequestParams(Map<String,String> params) {
|
||||
this.expectedParams = params;
|
||||
}
|
||||
|
||||
void setAuthInfoHeader(String authInfoHeader) {
|
||||
this.authInfoHeader = authInfoHeader;
|
||||
}
|
||||
|
||||
static LocalHttpServer startServer() throws IOException {
|
||||
void checkUserHash(String expectedUser) {
|
||||
boolean pass = true;
|
||||
if (!expectedUser.equals(lastRequestedUser)) {
|
||||
System.out.println("Username mismatch:");
|
||||
System.out.println("Expected: " + expectedUser);
|
||||
System.out.println("Received: " + lastRequestedUser);
|
||||
pass = false;
|
||||
}
|
||||
if (!lastRequestedUserhash.equalsIgnoreCase("true")) {
|
||||
System.out.println("Userhash mismatch:");
|
||||
pass = false;
|
||||
}
|
||||
if (!pass) {
|
||||
throw new RuntimeException("Test failed: checkUserHash");
|
||||
}
|
||||
}
|
||||
|
||||
void checkExpectedParams(String header) {
|
||||
if (expectedParams == null)
|
||||
return;
|
||||
expectedParams.forEach((name, value) -> {
|
||||
String rxValue = findParameter(header, name);
|
||||
if (!rxValue.equalsIgnoreCase(value)) {
|
||||
throw new RuntimeException("value mismatch "
|
||||
+ "name = " + name + " (" + rxValue + "/"
|
||||
+ value + ")");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static LocalHttpServer startServer(boolean usePort80) throws IOException {
|
||||
int port = usePort80 ? 80 : 0;
|
||||
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||
HttpServer httpServer = HttpServer.create(
|
||||
new InetSocketAddress(loopback, 0), 0);
|
||||
new InetSocketAddress(loopback, port), 0);
|
||||
LocalHttpServer localHttpServer = new LocalHttpServer(httpServer);
|
||||
localHttpServer.start();
|
||||
|
||||
@ -351,6 +546,7 @@ public class DigestAuth {
|
||||
|
||||
void start() {
|
||||
server.createContext("/test", this);
|
||||
server.createContext("/", this);
|
||||
server.start();
|
||||
System.out.println("HttpServer: started on port " + getAuthority());
|
||||
}
|
||||
@ -385,7 +581,10 @@ public class DigestAuth {
|
||||
t.getResponseHeaders().add("Authentication-Info",
|
||||
authInfoHeader);
|
||||
}
|
||||
checkExpectedParams(header);
|
||||
lastRequestedNonce = findParameter(header, "nonce");
|
||||
lastRequestedUser = findParameter(header, "username");
|
||||
lastRequestedUserhash = findParameter(header, "userhash");
|
||||
byte[] output = "hello".getBytes();
|
||||
t.sendResponseHeaders(200, output.length);
|
||||
t.getResponseBody().write(output);
|
||||
|
1
test/jdk/sun/net/www/http/HttpURLConnection/hosts
Normal file
1
test/jdk/sun/net/www/http/HttpURLConnection/hosts
Normal file
@ -0,0 +1 @@
|
||||
127.0.0.1 api.example.org
|
@ -25,14 +25,18 @@
|
||||
* @test
|
||||
* @bug 4432213
|
||||
* @modules java.base/sun.net.www
|
||||
* @run main/othervm -Dhttp.auth.digest.validateServer=true DigestTest
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Dhttp.auth.digest.validateServer=true DigestTest
|
||||
* @run main/othervm -Dhttp.auth.digest.validateServer=true
|
||||
-Dtest.succeed=true DigestTest
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Djava.net.preferIPv6Addresses=true
|
||||
* -Dhttp.auth.digest.validateServer=true DigestTest
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Dhttp.auth.digest.validateServer=true
|
||||
-Dtest.succeed=true DigestTest
|
||||
* -Dtest.succeed=true DigestTest
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Djava.net.preferIPv6Addresses=true
|
||||
* -Dhttp.auth.digest.validateServer=true
|
||||
* -Dtest.succeed=true DigestTest
|
||||
* @summary Need to support Digest Authentication for Proxies
|
||||
*/
|
||||
|
||||
|
@ -27,8 +27,9 @@
|
||||
* @summary Sanity check that NTLM will not be selected by the http protocol
|
||||
* handler when running on a profile that does not support NTLM
|
||||
* @modules java.base/sun.net.www.protocol.http:open
|
||||
* @run main/othervm NoNTLM
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true NoNTLM
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 NoNTLM
|
||||
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||
* -Djava.net.preferIPv6Addresses=true NoNTLM
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
|
Loading…
x
Reference in New Issue
Block a user