Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support NTLM auth #206

Closed
swankjesse opened this issue Jun 5, 2013 · 20 comments
Closed

Support NTLM auth #206

swankjesse opened this issue Jun 5, 2013 · 20 comments
Labels
enhancement Feature not a bug

Comments

@swankjesse
Copy link
Collaborator

No description provided.

@swankjesse
Copy link
Collaborator Author

After learning about NTLM, I don't think we want to implement it. I'm going to icebox this.

@swankjesse swankjesse modified the milestones: Icebox, 3.0 Mar 22, 2014
@post2shyam
Copy link

Eagerly awaiting the NTLM support.

@swankjesse
Copy link
Collaborator Author

@post2shyam don't hold your breath! The NTLM technology is not a good fit for OkHttp: in particular it interferes with connection management. If you'd like to fork and see what you can build, I'm curious to see what it looks like.

@swankjesse
Copy link
Collaborator Author

Also maybe possible to do NTLM as an interceptor? Worth investigating at least.

@SelvinPL
Copy link

SelvinPL commented Sep 29, 2015

Just a proof of concept: (NTLMEngineImpl is standalone version of org.apache.http.impl.auth.NTLMEngineImpl - removed all dependencies to org.apache.http.* and change org.apache.commons.codec.binary.Base64 to android Base64) - works fine with okhttp:2.4.0

usage:

final OkHttpClient client = new OkHttpClient();
client.setAuthenticator(new NTLMAuthenticator(usr, pwd, dom));

code:

public static class NTLMAuthenticator implements Authenticator {
    final NTLMEngineImpl engine = new NTLMEngineImpl();
    private final String domain;
    private final String username;
    private final String password;
    private final String ntlmMsg1;

