mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
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
14 changed files with 571 additions and 95 deletions
|
@ -224,6 +224,14 @@ of proxies.</P>
|
||||||
property is defined, then its value will be used as the domain
|
property is defined, then its value will be used as the domain
|
||||||
name.</P>
|
name.</P>
|
||||||
</OL>
|
</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>
|
<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
|
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
|
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.PasswordAuthentication;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.net.URL;
|
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.AccessController;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.text.Normalizer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import sun.net.NetProperties;
|
import sun.net.NetProperties;
|
||||||
import sun.net.www.HeaderParser;
|
import sun.net.www.HeaderParser;
|
||||||
import sun.nio.cs.ISO_8859_1;
|
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;
|
import static sun.net.www.protocol.http.HttpURLConnection.HTTP_CONNECT;
|
||||||
|
|
||||||
|
@ -57,22 +72,67 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
|
|
||||||
private String authMethod;
|
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";
|
"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
|
// true if http.auth.digest.quoteParameters Net property is true
|
||||||
private static final boolean delimCompatFlag;
|
private static final boolean delimCompatFlag;
|
||||||
|
|
||||||
|
private static final PlatformLogger logger =
|
||||||
|
HttpURLConnection.getHttpLogger();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
Boolean b = AccessController.doPrivileged(
|
Boolean b = AccessController.doPrivileged(
|
||||||
new PrivilegedAction<>() {
|
(PrivilegedAction<Boolean>) () -> NetProperties.getBoolean(compatPropName)
|
||||||
public Boolean run() {
|
|
||||||
return NetProperties.getBoolean(compatPropName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
delimCompatFlag = (b == null) ? false : b.booleanValue();
|
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.
|
// Authentication parameters defined in RFC2617.
|
||||||
|
@ -90,9 +150,18 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
private String cnonce;
|
private String cnonce;
|
||||||
private String nonce;
|
private String nonce;
|
||||||
private String algorithm;
|
private String algorithm;
|
||||||
|
// Normally same as algorithm, but excludes the -SESS suffix if present
|
||||||
|
private String digestName;
|
||||||
|
private String charset;
|
||||||
private int NCcount=0;
|
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;
|
private String cachedHA1;
|
||||||
|
|
||||||
// Force the HA1 value to be recalculated because the nonce has changed
|
// Force the HA1 value to be recalculated because the nonce has changed
|
||||||
|
@ -112,8 +181,10 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
serverQop = false;
|
serverQop = false;
|
||||||
opaque = null;
|
opaque = null;
|
||||||
algorithm = null;
|
algorithm = null;
|
||||||
|
digestName = null;
|
||||||
cachedHA1 = null;
|
cachedHA1 = null;
|
||||||
nonce = null;
|
nonce = null;
|
||||||
|
charset = null;
|
||||||
setNewCnonce();
|
setNewCnonce();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +222,24 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
redoCachedHA1 = true;
|
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) {
|
synchronized void setQop (String qop) {
|
||||||
if (qop != null) {
|
if (qop != null) {
|
||||||
String items[] = qop.split(",");
|
String items[] = qop.split(",");
|
||||||
|
@ -191,7 +280,13 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized String getAlgorithm () { return algorithm;}
|
synchronized String getAlgorithm () { return algorithm;}
|
||||||
|
synchronized String getDigestName () {
|
||||||
|
return digestName;
|
||||||
|
}
|
||||||
synchronized void setAlgorithm (String s) { algorithm=s;}
|
synchronized void setAlgorithm (String s) { algorithm=s;}
|
||||||
|
synchronized void setDigestName (String s) {
|
||||||
|
this.digestName = s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Parameters params;
|
Parameters params;
|
||||||
|
@ -309,6 +404,16 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
params.setNonce (p.findValue("nonce"));
|
params.setNonce (p.findValue("nonce"));
|
||||||
params.setOpaque (p.findValue("opaque"));
|
params.setOpaque (p.findValue("opaque"));
|
||||||
params.setQop (p.findValue("qop"));
|
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 uri="";
|
||||||
String method;
|
String method;
|
||||||
|
@ -333,11 +438,9 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
authMethod = Character.toUpperCase(authMethod.charAt(0))
|
authMethod = Character.toUpperCase(authMethod.charAt(0))
|
||||||
+ authMethod.substring(1).toLowerCase();
|
+ authMethod.substring(1).toLowerCase();
|
||||||
}
|
}
|
||||||
String algorithm = p.findValue("algorithm");
|
|
||||||
if (algorithm == null || algorithm.isEmpty()) {
|
if (!setAlgorithmNames(p, params))
|
||||||
algorithm = "MD5"; // The default, accoriding to rfc2069
|
return false;
|
||||||
}
|
|
||||||
params.setAlgorithm (algorithm);
|
|
||||||
|
|
||||||
// If authQop is true, then the server is doing RFC2617 and
|
// If authQop is true, then the server is doing RFC2617 and
|
||||||
// has offered qop=auth. We do not support any other modes
|
// 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
|
/* Calculate the Authorization header field given the request URI
|
||||||
* and based on the authorization information in params
|
* and based on the authorization information in params
|
||||||
*/
|
*/
|
||||||
|
@ -367,6 +502,14 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
String cnonce = params.getCnonce ();
|
String cnonce = params.getCnonce ();
|
||||||
String nonce = params.getNonce ();
|
String nonce = params.getNonce ();
|
||||||
String algorithm = params.getAlgorithm ();
|
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 ();
|
params.incrementNC ();
|
||||||
int nccount = params.getNCCount ();
|
int nccount = params.getNCCount ();
|
||||||
String ncstring=null;
|
String ncstring=null;
|
||||||
|
@ -378,10 +521,14 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
ncstring = zeroPad [len] + ncstring;
|
ncstring = zeroPad [len] + ncstring;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean session = algorithm.endsWith ("-sess");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = computeDigest(true, pw.getUserName(),passwd,realm,
|
response = computeDigest(true, pw.getUserName(),passwd,realm,
|
||||||
method, uri, nonce, cnonce, ncstring);
|
method, uri, nonce, cnonce, ncstring,
|
||||||
} catch (NoSuchAlgorithmException ex) {
|
digest, session, charset);
|
||||||
|
} catch (CharacterCodingException | NoSuchAlgorithmException ex) {
|
||||||
|
log(ex.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,11 +549,24 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
qopS = ", qop=auth";
|
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
|
String value = authMethod
|
||||||
+ " username=\"" + pw.getUserName()
|
+ " username=\"" + user
|
||||||
+ "\", realm=\"" + realm
|
+ "\", realm=\"" + realm
|
||||||
+ "\", nonce=\"" + nonce
|
+ "\", nonce=\"" + nonce
|
||||||
+ ncfield
|
+ ncfield
|
||||||
|
+ userhashField
|
||||||
+ ", uri=\"" + uri
|
+ ", uri=\"" + uri
|
||||||
+ "\", response=\"" + response + "\""
|
+ "\", response=\"" + response + "\""
|
||||||
+ algoS;
|
+ algoS;
|
||||||
|
@ -427,6 +587,27 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
checkResponse (header, method, url.getFile());
|
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)
|
public void checkResponse (String header, String method, String uri)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
char[] passwd = pw.getPassword();
|
char[] passwd = pw.getPassword();
|
||||||
|
@ -436,6 +617,9 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
String cnonce = params.cnonce;
|
String cnonce = params.cnonce;
|
||||||
String nonce = params.getNonce ();
|
String nonce = params.getNonce ();
|
||||||
String algorithm = params.getAlgorithm ();
|
String algorithm = params.getAlgorithm ();
|
||||||
|
String digest = params.getDigestName ();
|
||||||
|
Charset charset = params.getCharset();
|
||||||
|
validateDigest(digest);
|
||||||
int nccount = params.getNCCount ();
|
int nccount = params.getNCCount ();
|
||||||
String ncstring=null;
|
String ncstring=null;
|
||||||
|
|
||||||
|
@ -443,15 +627,21 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
throw new ProtocolException ("No authentication information in response");
|
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) {
|
if (nccount != -1) {
|
||||||
ncstring = Integer.toHexString (nccount).toUpperCase();
|
ncstring = Integer.toHexString (nccount).toUpperCase(Locale.ROOT);
|
||||||
int len = ncstring.length();
|
int len = ncstring.length();
|
||||||
if (len < 8)
|
if (len < 8)
|
||||||
ncstring = zeroPad [len] + ncstring;
|
ncstring = zeroPad [len] + ncstring;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String expected = computeDigest(false, username,passwd,realm,
|
String expected = computeDigest(false, username,passwd,realm, method, uri,
|
||||||
method, uri, nonce, cnonce, ncstring);
|
nonce, cnonce, ncstring, digest,
|
||||||
|
session, charset);
|
||||||
HeaderParser p = new HeaderParser (header);
|
HeaderParser p = new HeaderParser (header);
|
||||||
String rspauth = p.findValue ("rspauth");
|
String rspauth = p.findValue ("rspauth");
|
||||||
if (rspauth == null) {
|
if (rspauth == null) {
|
||||||
|
@ -468,34 +658,45 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
|
|
||||||
} catch (NoSuchAlgorithmException ex) {
|
} catch (NoSuchAlgorithmException ex) {
|
||||||
throw new ProtocolException ("Unsupported algorithm in response");
|
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(
|
private String computeDigest(
|
||||||
boolean isRequest, String userName, char[] password,
|
boolean isRequest, String userName, char[] password,
|
||||||
String realm, String connMethod,
|
String realm, String connMethod,
|
||||||
String requestURI, String nonceString,
|
String requestURI, String nonceString,
|
||||||
String cnonce, String ncValue
|
String cnonce, String ncValue,
|
||||||
) throws NoSuchAlgorithmException
|
String algorithm, boolean session,
|
||||||
|
Charset charset
|
||||||
|
) throws NoSuchAlgorithmException, CharacterCodingException
|
||||||
{
|
{
|
||||||
|
|
||||||
String A1, HashA1;
|
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) {
|
if ((HashA1 = params.getCachedHA1 ()) == null) {
|
||||||
String s = userName + ":" + realm + ":";
|
String s = userName + ":" + realm + ":";
|
||||||
String s1 = encode (s, password, md);
|
String s1 = encode (s, password, md, charset);
|
||||||
A1 = s1 + ":" + nonceString + ":" + cnonce;
|
A1 = s1 + ":" + nonceString + ":" + cnonce;
|
||||||
HashA1 = encode(A1, null, md);
|
HashA1 = encode(A1, null, md, charset);
|
||||||
params.setCachedHA1 (HashA1);
|
params.setCachedHA1 (HashA1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
A1 = userName + ":" + realm + ":";
|
A1 = userName + ":" + realm + ":";
|
||||||
HashA1 = encode(A1, password, md);
|
HashA1 = encode(A1, password, md, charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
String A2;
|
String A2;
|
||||||
|
@ -504,7 +705,7 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
} else {
|
} else {
|
||||||
A2 = ":" + requestURI;
|
A2 = ":" + requestURI;
|
||||||
}
|
}
|
||||||
String HashA2 = encode(A2, null, md);
|
String HashA2 = encode(A2, null, md, ISO_8859_1.INSTANCE);
|
||||||
String combo, finalHash;
|
String combo, finalHash;
|
||||||
|
|
||||||
if (params.authQop()) { /* RRC2617 when qop=auth */
|
if (params.authQop()) { /* RRC2617 when qop=auth */
|
||||||
|
@ -516,7 +717,7 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
nonceString + ":" +
|
nonceString + ":" +
|
||||||
HashA2;
|
HashA2;
|
||||||
}
|
}
|
||||||
finalHash = encode(combo, null, md);
|
finalHash = encode(combo, null, md, ISO_8859_1.INSTANCE);
|
||||||
return finalHash;
|
return finalHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,17 +731,28 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
"00000000", "0000000", "000000", "00000", "0000", "000", "00", "0"
|
"00000000", "0000000", "000000", "00000", "0000", "000", "00", "0"
|
||||||
};
|
};
|
||||||
|
|
||||||
private String encode(String src, char[] passwd, MessageDigest md) {
|
private String encode(String src, char[] passwd, MessageDigest md, Charset charset)
|
||||||
md.update(src.getBytes(ISO_8859_1.INSTANCE));
|
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) {
|
if (passwd != null) {
|
||||||
byte[] passwdBytes = new byte[passwd.length];
|
byte[] passwdBytes;
|
||||||
for (int i=0; i<passwd.length; i++)
|
if (isUtf8) {
|
||||||
passwdBytes[i] = (byte)passwd[i];
|
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);
|
md.update(passwdBytes);
|
||||||
Arrays.fill(passwdBytes, (byte)0x00);
|
Arrays.fill(passwdBytes, (byte)0x00);
|
||||||
}
|
}
|
||||||
byte[] digest = md.digest();
|
byte[] digest = md.digest();
|
||||||
|
|
||||||
StringBuilder res = new StringBuilder(digest.length * 2);
|
StringBuilder res = new StringBuilder(digest.length * 2);
|
||||||
for (int i = 0; i < digest.length; i++) {
|
for (int i = 0; i < digest.length; i++) {
|
||||||
int hashchar = ((digest[i] >>> 4) & 0xf);
|
int hashchar = ((digest[i] >>> 4) & 0xf);
|
||||||
|
@ -550,4 +762,15 @@ class DigestAuthentication extends AuthenticationInfo {
|
||||||
}
|
}
|
||||||
return res.toString();
|
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, \
|
jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \
|
||||||
DSA keySize < 1024, SHA1 denyAfter 2019-01-01
|
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
|
# Algorithm restrictions for Secure Socket Layer/Transport Layer Security
|
||||||
# (SSL/TLS/DTLS) processing
|
# (SSL/TLS/DTLS) processing
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
* @test
|
* @test
|
||||||
* @bug 4722333
|
* @bug 4722333
|
||||||
* @library /test/lib
|
* @library /test/lib
|
||||||
* @run main/othervm B4722333
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 B4722333
|
||||||
* @summary JRE Proxy Authentication Not Working with ISA2000
|
* @summary JRE Proxy Authentication Not Working with ISA2000
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
* @test
|
* @test
|
||||||
* @bug 4759514
|
* @bug 4759514
|
||||||
* @library /test/lib
|
* @library /test/lib
|
||||||
* @run main/othervm B4759514
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 B4759514
|
||||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true 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
|
* @summary Digest Authentication is erroniously quoting the nc value, contrary to RFC 2617
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,10 @@
|
||||||
* @test
|
* @test
|
||||||
* @bug 6870935
|
* @bug 6870935
|
||||||
* @modules java.base/sun.net.www
|
* @modules java.base/sun.net.www
|
||||||
* @run main/othervm -Dhttp.nonProxyHosts="" -Dhttp.auth.digest.validateProxy=true B6870935
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true
|
* -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
|
* -Dhttp.nonProxyHosts="" -Dhttp.auth.digest.validateProxy=true B6870935
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,11 @@ import jdk.test.lib.net.URIBuilder;
|
||||||
* @bug 8034170
|
* @bug 8034170
|
||||||
* @summary Digest authentication interop issue
|
* @summary Digest authentication interop issue
|
||||||
* @library /test/lib
|
* @library /test/lib
|
||||||
* @run main/othervm B8034170 unquoted
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 B8034170 unquoted
|
||||||
* @run main/othervm -Dhttp.auth.digest.quoteParameters=true B8034170 quoted
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true B8034170 unquoted
|
* -Dhttp.auth.digest.quoteParameters=true B8034170 quoted
|
||||||
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||||
|
* -Djava.net.preferIPv6Addresses=true B8034170 unquoted
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class B8034170 {
|
public class B8034170 {
|
||||||
|
|
|
@ -63,10 +63,10 @@ import java.util.stream.Stream;
|
||||||
* no real difference between BASICSERVER and BASIC - it should
|
* no real difference between BASICSERVER and BASIC - it should
|
||||||
* be transparent on the client side.
|
* be transparent on the client side.
|
||||||
* @run main/othervm HTTPSetAuthenticatorTest NONE SERVER PROXY SERVER307 PROXY305
|
* @run main/othervm HTTPSetAuthenticatorTest NONE SERVER PROXY SERVER307 PROXY305
|
||||||
* @run main/othervm HTTPSetAuthenticatorTest DIGEST SERVER
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPSetAuthenticatorTest DIGEST SERVER
|
||||||
* @run main/othervm HTTPSetAuthenticatorTest DIGEST PROXY
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPSetAuthenticatorTest DIGEST PROXY
|
||||||
* @run main/othervm HTTPSetAuthenticatorTest DIGEST PROXY305
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPSetAuthenticatorTest DIGEST PROXY305
|
||||||
* @run main/othervm HTTPSetAuthenticatorTest DIGEST SERVER307
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPSetAuthenticatorTest DIGEST SERVER307
|
||||||
* @run main/othervm HTTPSetAuthenticatorTest BASIC SERVER
|
* @run main/othervm HTTPSetAuthenticatorTest BASIC SERVER
|
||||||
* @run main/othervm HTTPSetAuthenticatorTest BASIC PROXY
|
* @run main/othervm HTTPSetAuthenticatorTest BASIC PROXY
|
||||||
* @run main/othervm HTTPSetAuthenticatorTest BASIC PROXY305
|
* @run main/othervm HTTPSetAuthenticatorTest BASIC PROXY305
|
||||||
|
|
|
@ -63,15 +63,16 @@ import static java.net.Proxy.NO_PROXY;
|
||||||
* server that perform Digest authentication;
|
* server that perform Digest authentication;
|
||||||
* PROXY305: The server attempts to redirect
|
* PROXY305: The server attempts to redirect
|
||||||
* the client to a proxy using 305 code;
|
* the client to a proxy using 305 code;
|
||||||
* @run main/othervm HTTPTest SERVER
|
* @run main/othervm -Dtest.debug=true -Dtest.digest.algorithm=SHA-512 HTTPTest SERVER
|
||||||
* @run main/othervm HTTPTest PROXY
|
* @run main/othervm -Dtest.debug=true -Dtest.digest.algorithm=SHA-256 HTTPTest SERVER
|
||||||
* @run main/othervm HTTPTest SERVER307
|
* @run main/othervm -Dtest.debug=true -Dhttp.auth.digest.reEnabledAlgorithms=MD5 HTTPTest SERVER
|
||||||
* @run main/othervm HTTPTest PROXY305
|
* @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
|
* @author danielfuchs
|
||||||
*/
|
*/
|
||||||
public class HTTPTest {
|
public class HTTPTest {
|
||||||
|
|
||||||
public static final boolean DEBUG =
|
public static final boolean DEBUG =
|
||||||
Boolean.parseBoolean(System.getProperty("test.debug", "false"));
|
Boolean.parseBoolean(System.getProperty("test.debug", "false"));
|
||||||
public static enum HttpAuthType { SERVER, PROXY, SERVER307, PROXY305 };
|
public static enum HttpAuthType { SERVER, PROXY, SERVER307, PROXY305 };
|
||||||
|
@ -194,6 +195,10 @@ public class HTTPTest {
|
||||||
// silently skip unsupported test combination
|
// silently skip unsupported test combination
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
String digestalg = System.getProperty("test.digest.algorithm");
|
||||||
|
if (digestalg == null || "".equals(digestalg))
|
||||||
|
digestalg = "MD5";
|
||||||
|
|
||||||
System.out.println("\n**** Testing " + protocol + " "
|
System.out.println("\n**** Testing " + protocol + " "
|
||||||
+ mode + " mode ****\n");
|
+ mode + " mode ****\n");
|
||||||
int authCount = AUTHENTICATOR.count.get();
|
int authCount = AUTHENTICATOR.count.get();
|
||||||
|
@ -205,7 +210,9 @@ public class HTTPTest {
|
||||||
HTTPTestServer.create(protocol,
|
HTTPTestServer.create(protocol,
|
||||||
mode,
|
mode,
|
||||||
AUTHENTICATOR,
|
AUTHENTICATOR,
|
||||||
getHttpSchemeType());
|
getHttpSchemeType(),
|
||||||
|
null,
|
||||||
|
digestalg);
|
||||||
try {
|
try {
|
||||||
expectedIncrement += run(server, protocol, mode);
|
expectedIncrement += run(server, protocol, mode);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -117,12 +117,22 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
HttpSchemeType schemeType,
|
HttpSchemeType schemeType,
|
||||||
HttpHandler delegate)
|
HttpHandler delegate)
|
||||||
throws IOException {
|
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(authType);
|
||||||
Objects.requireNonNull(auth);
|
Objects.requireNonNull(auth);
|
||||||
switch(authType) {
|
switch(authType) {
|
||||||
// A server that performs Server Digest authentication.
|
// A server that performs Server Digest authentication.
|
||||||
case SERVER: return createServer(protocol, authType, auth,
|
case SERVER: return createServer(protocol, authType, auth,
|
||||||
schemeType, delegate, "/");
|
schemeType, delegate, algorithm, "/");
|
||||||
// A server that pretends to be a Proxy and performs
|
// A server that pretends to be a Proxy and performs
|
||||||
// Proxy Digest authentication. If protocol is HTTPS,
|
// Proxy Digest authentication. If protocol is HTTPS,
|
||||||
// then this will create a HttpsProxyTunnel that will
|
// then this will create a HttpsProxyTunnel that will
|
||||||
|
@ -327,6 +337,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
HttpTestAuthenticator auth,
|
HttpTestAuthenticator auth,
|
||||||
HttpSchemeType schemeType,
|
HttpSchemeType schemeType,
|
||||||
HttpHandler delegate,
|
HttpHandler delegate,
|
||||||
|
String algorithm,
|
||||||
String path)
|
String path)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Objects.requireNonNull(authType);
|
Objects.requireNonNull(authType);
|
||||||
|
@ -336,7 +347,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
final HTTPTestServer server = new HTTPTestServer(impl, null, delegate);
|
final HTTPTestServer server = new HTTPTestServer(impl, null, delegate);
|
||||||
final HttpHandler hh = server.createHandler(schemeType, auth, authType);
|
final HttpHandler hh = server.createHandler(schemeType, auth, authType);
|
||||||
HttpContext ctxt = impl.createContext(path, hh);
|
HttpContext ctxt = impl.createContext(path, hh);
|
||||||
server.configureAuthentication(ctxt, schemeType, auth, authType);
|
server.configureAuthentication(ctxt, schemeType, auth, authType, algorithm);
|
||||||
impl.start();
|
impl.start();
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
@ -357,7 +368,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
: new HTTPTestServer(impl, null, delegate);
|
: new HTTPTestServer(impl, null, delegate);
|
||||||
final HttpHandler hh = server.createHandler(schemeType, auth, authType);
|
final HttpHandler hh = server.createHandler(schemeType, auth, authType);
|
||||||
HttpContext ctxt = impl.createContext(path, hh);
|
HttpContext ctxt = impl.createContext(path, hh);
|
||||||
server.configureAuthentication(ctxt, schemeType, auth, authType);
|
server.configureAuthentication(ctxt, schemeType, auth, authType, null);
|
||||||
impl.start();
|
impl.start();
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
|
@ -385,7 +396,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
? createProxy(protocol, targetAuthType,
|
? createProxy(protocol, targetAuthType,
|
||||||
auth, schemeType, targetDelegate, "/")
|
auth, schemeType, targetDelegate, "/")
|
||||||
: createServer(targetProtocol, targetAuthType,
|
: createServer(targetProtocol, targetAuthType,
|
||||||
auth, schemeType, targetDelegate, "/");
|
auth, schemeType, targetDelegate, "MD5", "/");
|
||||||
HttpServer impl = createHttpServer(protocol);
|
HttpServer impl = createHttpServer(protocol);
|
||||||
final HTTPTestServer redirectingServer =
|
final HTTPTestServer redirectingServer =
|
||||||
new HTTPTestServer(impl, redirectTarget, null);
|
new HTTPTestServer(impl, redirectTarget, null);
|
||||||
|
@ -431,11 +442,11 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
private void configureAuthentication(HttpContext ctxt,
|
private void configureAuthentication(HttpContext ctxt,
|
||||||
HttpSchemeType schemeType,
|
HttpSchemeType schemeType,
|
||||||
HttpTestAuthenticator auth,
|
HttpTestAuthenticator auth,
|
||||||
HttpAuthType authType) {
|
HttpAuthType authType, String algorithm) {
|
||||||
switch(schemeType) {
|
switch(schemeType) {
|
||||||
case DIGEST:
|
case DIGEST:
|
||||||
// DIGEST authentication is handled by the handler.
|
// DIGEST authentication is handled by the handler.
|
||||||
ctxt.getFilters().add(new HttpDigestFilter(auth, authType));
|
ctxt.getFilters().add(new HttpDigestFilter(auth, authType, algorithm));
|
||||||
break;
|
break;
|
||||||
case BASIC:
|
case BASIC:
|
||||||
// BASIC authentication is handled by the filter.
|
// BASIC authentication is handled by the filter.
|
||||||
|
@ -603,15 +614,21 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
public static String computeDigest(boolean isRequest,
|
public static String computeDigest(boolean isRequest,
|
||||||
String reqMethod,
|
String reqMethod,
|
||||||
char[] password,
|
char[] password,
|
||||||
|
String expectedAlgorithm,
|
||||||
DigestResponse params)
|
DigestResponse params)
|
||||||
throws NoSuchAlgorithmException
|
throws NoSuchAlgorithmException
|
||||||
{
|
{
|
||||||
|
|
||||||
String A1, HashA1;
|
String A1, HashA1;
|
||||||
String algorithm = params.getAlgorithm("MD5");
|
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) {
|
if (params.username == null) {
|
||||||
throw new IllegalArgumentException("missing username");
|
throw new IllegalArgumentException("missing username");
|
||||||
|
@ -776,13 +793,15 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
private final HttpTestAuthenticator auth;
|
private final HttpTestAuthenticator auth;
|
||||||
private final byte[] nonce;
|
private final byte[] nonce;
|
||||||
private final String ns;
|
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
|
super(authType, authType == HttpAuthType.SERVER
|
||||||
? "Digest Server" : "Digest Proxy");
|
? "Digest Server" : "Digest Proxy");
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
nonce = new byte[16];
|
nonce = new byte[16];
|
||||||
new Random(Instant.now().toEpochMilli()).nextBytes(nonce);
|
new Random(Instant.now().toEpochMilli()).nextBytes(nonce);
|
||||||
ns = new BigInteger(1, nonce).toString(16);
|
ns = new BigInteger(1, nonce).toString(16);
|
||||||
|
this.algorithm = (algorithm == null) ? "MD5" : algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -790,7 +809,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
throws IOException {
|
throws IOException {
|
||||||
he.getResponseHeaders().add(getAuthenticate(),
|
he.getResponseHeaders().add(getAuthenticate(),
|
||||||
"Digest realm=\"" + auth.getRealm() + "\","
|
"Digest realm=\"" + auth.getRealm() + "\","
|
||||||
+ "\r\n qop=\"auth\","
|
+ "\r\n qop=\"auth\", " + "algorithm=\"" + algorithm + "\", "
|
||||||
+ "\r\n nonce=\"" + ns +"\"");
|
+ "\r\n nonce=\"" + ns +"\"");
|
||||||
System.out.println(type + ": Requesting Digest Authentication "
|
System.out.println(type + ": Requesting Digest Authentication "
|
||||||
+ he.getResponseHeaders().getFirst(getAuthenticate()));
|
+ he.getResponseHeaders().getFirst(getAuthenticate()));
|
||||||
|
@ -823,7 +842,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean validate(String reqMethod, DigestResponse dg) {
|
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 "
|
System.out.println(type + ": Unsupported algorithm "
|
||||||
+ dg.algorithm);
|
+ dg.algorithm);
|
||||||
return false;
|
return false;
|
||||||
|
@ -854,7 +873,7 @@ public class HTTPTestServer extends HTTPTest {
|
||||||
|
|
||||||
boolean verify(String reqMethod, DigestResponse dg, char[] pw)
|
boolean verify(String reqMethod, DigestResponse dg, char[] pw)
|
||||||
throws NoSuchAlgorithmException {
|
throws NoSuchAlgorithmException {
|
||||||
String response = DigestResponse.computeDigest(true, reqMethod, pw, dg);
|
String response = DigestResponse.computeDigest(true, reqMethod, pw, algorithm, dg);
|
||||||
if (!dg.response.equals(response)) {
|
if (!dg.response.equals(response)) {
|
||||||
System.out.println(type + ": bad response returned by client: "
|
System.out.println(type + ": bad response returned by client: "
|
||||||
+ dg.response + " expected " + response);
|
+ 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.HttpExchange;
|
||||||
import com.sun.net.httpserver.HttpHandler;
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
import com.sun.net.httpserver.HttpServer;
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -34,24 +35,51 @@ import java.net.InetSocketAddress;
|
||||||
import java.net.PasswordAuthentication;
|
import java.net.PasswordAuthentication;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
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.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
|
* @test
|
||||||
* @bug 8138990
|
* @bug 8138990 8281561
|
||||||
* @summary Tests for HTTP Digest auth
|
* @summary Tests for HTTP Digest auth
|
||||||
* The impl maintains a cache for auth info,
|
* The impl maintains a cache for auth info,
|
||||||
* the testcases run in a separate JVM to avoid cache hits
|
* the testcases run in a separate JVM to avoid cache hits
|
||||||
* @modules jdk.httpserver
|
* @modules jdk.httpserver
|
||||||
* @run main/othervm DigestAuth good
|
* @run main/othervm DigestAuth bad
|
||||||
* @run main/othervm DigestAuth only_nonce
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth good
|
||||||
* @run main/othervm DigestAuth sha1
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth only_nonce
|
||||||
* @run main/othervm DigestAuth no_header
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=SHA-1 DigestAuth sha1-good
|
||||||
* @run main/othervm DigestAuth no_nonce
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 DigestAuth sha1-bad
|
||||||
* @run main/othervm DigestAuth no_qop
|
* @run main/othervm DigestAuth sha256
|
||||||
* @run main/othervm DigestAuth invalid_alg
|
* @run main/othervm DigestAuth sha512
|
||||||
* @run main/othervm DigestAuth validate_server
|
* @run main/othervm DigestAuth sha256-userhash
|
||||||
* @run main/othervm DigestAuth validate_server_no_qop
|
* @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 {
|
public class DigestAuth {
|
||||||
|
|
||||||
|
@ -88,6 +116,28 @@ public class DigestAuth {
|
||||||
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
|
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
|
||||||
+ "algorithm=\"SHA1\"";
|
+ "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 "
|
static final String WWW_AUTH_HEADER_INVALID_ALGORITHM = "Digest "
|
||||||
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
|
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
|
||||||
+ "algorithm=\"SHA123\"";
|
+ "algorithm=\"SHA123\"";
|
||||||
|
@ -106,23 +156,77 @@ public class DigestAuth {
|
||||||
+ "nc=00000001, "
|
+ "nc=00000001, "
|
||||||
+ "qop=auth";
|
+ "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 {
|
public static void main(String[] args) throws Exception {
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
throw new RuntimeException("No testcase specified");
|
throw new RuntimeException("No testcase specified");
|
||||||
}
|
}
|
||||||
String testcase = args[0];
|
String testcase = args[0];
|
||||||
|
System.out.println("Running test: " + testcase);
|
||||||
|
boolean usePort80 = args.length > 1 && args[1].equals("port80");
|
||||||
|
|
||||||
// start a local HTTP server
|
// start a local HTTP server
|
||||||
try (LocalHttpServer server = LocalHttpServer.startServer()) {
|
try (LocalHttpServer server = LocalHttpServer.startServer(usePort80)) {
|
||||||
|
|
||||||
// set authenticator
|
// set authenticator
|
||||||
AuthenticatorImpl auth = new AuthenticatorImpl();
|
AuthenticatorImpl auth = new AuthenticatorImpl();
|
||||||
Authenticator.setDefault(auth);
|
|
||||||
|
|
||||||
String url = String.format("http://%s/test/", server.getAuthority());
|
String url = String.format("http://%s/test/", server.getAuthority());
|
||||||
|
|
||||||
boolean success = true;
|
boolean success = true;
|
||||||
switch (testcase) {
|
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":
|
case "good":
|
||||||
// server returns a good WWW-Authenticate header
|
// server returns a good WWW-Authenticate header
|
||||||
server.setWWWAuthHeader(GOOD_WWW_AUTH_HEADER);
|
server.setWWWAuthHeader(GOOD_WWW_AUTH_HEADER);
|
||||||
|
@ -201,11 +305,36 @@ public class DigestAuth {
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "sha1":
|
case "sha1-good":
|
||||||
// server returns a good WWW-Authenticate header with SHA-1
|
// server returns a good WWW-Authenticate header with SHA-1
|
||||||
server.setWWWAuthHeader(WWW_AUTH_HEADER_SHA1);
|
server.setWWWAuthHeader(WWW_AUTH_HEADER_SHA1);
|
||||||
success = testAuth(url, auth, EXPECT_DIGEST);
|
success = testAuth(url, auth, EXPECT_DIGEST);
|
||||||
break;
|
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":
|
case "no_header":
|
||||||
// server returns no WWW-Authenticate header
|
// server returns no WWW-Authenticate header
|
||||||
success = testAuth(url, auth, EXPECT_FAILURE);
|
success = testAuth(url, auth, EXPECT_FAILURE);
|
||||||
|
@ -251,7 +380,7 @@ public class DigestAuth {
|
||||||
try {
|
try {
|
||||||
System.out.printf("Connect to %s, expected auth scheme is '%s'%n",
|
System.out.printf("Connect to %s, expected auth scheme is '%s'%n",
|
||||||
url, expectedScheme);
|
url, expectedScheme);
|
||||||
load(url);
|
load(url, auth);
|
||||||
|
|
||||||
if (expectedScheme == null) {
|
if (expectedScheme == null) {
|
||||||
System.out.println("Unexpected successful connection");
|
System.out.println("Unexpected successful connection");
|
||||||
|
@ -276,8 +405,9 @@ public class DigestAuth {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void load(String url) throws IOException {
|
static void load(String url, Authenticator auth) throws IOException {
|
||||||
URLConnection conn = new URL(url).openConnection();
|
HttpURLConnection conn = (HttpURLConnection)(new URL(url).openConnection());
|
||||||
|
conn.setAuthenticator(auth);
|
||||||
conn.setUseCaches(false);
|
conn.setUseCaches(false);
|
||||||
try (BufferedReader reader = new BufferedReader(
|
try (BufferedReader reader = new BufferedReader(
|
||||||
new InputStreamReader(conn.getInputStream()))) {
|
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 static class AuthenticatorImpl extends Authenticator {
|
||||||
|
|
||||||
private String lastRequestedScheme;
|
private String lastRequestedScheme;
|
||||||
private String lastRequestedPrompt;
|
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
|
@Override
|
||||||
public PasswordAuthentication getPasswordAuthentication() {
|
public PasswordAuthentication getPasswordAuthentication() {
|
||||||
lastRequestedScheme = getRequestingScheme();
|
lastRequestedScheme = getRequestingScheme();
|
||||||
lastRequestedPrompt = getRequestingPrompt();
|
lastRequestedPrompt = getRequestingPrompt();
|
||||||
System.out.println("AuthenticatorImpl: requested "
|
System.out.println("AuthenticatorImpl: requested "
|
||||||
+ lastRequestedScheme);
|
+ lastRequestedScheme);
|
||||||
|
return new PasswordAuthentication(user, pass.toCharArray());
|
||||||
return new PasswordAuthentication("Mufasa",
|
|
||||||
"Circle Of Life".toCharArray());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,6 +473,9 @@ public class DigestAuth {
|
||||||
private volatile String wwwAuthHeader = null;
|
private volatile String wwwAuthHeader = null;
|
||||||
private volatile String authInfoHeader = null;
|
private volatile String authInfoHeader = null;
|
||||||
private volatile String lastRequestedNonce;
|
private volatile String lastRequestedNonce;
|
||||||
|
private volatile String lastRequestedUser;
|
||||||
|
private volatile String lastRequestedUserhash;
|
||||||
|
private volatile Map<String,String> expectedParams;
|
||||||
|
|
||||||
private LocalHttpServer(HttpServer server) {
|
private LocalHttpServer(HttpServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
@ -335,14 +495,49 @@ public class DigestAuth {
|
||||||
this.wwwAuthHeader = wwwAuthHeader;
|
this.wwwAuthHeader = wwwAuthHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setExpectedRequestParams(Map<String,String> params) {
|
||||||
|
this.expectedParams = params;
|
||||||
|
}
|
||||||
|
|
||||||
void setAuthInfoHeader(String authInfoHeader) {
|
void setAuthInfoHeader(String authInfoHeader) {
|
||||||
this.authInfoHeader = 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();
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||||
HttpServer httpServer = HttpServer.create(
|
HttpServer httpServer = HttpServer.create(
|
||||||
new InetSocketAddress(loopback, 0), 0);
|
new InetSocketAddress(loopback, port), 0);
|
||||||
LocalHttpServer localHttpServer = new LocalHttpServer(httpServer);
|
LocalHttpServer localHttpServer = new LocalHttpServer(httpServer);
|
||||||
localHttpServer.start();
|
localHttpServer.start();
|
||||||
|
|
||||||
|
@ -351,6 +546,7 @@ public class DigestAuth {
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
server.createContext("/test", this);
|
server.createContext("/test", this);
|
||||||
|
server.createContext("/", this);
|
||||||
server.start();
|
server.start();
|
||||||
System.out.println("HttpServer: started on port " + getAuthority());
|
System.out.println("HttpServer: started on port " + getAuthority());
|
||||||
}
|
}
|
||||||
|
@ -385,7 +581,10 @@ public class DigestAuth {
|
||||||
t.getResponseHeaders().add("Authentication-Info",
|
t.getResponseHeaders().add("Authentication-Info",
|
||||||
authInfoHeader);
|
authInfoHeader);
|
||||||
}
|
}
|
||||||
|
checkExpectedParams(header);
|
||||||
lastRequestedNonce = findParameter(header, "nonce");
|
lastRequestedNonce = findParameter(header, "nonce");
|
||||||
|
lastRequestedUser = findParameter(header, "username");
|
||||||
|
lastRequestedUserhash = findParameter(header, "userhash");
|
||||||
byte[] output = "hello".getBytes();
|
byte[] output = "hello".getBytes();
|
||||||
t.sendResponseHeaders(200, output.length);
|
t.sendResponseHeaders(200, output.length);
|
||||||
t.getResponseBody().write(output);
|
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
|
* @test
|
||||||
* @bug 4432213
|
* @bug 4432213
|
||||||
* @modules java.base/sun.net.www
|
* @modules java.base/sun.net.www
|
||||||
* @run main/othervm -Dhttp.auth.digest.validateServer=true DigestTest
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true
|
|
||||||
* -Dhttp.auth.digest.validateServer=true DigestTest
|
* -Dhttp.auth.digest.validateServer=true DigestTest
|
||||||
* @run main/othervm -Dhttp.auth.digest.validateServer=true
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||||
-Dtest.succeed=true DigestTest
|
* -Djava.net.preferIPv6Addresses=true
|
||||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true
|
* -Dhttp.auth.digest.validateServer=true DigestTest
|
||||||
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||||
* -Dhttp.auth.digest.validateServer=true
|
* -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
|
* @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
|
* @summary Sanity check that NTLM will not be selected by the http protocol
|
||||||
* handler when running on a profile that does not support NTLM
|
* handler when running on a profile that does not support NTLM
|
||||||
* @modules java.base/sun.net.www.protocol.http:open
|
* @modules java.base/sun.net.www.protocol.http:open
|
||||||
* @run main/othervm NoNTLM
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5 NoNTLM
|
||||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true NoNTLM
|
* @run main/othervm -Dhttp.auth.digest.reEnabledAlgorithms=MD5
|
||||||
|
* -Djava.net.preferIPv6Addresses=true NoNTLM
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue