1 /*
   2  * Copyright (c) 2001, 2009, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug 6870935
  27  * @modules java.base/sun.net.www
  28  * @run main/othervm -Dhttp.nonProxyHosts="" -Dhttp.auth.digest.validateProxy=true B6870935
  29  */
  30 
  31 import java.io.*;
  32 import java.util.*;
  33 import java.net.*;
  34 import java.security.*;
  35 import sun.net.www.*;
  36 
  37 /* This is one simple test of the RFC2617 digest authentication behavior
  38  * It specifically tests that the client correctly checks the returned
  39  * Authentication-Info header field from the server and throws an exception
  40  * if the password is wrong
  41  */
  42 
  43 public class B6870935 {
  44 
  45     static char[] passwd = "password".toCharArray();
  46     static String username = "user";
  47     static String nonce = "abcdefghijklmnopqrstuvwxyz";
  48     static String realm = "wallyworld";
  49     static String uri = "http://www.ibm.com";
  50     static volatile boolean error = false;
  51 
  52     static class DigestServer extends Thread {
  53 
  54         ServerSocket s;
  55         InputStream  is;
  56         OutputStream os;
  57         int port;
  58 
  59         String reply1 = "HTTP/1.1 407 Proxy Authentication Required\r\n"+
  60             "Proxy-Authenticate: Digest realm=\""+realm+"\" domain=/ "+
  61             "nonce=\""+nonce+"\" qop=\"auth\"\r\n\r\n";
  62 
  63         String reply2 = "HTTP/1.1 200 OK\r\n" +
  64             "Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" +
  65             "Server: Apache/1.3.14 (Unix)\r\n" +
  66             "Content-Type: text/html; charset=iso-8859-1\r\n" +
  67             "Transfer-encoding: chunked\r\n\r\n"+
  68             "B\r\nHelloWorld1\r\n"+
  69             "B\r\nHelloWorld2\r\n"+
  70             "B\r\nHelloWorld3\r\n"+
  71             "B\r\nHelloWorld4\r\n"+
  72             "B\r\nHelloWorld5\r\n"+
  73             "0\r\n"+
  74             "Proxy-Authentication-Info: ";
  75 
  76         DigestServer (ServerSocket y) {
  77             s = y;
  78             port = s.getLocalPort();
  79         }
  80 
  81         public void run () {
  82             try {
  83                 Socket s1 = s.accept ();
  84                 is = s1.getInputStream ();
  85                 os = s1.getOutputStream ();
  86                 is.read ();
  87                 os.write (reply1.getBytes());
  88                 Thread.sleep (2000);
  89                 s1.close ();
  90 
  91                 s1 = s.accept ();
  92                 is = s1.getInputStream ();
  93                 os = s1.getOutputStream ();
  94                 is.read ();
  95                 // need to get the cnonce out of the response
  96                 MessageHeader header = new MessageHeader (is);
  97                 String raw = header.findValue ("Proxy-Authorization");
  98                 HeaderParser parser = new HeaderParser (raw);
  99                 String cnonce = parser.findValue ("cnonce");
 100                 String cnstring = parser.findValue ("nc");
 101                 String clientrsp = parser.findValue ("response");
 102                 String expected = computeDigest(
 103                         true, username,passwd,realm,
 104                         "GET", uri, nonce, cnonce, cnstring
 105                 );
 106                 if (!expected.equals(clientrsp)) {
 107                     s1.close ();
 108                     s.close ();
 109                     error = true;
 110                     return;
 111                 }
 112 
 113                 String reply = reply2 + getAuthorization (
 114                         realm, false, uri, "GET", cnonce,
 115                         cnstring, passwd, username
 116                 ) +"\r\n";
 117                 os.write (reply.getBytes());
 118                 Thread.sleep (2000);
 119                 s1.close ();
 120             }
 121             catch (Exception e) {
 122                 System.out.println (e);
 123                 e.printStackTrace();
 124             }
 125         }
 126 
 127         private String getAuthorization (String realm, boolean isRequest, String uri, String method, String cnonce, String cnstring, char[] password, String username) {
 128             String response;
 129 
 130             try {
 131                 response = computeDigest(isRequest, username,passwd,realm,
 132                                             method, uri, nonce, cnonce, cnstring);
 133             } catch (NoSuchAlgorithmException ex) {
 134                 return null;
 135             }
 136 
 137             String value = "Digest"
 138                             + " qop=\"auth"
 139                             + "\", cnonce=\"" + cnonce
 140                             + "\", rspauth=\"" + response
 141                             + "\", nc=\"" + cnstring + "\"";
 142             return (value+ "\r\n");
 143         }
 144 
 145         private String computeDigest(
 146                             boolean isRequest, String userName, char[] password,
 147                             String realm, String connMethod,
 148                             String requestURI, String nonceString,
 149                             String cnonce, String ncValue
 150                         ) throws NoSuchAlgorithmException
 151         {
 152 
 153             String A1, HashA1;
 154 
 155             MessageDigest md = MessageDigest.getInstance("MD5");
 156 
 157             {
 158                 A1 = userName + ":" + realm + ":";
 159                 HashA1 = encode(A1, password, md);
 160             }
 161 
 162             String A2;
 163             if (isRequest) {
 164                 A2 = connMethod + ":" + requestURI;
 165             } else {
 166                 A2 = ":" + requestURI;
 167             }
 168             String HashA2 = encode(A2, null, md);
 169             String combo, finalHash;
 170 
 171             { /* RRC2617 when qop=auth */
 172                 combo = HashA1+ ":" + nonceString + ":" + ncValue + ":" +
 173                             cnonce + ":auth:" +HashA2;
 174 
 175             }
 176             finalHash = encode(combo, null, md);
 177             return finalHash;
 178         }
 179 
 180         private final static char charArray[] = {
 181             '0', '1', '2', '3', '4', '5', '6', '7',
 182             '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
 183         };
 184 
 185         private String encode(String src, char[] passwd, MessageDigest md) {
 186             md.update(src.getBytes());
 187             if (passwd != null) {
 188                 byte[] passwdBytes = new byte[passwd.length];
 189                 for (int i=0; i<passwd.length; i++)
 190                     passwdBytes[i] = (byte)passwd[i];
 191                 md.update(passwdBytes);
 192                 Arrays.fill(passwdBytes, (byte)0x00);
 193             }
 194             byte[] digest = md.digest();
 195 
 196             StringBuffer res = new StringBuffer(digest.length * 2);
 197             for (int i = 0; i < digest.length; i++) {
 198                 int hashchar = ((digest[i] >>> 4) & 0xf);
 199                 res.append(charArray[hashchar]);
 200                 hashchar = (digest[i] & 0xf);
 201                 res.append(charArray[hashchar]);
 202             }
 203             return res.toString();
 204         }
 205     }
 206 
 207 
 208     static class MyAuthenticator extends Authenticator {
 209         public MyAuthenticator () {
 210             super ();
 211         }
 212 
 213         public PasswordAuthentication getPasswordAuthentication ()
 214         {
 215             return (new PasswordAuthentication (username, passwd));
 216         }
 217     }
 218 
 219 
 220     public static void main(String[] args) throws Exception {
 221         int nLoops = 1;
 222         int nSize = 10;
 223         int port, n =0;
 224         byte b[] = new byte[nSize];
 225         DigestServer server;
 226         ServerSocket sock;
 227 
 228         try {
 229             sock = new ServerSocket (0);
 230             port = sock.getLocalPort ();
 231         }
 232         catch (Exception e) {
 233             System.out.println ("Exception: " + e);
 234             return;
 235         }
 236 
 237         server = new DigestServer(sock);
 238         server.start ();
 239 
 240         try  {
 241 
 242             Authenticator.setDefault (new MyAuthenticator ());
 243             SocketAddress addr = new InetSocketAddress ("127.0.0.1", port);
 244             Proxy proxy = new Proxy (Proxy.Type.HTTP, addr);
 245             String s = "http://www.ibm.com";
 246             URL url = new URL(s);
 247             java.net.URLConnection conURL =  url.openConnection(proxy);
 248 
 249             InputStream in = conURL.getInputStream();
 250             int c;
 251             while ((c = in.read ()) != -1) {
 252             }
 253             in.close ();
 254         }
 255         catch(IOException e) {
 256             e.printStackTrace();
 257             error = true;
 258         }
 259         if (error) {
 260             throw new RuntimeException ("Error in test");
 261         }
 262     }
 263 }