    public NTLMAuthenticator(String username, String password, String domain) {
        this.domain = domain;
        this.username = username;
        this.password = password;
        String localNtlmMsg1 = null;
        try {
            localNtlmMsg1 = engine.generateType1Msg(null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        ntlmMsg1 = localNtlmMsg1;
    }

    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {
        final List<String> WWWAuthenticate = response.headers().values("WWW-Authenticate");
        if (WWWAuthenticate.contains("NTLM")) {
            return response.request().newBuilder().header("Authorization", "NTLM " + ntlmMsg1).build();
        }
        String ntlmMsg3 = null;
        try {
            ntlmMsg3 = engine.generateType3Msg(username, password, domain, "android-device", WWWAuthenticate.get(0).substring(5));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return response.request().newBuilder().header("Authorization", "NTLM " + ntlmMsg3).build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        return null;
    }
}

@sryuliwa
Copy link

great

@swankjesse
Copy link
Collaborator Author

No further action for us to take on this.

@zetazaw
Copy link

zetazaw commented Jul 13, 2016

@SelvinPL it works with Okhttp3.4.1 also. Thank you for enlightenment.

@javichaques
Copy link

javichaques commented Nov 2, 2016

@SelvinPL @swankjesse
Hi, which library i need to import to work?

@SelvinPL
Copy link

SelvinPL commented Nov 2, 2016

Non ... grab code from http://svn.apache.org/repos/asf/httpcomponents/httpclient/tags/4.5.2/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java ... make few obvious changes to get code compiled ... and that's it

27c27
< package org.apache.http.impl.auth;
---
> package org.apache.http.impl.standalone;
37a38
> import android.util.Base64;
39,43d39
< import org.apache.commons.codec.binary.Base64;
< import org.apache.http.Consts;
< import org.apache.http.annotation.NotThreadSafe;
< import org.apache.http.util.CharsetUtils;
< import org.apache.http.util.EncodingUtils;
51,52d46
< @NotThreadSafe
< final class NTLMEngineImpl implements NTLMEngine {
53a48,58
> public final class NTLMEngineImpl  {
>  public static class NTLMEngineException extends Exception{
> 
>      public NTLMEngineException(String message){
>          super(message);
>      }
> 
>      public NTLMEngineException(String message, Throwable e) {
>          super(message, e);
>      }
>  }
55c60
<     private static final Charset UNICODE_LITTLE_UNMARKED = CharsetUtils.lookup("UnicodeLittleUnmarked");
---
>     private static final Charset UNICODE_LITTLE_UNMARKED = Charset.forName("UnicodeLittleUnmarked");
57c62
<     private static final Charset DEFAULT_CHARSET = Consts.ASCII;
---
>     private static final Charset DEFAULT_CHARSET = Charset.forName("US-ASCII");
95c100
<         final byte[] bytesWithoutNull = "NTLMSSP".getBytes(Consts.ASCII);
---
>         final byte[] bytesWithoutNull = "NTLMSSP".getBytes(DEFAULT_CHARSET);
117c122
<      * @throws org.apache.http.HttpException
---
>      * @throws NTLMEngineException
584c589
<             final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(Consts.ASCII);
---
>             final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(DEFAULT_CHARSET);
590c595
<             final byte[] magicConstant = "KGS!@#$%".getBytes(Consts.ASCII);
---
>             final byte[] magicConstant = "KGS!@#$%".getBytes(DEFAULT_CHARSET);
821c826
<             messageContents = Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET));
---
>             messageContents = Base64.decode(messageBody.getBytes(DEFAULT_CHARSET), Base64.NO_WRAP);
960c965
<             return EncodingUtils.getAsciiString(Base64.encodeBase64(resp));
---
>             return Base64.encodeToString(resp, Base64.NO_WRAP);
1624c1629
<     @Override
---
> 
1631c1636
<     @Override
---
> 

@eGorets
Copy link

eGorets commented Apr 9, 2017

Hi guys. does any one have ready-to-use NTLMEngineImpl.java class? I'm having difficules with modification of it. Project version of SDK is 24

@zetazaw
Copy link

zetazaw commented Apr 10, 2017

have you try @SelvinPL advice? otherwise you can trace apache lib and extract from it. I hope i can make a small lib soon.

@SelvinPL
Copy link

I did provide link to the code and diff patch. it's all you need to use it.

@eGorets
Copy link

eGorets commented Apr 10, 2017

sorry, I've no experience to modify this quickly, so I thought some one has compiled class.
I tried to modify it manually and got lot of problems like there are not NTLMEngine and NTLMEngineException classes and other chain of unresolved links.

@harshVRastogi
Copy link

It's never coming to generate type3 message and at last it gives bad request. Please help.

@richmidwinter
Copy link

So... I made changes to get it to compile and got a jar building with it - https://github.com/richmidwinter/okhttp-ntlm

At runtime it tries to auth (against MS Exchange) 20 times getting a 401 and then quits out (it does get as far the type3 message for me). Pretty certain my auth details are correct though. Same credentials work via NTLM with the Insomnia app. Any ideas?

@richmidwinter
Copy link

Depending on jcifs' implementation instead works for me. Repo updated: https://github.com/richmidwinter/okhttp-ntlm

@SelvinPL
Copy link

Could you test something with "not working" version?
Try to use domain with uppercase like DOMAIN\username not domain\username
I think this could be a problem

@Dmitriy413
Copy link

Hello!

Sorry for english, I use google translator.

The problem is as follows:
I'm trying to call the service on tomcat 9 using NTLM authentication.
As a result, I get an HTTP / 1.1 500 error on my Type3Message message:
jcifs.smb.SmbException: The parameter is incorrect.

Details of my implementation:

  1. NTLM session:

REQUEST:

GET /api/private/dashboard/tests/info?countDay=7 HTTP/1.1
Host: 10.36.9.79:8443
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.4.1

RESPONSE:

HTTP/1.1 401 
WWW-Authenticate: NTLM
Content-Length: 0
Date: Tue, 27 Feb 2018 11:37:32 GMT
Connection: Keep-alive

==========================================================

REQUEST:

GET /api/private/dashboard/tests/info?countDay=7 HTTP/1.1
Authorization: NTLM TlRMTVNTUAABAAAABYIIoA==
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Host: 10.36.9.79:8443
Connection: Keep-Alive
User-Agent: okhttp/3.4.1

RESPONSE:

HTTP/1.1 401 
Set-Cookie: JSESSIONID=77BEDD61EFCB331DEEA3A1896FFD69F6; Path=/; Secure; HttpOnly
WWW-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAAAAAAAABAgAAVcYhEkMropU=
Content-Length: 0
Date: Tue, 27 Feb 2018 11:37:32 GMT
Connection: Keep-alive

==========================================================

REQUEST:

GET /api/private/dashboard/tests/info?countDay=7 HTTP/1.1
Authorization: NTLM TlRMTVNTUAADAAAAGAAYAEAAAAAwADAAWAAAAAAAAAAAAAAADgAOAIgAAAAAAAAAAAAAAAAAAAAAAAAAAQIAAOIs9/SgQmA/KYsY5YmJTB2AFbn1R8GK6i7qaY9Oa2U1LHHVJCT3BXUBAQAAAAAAADChgFq/r9MBVZDLQIXb/2wAAAAAAAAAAHMAYgB0AC0AdQBsAGEA
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Host: 10.36.9.79:8443
Connection: Keep-Alive
Cookie: JSESSIONID=77BEDD61EFCB331DEEA3A1896FFD69F6
User-Agent: okhttp/3.4.1

RESPONSE:

HTTP/1.1 500 
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 1750
Date: Tue, 27 Feb 2018 11:37:32 GMT
Connection: Close

<!doctype html><html lang="en"><head><title>HTTP Status 500 – Internal Server Error</title><style type="text/css">h1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} h2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} h3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} body {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} b {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} p {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;} a {color:black;} a.name {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 500 – Internal Server Error</h1><hr class="line" /><p><b>Type</b> Exception Report</p><p><b>Message</b> The parameter is incorrect.</p><p><b>Description</b> The server encountered an unexpected condition that prevented it from fulfilling the request.</p><p><b>Exception</b></p><pre>jcifs.smb.SmbException: The parameter is incorrect.
	jcifs.smb.SmbTransport.checkStatus(SmbTransport.java:563)
	jcifs.smb.SmbTransport.send(SmbTransport.java:663)
	jcifs.smb.SmbSession.sessionSetup(SmbSession.java:316)
	jcifs.smb.SmbSession.send(SmbSession.java:218)
	jcifs.smb.SmbTree.treeConnect(SmbTree.java:176)
	jcifs.smb.SmbSession.logon(SmbSession.java:147)
	jcifs.smb.SmbSession.logon(SmbSession.java:140)
	jcifs.http.NtlmHttpFilter.negotiate(NtlmHttpFilter.java:189)
	jcifs.http.NtlmHttpFilter.doFilter(NtlmHttpFilter.java:121)
</pre><p><b>Note</b> The full stack trace of the root cause is available in the server logs.</p><hr class="line" /><h3>Apache Tomcat/9.0.1</h3></body></html>
  1. Implementation in the JAVA code:

2.1 I use OkHttpClient ver. 3.4.1:

CLIENT = new OkHttpClient.Builder()
                    .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(TIMEOUT, TimeUnit.SECONDS)
                    .addInterceptor(new LoggingInterceptor())
                    .cookieJar(cookieJar)
                    .authenticator(new NTLMAuthenticator("LOGIN", "PASSWORS"))
                    .sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0])
                    .hostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }
                    })
                    .build();

