1 /*
   2  * Copyright (c) 2006, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.net.httpserver;
  27 
  28 /**
  29  * BasicAuthenticator provides an implementation of HTTP Basic
  30  * authentication. It is an abstract class and must be extended
  31  * to provide an implementation of {@link #checkCredentials(String,String)}
  32  * which is called to verify each incoming request.
  33  */
  34 public abstract class BasicAuthenticator extends Authenticator {
  35 
  36     protected String realm;
  37 
  38     /**
  39      * Creates a BasicAuthenticator for the given HTTP realm
  40      * @param realm The HTTP Basic authentication realm
  41      * @throws NullPointerException if the realm is an empty string
  42      */
  43     public BasicAuthenticator (String realm) {
  44         this.realm = realm;
  45     }
  46 
  47     /**
  48      * returns the realm this BasicAuthenticator was created with
  49      * @return the authenticator's realm string.
  50      */
  51     public String getRealm () {
  52         return realm;
  53     }
  54 
  55     public Result authenticate (HttpExchange t)
  56     {
  57         Headers rmap = (Headers) t.getRequestHeaders();
  58         /*
  59          * look for auth token
  60          */
  61         String auth = rmap.getFirst ("Authorization");
  62         if (auth == null) {
  63             Headers map = (Headers) t.getResponseHeaders();
  64             map.set ("WWW-Authenticate", "Basic realm=" + "\""+realm+"\"");
  65             return new Authenticator.Retry (401);
  66         }
  67         int sp = auth.indexOf (' ');
  68         if (sp == -1 || !auth.substring(0, sp).equals ("Basic")) {
  69             return new Authenticator.Failure (401);
  70         }
  71         byte[] b = Base64.base64ToByteArray (auth.substring(sp+1));
  72         String userpass = new String (b);
  73         int colon = userpass.indexOf (':');
  74         String uname = userpass.substring (0, colon);
  75         String pass = userpass.substring (colon+1);
  76 
  77         if (checkCredentials (uname, pass)) {
  78             return new Authenticator.Success (
  79                 new HttpPrincipal (
  80                     uname, realm
  81                 )
  82             );
  83         } else {
  84             /* reject the request again with 401 */
  85 
  86             Headers map = (Headers) t.getResponseHeaders();
  87             map.set ("WWW-Authenticate", "Basic realm=" + "\""+realm+"\"");
  88             return new Authenticator.Failure(401);
  89         }
  90     }
  91 
  92     /**
  93      * called for each incoming request to verify the
  94      * given name and password in the context of this
  95      * Authenticator's realm. Any caching of credentials
  96      * must be done by the implementation of this method
  97      * @param username the username from the request
  98      * @param password the password from the request
  99      * @return <code>true</code> if the credentials are valid,
 100      *    <code>false</code> otherwise.
 101      */
 102     public abstract boolean checkCredentials (String username, String password);
 103 }
 104 
 105 class Base64 {
 106 
 107     /**
 108      * Translates the specified byte array into a Base64 string as per
 109      * Preferences.put(byte[]).
 110      */
 111     static String byteArrayToBase64(byte[] a) {
 112         return byteArrayToBase64(a, false);
 113     }
 114 
 115     /**
 116      * Translates the specified byte array into an "aternate representation"
 117      * Base64 string.  This non-standard variant uses an alphabet that does
 118      * not contain the uppercase alphabetic characters, which makes it
 119      * suitable for use in situations where case-folding occurs.
 120      */
 121     static String byteArrayToAltBase64(byte[] a) {
 122         return byteArrayToBase64(a, true);
 123     }
 124 
 125     private static String byteArrayToBase64(byte[] a, boolean alternate) {
 126         int aLen = a.length;
 127         int numFullGroups = aLen/3;
 128         int numBytesInPartialGroup = aLen - 3*numFullGroups;
 129         int resultLen = 4*((aLen + 2)/3);
 130         StringBuffer result = new StringBuffer(resultLen);
 131         char[] intToAlpha = (alternate ? intToAltBase64 : intToBase64);
 132 
 133         // Translate all full groups from byte array elements to Base64
 134         int inCursor = 0;
 135         for (int i=0; i<numFullGroups; i++) {
 136             int byte0 = a[inCursor++] & 0xff;
 137             int byte1 = a[inCursor++] & 0xff;
 138             int byte2 = a[inCursor++] & 0xff;
 139             result.append(intToAlpha[byte0 >> 2]);
 140             result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
 141             result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]);
 142             result.append(intToAlpha[byte2 & 0x3f]);
 143         }
 144 
 145         // Translate partial group if present
 146         if (numBytesInPartialGroup != 0) {
 147             int byte0 = a[inCursor++] & 0xff;
 148             result.append(intToAlpha[byte0 >> 2]);
 149             if (numBytesInPartialGroup == 1) {
 150                 result.append(intToAlpha[(byte0 << 4) & 0x3f]);
 151                 result.append("==");
 152             } else {
 153                 // assert numBytesInPartialGroup == 2;
 154                 int byte1 = a[inCursor++] & 0xff;
 155                 result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
 156                 result.append(intToAlpha[(byte1 << 2)&0x3f]);
 157                 result.append('=');
 158             }
 159         }
 160         // assert inCursor == a.length;
 161         // assert result.length() == resultLen;
 162         return result.toString();
 163     }
 164 
 165     /**
 166      * This array is a lookup table that translates 6-bit positive integer
 167      * index values into their "Base64 Alphabet" equivalents as specified
 168      * in Table 1 of RFC 2045.
 169      */
 170     private static final char intToBase64[] = {
 171         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 172         'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 173         'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 174         'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 175         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
 176     };
 177 
 178     /**
 179      * This array is a lookup table that translates 6-bit positive integer
 180      * index values into their "Alternate Base64 Alphabet" equivalents.
 181      * This is NOT the real Base64 Alphabet as per in Table 1 of RFC 2045.
 182      * This alternate alphabet does not use the capital letters.  It is
 183      * designed for use in environments where "case folding" occurs.
 184      */
 185     private static final char intToAltBase64[] = {
 186         '!', '"', '#', '$', '%', '&', '\'', '(', ')', ',', '-', '.', ':',
 187         ';', '<', '>', '@', '[', ']', '^',  '`', '_', '{', '|', '}', '~',
 188         'a', 'b', 'c', 'd', 'e', 'f', 'g',  'h', 'i', 'j', 'k', 'l', 'm',
 189         'n', 'o', 'p', 'q', 'r', 's', 't',  'u', 'v', 'w', 'x', 'y', 'z',
 190         '0', '1', '2', '3', '4', '5', '6',  '7', '8', '9', '+', '?'
 191     };
 192 
 193     /**
 194      * Translates the specified Base64 string (as per Preferences.get(byte[]))
 195      * into a byte array.
 196      *
 197      * @throw IllegalArgumentException if <tt>s</tt> is not a valid Base64
 198      *        string.
 199      */
 200     static byte[] base64ToByteArray(String s) {
 201         return base64ToByteArray(s, false);
 202     }
 203 
 204     /**
 205      * Translates the specified "aternate representation" Base64 string
 206      * into a byte array.
 207      *
 208      * @throw IllegalArgumentException or ArrayOutOfBoundsException
 209      *        if <tt>s</tt> is not a valid alternate representation
 210      *        Base64 string.
 211      */
 212     static byte[] altBase64ToByteArray(String s) {
 213         return base64ToByteArray(s, true);
 214     }
 215 
 216     private static byte[] base64ToByteArray(String s, boolean alternate) {
 217         byte[] alphaToInt = (alternate ?  altBase64ToInt : base64ToInt);
 218         int sLen = s.length();
 219         int numGroups = sLen/4;
 220         if (4*numGroups != sLen)
 221             throw new IllegalArgumentException(
 222                 "String length must be a multiple of four.");
 223         int missingBytesInLastGroup = 0;
 224         int numFullGroups = numGroups;
 225         if (sLen != 0) {
 226             if (s.charAt(sLen-1) == '=') {
 227                 missingBytesInLastGroup++;
 228                 numFullGroups--;
 229             }
 230             if (s.charAt(sLen-2) == '=')
 231                 missingBytesInLastGroup++;
 232         }
 233         byte[] result = new byte[3*numGroups - missingBytesInLastGroup];
 234 
 235         // Translate all full groups from base64 to byte array elements
 236         int inCursor = 0, outCursor = 0;
 237         for (int i=0; i<numFullGroups; i++) {
 238             int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
 239             int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
 240             int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
 241             int ch3 = base64toInt(s.charAt(inCursor++), alphaToInt);
 242             result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
 243             result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
 244             result[outCursor++] = (byte) ((ch2 << 6) | ch3);
 245         }
 246 
 247         // Translate partial group, if present
 248         if (missingBytesInLastGroup != 0) {
 249             int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
 250             int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
 251             result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
 252 
 253             if (missingBytesInLastGroup == 1) {
 254                 int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
 255                 result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
 256             }
 257         }
 258         // assert inCursor == s.length()-missingBytesInLastGroup;
 259         // assert outCursor == result.length;
 260         return result;
 261     }
 262 
 263     /**
 264      * Translates the specified character, which is assumed to be in the
 265      * "Base 64 Alphabet" into its equivalent 6-bit positive integer.
 266      *
 267      * @throw IllegalArgumentException or ArrayOutOfBoundsException if
 268      *        c is not in the Base64 Alphabet.
 269      */
 270     private static int base64toInt(char c, byte[] alphaToInt) {
 271         int result = alphaToInt[c];
 272         if (result < 0)
 273             throw new IllegalArgumentException("Illegal character " + c);
 274         return result;
 275     }
 276 
 277     /**
 278      * This array is a lookup table that translates unicode characters
 279      * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
 280      * into their 6-bit positive integer equivalents.  Characters that
 281      * are not in the Base64 alphabet but fall within the bounds of the
 282      * array are translated to -1.
 283      */
 284     private static final byte base64ToInt[] = {
 285         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 286         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 287         -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
 288         55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
 289         5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
 290         24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
 291         35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
 292     };
 293 
 294     /**
 295      * This array is the analogue of base64ToInt, but for the nonstandard
 296      * variant that avoids the use of uppercase alphabetic characters.
 297      */
 298     private static final byte altBase64ToInt[] = {
 299         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 300         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1,
 301         2, 3, 4, 5, 6, 7, 8, -1, 62, 9, 10, 11, -1 , 52, 53, 54, 55, 56, 57,
 302         58, 59, 60, 61, 12, 13, 14, -1, 15, 63, 16, -1, -1, -1, -1, -1, -1,
 303         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 304         -1, -1, -1, 17, -1, 18, 19, 21, 20, 26, 27, 28, 29, 30, 31, 32, 33,
 305         34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
 306         51, 22, 23, 24, 25
 307     };
 308 
 309 }