1 /*
   2  * Copyright (c) 2001, 2010, 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 4432213
  27  * @run main/othervm -Dhttp.auth.digest.validateServer=true DigestTest
  28  * @summary  Need to support Digest Authentication for Proxies
  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 class DigestServer extends Thread {
  44 
  45     ServerSocket s;
  46     Socket   s1;
  47     InputStream  is;
  48     OutputStream os;
  49     int port;
  50 
  51     String reply1 = "HTTP/1.1 401 Unauthorized\r\n"+
  52         "WWW-Authenticate: Digest realm=\""+realm+"\" domain=/ "+
  53         "nonce=\""+nonce+"\" qop=\"auth\"\r\n\r\n";
  54 
  55     String reply2 = "HTTP/1.1 200 OK\r\n" +
  56         "Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" +
  57         "Server: Apache/1.3.14 (Unix)\r\n" +
  58         "Content-Type: text/html; charset=iso-8859-1\r\n" +
  59         "Transfer-encoding: chunked\r\n\r\n"+
  60         "B\r\nHelloWorld1\r\n"+
  61         "B\r\nHelloWorld2\r\n"+
  62         "B\r\nHelloWorld3\r\n"+
  63         "B\r\nHelloWorld4\r\n"+
  64         "B\r\nHelloWorld5\r\n"+
  65         "0\r\n"+
  66         "Authentication-Info: ";
  67 
  68     DigestServer (ServerSocket y) {
  69         s = y;
  70         port = s.getLocalPort();
  71     }
  72 
  73     public void run () {
  74         try {
  75                 s1 = s.accept ();
  76                 is = s1.getInputStream ();
  77                 os = s1.getOutputStream ();
  78                 is.read ();
  79                 os.write (reply1.getBytes());
  80                 Thread.sleep (2000);
  81                 s1.close ();
  82 
  83                 s1 = s.accept ();
  84                 is = s1.getInputStream ();
  85                 os = s1.getOutputStream ();
  86                 is.read ();
  87                 // need to get the cnonce out of the response
  88                 MessageHeader header = new MessageHeader (is);
  89                 String raw = header.findValue ("Authorization");
  90                 HeaderParser parser = new HeaderParser (raw);
  91                 String cnonce = parser.findValue ("cnonce");
  92                 String cnstring = parser.findValue ("nc");
  93 
  94                 String reply = reply2 + getAuthorization (uri, "GET", cnonce, cnstring) +"\r\n";
  95                 os.write (reply.getBytes());
  96                 Thread.sleep (2000);
  97                 s1.close ();
  98         } catch (Exception e) {
  99             System.out.println (e);
 100             e.printStackTrace();
 101         } finally {
 102             try { s.close(); } catch (IOException unused) {}
 103         }
 104     }
 105 
 106     static char[] passwd = "password".toCharArray();
 107     static String username = "user";
 108     static String nonce = "abcdefghijklmnopqrstuvwxyz";
 109     static String realm = "wallyworld";
 110     static String uri = "/foo.html";
 111 
 112     private String getAuthorization (String uri, String method, String cnonce, String cnstring) {
 113         String response;
 114 
 115         try {
 116             response = computeDigest(false, username,passwd,realm,
 117                                         method, uri, nonce, cnonce, cnstring);
 118         } catch (NoSuchAlgorithmException ex) {
 119             return null;
 120         }
 121 
 122         String value = "Digest"
 123                         + " qop=auth\""
 124                         + "\", cnonce=\"" + cnonce
 125                         + "\", rspauth=\"" + response
 126                         + "\", nc=\"" + cnstring + "\"";
 127         return (value+ "\r\n");
 128     }
 129 
 130     private String computeDigest(
 131                         boolean isRequest, String userName, char[] password,
 132                         String realm, String connMethod,
 133                         String requestURI, String nonceString,
 134                         String cnonce, String ncValue
 135                     ) throws NoSuchAlgorithmException
 136     {
 137 
 138         String A1, HashA1;
 139 
 140         MessageDigest md = MessageDigest.getInstance("MD5");
 141 
 142         {
 143             A1 = userName + ":" + realm + ":";
 144             HashA1 = encode(A1, password, md);
 145         }
 146 
 147         String A2;
 148         if (isRequest) {
 149             A2 = connMethod + ":" + requestURI;
 150         } else {
 151             A2 = ":" + requestURI;
 152         }
 153         String HashA2 = encode(A2, null, md);
 154         String combo, finalHash;
 155 
 156         { /* RRC2617 when qop=auth */
 157             combo = HashA1+ ":" + nonceString + ":" + ncValue + ":" +
 158                         cnonce + ":auth:" +HashA2;
 159 
 160         }
 161         finalHash = encode(combo, null, md);
 162         return finalHash;
 163     }
 164 
 165     private final static char charArray[] = {
 166         '0', '1', '2', '3', '4', '5', '6', '7',
 167         '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
 168     };
 169 
 170     private String encode(String src, char[] passwd, MessageDigest md) {
 171         md.update(src.getBytes());
 172         if (passwd != null) {
 173             byte[] passwdBytes = new byte[passwd.length];
 174             for (int i=0; i<passwd.length; i++)
 175                 passwdBytes[i] = (byte)passwd[i];
 176             md.update(passwdBytes);
 177             Arrays.fill(passwdBytes, (byte)0x00);
 178         }
 179         byte[] digest = md.digest();
 180 
 181         StringBuffer res = new StringBuffer(digest.length * 2);
 182         for (int i = 0; i < digest.length; i++) {
 183             int hashchar = ((digest[i] >>> 4) & 0xf);
 184             res.append(charArray[hashchar]);
 185             hashchar = (digest[i] & 0xf);
 186             res.append(charArray[hashchar]);
 187         }
 188         return res.toString();
 189     }
 190 
 191 }
 192 
 193 public class DigestTest {
 194 
 195     static class MyAuthenticator extends Authenticator {
 196         public MyAuthenticator () {
 197             super ();
 198         }
 199 
 200         public PasswordAuthentication getPasswordAuthentication ()
 201         {
 202             return (new PasswordAuthentication ("user", "Wrongpassword".toCharArray()));
 203         }
 204     }
 205 
 206 
 207     public static void main(String[] args) throws Exception {
 208         int port;
 209         DigestServer server;
 210         ServerSocket sock;
 211 
 212         try {
 213             sock = new ServerSocket (0);
 214             port = sock.getLocalPort ();
 215         }
 216         catch (Exception e) {
 217             System.out.println ("Exception: " + e);
 218             return;
 219         }
 220 
 221         server = new DigestServer(sock);
 222         server.start ();
 223         boolean passed = false;
 224 
 225         try  {
 226             Authenticator.setDefault (new MyAuthenticator ());
 227             String s = "http://localhost:" + port + DigestServer.uri;
 228             URL url = new URL(s);
 229             java.net.URLConnection conURL =  url.openConnection();
 230 
 231             InputStream in = conURL.getInputStream();
 232             while (in.read () != -1) {}
 233             in.close ();
 234         } catch(ProtocolException e) {
 235             passed = true;
 236         }
 237 
 238         if (!passed) {
 239             throw new RuntimeException ("Expected a ProtocolException from wrong password");
 240         }
 241     }
 242 }