Last active
June 18, 2024 21:23
-
-
Save Wh1terat/437a9274c7790aea75a208a944d255b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from __future__ import annotations | |
import secrets | |
import gzip | |
import json | |
import uuid | |
import requests | |
from dataclasses import dataclass, asdict, is_dataclass, field | |
from Crypto.PublicKey import RSA | |
from Crypto.Cipher import AES, PKCS1_v1_5 | |
from Crypto.Hash import HMAC, SHA256 | |
from Crypto.Util.Padding import pad | |
from base64 import urlsafe_b64encode | |
from datetime import datetime | |
class DexcomAPI: | |
BASE_URL = "https://shareous1.dexcom.com/ShareWebServices/Services" | |
RSA_PUB = """-----BEGIN PUBLIC KEY----- | |
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhR1op97RL74ZPlwmTCz/ | |
gqJBmezl9XnEd9+qGtBadd9GP8yxTvhxysxS35Hs0vdRty7/uvOiG26Bmy2NAsUw | |
xaaTy9Jf7Knceg4Zb5HpxcZR7Oku7RBuP9wTqDvLw/DLWIpq/n3norwwfZ5kQtB2 | |
Q6n/WN6DS6dkJvWozXJS1moBoN66znX3jJDMaq8KSW6xOg1tBPoA7ki3Kgb/NeO8 | |
xspYhWtjuC7HHxI5O+1elaGgs+Bb5qB2ctKqs909gtcrH62Vo+CdeMVdOHlluaTP | |
TwudnaVu5zSu0ubcMyca0I4O8IloPJT3buExc2iP4uZtN3lfpjft7PGXAp95QMS4 | |
1wIDAQAB | |
-----END PUBLIC KEY-----""" | |
def __init__(self, accid, password, devicekey): | |
self._appid = "d89443d2-327c-4a6f-89e5-496bbb0317db" | |
self._accid = accid | |
self._password = password | |
self._devicekey = devicekey | |
self._session = requests.session() | |
self._session.headers.update( | |
{ | |
"User-Agent": "Dexcom%20Follow/3311 CFNetwork/1406.0.4 Darwin/22.4.0", | |
"Content-Type": "application/json", | |
} | |
) | |
@dataclass | |
class BaseDC: | |
def __post_init__(self): | |
try: | |
self.Timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") | |
self.RequestId = str(uuid.uuid4()) | |
except AttributeError: | |
pass | |
@dataclass | |
class EncryptionKey: | |
KeyId: int | |
Key: str | |
IV: str | |
@dataclass | |
class SignedRequestHeader(BaseDC): | |
AccId: str | |
AppId: str | |
EncKey: EncryptionKey | |
IsZip: int | |
Timestamp: str = field(init=False) | |
@dataclass | |
class SignedRequestHeaderUnencrypted(BaseDC): | |
AccId: str | |
AppId: str | |
IsZip: int | |
Timestamp: str = field(init=False) | |
@dataclass | |
class KeyRequest: | |
KeyRequest: KeyRequestBody | |
@dataclass | |
class KeyRequestBody(BaseDC): | |
AppId: str | |
Password: str | |
Key: str | |
RequestId: str = field(init=False) | |
Timestamp: str = field(init=False) | |
KeyHmac: str | |
def _b64enc(self, data, gz=False): | |
if isinstance(data, str): | |
data = data.encode() | |
if gz: | |
data = gzip.compress(data) | |
return urlsafe_b64encode(data).decode("utf-8").replace("=", "") | |
def _rsa_enc(self, data): | |
cipher = PKCS1_v1_5.new(RSA.importKey(self.RSA_PUBKEY)) | |
return self._b64enc(cipher.encrypt(data)) | |
def _aes_enc(self, data, key, iv): | |
cipher = AES.new(key, AES.MODE_CBC, iv) | |
return self._b64enc(cipher.encrypt(pad(data, AES.block_size))) | |
def _hmac(self, data, secret): | |
h = HMAC.new(secret.encode(), digestmod=SHA256) | |
h.update(data.encode()) | |
return self._b64enc(h.digest()[:16]) | |
def _uuid(self, uuid1, uuid2): | |
tmp1 = self._hmac(uuid2, uuid1 + uuid2) | |
tmp2 = self._hmac(uuid2, uuid1 + tmp1) | |
return uuid1 + tmp2 | |
def _tojson(self, data): | |
if is_dataclass(data): | |
data = asdict(data) | |
return json.dumps(data, separators=(",", ":")) | |
def _jwt(self, obj, data, uuid): | |
jwt = "{}.{}".format(self._b64enc(self._tojson(obj)), data) | |
jwt += ".{}".format(self._hmac(jwt, self._uuid(self._accid, uuid))) | |
return f'"{jwt}"' | |
def gen_payload(self, data, enc=False): | |
if not isinstance(data, str): | |
data = self._tojson(data) | |
iszip = 1 if len(data) > 500 else 0 | |
if enc: | |
aes_key = secrets.token_bytes(16) | |
aes_iv = secrets.token_bytes(16) | |
return self._jwt( | |
self.SignedRequestHeader( | |
self._accid, | |
self._appid, | |
self.EncryptionKey( | |
101, | |
self._rsa_enc(aes_key), | |
self._rsa_enc(aes_iv) | |
), | |
iszip, | |
), | |
self._aes_enc(self._b64enc(data, iszip).encode(), aes_key, aes_iv), | |
self._devicekey, | |
) | |
else: | |
return self._jwt( | |
self.SignedRequestHeaderUnencrypted( | |
self._accid, | |
self._appid, | |
iszip | |
), | |
self._b64enc(data, iszip), | |
self._devicekey, | |
) | |
def req_post(self, endpoint, enc=False, data={}): | |
res = self._session.post( | |
f"{self.BASE_URL}{endpoint}", self.gen_payload(data, enc) | |
) | |
return res.json() | |
def devicekey_request(self): | |
req = self.KeyRequest( | |
self.KeyRequestBody( | |
self._appid, | |
self._password, | |
self._devicekey, | |
self._hmac(self._devicekey, self._accid + self._devicekey), | |
) | |
) | |
return self.req_post("/Subscriber/DeviceKeys", req, True) | |
def main(): | |
# accid & password from CreateSubscriberAccount3 | |
# devicekey should be random - but consistent once posted to DeviceKeys | |
dx = DexcomAPI( | |
accid="CHANGEME", | |
password="CHANGEME", | |
devicekey="00000000-0000-0000-0000-00000000000", | |
) | |
# res = dx.devicekey_request() | |
# print(res) | |
res = dx.req_post("/Subscriber/ReadSubscriber2") | |
print(json.dumps(res, indent=4, sort_keys=True)) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment