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]

Reply via email to