Tuesday 17 December 2013

NTLMv2 authentication using Java Client!

NTLMv2 Authentication using Java Client
=======================================

Preconditions:
1. Java6/7

2. You should have MS exchange enabled to send NTLMv2 response only.

If you don't have MS exchange, then in that case you can use any NTLMv2 enable proxy server to authenticate your requests. The proxy server which could be used is : squid. Please follow the squid documentation to enable NTLMv2 authentication. May be I can write another blog to set and enable squid proxy server on linux box (Ubuntu).

The below java code can be used to authenticate against any server which supports Basic, digest and NTLM (v1/v2) authentication mechanism. When you try to authenticate your client against a server, client first send NTLMSSP_NEGOTIATE, server sends back the NTLMSSP_CHALLENGE(which authentication scheme the server supports) with 401 (unauthorize), then client send NTLMSSP_AUTH for final authentication. The server sends back 200OK which means the client is authenticated.


The below code is sending a POST (over http) request to MS exchange server

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.net.URL;


public class NTLMV2Authenticate {

                public static void main(String[] args) throws Exception {
                 
   
                                String urlStr = "http://<hostname>/<path>";
                                String domain ="Domain_name"; // May also be referred as realm
                                String userName = "username";
                                String password = "password";              

                                String responseText = getAuthenticatedResponse(urlStr, domain, userName, password);

                    System.out.println("response: " + responseText);
                }

                private static String getAuthenticatedResponse(final String urlStr, final String domain, final String userName, final String password) throws IOException {

                    StringBuilder response = new StringBuilder();

                                Authenticator.setDefault(new Authenticator() {
                        @Override
                        public PasswordAuthentication getPasswordAuthentication() {
                            return new PasswordAuthentication(domain + "\\" + userName, password.toCharArray());
                        }
                    });
                     //String requestMessage =null;
                   
                     String requestMessage =""+
                     "<soap:Envelope \n"+
                     "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n" +
                     "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" \n" +
                     "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" \n" +
                     "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"> \n"+
                     "<soap:Header> \n "+
                     "<t:RequestServerVersion Version=\"Exchange2007_SP1\"/> \n"+
                     "<t:ExchangeImpersonation> \n"+
                     "<t:ConnectingSID> \n"+
                     "<t:PrimarySmtpAddress>user1@domain.com</t:PrimarySmtpAddress>\n"+
                     "</t:ConnectingSID>\n"+
                     "</t:ExchangeImpersonation>\n"+
                     "</soap:Header>\n"+
                     "<soap:Body>\n"+
                     "<GetFolder xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\"\n"+
                     "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"> \n"+
                     "<FolderShape>"+
                     "<t:BaseShape>Default</t:BaseShape>"+
                     "</FolderShape>"+
                     "<FolderIds>"+
                     "<t:DistinguishedFolderId Id=\"deleteditems\"> \n"+
                     "<t:Mailbox>"+
                     "<t:EmailAddress>user1@domain.com</t:EmailAddress>"+
                     "</t:Mailbox>"+
                     "</t:DistinguishedFolderId>"+
                     "</FolderIds>"+
                     "</GetFolder>"+
                     "</soap:Body>"+
                     "</soap:Envelope>";
                   
                    URL urlRequest = new URL(urlStr);
                    HttpURLConnection conn = (HttpURLConnection) urlRequest.openConnection();
                    conn.setDoOutput(true);
                    conn.setDoInput(true);
                    conn.setRequestMethod("POST");
                    conn.setDoInput(true);
                    conn.setDoOutput(true);
                    //use caching
                    conn.setUseCaches(false);
                    conn.setRequestProperty("Content-Type", "text/xml");
                 
                    // Write post data
                    DataOutputStream out = new DataOutputStream(
                    conn.getOutputStream());
                    out.writeBytes(requestMessage);
                    out.flush();
                    out.close();
                 

                    InputStream stream = conn.getInputStream();
                    BufferedReader in = new BufferedReader(new InputStreamReader(stream));
                    String str = "";
                    while ((str = in.readLine()) != null) {
                        response.append(str);
                    }
                    in.close();                    

                    return response.toString();
                }

}

The below code can be used to authenticate over NTLMv2 using HTTPs.
<Code>
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;

public class NTLMV2Authenticate {

                public static void main(String[] args) throws Exception {
                 
   
                                String urlStr = "https://<Hostname returned while installing certificate CN:<hostname>>/<path>";
                                String domain ="Domain_name"; // May also be referred as realm
                                String userName = "username";
                                String password = "password";              

                                String responseText = getAuthenticatedResponse(urlStr, domain, userName, password);

                    System.out.println("response: " + responseText);
                }

                private static String getAuthenticatedResponse(final String urlStr, final String domain, final String userName, final String password) throws IOException {

                    StringBuilder response = new StringBuilder();

                                Authenticator.setDefault(new Authenticator() {
                        @Override
                        public PasswordAuthentication getPasswordAuthentication() {
                            return new PasswordAuthentication(domain + "\\" + userName, password.toCharArray());
                        }
                    });
                     //String requestMessage =null;
                   
                     String requestMessage =""+
                     "<soap:Envelope \n"+
                     "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n" +
                     "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" \n" +
                     "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" \n" +
                     "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"> \n"+
                     "<soap:Header> \n "+
                     "<t:RequestServerVersion Version=\"Exchange2007_SP1\"/> \n"+
                     "<t:ExchangeImpersonation> \n"+
                     "<t:ConnectingSID> \n"+
                     "<t:PrimarySmtpAddress>user1@domain.com</t:PrimarySmtpAddress>\n"+
                     "</t:ConnectingSID>\n"+
                     "</t:ExchangeImpersonation>\n"+
                     "</soap:Header>\n"+
                     "<soap:Body>\n"+
                     "<GetFolder xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\"\n"+
                     "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"> \n"+
                     "<FolderShape>"+
                     "<t:BaseShape>Default</t:BaseShape>"+
                     "</FolderShape>"+
                     "<FolderIds>"+
                     "<t:DistinguishedFolderId Id=\"deleteditems\"> \n"+
                     "<t:Mailbox>"+
                     "<t:EmailAddress>user1@domain.com</t:EmailAddress>"+
                     "</t:Mailbox>"+
                     "</t:DistinguishedFolderId>"+
                     "</FolderIds>"+
                     "</GetFolder>"+
                     "</soap:Body>"+
                     "</soap:Envelope>";
                   
                    URL urlRequest = new URL(urlStr);
                    HttpsURLConnection conn = (HttpsURLConnection) urlRequest.openConnection();
                    conn.setDoOutput(true);
                    conn.setDoInput(true);
                    conn.setRequestMethod("POST");
                    conn.setDoInput(true);
                    conn.setDoOutput(true);
                    //use caching
                    conn.setUseCaches(false);
                    conn.setRequestProperty("Content-Type", "text/xml");
                 
                    // Write post data
                    DataOutputStream out = new DataOutputStream(
                    conn.getOutputStream());
                    out.writeBytes(requestMessage);
                    out.flush();
                    out.close();
                 

                    InputStream stream = conn.getInputStream();
                    BufferedReader in = new BufferedReader(new InputStreamReader(stream));
                    String str = "";
                    while ((str = in.readLine()) != null) {
                        response.append(str);
                    }
                    in.close();                    

                    return response.toString();
                }

}



To deal with certificate issue we need to install the certificate using the following code. The below code fetches the certificate from xchange and ask you to install it. Press "1" to install the same. This code is only valid for self signed certificates and can only be used in development environment.
In order to execute the code you need to pass exchange server hostname as first argument from CLI. You might also need to pass the SSL port of exchange if it is other than 443.

Usage : java installCert <hostname>:port

if you want execute this from IDE (eclipse) use the following steps:

Click "Run" -> "Run Configurations"
Select the application code (installCert) from left frame
Click on the "Arguments" tab
In the text box Enter: <exchange server hostname>:port    => Port is required if it is different from 443.
Click "Run"

Follow the output in Eclipse Console. If it is already trusted it will display " No errors, already trusted"

Note : While installing the cetificate note down the CN name which is returned from the server. It would be like "CN:<hostname> ..."
This is the hostname which should be used while authenticating your java client. Please check the code for SSL above.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class InstallCert {

    public static void main(final String[] args) throws Exception {
        String host;
        int port;
        char[] passphrase;
        if ((args.length == 1) || (args.length == 2)) {
            final String[] c = args[0].split(":");
            host = c[0];
            port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
            final String p = (args.length == 1) ? "changeit" : args[1];
            passphrase = p.toCharArray();
        } else {
            System.out.println(
                    "Usage: java InstallCert <host>[:port] [passphrase]");
            return;
        }

        File file = new File("c:\\Amit\\jssecacerts");
        if (file.isFile() == false) {
            final char SEP = File.separatorChar;
            final File dir = new File(System.getProperty("java.home")
                    + SEP + "lib" + SEP + "security");
            file = new File(dir, "c:\\Amit\\jssecacerts");
            if (file.isFile() == false) {
                file = new File(dir, "cacerts");
            }
        }

        System.out.println("Loading KeyStore " + file + "...");
        final InputStream in = new FileInputStream(file);
        final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, passphrase);
        in.close();

        final SSLContext context = SSLContext.getInstance("TLS");
        final TrustManagerFactory tmf =
                TrustManagerFactory.getInstance(TrustManagerFactory
                        .getDefaultAlgorithm());
        tmf.init(ks);
        final X509TrustManager defaultTrustManager =
                (X509TrustManager) tmf.getTrustManagers()[0];
        final SavingTrustManager tm = new SavingTrustManager(
                defaultTrustManager);
        context.init(null, new TrustManager[] { tm }, null);
        final SSLSocketFactory factory = context.getSocketFactory();

        System.out.println("Opening connection to "
                + host + ":" + port + "...");
        final SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
        socket.setSoTimeout(10000);
        try {
            System.out.println("Starting SSL handshake...");
            socket.startHandshake();
            socket.close();
            System.out.println();
            System.out.println("No errors, certificate is already trusted");
        } catch (final SSLException e) {
            System.out.println();
            e.printStackTrace(System.out);
        }

        final X509Certificate[] chain = tm.chain;
        if (chain == null) {
            System.out.println("Could not obtain server certificate chain");
            return;
        }

        final BufferedReader reader =
                new BufferedReader(new InputStreamReader(System.in));

        System.out.println();
        System.out.println("Server sent " + chain.length + " certificate(s):");
        System.out.println();
        final MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        final MessageDigest md5 = MessageDigest.getInstance("MD5");
        for (int i = 0; i < chain.length; i++) {
            final X509Certificate cert = chain[i];
            System.out.println(" " + (i + 1) + " Subject "
                    + cert.getSubjectDN());
            System.out.println("   Issuer  " + cert.getIssuerDN());
            sha1.update(cert.getEncoded());
            System.out.println("   sha1    " + toHexString(sha1.digest()));
            md5.update(cert.getEncoded());
            System.out.println("   md5     " + toHexString(md5.digest()));
            System.out.println();
        }

        System.out.println("Enter certificate to add to trusted keystore"
                + " or 'q' to quit: [1]");
        final String line = reader.readLine().trim();
        int k;
        try {
            k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
        } catch (final NumberFormatException e) {
            System.out.println("KeyStore not changed");
            return;
        }

        final X509Certificate cert = chain[k];
        final String alias = host + "-" + (k + 1);
        ks.setCertificateEntry(alias, cert);

        final OutputStream out = new FileOutputStream(file);
        ks.store(out, passphrase);
        out.close();

        System.out.println();
        System.out.println(cert);
        System.out.println();
        System.out.println(
                "Added certificate to keystore 'cacerts' using alias '"
                        + alias + "'");
    }

    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(final byte[] bytes) {
        final StringBuilder sb = new StringBuilder(bytes.length * 3);
        for (int b : bytes) {
            b &= 0xff;
            sb.append(HEXDIGITS[b >> 4]);
            sb.append(HEXDIGITS[b & 15]);
            sb.append(' ');
        }
        return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

        private final X509TrustManager tm;
        private X509Certificate[] chain;

        SavingTrustManager(final X509TrustManager tm) {
            this.tm = tm;
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
            // throw new UnsupportedOperationException();
        }

        @Override
        public void checkClientTrusted(final X509Certificate[] chain,
                final String authType)
                throws CertificateException {
            throw new UnsupportedOperationException();
        }

        @Override
        public void checkServerTrusted(final X509Certificate[] chain,
                final String authType)
                throws CertificateException {
            this.chain = chain;
            this.tm.checkServerTrusted(chain, authType);
        }
    }
}


Feel free to post your comments.


1 comment:

  1. Hey Amit, Nice blog...I am using your code to send an NTLM authentication request to a sharepoint server. I am getting an HTTP 400 error (not much description about the error)..since its 400, I assume the server is able to get my request but not able to understand it.

    The request being sent here is empty, and in return I expect a token id from the server. If I use the same details via SOAP UI, I get the token back but not via the code. Any suggestion ?

    ReplyDelete