kclejeune / TMobile-ISP-Client

mobile friendly, self hosted status dashboard for T-Mobile ISP routers
MIT License
33 stars 4 forks source link

Feature: add an authentication endpoint #4

Open kclejeune opened 3 years ago

kclejeune commented 3 years ago

what is it

reverse engineer the authentication endpoint. First step towards implementing #1.

how to do it

currently, the endpoint does:

magic crypto?

const s = cryptoJS.sha256(t.username, t.password)
  , r = cryptoJS.sha256url(s, i.nonce);
let o = `userhash=${cryptoJS.sha256url(t.username, i.nonce)}&RandomKeyhash=${cryptoJS.sha256url(i.randomKey, i.
nonce)}&response=${r}&nonce=${cryptoJS.base64url_escape(i.nonce)}`;
const a = sjcl.codec.base64
  , l = a.fromBits(sjcl.random.randomWords(4, 0))
  , c = a.fromBits(sjcl.random.randomWords(4, 0));
o += `&enckey=${cryptoJS.base64url_escape(l)}`,
o += `&enciv=${cryptoJS.base64url_escape(c)}`,
highvolt-dev commented 3 years ago

And for good measure:


((sjcl.beware && sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]) ||
  function(){})();

var cryptoJS = (function() {

    var base64url_escape = function(b64) {
        var out = "";
        for(i = 0; i < b64.length; i++) {
            var c = b64.charAt(i);
            if (c == '+') {
                out += '-';
            } else if (c == '/') {
                out += '_';
            } else if (c == '=') {
                out += '.';
            } else {
                out += c;
            }
        }
        return out;
    };

    var encrypt = function(pubkey, plaintext) {
        var aeskey = sjcl.random.randomWords(4, 0);
        var iv = sjcl.random.randomWords(4, 0);
        var pt = sjcl.codec.utf8String.toBits(plaintext);
        var aes = new sjcl.cipher.aes(aeskey);
        var ct = sjcl.mode.cbc.encrypt(aes, pt, iv);

        var rsa = new JSEncrypt(); 
        if(rsa.setPublicKey(pubkey) == false)
            return fasle;

        var base64url = sjcl.codec.base64url;
        var base64 = sjcl.codec.base64;
        var aesinfo = base64.fromBits(aeskey) + ' ' + base64.fromBits(iv);
        var ck = rsa.encrypt(aesinfo);
        if(ck == false)
            return false;

        return {
            ct:base64url.fromBits(ct),
            ck:base64url_escape(ck)
        };
    };

    var aes_decrypt = function(ciphertext) {
        var sid = localStorage.getItem('sid');
        var keyinfo = localStorage.getItem(sid);
        if(keyinfo && ciphertext.length > 0)
        {
            var key_iv = keyinfo.split(' ');
            var key = sjcl.codec.base64.toBits(key_iv[0]);
            var iv = sjcl.codec.base64.toBits(key_iv[1]);
            var ct = sjcl.codec.base64.toBits(ciphertext);
            var aes = new sjcl.cipher.aes(key);
            try{
                var pt = sjcl.mode.cbc.decrypt(aes, ct, iv);
                return sjcl.codec.utf8String.fromBits(pt);
            }catch(err) {
                console.log(err.message);
            }
        }

        return ciphertext;
    };

    var encrypt_post_data = function(pubkey, plaintext) {
        var p = encrypt(pubkey, plaintext);
        return  'encrypted=1&ct=' + p.ct + '&ck=' + p.ck;
    };

    var sha256 = function(val1, val2) {
        var out = sjcl.hash.sha256.hash(val1 + ":" + val2);
        return sjcl.codec.base64.fromBits(out);
    };

    var sha256url = function(val1, val2) {
        return base64url_escape(sha256(val1, val2));
    };

    var sha256url = function(val1, val2) {
        return base64url_escape(sha256(val1, val2));
    };

    var clearModalReference =  function() {        
        document.getElementsByTagName('body')[0].classList.remove('modal-open');
        clearElementByTagName('ngb-modal-backdrop');
        clearElementByTagName('ngb-modal-window');
    };
    var clearElementByTagName =  function(element) {
        var element = document.getElementsByTagName(element), index;
        for (index = element.length - 1; index >= 0; index--) {
            element[index].parentNode.removeChild(element[index]);
        }
    };

    return {
        encrypt : encrypt,
        encrypt_post_data: encrypt_post_data,
        sha256: sha256,
        sha256url: sha256url,
        base64url_escape: base64url_escape,
        aes_decrypt : aes_decrypt,
        clearModalReference: clearModalReference
    }
})();
highvolt-dev commented 3 years ago

The randomwords are generated from the pseudrorandom number generator from the Stanford Javascript Crypto Library https://github.com/bitwiseshiftleft/sjcl

  /** Generate several random words, and return them in an array.
   * A word consists of 32 bits (4 bytes)
   * @param {Number} nwords The number of words to generate.
   */
  randomWords: function (nwords, paranoia) {
highvolt-dev commented 3 years ago

Got a working implementation tonight in python. My script checks for n41 or failed pings to google and will reboot if on another 5G band or on ping failure. Let me know if you run into any issues implementing this. Should be easy since the official router interface is in JavaScript.

highvolt-dev commented 3 years ago

Reference implementation here: https://github.com/highvolt-dev/tmo-monitor

kclejeune commented 3 years ago

Got a working implementation tonight in python. My script checks for n41 or failed pings to google and will reboot if on another 5G band or on pingfailure. Let me know if you run into any issues implementing this. Should be easy since the official router interface is in JavaScript.

Looks great, should be really easy to port to typescript. I think I can probably just include cryptoJS and sjcl from npm, but it's good to have the source code for those handy too.

Thank you!

highvolt-dev commented 3 years ago

@kclejeune you're mostly right, but the reason I posted the cryptojs module that the router is using is because it is not using the cryptojs npm package. It's implementing those functions with sjcl. If you look at the function call signature for the two, you'll see that https://cryptojs.gitbook.io/docs/ shows the npm package takes a single message parameter, but the router's custom functions are taking the SHA256 digest of two string parameters concatenated together with a colon.

kclejeune commented 3 years ago

@kclejeune you're mostly right, but the reason I posted the cryptojs module that the router is using is because it is not using the cryptojs npm package. It's implementing those functions with sjcl. If you look at the function call signature for the two, you'll see that https://cryptojs.gitbook.io/docs/ shows the npm package takes a single message parameter, but the router's custom functions are taking the SHA256 digest of two string parameters concatenated together with a colon.

ah, good catch - thanks

highvolt-dev commented 2 years ago

@kclejeune are you working on this or do you want a PR?

kclejeune commented 2 years ago

@kclejeune are you working on this or do you want a PR?

I'm finishing up my masters thesis and should be done in mid-January. I haven't started working on this due to lack of time, but likely will once my thesis work is complete.

I'd be happy to review a PR for it if you or someone else opens one before I can get to it!

kclejeune commented 2 years ago

@kclejeune are you working on this or do you want a PR?

Just as an update, my thesis defense is scheduled for January 12 so I hope to start working on this sometime the week of January 17.

I'm loving the work that you've done in your visualization project so far, so I'm hoping that we can work together to combine our work at some point too!

highvolt-dev commented 2 years ago

@kclejeune happy to collaborate!

highvolt-dev commented 2 years ago

Firmware version 1.2103.00.0338 introduced some changes in the web app's authentication payload.