mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-10 01:51:33 -10:00
Fix Xbox authentication with compact JWE tickets
Remove the d= prefix from recognized compact JWE tickets at the Xbox authentication boundary, preserving opaque tickets and other token formats. This fixes HTTP 400 responses that blocked game downloads on a tested AYN Thor build. Add two regression tests and register the patch in prepare.py.
This commit is contained in:
1 parent
6d3ae89d62
commit
d55e05a0c5
2 files changed
+80
-1
No files matched your search
@@ -0,0 +1,79 @@
|
||||
--- a/crates/xodus/src/models/secrets.rs
|
||||
+++ b/crates/xodus/src/models/secrets.rs
|
||||
@@ -80,9 +80,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
-/// Store and licensing services reject delegation-compact tickets in the "d="
|
||||
-/// form that Xbox Live sign-in needs (InvalidMsaTicketFormat) and accept the
|
||||
-/// same value as "t=". Convert only where a ticket is sent to those services.
|
||||
+// Xbox accepts compact JWE tickets without the delegation prefix; opaque
|
||||
+// delegation tickets keep their original wire representation.
|
||||
+pub fn xbox_ticket(ticket: String) -> String {
|
||||
+ match ticket.strip_prefix("d=") {
|
||||
+ Some(raw) if is_compact_jwe(raw) => raw.to_owned(),
|
||||
+ _ => ticket,
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/// Store services accept delegation-compact tickets in the "t=" form.
|
||||
+/// Convert only where a ticket is sent to those services.
|
||||
pub fn store_ticket(ticket: String) -> String {
|
||||
match ticket.strip_prefix("d=") {
|
||||
Some(value) => format!("t={value}"),
|
||||
--- a/crates/xodus/src/models/licensing_ticket_tests.rs
|
||||
+++ b/crates/xodus/src/models/licensing_ticket_tests.rs
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Public synthetic data only, no account, keyring or network access.
|
||||
-use super::{Token, licensing_ticket, store_ticket};
|
||||
+use super::{Token, licensing_ticket, store_ticket, xbox_ticket};
|
||||
use crate::models::soap;
|
||||
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use serde_json::json;
|
||||
@@ -77,3 +77,35 @@
|
||||
let long_header = jwe(json!({"alg":"dir","enc":"A256GCM","kid":"a".repeat(9000)}), "");
|
||||
assert_eq!(licensing_ticket(format!("d={long_header}")), format!("t={long_header}"));
|
||||
}
|
||||
+
|
||||
+#[test]
|
||||
+fn credential_xbox_ticket_compact_jwe_uses_bare_wire_format_without_changing_store_conversion() {
|
||||
+ for raw in [
|
||||
+ jwe(json!({"alg":"dir","enc":"A128CBC-HS256"}), ""),
|
||||
+ jwe(json!({"alg":"RSA-OAEP-256","enc":"A256GCM"}), "a2V5"),
|
||||
+ ] {
|
||||
+ let Token::Compact(original) = delegation(&raw) else { panic!("fixture"); };
|
||||
+ assert_eq!(original, format!("d={raw}"));
|
||||
+ assert_eq!(store_ticket(original.clone()), format!("t={raw}"));
|
||||
+ assert_eq!(xbox_ticket(original), raw);
|
||||
+ assert_eq!(xbox_ticket(raw.clone()), raw);
|
||||
+ assert_eq!(xbox_ticket(format!("t={raw}")), format!("t={raw}"));
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+#[test]
|
||||
+fn credential_xbox_ticket_opaque_and_malformed_tokens_are_preserved() {
|
||||
+ let valid = jwe(json!({"alg":"dir","enc":"A256GCM"}), "");
|
||||
+ let mut cases = vec!["d=opaque".to_string(), "opaque".into(), "".into(), "d=".into(),
|
||||
+ "d=opaque..iv.ciphertext.tag".into(), "d=eyJ..a.b.c".into(),
|
||||
+ format!("d={valid}.extra"), format!("d={}", valid.replace("..", ".+.")),
|
||||
+ format!("d={}", jwe(json!({"alg":"dir"}), "")),
|
||||
+ format!("d={}", jwe(json!({"alg":"dir","enc":""}), "")),
|
||||
+ format!("d={}", jwe(json!({"alg":"dir","enc":"A256GCM","kid":"a".repeat(9000)}), ""))];
|
||||
+ let mut parts: Vec<_> = valid.split('.').map(str::to_string).collect();
|
||||
+ for index in [2, 3, 4] {
|
||||
+ let old = parts[index].clone(); parts[index].clear();
|
||||
+ cases.push(format!("d={}", parts.join("."))); parts[index] = old;
|
||||
+ }
|
||||
+ for ticket in cases { assert_eq!(xbox_ticket(ticket.clone()), ticket); }
|
||||
+}
|
||||
--- a/crates/xodus/src/api/xbox/auth.rs
|
||||
+++ b/crates/xodus/src/api/xbox/auth.rs
|
||||
@@ -12,7 +12,7 @@
|
||||
properties: UserAuthProperties {
|
||||
auth_method: "RPS".to_string(),
|
||||
site_name: "user.auth.xboxlive.com".to_string(),
|
||||
- rps_ticket,
|
||||
+ rps_ticket: crate::models::secrets::xbox_ticket(rps_ticket),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ recipe = Path(__file__).resolve().parent
|
||||
cache = Path(sys.argv[1]).resolve()
|
||||
lock = json.loads((recipe / 'backend.lock.json').read_text())
|
||||
local_patches = ['droiddeck-credentials.patch', 'droiddeck-launch-view.patch', 'droiddeck-library-subscriptions.patch',
|
||||
'droiddeck-credential-fallback.patch', 'droiddeck-credential-diagnostics.patch', 'droiddeck-cloud-token.patch']
|
||||
'droiddeck-credential-fallback.patch', 'droiddeck-credential-diagnostics.patch', 'droiddeck-cloud-token.patch', 'droiddeck-xbox-ticket.patch']
|
||||
identity = hashlib.sha256((recipe / 'backend.lock.json').read_bytes() + b''.join((recipe / name).read_bytes() for name in local_patches)).hexdigest()
|
||||
source = cache / ('source-' + identity[:16])
|
||||
if not (source / '.prepared').is_file():
|
||||
|
||||
Reference in new issue
Block a user