class BAASClient: def __init__(self): self.client = HTTPClient() self.url = "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com" self.user_agent = "libcurl (nnAccount; 789f928b-138e-4b2f-afeb-1acae821d897; SDK 9.3.0.0; Add-on 9.3.0.0)" self.power_state = "FA" self.access_token = None self.login_token = None def set_url(self, url): self.url = url def set_user_agent(self, agent): self.user_agent = agent def set_power_state(self, state): self.power_state = state def request(self, req, token, use_power_state): req.headers["Host"] = self.url req.headers["User-Agent"] = self.user_agent req.headers["Accept"] = "*/*" if token: req.headers["Authorization"] = token if use_power_state: req.headers["X-Nintendo-PowerState"] = self.power_state req.headers["Content-Length"] = 0 req.headers["Content-Type"] = "application/x-www-form-urlencoded" response = self.client.request(req, True) if response.status not in [200, 201]: logger.warning("BAAS request returned error: %s" %response.json) raise BAASError("BAAS request failed: %s" %response.json["title"]) return response def authenticate(self, device_token): req = HTTPRequest.post("/1.0.0/application/token") req.form["grantType"] = "public_client" req.form["assertion"] = device_token response = self.request(req, False, True) self.access_token = response.json["tokenType"] + " " + response.json["accessToken"] return response.json def login(self, id, password, app_token=None): req = HTTPRequest.post("/1.0.0/login") req.form["id"] = id req.form["password"] = password if app_token: req.form["appAuthNToken"] = app_token response = self.request(req, self.access_token, True) self.login_token = response.json["tokenType"] + " " + response.json["accessToken"] return response.json def register(self): req = HTTPRequest.post("/1.0.0/users") response = self.request(req, self.access_token, False) return response.json
class BAASClient: def __init__(self): self.client = HTTPClient() self.url = "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com" self.user_agent = USER_AGENT[LATEST_VERSION] self.power_state = "FA" self.access_token = None self.login_token = None def set_url(self, url): self.url = url def set_user_agent(self, agent): self.user_agent = agent def set_power_state(self, state): self.power_state = state def set_system_version(self, version): if version not in USER_AGENT: raise ValueError("Unknown system version") self.user_agent = USER_AGENT[version] def request(self, req, token, use_power_state): req.headers["Host"] = self.url req.headers["User-Agent"] = self.user_agent req.headers["Accept"] = "*/*" if token: req.headers["Authorization"] = token if use_power_state: req.headers["X-Nintendo-PowerState"] = self.power_state req.headers["Content-Length"] = 0 req.headers["Content-Type"] = "application/x-www-form-urlencoded" response = self.client.request(req, True) if response.status not in [200, 201]: logger.warning("BAAS request returned error: %s" % response.json) raise BAASError(response.json["title"]) return response def authenticate(self, device_token): req = HTTPRequest.post("/1.0.0/application/token") req.form["grantType"] = "public_client" req.form["assertion"] = device_token response = self.request(req, None, True) self.access_token = response.json["tokenType"] + " " + response.json[ "accessToken"] return response.json def login(self, id, password, app_token=None): req = HTTPRequest.post("/1.0.0/login") req.form["id"] = "%016x" % id req.form["password"] = password if app_token: req.form["appAuthNToken"] = app_token response = self.request(req, self.access_token, True) self.login_token = response.json["tokenType"] + " " + response.json[ "accessToken"] return response.json def register(self): req = HTTPRequest.post("/1.0.0/users") response = self.request(req, self.access_token, False) return response.json
class DAuthClient: def __init__(self, keyset): self.client = HTTPClient() self.keyset = keyset self.cert = None self.region = 1 self.client_id = 0x8F849B5D34778D8E self.url = "dauth-lp1.ndas.srv.nintendo.net" self.user_agent = "libcurl (nnDauth; 16f4553f-9eee-4e39-9b61-59bc7c99b7c8; SDK 9.3.0.0)" self.system_digest = "CusHY#00090200#Uxxmc8gYnfMqxzdZdygZ_OrKo98O7QA65s_EkZnGsDo=" self.power_state = "FA" self.key_generation = 11 def set_certificate(self, cert, key): self.cert = cert, key def set_platform_region(self, region): self.region = region def set_client_id(self, id): self.client_id = id def set_url(self, url): self.url = url def set_user_agent(self, agent): self.user_agent = agent def set_system_digest(self, digest): self.system_digest = digest def set_power_state(self, state): self.power_state = state def set_key_generation(self, keygen): self.key_generation = keygen def request(self, req): req.certificate = self.cert req.headers["Host"] = self.url req.headers["User-Agent"] = self.user_agent req.headers["Accept"] = "*/*" req.headers["X-Nintendo-PowerState"] = self.power_state req.headers["Content-Length"] = 0 req.headers["Content-Type"] = "application/x-www-form-urlencoded" response = self.client.request(req, True) if response.status != 200: if response.json is not None: logger.error("DAuth request returned errors:") for error in response.json["errors"]: logger.error(" (%s) %s", error["code"], error["message"]) raise DAuthError("DAuth request failed: %s" % response.json["errors"][0]["message"]) else: logger.error("DAuth request returned status code %i", response.status) raise DAuthError("DAuth request failed with status %i" % response.status) return response def challenge(self): req = HTTPRequest.post("/v6/challenge") req.form["key_generation"] = self.key_generation response = self.request(req) return response.json def device_token(self): challenge = self.challenge() data = b64decode(challenge["data"]) req = HTTPRequest.post("/v6/device_auth_token") req.form["challenge"] = challenge["challenge"] req.form["client_id"] = "%016x" % self.client_id if self.region == 2: req.form["ist"] = "true" else: req.form["ist"] = "false" req.form["key_generation"] = self.key_generation req.form["system_version"] = self.system_digest req.form["mac"] = self.calculate_mac(req.form.encode(), data) response = self.request(req) return response.json def get_master_key(self): keygen = self.key_generation keyname = "master_key_%02x" % (keygen - 1) return self.keyset.get(keyname) def decrypt_key(self, key, kek): aes = AES.new(kek, AES.MODE_ECB) return aes.decrypt(key) def calculate_mac(self, form, data): kek_source = self.keyset.get("aes_kek_generation_source") master_key = self.get_master_key() key = self.decrypt_key(kek_source, master_key) key = self.decrypt_key(DAUTH_SOURCE, key) key = self.decrypt_key(data, key) mac = CMAC.new(key, ciphermod=AES) mac.update(form.encode()) return b64encode(mac.digest())
class AAuthClient: def __init__(self): self.client = HTTPClient() self.url = "aauth-lp1.ndas.srv.nintendo.net" self.user_agent = "libcurl (nnAccount; 789f928b-138e-4b2f-afeb-1acae821d897; SDK 9.3.0.0; Add-on 9.3.0.0)" self.power_state = "FA" def set_url(self, url): self.url = url def set_user_agent(self, agent): self.user_agent = agent def set_power_state(self, state): self.power_state = state def request(self, req, use_power_state): req.headers["Host"] = self.url req.headers["User-Agent"] = self.user_agent req.headers["Accept"] = "*/*" if use_power_state: req.headers["X-Nintendo-PowerState"] = self.power_state req.headers["Content-Length"] = 0 req.headers["Content-Type"] = "application/x-www-form-urlencoded" response = self.client.request(req, True) if response.status != 200: if response.json is not None: logger.error("AAuth request returned errors:") for error in response.json["errors"]: logger.error(" (%s) %s", error["code"], error["message"]) raise AAuthError("AAuth request failed: %s" %response.json["errors"][0]["message"]) else: logger.error("DAuth request returned status code %i", response.status) raise AAuthError("AAuth request failed with status %i" %response.status) return response def verify_ticket(self, ticket, title_id): if len(ticket) != 0x2C0: raise ValueError("Ticket has unexpected size") if struct.unpack_from("<I", ticket)[0] != 0x10004: raise ValueError("Ticket has invalid signature type") if struct.unpack_from(">Q", ticket, 0x2A0)[0] != title_id: raise ValueError("Ticket has different title id") if struct.unpack_from(">Q", ticket, 0x2A8)[0] != ticket[0x285]: raise ValueError("Ticket has inconsistent master key revision") def auth_digital(self, title_id, title_version, device_token, ticket): self.verify_ticket(ticket, title_id) plain_key = get_random_bytes(16) aes = AES.new(plain_key, AES.MODE_CBC, iv=bytes(16)) encrypted_ticket = aes.encrypt(pad(ticket, 16)) rsa_key = RSA.construct((RSA_MODULUS, RSA_EXPONENT)) rsa = PKCS1_OAEP.new(rsa_key, SHA256) encrypted_key = rsa.encrypt(plain_key) req = HTTPRequest.post("/v3/application_auth_token") req.form["application_id"] = "%016x" %title_id req.form["application_version"] = "%08x" %title_version req.form["device_auth_token"] = device_token req.form["media_type"] = "DIGITAL" req.form["cert"] = b64encode(encrypted_ticket) req.form["cert_key"] = b64encode(encrypted_key) response = self.request(req, True) return response.json
class HppClient(service.RMCClientBase): def __init__(self, settings): super().__init__(settings) self.game_server_id = None self.access_key = None self.nex_version = None self.pid = None self.password = None self.environment = "L1" self.key_derivation = kerberos.KeyDerivationOld(65000, 1024) self.client = HTTPClient() self.call_id = 0 def set_environment(self, env): self.environment = env def configure(self, game_server_id, access_key, nex_version): self.game_server_id = game_server_id self.access_key = access_key self.nex_version = nex_version self.settings.set("nex.version", 20000) def login(self, pid, password): self.pid = pid self.password = password def host(self): return "hpp-%08x-%s.n.app.nintendo.net" % (self.game_server_id, self.environment.lower()) def send_request(self, protocol, method, body): call_id = self.call_id self.call_id += 1 message = service.RMCMessage.request(self.settings, protocol, method, call_id, body) data = message.encode() key1 = bytes.fromhex(self.access_key).ljust(8, b"\0") key2 = self.key_derivation.derive_key(self.password.encode(), self.pid) signature1 = hmac.new(key1, data, hashlib.md5).hexdigest() signature2 = hmac.new(key2, data, hashlib.md5).hexdigest() random = secrets.token_hex(8).upper() req = HTTPRequest.post("https://%s/hpp/" % self.host()) req.headers["Host"] = self.host() req.headers["pid"] = str(self.pid) req.headers["version"] = self.nex_version req.headers["token"] = "normaltoken" req.headers["signature1"] = signature1.upper() req.headers["signature2"] = signature2.upper() req.headers["Content-Type"] = "multipart/form-data" req.headers["Content-Length"] = 0 req.boundary = "--------BOUNDARY--------" + random req.files["file"] = data response = self.client.request(req, True) if response.status != 200: raise ValueError("Hpp request failed with status %i" % response.status) stream = streams.StreamIn(response.body, self.settings) if stream.u32() != stream.available(): raise ValueError("Hpp response has unexpected size") success = stream.bool() if not success: error = stream.u32() if call_id != stream.u32(): raise ValueError("Hpp error response has unexpected call id") if not stream.eof(): raise ValueError("Hpp error response is bigger than expected") raise common.RMCError(error) if call_id != stream.u32(): raise ValueError("Hpp response has unexpected call id") method_id = stream.u32() if method_id != method | 0x8000: raise ValueError("Hpp response has unexpected method id") return stream.readall()
class DAuthClient: def __init__(self, keyset): self.client = HTTPClient() self.keyset = keyset self.cert = None self.region = 1 self.client_id = 0x8F849B5D34778D8E self.url = "dauth-lp1.ndas.srv.nintendo.net" self.user_agent = USER_AGENT[LATEST_VERSION] self.system_digest = SYSTEM_VERSION_DIGEST[LATEST_VERSION] self.key_generation = KEY_GENERATION[LATEST_VERSION] self.power_state = "FA" def set_certificate(self, cert, key): self.cert = cert, key def set_platform_region(self, region): self.region = region def set_client_id(self, id): self.client_id = id def set_url(self, url): self.url = url def set_user_agent(self, agent): self.user_agent = agent def set_system_digest(self, digest): self.system_digest = digest def set_system_version(self, version): if version not in USER_AGENT: raise ValueError("Unknown system version") self.user_agent = USER_AGENT[version] self.system_digest = SYSTEM_VERSION_DIGEST[version] self.key_generation = KEY_GENERATION[version] def set_power_state(self, state): self.power_state = state def set_key_generation(self, keygen): self.key_generation = keygen def request(self, req): req.certificate = self.cert req.headers["Host"] = self.url req.headers["User-Agent"] = self.user_agent req.headers["Accept"] = "*/*" req.headers["X-Nintendo-PowerState"] = self.power_state req.headers["Content-Length"] = 0 req.headers["Content-Type"] = "application/x-www-form-urlencoded" response = self.client.request(req, True) if response.status != 200: if response.json is not None: logger.error("DAuth request returned errors:") errors = response.json["errors"] for error in errors: logger.error(" (%s) %s", error["code"], error["message"]) raise DAuthError(status_code=response.status, errors=errors) else: logger.error("DAuth request returned status code %i", response.status) raise DAuthError(status_code=response.status) return response def challenge(self): req = HTTPRequest.post("/v6/challenge") req.form["key_generation"] = self.key_generation response = self.request(req) return response.json def device_token(self): challenge = self.challenge() data = b64decode(challenge["data"]) req = HTTPRequest.post("/v6/device_auth_token") req.form["challenge"] = challenge["challenge"] req.form["client_id"] = "%016x" %self.client_id if self.region == 2: req.form["ist"] = "true" else: req.form["ist"] = "false" req.form["key_generation"] = self.key_generation req.form["system_version"] = self.system_digest req.form["mac"] = self.calculate_mac(req.form.encode(), data) response = self.request(req) return response.json def get_master_key(self): keygen = self.key_generation keyname = "master_key_%02x" %(keygen - 1) return self.keyset.get(keyname) def decrypt_key(self, key, kek): aes = AES.new(kek, AES.MODE_ECB) return aes.decrypt(key) def calculate_mac(self, form, data): kek_source = self.keyset.get("aes_kek_generation_source") master_key = self.get_master_key() key = self.decrypt_key(kek_source, master_key) key = self.decrypt_key(DAUTH_SOURCE, key) key = self.decrypt_key(data, key) mac = CMAC.new(key, ciphermod=AES) mac.update(form.encode()) return b64encode(mac.digest())
class NNASClient: def __init__(self): self.client = HTTPClient() cert = ssl.SSLCertificate.load(CERT, ssl.TYPE_PEM) key = ssl.SSLPrivateKey.load(KEY, ssl.TYPE_PEM) self.cert = cert, key self.url = "account.nintendo.net" self.client_id = "a2efa818a34fa16b8afbc8a74eba3eda" self.client_secret = "c91cdb5658bd4954ade78533a339cf9a" self.platform_id = 1 self.device_type = 2 self.device_id = None self.serial_number = None self.system_version = 0x250 self.device_cert = None self.region = 4 self.country = "NL" self.language = "en" self.fpd_version = 0 self.environment = "L1" self.title_id = None self.title_version = None self.auth_token = None def set_certificate(self, cert, key): self.cert = cert, key def set_url(self, url): self.url = url def set_client_id(self, client_id): self.client_id = client_id def set_client_secret(self, client_secret): self.client_secret = client_secret def set_platform_id(self, platform_id): self.platform_id = platform_id def set_device_type(self, device_type): self.device_type = device_type def set_device(self, device_id, serial_number, system_version, cert=None): self.device_id = device_id self.serial_number = serial_number self.system_version = system_version self.device_cert = cert def set_locale(self, region, country, language): self.region = region self.country = country self.language = language def set_fpd_version(self, version): self.fpd_version = version def set_environment(self, environment): self.environment = environment def set_title(self, title_id, title_version): self.title_id = title_id self.title_version = title_version def prepare(self, req, auth=None, cert=None): req.certificate = self.cert req.headers["Host"] = self.url req.headers["X-Nintendo-Platform-ID"] = self.platform_id req.headers["X-Nintendo-Device-Type"] = self.device_type if self.device_id is not None: req.headers["X-Nintendo-Device-ID"] = self.device_id if self.serial_number is not None: req.headers["X-Nintendo-Serial-Number"] = self.serial_number req.headers["X-Nintendo-System-Version"] = "%04X" %self.system_version req.headers["X-Nintendo-Region"] = self.region req.headers["X-Nintendo-Country"] = self.country req.headers["Accept-Language"] = self.language if auth is None: req.headers["X-Nintendo-Client-ID"] = self.client_id req.headers["X-Nintendo-Client-Secret"] = self.client_secret req.headers["Accept"] = "*/*" req.headers["X-Nintendo-FPD-Version"] = "%04X" %self.fpd_version req.headers["X-Nintendo-Environment"] = self.environment if self.title_id is not None: req.headers["X-Nintendo-Title-ID"] = "%016X" %self.title_id req.headers["X-Nintendo-Unique-ID"] = "%05X" %((self.title_id >> 8) & 0xFFFFF) if self.title_version is not None: req.headers["X-Nintendo-Application-Version"] = "%04X" %self.title_version if cert is not None: req.headers["X-Nintendo-Device-Cert"] = cert if auth is not None: req.headers["Authorization"] = auth def request(self, req): response = self.client.request(req, True) if response.error(): logger.error("Account request returned status code %i\n%s", response.status, response.text) raise NNASError("Account request failed with status %i" %response.status) return response.xml def login(self, username, password, password_type=None): req = HTTPRequest.post("/v1/api/oauth20/access_token/generate") self.prepare(req, cert=self.device_cert) req.form["grant_type"] = "password" req.form["user_id"] = util.urlencode(username) req.form["password"] = util.urlencode(password) if password_type is not None: req.form["password_type"] = password_type response = self.request(req) self.auth_token = "Bearer " + response["access_token"]["token"].value def get_emails(self): req = HTTPRequest.get("/v1/api/people/@me/emails") self.prepare(req, self.auth_token) return [Email.parse(email) for email in self.request(req)] def get_profile(self): req = HTTPRequest.get("/v1/api/people/@me/profile") self.prepare(req, self.auth_token) return Profile.parse(self.request(req)) def get_nex_token(self, game_server_id): req = HTTPRequest.get("/v1/api/provider/nex_token/@me") req.params["game_server_id"] = "%08X" %game_server_id self.prepare(req, self.auth_token) return NexToken.parse(self.request(req)) #The following functions can be used without logging in def get_miis(self, pids): req = HTTPRequest.get("/v1/api/miis") req.params["pids"] = util.urlencode(",".join([str(pid) for pid in pids])) self.prepare(req) response = self.request(req) return [Mii.parse(mii) for mii in response] def get_pids(self, nnids): req = HTTPRequest.get("/v1/api/admin/mapped_ids") req.params["input_type"] = "user_id" req.params["output_type"] = "pid" req.params["input"] = util.urlencode(",".join(nnids)) self.prepare(req) response = self.request(req) return {id["in_id"].value: int(id["out_id"].value) for id in response} def get_nnids(self, pids): req = HTTPRequest.get("/v1/api/admin/mapped_ids") req.params["input_type"] = "pid" req.params["output_type"] = "user_id" req.params["input"] = util.urlencode(",".join([str(pid) for pid in pids])) self.prepare(req) response = self.request(req) return {int(id["in_id"].value): id["out_id"].value for id in response} def get_mii(self, pid): return self.get_miis([pid])[0] def get_pid(self, nnid): return self.get_pids([nnid])[nnid] def get_nnid(self, pid): return self.get_nnids([pid])[pid]