Salamek / huawei-lte-api

API For huawei LAN/WAN LTE Modems
GNU Lesser General Public License v3.0
376 stars 92 forks source link

adding support for dialup profiles editing and mainly setting new default profile #108

Closed arekm closed 3 years ago

arekm commented 3 years ago

dialup/profiles seems to be able to be used for profile modification (including changing default profile number).

POST api/dialup/profiles

<request>
    <Delete></Delete>
    <SetDefault></SetDefault>
    <Modify>0</Modify>
</request>

Delete - if we want delete profile nr X SetDefault - if we want to set profile X as default

Info from http://forum.jdtech.pl/Watek-hilink-api-dla-urzadzen-huawei#dialup

On my huawei 5g cpe 2 just doing

    def set_default_profile(self, default: int=0) -> SetResponseType:
        return self._connection.post_set('dialup/profiles', {
            'setdefault': default,
            'delete': '',
            'modify': 0
        })

doesn't work unfortunately

huawei_lte_api.exceptions.ResponseErrorException: 100001: Unknown

but tracking router UI in inspector and when changing default profile (which is just editing profile with one checkbox active) uses POST on api/dialup/profiles sending

64343c7ec3b946001fe5e4fa30965dc6c8f3feb5c3059f6ac37bbc263e4481ab13086f0131c2bc6876a1392d3073cf8dddcceac4f11a202ec6f6feed22edb5eba818db8f1ddd1a652e5674eabcb60aea24c41d44789e087518e51c1e4f4b0e11db36a0b28f8b67afca5f39518b2821f6bad17f68a274346caa9be595ce2700ecdc65c4dfb08d9d327082197fff35a8db61e5e8e5566ccc13e7be891e4e3aed30e85c0c8fcd7989d7e38dbddfa044d08f818e06968225ca97bfe4a795785d77273ca4af6f9fcb2cf5e54e45c7bfb40b496771578ad5f23e274057dbba8776d569ae78a834ecd4a1ff07d56ba1ac86e58fdb9c0b38ed7b2bbae83c8d0269b0a682

which is rsa encrypted

 "<?xml version="1.0" encoding="UTF-8"?><request><Delete>0</Delete><SetDefault>3</SetDefault><Modify>2</Modify><Profile><Index>3</Index><IsValid>1</IsValid><Name>Play Test</Name><ApnIsStatic>1</ApnIsStatic><ApnName>internet</ApnName><DialupNum>*99#</DialupNum><Username></Username><AuthMode>0</AuthMode><IpIsStatic></IpIsStatic><IpAddress></IpAddress><DnsIsStatic>0</DnsIsStatic><PrimaryDns></PrimaryDns><SecondaryDns></SecondaryDns><ReadOnly>0</ReadOnly><iptype>0</iptype></Profile></request>"

with keys from

api/webserver/publickey

https://adventuresinadigitalland.blogspot.com/2019/03/huawei-modemrouter-sending-encrypted.html

arekm commented 3 years ago

enc is missing in ''application/x-www-form-urlencoded; charset=UTF-8'

It should be: 'application/x-www-form-urlencoded; charset=UTF-8;enc'

Salamek commented 3 years ago

@arekm my router is using ''application/x-www-form-urlencoded; charset=UTF-8' and additional header:

headers['encrypt_transmit'] = 'encrypt_transmit'

And it works, it is not working for your router in this configuration?

arekm commented 3 years ago

Mine uses 'application/x-www-form-urlencoded; charset=UTF-8;enc' and doesn't send 'encrypt_transmit'. Without ';enc' I'm getting "huawei_lte_api.exceptions.ResponseErrorException: 100001: Unknown" on "POST /api/dialup/profiles".

Also it actually doesn't do GET api/webserver/publickey but instead takes rsa components from

POST api/user/challenge_login
<?xml version="1.0" encoding="UTF-8"?><request><username>admin</username><firstnonce>c240....60fb</firstnonce><mode>1</mode></request>

which returns

<?xml version="1.0" encoding="UTF-8"?><response><salt>09....0c2</salt><modeselected>1</modeselected><servernonce>c240....KtPCW0dvISb0</servernonce><newType>0</newType><iterations>1000</iterations></response>

then

POST api/user/authentication_login
<?xml version="1.0" encoding="UTF-8"?><request><clientproof>b7071a3....0b5508</clientproof><finalnonce>c240c....Sb0</finalnonce></request>

which returns:

<?xml version="1.0" encoding="UTF-8"?><response><serversignature>45151a0c0e...a11a5baae9d992</serversignature><rsapubkeysignature>b01...72c</rsapubkeysignature><rsae>01...01</rsae><rsan>c2767b...4b</rsan></response>

but api/webserver/publickey also exists on my router and returns the same rsa e/n, so most likely a way to get e/n doesn't matter. Also user.state_login() returns the same values in huawei lte api script that api/user/state-login from browser does. So I guess both ways of logging end up in the same "logged in" state.

