Skip to content

Instantly share code, notes, and snippets.

@notmarek
Created January 29, 2025 20:51
Show Gist options
  • Save notmarek/f41548dc5ac1950182a2f0cee566e496 to your computer and use it in GitHub Desktop.
Save notmarek/f41548dc5ac1950182a2f0cee566e496 to your computer and use it in GitHub Desktop.
Simple script to grab latest direct ota url for your kindle
from Crypto.Hash import SHA256
from Crypto.PublicKey import ECC
from Crypto.Signature import DSS
from urllib.parse import urlparse
import re
import json
import requests
import datetime
import base64
import sys
class Kindle:
def __init__(self, token_file="token.json"):
# you can grab your token from /var/local/token/amzn.acountid, it should work as is
tokens = json.load(open(token_file, "r"))
self.ADP_SIGNING_KEY = ECC.import_key(tokens["device_private_key"])
self.ECDSA_SIGNER = DSS.new(
self.ADP_SIGNING_KEY, "deterministic-rfc6979", "der"
)
self.ADP_TOKEN = tokens["device_token"]
self.HEADERS = {
"User-Agent": "Java/1.8.0_341-ea",
"Content-Type": "application/json",
}
self.SESSION = requests.session()
def get_latest_ota(self, codename):
request = {
"buildDimensions": {
"platform": "juno",
"ro.build.lab126.sign.type": "release",
},
"inventory": [
{
"softwareComponentId": f"com.lab126.eink.{codename}.os",
"softwareComponentVersionCode": 1,
},
{
"softwareComponentId": f"com.lab126.eink.{codename}.app.nh",
"softwareComponentVersionCode": 0,
},
],
"softwareComponentTypes": ["OS", "APPLICATION"],
"demoModeStatus": "NOT_ENABLED",
}
res = self.SESSION.send(
self.signed_request(
"POST",
"https://softwareupdates.amazon.com/software/inventory2",
headers={"Content-type": "application/x-www-form-urlencoded"},
body=request,
)
)
return res.json()
def signed_request(self, method, url, headers=None, body=None):
# adapted from amazon_api.py signature type is different for the kindle ECDSA vs RSA
if not headers:
headers = {}
if isinstance(body, object):
body = json.dumps(body)
elif not body:
body = ""
date = datetime.datetime.now(datetime.timezone.utc).isoformat("T")[:-7] + "Z"
u = urlparse(url)
path = f"{u.path}"
if u.query != "":
path += f"{u.params}?{u.query}"
data = f"{method}\n{path}\n{date}\n{body}\n{self.ADP_TOKEN}"
signed_encoded = base64.b64encode(
self.ECDSA_SIGNER.sign(SHA256.new(data.encode()))
)
signature = f"{signed_encoded.decode()}:{date}"
headers.update(self.HEADERS)
headers.update(
{
"x-adp-token": self.ADP_TOKEN,
"x-adp-alg": "SHA256withECDSA:1.0",
"x-adp-signature": signature,
}
)
return requests.Request(method, url, headers, data=body).prepare()
def yoink_model_info():
res = requests.get("https://kindlemodding.org/models.json")
matches = re.findall('"board":\\s*?"(.*?)",', res.text)
for i in range(len(matches) - 1, -1, -1):
match = matches[i]
if "/" in match:
matches.remove(match)
matches.extend(match.split("/"))
return [m.lower() for m in matches]
if __name__ == "__main__":
token_path = sys.argv[1] if len(sys.argv) > 1 else ""
codename = sys.argv[2] if len(sys.argv) > 2 else ""
if token_path == "":
print("You can grab a token file for your own kindle by doing \"cat /var/local/token/$(cat /var/local/token/activeprofile.txt)\" on your jailbroken kindle.")
k = Kindle(input("Path to tokens.json: ") if token_path == "" else token_path)
if codename == "":
models = yoink_model_info()
print("Grabbed model names :)")
for model in models:
res = k.get_latest_ota(model)
if res["availableUpdates"] != []:
print(f"Latest OTA info for {model}: ")
print(json.dumps(res, indent=4))
break
else:
res = k.get_latest_ota(codename)
if res["availableUpdates"] != []:
print(f"Latest OTA info for {codename}: ")
print(json.dumps(res, indent=4))
else:
print(json.dumps(res, indent=4))
print(f"Codename ({codename}) doesn't match provided key, make sure its lowercase or try the normal bruteforce method.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment