/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2009 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program 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 for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package com.funambol.util; import java.io.IOException; import java.util.Random; import com.funambol.platform.HttpConnectionAdapter; /** * An authentication object for http digest authentication. */ public class HttpDigestAuthentication implements HttpAuthentication { private String realm; private String qop; private String nonce; private String opaque; private String username; private String password; private String uri; private String authInfoResponse; private boolean retryWithAuth = false; private boolean doAuthentication = false; private static final char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', }; // ------------------------------------------------------------ Constructors /** * Create the digest authentication object with a username, password, and uri. * * @param username The username of the client being authenticated * @param password The password for the client being authenticated * @param uri The uri to authenticate against */ public HttpDigestAuthentication(final String user, final String pass, final String authUri) { username = user; password = pass; uri = authUri; doAuthentication = true; retryWithAuth = false; } /** * Extract a specific property from a string of property value pairs separated * by '=' * * @param str The string of properties * @param prop The property to extract * * @return The value of the property, if it exists */ public static String extractDigestProp(String str, String prop) { int start; int end; char prev = '\0'; start = str.indexOf(prop + "=\"") + prop.length() + 2; end = start; for (int i=start; i < str.length(); i++) { if (str.charAt(i) == '"' && prev != '\\') { end = i; break; } prev = str.charAt(i); } return str.substring(start, end); } /** * Convert a hash to a hexadecimal number. * * @param hash The byte array hash to convert * * @return The byte array in hexadecimal format */ public static final String toHex(byte hash[]) { StringBuffer buf = new StringBuffer(hash.length * 2); for (int idx=0; idx<hash.length; idx++) { buf.append(hex[(hash[idx] >> 4) & 0x0f]).append( hex[hash[idx] & 0x0f]); } return buf.toString(); } /** * Generate an authentication response of digest properties and values. * * @return An authentication response. */ protected String generateAuthResponseString() { return generateAuthResponseString(new Long(new Random().nextLong())); } /** * Generate an authentication response of digest properties and values, * given a seed to generate a client nonce. * * * @return An authentication response */ protected String generateAuthResponseString(Long seed) { StringBuffer buf = new StringBuffer("Digest"); MD5 md5 = new MD5(); String nc = "00000001"; String ha1; String ha2; String cnonce = ""; cnonce = toHex(md5.calculateMD5(seed.toString().getBytes())); String response; buf.append(" username=\""); buf.append(username); buf.append("\", realm=\""); buf.append(realm); buf.append("\", nonce=\""); buf.append(nonce); buf.append("\", uri=\""); buf.append(uri); buf.append("\", qop=\""); buf.append(qop); buf.append("\", nc=\""); buf.append(nc); buf.append("\", cnonce=\""); buf.append(cnonce); buf.append("\", response=\""); ha1 = calculateHa1(username, password, realm); ha2 = calculateHa2(uri); response = calculateResponse(ha1, ha2, nonce, qop, cnonce, nc); buf.append(response); buf.append("\", opaque=\""); buf.append(opaque + "\""); return buf.toString(); } /** * Process the given authentication information. * * @param info The authentication string of digest properties and values * * @return Whether or not the given info contained digest authentication information */ protected boolean processAuthInfo(String info) { String t = info.trim(); int indexOfRealm; if (t.substring(0, 6).equals("Digest")) { realm = extractDigestProp(t, "realm"); qop = extractDigestProp(t, "qop"); nonce = extractDigestProp(t, "nonce"); opaque = extractDigestProp(t, "opaque"); authInfoResponse = generateAuthResponseString(); return true; } return false; } /** * Set the authentication property in the given HTTP connection. * * @param c The connection on which the authentication information is being set * * @return Whether or not the authentication property was set for the given * HTTP connection */ public boolean handleAuthentication(HttpConnectionAdapter c) throws IOException { boolean didSetAuthProp = false; if (doAuthentication && retryWithAuth) { c.setRequestProperty("Authorization", authInfoResponse); didSetAuthProp = true; } return didSetAuthProp; } /** * Check the given connection for a 401 HTTP error and process any authentication * information that is present. * * @param c The connection to check for and process HTTP errors. * * @return Whether or not an error was processed. */ public boolean processHttpError(HttpConnectionAdapter c) throws IOException { boolean processedAuthInfo = false; int httpCode = c.getResponseCode(); if (doAuthentication && httpCode == 401) { if (processAuthInfo(c.getHeaderField("WWW-Authenticate"))) { retryWithAuth = !retryWithAuth; processedAuthInfo = true; } //cannot handle the given auth info } else { retryWithAuth = false; } return processedAuthInfo; } /** * Calculate ha1 given a username, realm, and password. * * @param username The username of the user being authenticated * @param realm The authentication realm * @param password The password of the user being authenticated * * @return A calculated ha1. * */ public static String calculateHa1(String username, String password, String realm) { MD5 md5 = new MD5(); return toHex(md5.calculateMD5((username + ":" + realm + ":" + password).getBytes())); } /** * Calculate ha2 given a uri. * * @param uri The uri to authenticate against. * * @return A calculated ha2 */ public static String calculateHa2(String uri) { MD5 md5 = new MD5(); return toHex(md5.calculateMD5(("POST" + ":" + uri).getBytes())); } /** * Calculate the response given ha1 and ha2. * * @param ha1 A calculated ha1 value * @param ha2 A calculated ha2 value * @param cnonce The client nonce * @param nc The nonce count * * @return A calculated response */ public static String calculateResponse(String ha1, String ha2, String nonce, String qop, String cnonce, String nc) { MD5 md5 = new MD5(); return toHex(md5.calculateMD5((ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2).getBytes())); } /** * Return whether or not to retry requests with authentication information. * * @return Whether or not to retry requests */ public boolean getRetryWithAuth() { return retryWithAuth; } /** * Set the username of the client being authenticated. * * @param value The username to authenticate with */ public void setUsername(String value) { username = value; } /** * Set the password for the authentication. * * @param value The password to authenticate with */ public void setPassword(String value) { password = value; } /** * Set the URI to authenticate against. * * @param value The URI to authenticate against */ public void setUri(String value) { uri = value; } }