/
niceSOFT
/
openjdk21
Обзор
Документация
Войти
/
niceSOFT
/
openjdk21
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.java
774 строки
27 KB
Darragh Clarke
7065228: To interpret case-insensitive string locale independently
22 май 2023, 13:53
22 май 2023, 13:53
05e99db
Код
Авторство
О чём код?
/* * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.net.www.protocol.http; 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; /** * DigestAuthentication: Encapsulate an http server authentication using * the "Digest" scheme, as described in RFC2069 and updated in RFC2617 * * @author Bill Foote */ class DigestAuthentication extends AuthenticationInfo { @java.io.Serial private static final long serialVersionUID = 100L; private String authMethod; 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( (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. // One instance of these may be shared among several DigestAuthentication // instances as a result of a single authorization (for multiple domains) // There don't appear to be any blocking IO calls performed from // within the synchronized code blocks in the Parameters class, so there don't // seem to be any need to migrate it to using java.util.concurrent.locks static class Parameters implements java.io.Serializable { private static final long serialVersionUID = -3584543755194526252L; private boolean serverQop; // server proposed qop=auth private String opaque; 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; // 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 private boolean redoCachedHA1 = true; private static final int cnonceRepeat = 5; private static final int cnoncelen = 40; /* number of characters in cnonce */ private static Random random; static { random = new Random(); } Parameters () { serverQop = false; opaque = null; algorithm = null; digestName = null; cachedHA1 = null; nonce = null; charset = null; setNewCnonce(); } boolean authQop () { return serverQop; } synchronized void incrementNC() { NCcount ++; } synchronized int getNCCount () { return NCcount; } int cnonce_count = 0; /* each call increments the counter */ synchronized String getCnonce () { if (cnonce_count >= cnonceRepeat) { setNewCnonce(); } cnonce_count++; return cnonce; } synchronized void setNewCnonce () { byte bb[] = new byte [cnoncelen/2]; char cc[] = new char [cnoncelen]; random.nextBytes (bb); for (int i=0; i<(cnoncelen/2); i++) { int x = bb[i] + 128; cc[i*2]= (char) ('A'+ x/16); cc[i*2+1]= (char) ('A'+ x%16); } cnonce = new String (cc, 0, cnoncelen); cnonce_count = 0; 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(","); for (String item : items) { if ("auth".equalsIgnoreCase(item.trim())) { serverQop = true; return; } } } serverQop = false; } synchronized String getOpaque () { return opaque;} synchronized void setOpaque (String s) { opaque=s;} synchronized String getNonce () { return nonce;} synchronized void setNonce (String s) { if (nonce == null || !s.equals(nonce)) { nonce=s; NCcount = 0; redoCachedHA1 = true; } } synchronized String getCachedHA1 () { if (redoCachedHA1) { return null; } else { return cachedHA1; } } synchronized void setCachedHA1 (String s) { cachedHA1=s; redoCachedHA1=false; } 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; /** * Create a DigestAuthentication */ public DigestAuthentication(boolean isProxy, URL url, String realm, String authMethod, PasswordAuthentication pw, Parameters params){ super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION, AuthScheme.DIGEST, url, realm); this.authMethod = authMethod; this.pw = pw; this.params = params; } public DigestAuthentication(boolean isProxy, String host, int port, String realm, String authMethod, PasswordAuthentication pw, Parameters params) { super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION, AuthScheme.DIGEST, host, port, realm); this.authMethod = authMethod; this.pw = pw; this.params = params; } /** * @return true if this authentication supports preemptive authorization */ @Override public boolean supportsPreemptiveAuthorization() { return true; } /** * Recalculates the request-digest and returns it. * * <P> Used in the common case where the requestURI is simply the * abs_path. * * @param url * the URL * * @param method * the HTTP method * * @return the value of the HTTP header this authentication wants set */ @Override public String getHeaderValue(URL url, String method) { return getHeaderValueImpl(url.getFile(), method); } /** * Recalculates the request-digest and returns it. * * <P> Used when the requestURI is not the abs_path. The exact * requestURI can be passed as a String. * * @param requestURI * the Request-URI from the HTTP request line * * @param method * the HTTP method * * @return the value of the HTTP header this authentication wants set */ String getHeaderValue(String requestURI, String method) { return getHeaderValueImpl(requestURI, method); } /** * Check if the header indicates that the current auth. parameters are stale. * If so, then replace the relevant field with the new value * and return true. Otherwise return false. * returning true means the request can be retried with the same userid/password * returning false means we have to go back to the user to ask for a new * username password. */ @Override public boolean isAuthorizationStale (String header) { HeaderParser p = new HeaderParser (header); String s = p.findValue ("stale"); if (s == null || !s.equals("true")) return false; String newNonce = p.findValue ("nonce"); if (newNonce == null || newNonce.isEmpty()) { return false; } params.setNonce (newNonce); return true; } /** * Set header(s) on the given connection. * @param conn The connection to apply the header(s) to * @param p A source of header values for this connection, if needed. * @param raw Raw header values for this connection, if needed. * @return true if all goes well, false if no headers were set. */ @Override public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { // no need to synchronize here: // already locked by s.n.w.p.h.HttpURLConnection assert conn.isLockHeldByCurrentThread(); 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; if (type == PROXY_AUTHENTICATION && conn.tunnelState() == HttpURLConnection.TunnelState.SETUP) { uri = HttpURLConnection.connectRequestURI(conn.getURL()); method = HTTP_CONNECT; } else { try { uri = conn.getRequestURI(); } catch (IOException e) {} method = conn.getMethod(); } if (params.nonce == null || authMethod == null || pw == null || realm == null) { return false; } if (authMethod.length() >= 1) { // Method seems to get converted to all lower case elsewhere. // It really does need to start with an upper case letter // here. authMethod = Character.toUpperCase(authMethod.charAt(0)) + authMethod.substring(1).toLowerCase(Locale.ROOT); } 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 // and if auth is not offered we fallback to the RFC2069 behavior if (params.authQop()) { params.setNewCnonce(); } String value = getHeaderValueImpl (uri, method); if (value != null) { conn.setAuthenticationProperty(getHeaderName(), value); return true; } else { return false; } } // 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, according 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 */ private String getHeaderValueImpl (String uri, String method) { String response; char[] passwd = pw.getPassword(); boolean qop = params.authQop(); String opaque = params.getOpaque(); 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; if (nccount != -1) { ncstring = Integer.toHexString(nccount); int len = ncstring.length(); if (len < 8) ncstring = zeroPad [len] + ncstring; } boolean session = algorithm.endsWith ("-sess"); try { response = computeDigest(true, pw.getUserName(),passwd,realm, method, uri, nonce, cnonce, ncstring, digest, session, charset); } catch (CharacterCodingException | NoSuchAlgorithmException ex) { log(ex.getMessage()); return null; } String ncfield = "\""; if (qop) { ncfield = "\", nc=" + ncstring; } String algoS, qopS; if (delimCompatFlag) { // Put quotes around these String value parameters algoS = ", algorithm=\"" + algorithm + "\""; qopS = ", qop=\"auth\""; } else { // Don't put quotes around them, per the RFC algoS = ", algorithm=" + algorithm; 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=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + ncfield + userhashField + ", uri=\"" + uri + "\", response=\"" + response + "\"" + algoS; if (opaque != null) { value += ", opaque=\"" + opaque + "\""; } if (cnonce != null) { value += ", cnonce=\"" + cnonce + "\""; } if (qop) { value += qopS; } return value; } public void checkResponse (String header, String method, URL url) throws IOException { 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(); String username = pw.getUserName(); boolean qop = params.authQop(); String opaque = params.getOpaque(); 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; if (header == null) { 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(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, digest, session, charset); HeaderParser p = new HeaderParser (header); String rspauth = p.findValue ("rspauth"); if (rspauth == null) { throw new ProtocolException ("No digest in response"); } if (!rspauth.equals (expected)) { throw new ProtocolException ("Response digest invalid"); } /* Check if there is a nextnonce field */ String nextnonce = p.findValue ("nextnonce"); if (nextnonce != null && !nextnonce.isEmpty()) { params.setNonce (nextnonce); } } 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, String algorithm, boolean session, Charset charset ) throws NoSuchAlgorithmException, CharacterCodingException { String A1, HashA1; MessageDigest md = MessageDigest.getInstance(algorithm); if (session) { if ((HashA1 = params.getCachedHA1 ()) == null) { String s = userName + ":" + realm + ":"; String s1 = encode (s, password, md, charset); A1 = s1 + ":" + nonceString + ":" + cnonce; HashA1 = encode(A1, null, md, charset); params.setCachedHA1 (HashA1); } } else { A1 = userName + ":" + realm + ":"; HashA1 = encode(A1, password, md, charset); } String A2; if (isRequest) { A2 = connMethod + ":" + requestURI; } else { A2 = ":" + requestURI; } String HashA2 = encode(A2, null, md, ISO_8859_1.INSTANCE); String combo, finalHash; if (params.authQop()) { /* RRC2617 when qop=auth */ combo = HashA1+ ":" + nonceString + ":" + ncValue + ":" + cnonce + ":auth:" +HashA2; } else { /* for compatibility with RFC2069 */ combo = HashA1 + ":" + nonceString + ":" + HashA2; } finalHash = encode(combo, null, md, ISO_8859_1.INSTANCE); return finalHash; } private static final char charArray[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private static final String zeroPad[] = { // 0 1 2 3 4 5 6 7 "00000000", "0000000", "000000", "00000", "0000", "000", "00", "0" }; 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; 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); res.append(charArray[hashchar]); hashchar = (digest[i] & 0xf); res.append(charArray[hashchar]); } 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; } }