/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Nam Nguyen */ package com.caucho.quercus.lib.curl; import com.caucho.quercus.QuercusModuleException; import com.caucho.util.Base64; import com.caucho.util.CharBuffer; import com.caucho.util.RandomUtil; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Authentication { /** * Returns an authorization response string. * Supports digest and basic only. */ public static String getAuthorization(String user, String pass, String requestMethod, String uri, String header) { if (header.startsWith("Digest")) return digest(user, pass, requestMethod, uri, header); else return basic(user, pass); } /** * Returns a basic encoded response string. */ public static String basic(String user, String pass) { StringBuilder sb = new StringBuilder(); sb.append(user); sb.append(':'); sb.append(pass); return basic(sb.toString()); } public static String basic(String usernamePassword) { StringBuilder sb = new StringBuilder(); sb.append("Basic "); sb.append(Base64.encode(usernamePassword)); return sb.toString(); } /** * Returns a digest encoded response string. */ public static String digest(String user, String pass, String requestMethod, String uri, String header) { StringBuilder sb = new StringBuilder(); sb.append("Digest "); sb.append("username=\""); sb.append(user); Scanner scanner = new Scanner(header); String realm = ""; String nonce = ""; String qop = ""; String opaque = null; String algorithm = null; String key; while ((key = scanner.readKey()) != null) { String value = scanner.readValue(); if (key.equals("realm")) realm = value; else if (key.equals("nonce")) nonce = value; else if (key.equals("qop")) qop = value; else if (key.equals("opaque")) opaque = value; else if (key.equals("algorithm")) algorithm = value; } scanner.close(); sb.append("\", realm=\""); sb.append(realm); sb.append("\", nonce=\""); sb.append(nonce); sb.append("\", uri=\""); sb.append(uri); sb.append("\", qop=\""); sb.append("auth"); String cnonce = Base64.encode(String.valueOf(RandomUtil.getRandomLong())); sb.append("\", cnonce=\""); sb.append(cnonce); String nc = "00000001"; sb.append("\", nc=\""); sb.append(nc); if (opaque != null) { sb.append("\", opaque=\""); sb.append(opaque); } if (algorithm != null) { sb.append("\", algorithm=\""); sb.append(algorithm); } else algorithm = "MD5"; sb.append("\", response=\""); appendResponse(sb, user, realm, pass, requestMethod, uri, nonce, nc, cnonce, qop, algorithm); sb.append('"'); return sb.toString(); } /** * Appends the authorization string to the StringBuilder. */ private static void appendResponse(StringBuilder sb, String user, String realm, String pass, String requestMethod, String uri, String nonce, String nc, String cnonce, String qop, String algorithm) { MessageDigest resultDigest = null; MessageDigest scratchDigest = null; try { resultDigest = MessageDigest.getInstance(algorithm); scratchDigest = MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { throw new QuercusModuleException(e); } { md5(scratchDigest, user); scratchDigest.update((byte)':'); md5(scratchDigest, realm); scratchDigest.update((byte)':'); md5(scratchDigest, pass); update(resultDigest, scratchDigest.digest()); resultDigest.update((byte)':'); } md5(resultDigest, nonce); resultDigest.update((byte)':'); md5(resultDigest, nc); resultDigest.update((byte)':'); md5(resultDigest, cnonce); resultDigest.update((byte)':'); md5(resultDigest, qop); resultDigest.update((byte)':'); { scratchDigest.reset(); md5(scratchDigest, requestMethod); scratchDigest.update((byte)':'); md5(scratchDigest, uri); update(resultDigest, scratchDigest.digest()); } appendHex(sb, resultDigest.digest()); } /** * Updates MD5 hash. */ private static void md5(MessageDigest md, String string) { int length = string.length(); for (int i = 0; i < length; i++) { md.update((byte)string.charAt(i)); } } /** * Updates MD5 hash result. */ private static void update(MessageDigest resultDigest, byte[] digest) { for (int i = 0; i < digest.length; i++) { int d1 = (digest[i] >> 4) & 0xf; int d2 = (digest[i] & 0xf); resultDigest.update((byte)toHexChar(d1)); resultDigest.update((byte)toHexChar(d2)); } } /** * Appends hex characters to StringBuilder. */ private static void appendHex(StringBuilder sb, byte[] digest) { for (int i = 0; i < digest.length; i++) { int d1 = (digest[i] >> 4) & 0xf; int d2 = (digest[i] & 0xf); sb.append(toHexChar(d1)); sb.append(toHexChar(d2)); } } private static char toHexChar(int d) { d &= 0xf; if (d < 10) return (char) (d + '0'); else return (char) (d - 10 + 'a'); } } /** * Represents a HTTP header field values scanner. */ class Scanner { String _header; int _position; int _length; CharBuffer _cb; Scanner(String header) { _header = header; _position = header.indexOf("Digest") + "Digest".length(); _length = header.length(); _cb = CharBuffer.allocate(); } String readKey() { int ch = skipWhitespace(); if (ch < 0) return null; if (ch == ',') ch = skipWhitespace(); do { _cb.append((char)ch); } while ((ch = read()) != '='); // discard quote read(); String key = _cb.toString(); _cb.clear(); return key; } String readValue() { int ch; while ((ch = read()) != '"') { _cb.append((char)ch); } String value = _cb.toString(); _cb.clear(); return value; } int skipWhitespace() { int ch; while ((ch = read()) >= 0) { if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n' && ch != '\f') break; } return ch; } int read() { if (_position >= _length) return -1; else return _header.charAt(_position++); } void close() { _cb.free(); } }