/*
* 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 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 {
// Flag values determined empirically through observation of successful
// authentication using Firefox and WireShark
private static int NTLMV2_FLAGS = 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 = NTLMV2_FLAGS
& ~NtlmFlags.NTLMSSP_NEGOTIATE_OEM;
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;
if ("407".equals(status)) {
// proxy authentication required
authHeader = HttpConstants.PROXY_AUTHORIZATION;
challenges = getChallenges(response,
HttpConstants.PROXY_AUTHENTICATE);
} else if ("401".equals(status)) {
// www authentication required
authHeader = HttpConstants.AUTHORIZATION;
challenges = getChallenges(response, HttpConstants.AUTHENTICATE);
}
if (challenges == null || challenges.size() == 0)
return false;
String challenge = selectChallenge(challenges);
if (challenge == null)
return false;
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>();
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);
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));
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);
return "NTLM "
+ Base64
.encodeBytes(type3.toByteArray(), Base64.NO_OPTIONS);
}
}
protected String selectChallenge(List<String> challenges) {
if (challenges.size() == 1)
return challenges.get(0);
Iterator<String> it = challenges.iterator();
while (it.hasNext()) {
String challenge = it.next();
if (challenge.toLowerCase().startsWith("basic"))
return challenge;
}
if (challenges.contains("NTLM")) {
return "NTLM";
}
return null;
}
}