Anyway adding ';enc' and client.dial_up.set_default_profile(2) ends up with http.client.RemoteDisconnected: Remote end closed connection without response

Not sure what it doesn't like. Still playing with it.

arekm commented 3 years ago

Encryption is done a bit differently for my router:

diff --git a/huawei_lte_api/Session.py b/huawei_lte_api/Session.py
index 02f6b44..f5e6dbd 100644
--- a/huawei_lte_api/Session.py
+++ b/huawei_lte_api/Session.py
@@ -145,12 +145,13 @@ class Session:
         return urllib.parse.urljoin(self.url + '{}/'.format(prefix), endpoint)

     def _encrypt_data(self, data: bytes) -> bytes:
+        rsapading = self._get_rsa_pading()
         pubkey_data = self._get_encryption_key()
         rsa_e = pubkey_data.get('encpubkeye')
         rsa_n = pubkey_data.get('encpubkeyn')
         if not rsa_n or not rsa_e:
             raise Exception('No pub key was found')
-        return Tools.rsa_encrypt(rsa_e, rsa_n, data)
+        return Tools.rsa_encrypt(rsa_e, rsa_n, data, rsapading)

     def post_get(self,
                  endpoint: str,
@@ -186,7 +187,7 @@ class Session:
             -> Union[GetResponseType, SetResponseType]:

         headers = {
-            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' if is_encrypted else 'application/xml'
+            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8;enc' if is_encrypted else 'application/xml'
         }

         if is_encrypted:
@@ -276,6 +277,10 @@ class Session:
             self.encryption_key = self.get('webserver/publickey')
         return self.encryption_key

+    def _get_rsa_pading(self) -> int:
+        state_login = self.get('user/state-login')
+        return int(state_login['rsapadingtype']) if 'rsapadingtype' in state_login else 0
+
     def close(self) -> None:
         self.requests_session.close()

diff --git a/huawei_lte_api/Tools.py b/huawei_lte_api/Tools.py
index 5589d8c..765f44f 100644
--- a/huawei_lte_api/Tools.py
+++ b/huawei_lte_api/Tools.py
@@ -3,7 +3,7 @@ from typing import Union
 from binascii import hexlify
 import math
 import base64
-from Cryptodome.Cipher import PKCS1_v1_5
+from Cryptodome.Cipher import PKCS1_v1_5, PKCS1_OAEP
 from Cryptodome.PublicKey.RSA import construct

@@ -28,16 +28,21 @@ class Tools:
         return data

     @staticmethod
-    def rsa_encrypt(rsa_e: str, rsa_n: str, data: bytes) -> bytes:
+    def rsa_encrypt(rsa_e: str, rsa_n: str, data: bytes, rsapading: int = 0) -> bytes:
         modulus = int(rsa_n, 16)
         exponent = int(rsa_e, 16)
         b64data = base64.b64encode(data)
         pubkey = construct((modulus, exponent))
-        cipher = PKCS1_v1_5.new(pubkey)
-        blocks = int(math.ceil(len(b64data) / 245.0))
+        if rsapading == 1:
+            num = 214
+            cipher = PKCS1_OAEP.new(pubkey)
+        else:
+            num = 245
+            cipher = PKCS1_v1_5.new(pubkey)
+        blocks = int(math.ceil(len(b64data) / float(num)))
         result_chunks = []
         for i in range(blocks):
-            block = b64data[i * 245:(i + 1) * 245]
+            block = b64data[i * num:(i + 1) * num]
             d = cipher.encrypt(block)
             result_chunks.append(d)
         result = hexlify(b''.join(result_chunks))

h.txt

note: huawei uses 'rsapadingtype' in state-login result but probably the rest of code should use proper name 'rsapaddingtype' (I used original huawei name, with typo).

Also ";enc" should be conditional (but actually non encrypted requests do work fine on this router even if ";enc" is there)

Salamek commented 3 years ago

@arekm Ok, your patch should be part of 1.5.1 release

arekm commented 3 years ago

Thanks.

diff --git a/huawei_lte_api/Session.py b/huawei_lte_api/Session.py
index e7c43a3..15aa3f4 100644
--- a/huawei_lte_api/Session.py
+++ b/huawei_lte_api/Session.py
@@ -186,12 +186,13 @@ class Session:
               is_encrypted: bool = False) \
             -> Union[GetResponseType, SetResponseType]:

-        headers = {
-            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8;enc' if is_encrypted else 'application/xml'
-        }
+        headers = {}

         if is_encrypted:
+            headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8;enc'
             headers['encrypt_transmit'] = 'encrypt_transmit'
+        else:
+            headers['Content-Type'] = 'application/xml'

         if self.request_verification_tokens:
             if len(self.request_verification_tokens) > 1: