NativeScript/NativeScript

NTLM authentication

Open

#4,665 opened on 2017年8月7日

GitHub で見る
 (2 comments) (0 reactions) (0 assignees)TypeScript (23,592 stars) (1,634 forks)batch import
help wantedos: ios

説明

I am trying to use this patched version of ntlm.js (https://github.com/hdeshev/nativescript-ntlm-demo) in my project to do NTLM authentication. It seems to work fine on Android, but not on iOS.

So I have a test user's credentials (testuser / testing) and I created a new Ntlm.login function on the login click button. I have debugged and gone through all steps as much as possible, but cannot see where it is going wrong in iOS. First, I will show the various functions:

login click event:

   submit() {

        Ntlm.setCredentials('ADMIN', 'teststaff', 'testing');
        
        Ntlm.login('[url]')
            .then(() => {
                console.log('Success');
            })
            .catch(error => {
                console.log('Failed');
            })
            
    }

Setting the NTLM credentials

Ntlm.setCredentials = function(domain, username, password) {
   
    var magic = 'KGS!@#$%'; // Create LM password hash.
    var lmPassword = password.toUpperCase().substr(0, 14);
    while (lmPassword.length < 14) lmPassword += '\0';
    var key1 = Ntlm.createKey(lmPassword);
    var key2 = Ntlm.createKey(lmPassword.substr(7));
    var lmHashedPassword = des(key1, magic, 1, 0) + des(key2, magic, 1, 0);
    var ntPassword = ''; // Create NT password hash.
    for (var i = 0; i < password.length; i++)
        ntPassword += password.charAt(i) + '\0';
    var ntHashedPassword = str_md4(ntPassword);
    
    Ntlm.domain = domain;
    Ntlm.username = username;
    Ntlm.lmHashedPassword = lmHashedPassword;
    Ntlm.ntHashedPassword = ntHashedPassword;
};

Here is the Ntlm.login() function:

Ntlm.login = function(url) {
    return new Promise((resolve, reject) => {
        if (!Ntlm.domain || !Ntlm.username || !Ntlm.lmHashedPassword || !Ntlm.ntHashedPassword) {
            Ntlm.error('No NTLM credentials specified. Use Ntlm.setCredentials(...) before making calls.');
        }
        var hostname = Ntlm.getLocation(url).hostname;
        var msg1 = Ntlm.createMessage1(hostname);
        var request = new XMLHttpRequest();

        request.onload = function() {
            var response = request.getResponseHeader('WWW-Authenticate');
            var challenge = Ntlm.getChallenge(response);
            var msg3 = Ntlm.createMessage3(challenge, hostname);
            request.open('GET', url, false);
            var authorization = 'NTLM ' + msg3.toBase64();
            request.setRequestHeader('Authorization', authorization);
            request.onload = function() {                
                if (request.readyState == 4 && request.status == 200) {
                    resolve(request.status);
                }
                else if (request.readyState == 4 && request.status != 200) {
                    reject(request.status);
                }
            };
            request.send(null);
        };
        request.open('GET', url, false);
        request.setRequestHeader('Authorization', 'NTLM ' + msg1.toBase64());
        request.send(null);   
    })
};

I won't include some of the other functions, such as des() as quite long. I put in some console.logs to try and get a step by step analysis of what is going on in the NTLM authentication process. Here are the logs for android:

NTLM WALKTHROUGH ON ANDROID

Step 1: Creates a cryptographic hash of the users password:
lmHashedPassword = -UE}{}*ªÓ´5µî
ntHashedPassword = |SÏ¥ê}›;–Š ûQ£õ

Step 2: Sends the users name to the server, with the following Authorisation header:
NTLM TlRMTVNTUAABAAAAA7IAAAUABQBEAAAAJAAkACAAAABHQVRFV0FZLlNUUEFVTFNDQVRIT0xJQ0NPTExFR0UuQ08uVUtBRE1JTg==

Step 3: Server sends a challenge back to client:
¡2@‚³Q%Ï

Step 4: Client encrypts this challenge with the hash of the users password and sends back to server (response).
The Authorization header is: NTLM TlRMTVNTUAADAAAAGAAYAKQAAAAYABgAvAAAAAoACgBAAAAAEgASAEoAAABIAEgAXAAAAAAAAADUAAAAAYIAAEEARABNAEkATgB0AGUAcwB0AHMAdABhAGYAZgBHAEEAVABFAFcAQQBZAC4AUwBUAFAAQQBVAEwAUwBDAEEAVABIAE8ATABJAEMAQwBPAEwATABFAEcARQAuAEMATwAuAFUASwBsEslcvTQhhY3+RgKtqufBzFrmufFKNkAHXJRcA6ThOAU105+NJBGnsn2ri6Ziuv8=

Step 5: Now the server has sent the username, challenge and response to the Domain Controller.
The DC compares and returns status of: 200

Here are the logs for iOS:

NTLM WALKTHROUGH ON IOS

Step 1: Creates a cryptographic hash of the users password:
lmHashedPassword = -UE}{}*ªÓ´5µî
ntHashedPassword = |SÏ¥ê}›;–Š ûQ£õ

Step 2: Sends the users name to the server, with the following Authorisation header:
NTLM TlRMTVNTUAABAAAAA7IAAAUABQBEAAAAJAAkACAAAABHQVRFV0FZLlNUUEFVTFNDQVRIT0xJQ0NPTExFR0UuQ08uVUtBRE1JTg==

Step 3: Server sends a challenge back to client:
q‘v¹,

Step 4: Client encrypts this challenge with the hash of the users password and sends back to server (response).
The Authorization header is: NTLM TlRMTVNTUAADAAAAGAAYAKQAAAAYABgAvAAAAAoACgBAAAAAEgASAEoAAABIAEgAXAAAAAAAAADUAAAAAYIAAEEARABNAEkATgB0AGUAcwB0AHMAdABhAGYAZgBHAEEAVABFAFcAQQBZAC4AUwBUAFAAQQBVAEwAUwBDAEEAVABIAE8ATABJAEMAQwBPAEwATABFAEcARQAuAEMATwAuAFUASwAP9HN5WjPCs9hMRrmttnYHieFrThwyUAWanKWtVdzOqDOJ2isUdQeV0ISmv9TT0ek=

Step 5: Now the server has sent the username, challenge and response to the Domain Controller.
The DC compares returns status of: 401

I think there is a difference in the way iOS shows console.logs as seems that they cant display some encoded characters whereas Android can. Any ideas why iOS refuses to work?


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

コントリビューターガイド