mirror of
https://github.com/qtmleap/revkit.git
synced 2026-10-11 04:11:51 +02:00
feat(cbor): Netflix カスタム CBOR エンコーダー実装 — パースエラー解消
- nf_cbor_encode(): Netflix 互換 CBOR エンコーディング - 全整数キーを 8 バイト形式 (0x1b + uint64) でエンコード - 全 map の前に tag(55799) (0xd9d9f7) を付与 - test_appboot_e2e.py を cbor2.dumps → nf_cbor_encode に移行 - サーバーエラーが errorcode=1 (パース失敗) → errorcode=6 (AppID検証) に前進 - verify_full_key_chain.py 13/13 PASS 維持 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
455de1ebee
commit
e74869dbf5
@@ -65,6 +65,98 @@ class EncodeError(Exception):
|
||||
"""CBOR MSL メッセージの構築に失敗した場合."""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Netflix カスタム CBOR エンコーダー
|
||||
#
|
||||
# Netflix iOS の MSL 実装は標準 CBOR とは異なるエンコーディング規則を使用:
|
||||
# 1. 全ての整数値を 8 バイト形式 (major type 0, additional info 27 = 0x1b) でエンコード
|
||||
# 2. 全ての map の前に CBOR self-describe tag 55799 (0xd9d9f7) を付与
|
||||
# 標準 cbor2 ライブラリは最短形式を使うため、サーバーが "Error parsing MSL encodable" を返す。
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
import struct as _struct
|
||||
|
||||
|
||||
def _nf_encode_uint(value: int) -> bytes:
|
||||
"""整数値を Netflix 形式 (常に 8 バイト) でエンコードする."""
|
||||
return b"\x1b" + _struct.pack(">Q", value)
|
||||
|
||||
|
||||
def _nf_encode_bytes(data: bytes) -> bytes:
|
||||
"""バイト列を CBOR bstr としてエンコードする."""
|
||||
length = len(data)
|
||||
if length < 24:
|
||||
return bytes([0x40 | length]) + data
|
||||
elif length < 256:
|
||||
return b"\x58" + bytes([length]) + data
|
||||
elif length < 65536:
|
||||
return b"\x59" + _struct.pack(">H", length) + data
|
||||
else:
|
||||
return b"\x5a" + _struct.pack(">I", length) + data
|
||||
|
||||
|
||||
def _nf_encode_text(text: str) -> bytes:
|
||||
"""テキスト文字列を CBOR tstr としてエンコードする."""
|
||||
data = text.encode("utf-8")
|
||||
length = len(data)
|
||||
if length < 24:
|
||||
return bytes([0x60 | length]) + data
|
||||
elif length < 256:
|
||||
return b"\x78" + bytes([length]) + data
|
||||
elif length < 65536:
|
||||
return b"\x79" + _struct.pack(">H", length) + data
|
||||
else:
|
||||
return b"\x7a" + _struct.pack(">I", length) + data
|
||||
|
||||
|
||||
def nf_cbor_encode(obj: object) -> bytes:
|
||||
"""Netflix 互換の CBOR エンコードを行う.
|
||||
|
||||
- dict → tag(55799) + map, 整数キーは 8 バイト形式
|
||||
- bytes → bstr
|
||||
- str → tstr
|
||||
- int → 8 バイト uint
|
||||
- bool → CBOR true/false
|
||||
- list → array
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
items = sorted(obj.items(), key=lambda kv: (isinstance(kv[0], str), kv[0]))
|
||||
header = b"\xd9\xd9\xf7" # tag(55799)
|
||||
n = len(items)
|
||||
if n < 24:
|
||||
header += bytes([0xA0 | n])
|
||||
else:
|
||||
header += b"\xb8" + bytes([n])
|
||||
body = b""
|
||||
for k, v in items:
|
||||
if isinstance(k, int):
|
||||
body += _nf_encode_uint(k)
|
||||
elif isinstance(k, str):
|
||||
body += _nf_encode_text(k)
|
||||
else:
|
||||
raise EncodeError(f"Unsupported key type: {type(k)}")
|
||||
body += nf_cbor_encode(v)
|
||||
return header + body
|
||||
elif isinstance(obj, bytes):
|
||||
return _nf_encode_bytes(obj)
|
||||
elif isinstance(obj, str):
|
||||
return _nf_encode_text(obj)
|
||||
elif isinstance(obj, int):
|
||||
return _nf_encode_uint(obj)
|
||||
elif isinstance(obj, bool):
|
||||
return b"\xf5" if obj else b"\xf4"
|
||||
elif isinstance(obj, list):
|
||||
n = len(obj)
|
||||
if n < 24:
|
||||
header = bytes([0x80 | n])
|
||||
else:
|
||||
header = b"\x98" + bytes([n])
|
||||
return header + b"".join(nf_cbor_encode(item) for item in obj)
|
||||
else:
|
||||
raise EncodeError(f"Unsupported type: {type(obj)}")
|
||||
|
||||
|
||||
class CborMslEncoder:
|
||||
"""iOS CBOR MSL リクエストメッセージを構築するエンコーダー."""
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ from netflix_msl.constants import ( # noqa: E402
|
||||
IOS_KDF_PSK,
|
||||
IOS_KEY336_DEVICE_HEADER,
|
||||
)
|
||||
from netflix_msl.cbor_encoder import nf_cbor_encode # noqa: E402
|
||||
from netflix_msl.crypto import NetflixCrypto # noqa: E402
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -238,7 +239,7 @@ def build_key_request_data(
|
||||
KEYEX_IDENTITY: esn, # identity = 完全 ESN (サフィックスなし)
|
||||
KEYEX_NONCE: k9_xor_nonce,
|
||||
}
|
||||
return cbor2.dumps(key_request), k9_xor_nonce, nonce_7b
|
||||
return nf_cbor_encode(key_request), k9_xor_nonce, nonce_7b
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -264,9 +265,12 @@ def build_appboot_message(
|
||||
"""
|
||||
signature = hmac_mod.new(sign_key_0, key_request_bytes, hashlib.sha256).digest()
|
||||
|
||||
return cbor2.dumps(
|
||||
# entity_auth_data を Netflix 形式 CBOR でエンコード
|
||||
ead_bytes = nf_cbor_encode(entity_auth_data)
|
||||
|
||||
return nf_cbor_encode(
|
||||
{
|
||||
KEY_ENTITY_AUTH: cbor2.dumps(entity_auth_data),
|
||||
KEY_ENTITY_AUTH: ead_bytes,
|
||||
KEY_KEY_EXCHANGE: key_request_bytes,
|
||||
KEY_MESSAGE_SIG: signature,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user