2.2 I use retrofit ver. 2.1.0:

Retrofit getRetrofit () {
         return new Retrofit.Builder ()
                 .baseUrl (API_ENDPOINT)
                 .addConverterFactory (GsonConverterFactory.create ())
                 .client (CLIENT)
                 .build ();

2.3 Source code for the NTLMAuthenticator class:

package ...android.mainapplication;

import android.support.annotation.NonNull;
import android.util.Log;

import java.io.IOException;
import java.util.List;

import jcifs.ntlmssp.NtlmFlags;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.util.Base64;
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;


public class NTLMAuthenticator  implements Authenticator {

    final String TAG = "MobileLog";

    private String mLogin;
    private String mPassword;
    private String mDomain;
    private String mWorkstation;

    JCIFSEngine jcifsEngine = new JCIFSEngine();

    public NTLMAuthenticator(@NonNull String login, @NonNull String password) {
        this(login, password, "", "");

        jcifs.smb.SmbException d;
        Log.d(TAG, "--- NTLMAuthenticator login: " + login);
    }

    public NTLMAuthenticator(@NonNull String login, @NonNull String password, @NonNull String domain, @NonNull String workstation) {
        mLogin = login;
        mPassword = password;
        mDomain = domain;
        mWorkstation = workstation;
    }

    @Override
    public Request authenticate(Route route, Response response) throws IOException {

        Log.d(TAG, "--- NTLMAuthenticator authenticate  start" );

        List<String> authHeaders = response.headers("WWW-Authenticate");
        if (authHeaders != null) {

            boolean ntlm = false;
            String ntlmValue = null;
            for (String authHeader : authHeaders) {

                Log.d(TAG, "--- NTLMAuthenticator authenticate  WWW-Authenticate: " + authHeader );

                if (authHeader.equalsIgnoreCase("NTLM")) {
                    ntlm = true;
                }
                if (authHeader.startsWith("NTLM ")) {
                    ntlmValue = authHeader.substring(5);
                }
            }
            if (ntlmValue != null) {
                String type3Msg = null;
                try {
                    type3Msg = jcifsEngine.generateType3Msg(mLogin, mPassword, mDomain, mWorkstation, ntlmValue);
                } catch (Exception e) {
                    Log.d(TAG, "--- NTLMAuthenticator generateType3Msg  Exception: " + e.getMessage() );
                    e.printStackTrace();
                }
                String ntlmHeader = "NTLM " + type3Msg;
                Log.d(TAG, "--- NTLMAuthenticator authenticate  type3Msg: " + ntlmHeader);

                return response.request().newBuilder().header("Authorization", ntlmHeader)
                        .header("Accept-Language", "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3")
                        .header("Accept-Encoding", "gzip, deflate")
                        .header("Upgrade-Insecure-Requests", "1")
                        .build();

            }
            if (ntlm) {
                String type1Msg = null;
                try {
                    type1Msg = jcifsEngine.generateType1Msg(mDomain, mWorkstation);
                } catch (Exception e) {
                    Log.d(TAG, "--- NTLMAuthenticator generateType1Msg  Exception: " + e.getMessage() );
                    e.printStackTrace();
                }
                String header = "NTLM " + type1Msg;
                Log.d(TAG, "--- NTLMAuthenticator authenticate type1Msg: " + header);

                return response.request().newBuilder().header("Authorization", header)
                        .header("Accept-Language", "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3")
                        .header("Accept-Encoding", "gzip, deflate")
                        .header("Upgrade-Insecure-Requests", "1")
                        .build();
            }
        }

        Log.d(TAG, "--- NTLMAuthenticator authenticate  debug 5" );
        return null;
    }
}

// JCIFSEngine
 class JCIFSEngine {

    final String TAG = "ULAMobileLog";

    public String generateType1Msg(
            String domain,
            String workstation) throws Exception {

        Type1Message t1m = new Type1Message(
                Type1Message.getDefaultFlags(),
                domain,
                workstation);
        Log.d(TAG, "--- NTLMAuthenticator generateType1Msg  t1m: " + t1m.toString() );

        return Base64.encode(t1m.toByteArray());
    }

    public String generateType3Msg(
            String username,
            String password,
            String domain,
            String workstation,
            String challenge) throws Exception {
        Type2Message t2m;
        try {
            t2m = new Type2Message(Base64.decode(challenge));
            Log.d(TAG, "--- NTLMAuthenticator generateType1Msg  t2m: " + t2m.toString() );
        } catch (IOException ex) {
            throw new Exception("Invalid Type2 message", ex);
        }


        final int type2Flags = t2m.getFlags();
        final int type3Flags = type2Flags
                & (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));


        Type3Message t3m = new Type3Message(
                t2m,
                password,
                domain,
                username,
                workstation, type3Flags);
        Log.d(TAG, "--- NTLMAuthenticator generateType1Msg  t3m: " + t3m.toString() );
        return Base64.encode(t3m.toByteArray());
    }

}

I will be glad to any help.

@swankjesse swankjesse removed this from the Icebox milestone Nov 4, 2018
@marcelbonnet
Copy link

Just a proof of concept: (NTLMEngineImpl is standalone version of org.apache.http.impl.auth.NTLMEngineImpl - removed all dependencies to org.apache.http.* and change org.apache.commons.codec.binary.Base64 to android Base64) - works fine with okhttp:2.4.0

usage:

final OkHttpClient client = new OkHttpClient();
client.setAuthenticator(new NTLMAuthenticator(usr, pwd, dom));

code:

public static class NTLMAuthenticator implements Authenticator {
    final NTLMEngineImpl engine = new NTLMEngineImpl();
    private final String domain;
    private final String username;
    private final String password;
    private final String ntlmMsg1;

    public NTLMAuthenticator(String username, String password, String domain) {
        this.domain = domain;
        this.username = username;
        this.password = password;
        String localNtlmMsg1 = null;
        try {
            localNtlmMsg1 = engine.generateType1Msg(null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        ntlmMsg1 = localNtlmMsg1;
    }

    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {
        final List<String> WWWAuthenticate = response.headers().values("WWW-Authenticate");
        if (WWWAuthenticate.contains("NTLM")) {
            return response.request().newBuilder().header("Authorization", "NTLM " + ntlmMsg1).build();
        }
        String ntlmMsg3 = null;
        try {
            ntlmMsg3 = engine.generateType3Msg(username, password, domain, "android-device", WWWAuthenticate.get(0).substring(5));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return response.request().newBuilder().header("Authorization", "NTLM " + ntlmMsg3).build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        return null;
    }
}

Still working on 'com.squareup.okhttp3:okhttp:3.10.0' .
Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature not a bug
Projects
None yet
Development

No branches or pull requests