I'm still rationalising what I did, but hopefully this'll help you get
started understanding it...
When you say "from the client side" - I'm running the test case throught the
ServletTestRunner so it's really all running on the server anyway - the
authentication is negotiated through the NTLMAuthentication class (see
below).
The test case has its beginXXX() method which does a setAuthentication(new
NTLMAuthentication("userName", "password", "domainName", "workstationID");
and these are all set directly in the test case.
Then what happens is the configure() method does the three-way negotiation
which I post below.
The NTLMAuthentication class has the following:
Private member variables:
/**
* The credentials to be used for authentication.
*/
private String domain = null;
private String workstation = null;
private byte domainLength = 0;
/**
* The length in bytes of the credentials. Needed for the
* response messages.
*/
private byte userLength = 0;
private byte passLength = 0;
private byte workstationLength = 0;
Constructor:
public NTLMAuthentication(String theName, String thePassword,
String theDomain, String theWorkstation) {
super(theName, thePassword);
domain = theDomain;
workstation = theWorkstation;
domainLength = (byte) (domain.length() * 2);
userLength = (byte) (name.length() * 2);
passLength = (byte) (password.length() * 2);
workstationLength = (byte) (workstation.length() * 2);
}
And then the configure method calls the authenticate method:
public void authenticate(WebRequest theRequest,
Configuration theConfiguration) {
try {
// Create a helper that will connect to the server, negotiating until
we have a valid header for theRequest
String resource = ((WebConfiguration)
theConfiguration).getRedirectorURL(theRequest);
ConnectionHelper helper =
ConnectionHelperFactory.getConnectionHelper(resource,
theConfiguration);
// Intermediate request object to do the negotiation
WebRequest newRequest = new WebRequest((WebConfiguration)
theConfiguration);
// perform first 2 steps of authentication (if required)
for (int step = 0; step < 2; step++) {
// Make the connection using a default web request.
HttpURLConnection connection = helper.connect(
newRequest,theConfiguration);
// Is authentication required?
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
// authentication not required
break;
}
else if (connection.getResponseCode() ==
HttpURLConnection.HTTP_UNAUTHORIZED) {
byte[] responseMsg = null;
// Now check the header
String msg = connection.getHeaderField("WWW-Authenticate");
if (msg != null && msg.startsWith("NTLM")) {
// get the response message.
responseMsg = getResponseMessage(msg);
if (step == 0) {
// type 1 response message required. Still need one more
attempt
//before we can set the header for the original request
newRequest = new WebRequest( (WebConfiguration)
theConfiguration);
prepareRequestHeader(newRequest, responseMsg);
}
else {
// type 3 response message required
// the connection will happen after the call to this method -
this is the
// bit that sets up the call....
prepareRequestHeader(theRequest, responseMsg);
}
}
}
else {
// we would only expect ok and unauthorised - anything else is a
problem
throw new ChainedRuntimeException(
"Unable to authenticate, server error encountered - response:
" +
connection.getResponseCode() + ", message: " +
connection.getResponseMessage());
}
}
}
catch (Throwable e) {
throw new ChainedRuntimeException("Failed to authenticate "
+ "the principal", e);
}
}
And these are all the helper methods:
/**
* Returns a byte array containing a Type 1 message. T
* @return byte[] the Type 1 message
*/
private byte[] getType1Message() {
ArrayList encodedPart = new ArrayList();
byte[] retVal = {
//NTLM SSP Signature
(byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M', (byte) 'S', (byte)
'S', (byte) 'P', 0,
// type 1 indicator
1, 0, 0, 0,
// flags
5, 2, 0, 0,
// domain name security buffer
domainLength, 0, domainLength, 0, 43,
// domain name security buffer
workstationLength, 0, workstationLength, 0, (byte) (43 +
domainLength)};
encodedPart.add(retVal);
encodedPart.add(chunkedString(domain)); // domain
encodedPart.add(chunkedString(workstation)); // workstation
return toByteArray(encodedPart);
}
/**
* Returns a byte array containing a Type 3 message. T
* @return byte[] the Type 3 message
*/
private byte[] getType3Message(byte[] msg) {
ArrayList encodedPart = new ArrayList();
byte[] challenge = new byte[8];
// extract the challenge key from the server's response message.
// 8 bytes from offset 24.
for (int i = 0; i < 8; i++) {
challenge[i] = msg[24 + i];
}
// Build up the type 3 message.
byte[] retVal = {
//NTLM SSP Signature
(byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M', (byte) 'S', (byte)
'S', (byte) 'P', 0,
// type 3 indicator
3, 0, 0, 0,
// LM Response security buffer. 24 bytes long, 24 allocated, 114
offset
24, 0, 24, 0, 114, 0, 0, 0,
// NTLM Response security buffer. 24 bytes, 24 allocated, 138 offset
24, 0, 24, 0, (byte) 138, 0, 0, 0,
// Domain name security buffer
domainLength, 0, domainLength, 0, 64, 0, 0, 0,
// User name security buffer
userLength, 0, userLength, 0, (byte) (64 + domainLength), 0, 0, 0,
//Workstation name security buffer
workstationLength, 0, workstationLength, 0, (byte) (64 +
domainLength + userLength), 0, 0, 0,
// Session key security buffer (offset is 1 past the whole message -
it is optional and not supplied here)
0, 0, 0, 0, (byte) (64 + domainLength + userLength +
workstationLength + 24 + 24), 0, 0, 0,
// flags
5, 2, 0, 0};
encodedPart.add(retVal);
encodedPart.add(chunkedString(domain)); // domain
encodedPart.add(chunkedString(name)); // user name
encodedPart.add(chunkedString(workstation)); // workstation
try {
encodedPart.add(Responses.getLMResponse(password, challenge)); // lm
response
encodedPart.add(Responses.getNTLMResponse(password, challenge)); //
ntlm response
}
catch (Exception ex) {
ex.printStackTrace();
}
return toByteArray(encodedPart);
}
/**
* Returns a byte array of the specified string - each byte is
null-terminated
* so a string of "baa" would result in a byte array of {'b', 0, 'a', 0,
'a', 0}
* @param in the String to chunk
* @return byte[] the chunked byte array
*/
private byte[] chunkedString(String in) {
byte[] retVal = new byte[in.length() * 2];
for (int i = 0; i < in.length(); i++) {
retVal[i * 2] = (byte) in.charAt(i);
retVal[i * 2 + 1] = 0;
}
return retVal;
}
/**
* Concatenates an ArrayList of byte arrays into one single byte array
* @param list ArrayList containing byte arrays
* @return byte[] single concatenated byte array
*/
private byte[] toByteArray(ArrayList list) {
ListIterator iter = list.listIterator();
// get the total size of all elements (flattened size)
int size = 0;
while (iter.hasNext()) {
size += ( (byte[]) iter.next()).length;
}
// now build the flat array
byte[] retVal = new byte[size];
iter = list.listIterator();
int idx = 0; //placeholder
while (iter.hasNext()) {
byte[] thisArray = (byte[]) iter.next();
for (int i = 0; i < thisArray.length; i++) {
retVal[idx] = thisArray[i];
idx++;
}
}
return retVal;
}
/**
* Returns the appropriate Type 1 or Type 3 response message depending on
the supplied server message.
*
* @param serverMessage The message supplied by the server
* @return byte[] the appropriate client response
* @throws java.lang.Exception
*/
private byte[] getResponseMessage(String serverMessage) throws Exception {
byte[] responseMsg = null;
// is this the first challenge?
if (!serverMessage.equals("NTLM ")) {
// Type 1 message required
responseMsg = getType1Message();
}
else {
byte[] src = new sun.misc.BASE64Decoder().decodeBuffer(serverMessage.
substring(5));
if (src[8] == 2) {
// Type 2 supplied by server, type 3 message required from client
responseMsg = getType3Message(src);
}
}
// can't determine response message - problem
if (responseMsg == null) {
throw new ChainedRuntimeException("Unable to login, " +
"probably due to bad username/password.");
}
return responseMsg;
}
/**
* Sets up the NTLM authentication response in the client's header
* @param request The client's next request object
* @param responseMsg The message to put in the client's header
* @throws java.lang.Throwable
*/
private void prepareRequestHeader(WebRequest request,
byte[] responseMsg) throws
Throwable {
// create the value to put in the header: NTLM followed by Base64
encoded version of message
String ntlmHeader = "NTLM " +
new sun.misc.BASE64Encoder().encode(responseMsg);
// remove any newline characters
ntlmHeader = ntlmHeader.replaceAll("\n", "");
// must go in the Authorization header - server expects it there.
request.addHeader("Authorization", ntlmHeader);
}
Other sources required for this to work are:
A Responses class which I grabbed from a very good article on the
handshaking on SourceForge: http://davenport.sourceforge.net/ntlm.html (at
the bottom is the Responses class source). There is no specified MD4
implementation so I modified the ntlmHash method to use the jCIFS supplied
one : MessageDigest md4 = new jcifs.util.MD4();
There's a lot there - hope it helps.
-----Original Message-----
From: Simon Gibbs [mailto:[EMAIL PROTECTED]
Sent: 20 April 2004 20:14
To: Cactus Users List
Subject: RE: NTLMAuthentication - SOLVED!
On Tue, 2004-04-20 at 10:28, [EMAIL PROTECTED] wrote:
> The problem I had was that I was trying to do the full authentication
> in the
> configure() - what I am now doing (which works) is the following:
>
[snip detailed protocol info]
I'm interested to know, on the client side, where is the authentication
information coming from?
I ask mainly to get a feel for the very beginning of the process. Could you
post the a test case (real or faked)?
SJG
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
------------------------------------------------------------------------
For more information about Barclays Capital, please
visit our web site at http://www.barcap.com.
Internet communications are not secure and therefore the Barclays
Group does not accept legal responsibility for the contents of this
message. Although the Barclays Group operates anti-virus programmes,
it does not accept responsibility for any damage whatsoever that is
caused by viruses being passed. Any views or opinions presented are
solely those of the author and do not necessarily represent those of the
Barclays Group. Replies to this email may be monitored by the Barclays
Group for operational or business reasons.
------------------------------------------------------------------------
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]