/*
* This file is part of the OWASP Proxy, a free intercepting proxy library.
* Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to:
* The Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.owasp.proxy.http;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import jcifs.Config;
import jcifs.ntlmssp.NtlmFlags;
import jcifs.ntlmssp.NtlmMessage;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import org.owasp.proxy.util.Base64;
public class HttpAuthenticator {
static {
Config.setProperty("jcifs.smb.client.useUnicode", "false");
}
// Flag values determined empirically through observation of successful
// authentication using Firefox and WireShark
private static int NTLMV2_FLAGS = 0xa2088207;
// NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2
// | NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN
// | NtlmFlags.NTLMSSP_NEGOTIATE_NTLM
// | NtlmFlags.NTLMSSP_REQUEST_TARGET
// | NtlmFlags.NTLMSSP_NEGOTIATE_OEM
// | NtlmFlags.NTLMSSP_NEGOTIATE_UNICODE;
private static int NTLMV2_FLAGS_TYPE3 = 0xa2888205;
// NTLMV2_FLAGS
// & ~NtlmFlags.NTLMSSP_NEGOTIATE_OEM;
private static Logger log = Logger.getLogger(HttpAuthenticator.class.getName());
public static boolean requiresAuthentication(ResponseHeader response)
throws MessageFormatException {
String status = response.getStatus();
return "401".equals(status) || "407".equals(status);
}
public boolean authenticate(MutableRequestHeader request,
ResponseHeader response, ResponseHeader response2)
throws IOException, MessageFormatException {
if (response == null) {
// TODO: preemptive authentication, not yet supported
return false;
}
String status = response.getStatus();
String authHeader = null;
List<String> challenges = null, challenges2 = null;
if ("407".equals(status)) {
// proxy authentication required
authHeader = HttpConstants.PROXY_AUTHORIZATION;
challenges = getChallenges(response,
HttpConstants.PROXY_AUTHENTICATE);
challenges2 = getChallenges(response2,
HttpConstants.PROXY_AUTHENTICATE);
} else if ("401".equals(status)) {
// www authentication required
authHeader = HttpConstants.AUTHORIZATION;
challenges = getChallenges(response, HttpConstants.AUTHENTICATE);
challenges2 = getChallenges(response2, HttpConstants.AUTHENTICATE);
}
if (challenges == null || challenges.size() == 0)
return false;
String challenge = selectChallenge(challenges);
if (challenge == null)
return false;
if (challenge.startsWith("NTLM") && "HTTP/1.0".equals(request.getVersion()))
// NTLM requires Connection keepalives, I think
request.setVersion("HTTP/1.1");
String challenge2 = selectChallenge(challenges2);
if (challenge2 != null && challenge2.startsWith(challenge))
return false; // NTLM failed, prompting "NTLM" again
String authResponse = constructResponse(request.getTarget(), challenge);
if (authResponse == null)
return false;
request.setHeader(authHeader, authResponse);
return true;
}
private static List<String> getChallenges(ResponseHeader response,
String header) throws MessageFormatException {
List<String> challenges = new ArrayList<String>();
if (response != null) {
NamedValue[] headers = response.getHeaders();
for (int i = 0; i < headers.length; i++) {
if (header.equalsIgnoreCase(headers[i].getName())) {
challenges.add(headers[i].getValue());
}
}
}
return challenges;
}
protected String constructResponse(InetSocketAddress target,
String challenge) throws IOException {
if (challenge.startsWith("NTLM")) {
return performNtlmAuthentication(target, challenge);
} else if (challenge.startsWith("Digest")) {
return performDigestAuthentication(target, challenge);
} else if (challenge.startsWith("Basic")) {
return performBasicAuthentication(target, challenge);
}
return null;
}
protected String performBasicAuthentication(InetSocketAddress target,
String challenge) throws IOException {
int index = challenge.indexOf("realm=");
if (index == -1)
return null;
String realm = challenge.substring(index + 6); // "realm="
String hostname = target.getHostName();
InetAddress addr = target.isUnresolved() ? null : target.getAddress();
PasswordAuthentication pa = Authenticator
.requestPasswordAuthentication(hostname, addr,
target.getPort(), "HTTP", realm, "Basic Authentication");
if (pa == null)
return null;
String auth = pa.getUserName() + ":" + new String(pa.getPassword());
return "Basic "
+ Base64.encodeBytes(auth.getBytes(), Base64.NO_OPTIONS);
}
protected String performDigestAuthentication(InetSocketAddress target,
String challenge) throws IOException {
return null;
}
protected String performNtlmAuthentication(InetSocketAddress target,
String challenge) throws IOException {
if (challenge.length() == 4) {
NtlmMessage type1 = new Type1Message(NTLMV2_FLAGS, null, null);
log.info(type1.toString());
return "NTLM "
+ Base64.encodeBytes(type1.toByteArray(), Base64.NO_OPTIONS);
} else {
challenge = challenge.substring(5); // "NTLM "
Type2Message type2 = new Type2Message(Base64.decode(challenge,
Base64.NO_OPTIONS));
log.info(type2.toString());
String domain = type2.getTarget();
String hostname = target.getHostName();
InetAddress addr = target.isUnresolved() ? null : target
.getAddress();
PasswordAuthentication pa = Authenticator
.requestPasswordAuthentication(hostname, addr,
target.getPort(), "HTTP", domain, "NTLM");
if (pa == null)
return null;
String username = pa.getUserName();
String password = new String(pa.getPassword());
int slash = username.indexOf('\\');
if (slash > -1) {
domain = username.substring(0, slash);
username = username.substring(slash + 1);
}
Type3Message type3 = new Type3Message(type2, password, "" /*domain*/,
username, null, NTLMV2_FLAGS_TYPE3);
log.info(type3.toString());
return "NTLM "
+ Base64.encodeBytes(type3.toByteArray(), Base64.NO_OPTIONS);
}
}
protected String selectChallenge(List<String> challenges) {
String challenge = null;
if (challenges.size() == 1) {
challenge = challenges.get(0);
} else {
Iterator<String> it = challenges.iterator();
while (it.hasNext()) {
String ch = it.next();
if (ch.toLowerCase().startsWith("basic")) {
challenge = ch;
break;
}
}
if (challenges.contains("NTLM")) {
challenge = "NTLM";
}
}
return challenge;
}
}