# Netflix MSL クライアント仕様: iOS
共通仕様: [00_common.md](00_common.md)
---
## 1. フロー
```mermaid
%%{init: {'theme': 'dark'}}%%
sequenceDiagram
participant App as iOS App (Argo)
participant MSL as MSL Gateway
(ios.prod.cloud)
participant FP as FairPlay CDM
rect rgba(80, 80, 80, 0.3)
Note over App,MSL: 認証・ESN 取得
App->>MSL: /getProxyEsn
MSL-->>App: PRV ESN + PXA ESN
end
rect rgba(60, 100, 60, 0.3)
Note over App,FP: マニフェスト → ライセンス → 復号
App->>MSL: /manifest (MSL暗号化)
MSL-->>App: マニフェスト (HEVC + H.264 streams)
App->>FP: SPC 生成 (Server Playback Context)
FP-->>App: SPC データ
App->>MSL: /nq/iosplatform/pbo_license/router
Note right of App: licenseType=standard
SPC (FairPlay challenge)
MSL-->>App: CKC (Content Key Context)
App->>FP: CKC をインストール
FP-->>App: コンテンツ鍵 ready
Note over App: AVPlayer で HLS セグメント復号・再生
end
rect rgba(80, 80, 80, 0.3)
Note over App,MSL: テレメトリ
loop 再生中
App->>MSL: /msl/playapi/ios/event
App->>MSL: /msl/playapi/ios/logblob
end
end
```
---
## 2. 認証
| 項目 | 値 |
|------|---|
| ESN プレフィックス | `NFAPPL-02-` |
| PRV ESN 例 | `NFAPPL-02-IPHONE9=1-AD0455EF27D3A7B8...` |
| PXA ESN 例 | `NFAPPL-02-IPHONE9=1-PXA-0202P2P3KTB3...` |
| ESN 取得方法 | `/getProxyEsn` で動的取得 |
| DRM | **FairPlay** (Widevine ではない) |
| MSL トランスポート | NSURLSession (HTTPS) |
| MSL ペイロード暗号化 | AES-CBC + HMAC-SHA256 |
### 二重 ESN 体系
| ESN 種別 | 用途 | 形式 |
|---------|------|------|
| PRV (Private) | ライセンス取得、MSL 通信 | `NFAPPL-02-{MODEL}-{hash}` |
| PXA (Proxy Auth) | HTTP 直接通信 (Falcor UI) | `NFAPPL-02-{MODEL}-PXA-{hash}` |
### HTTP ヘッダー (Falcor 直接通信時)
```
X-Netflix.client.ftl.esn: {PXA ESN}
X-Netflix.client.type: argo
User-Agent: Argo/15.48.1 (iPhone; iOS 15.8.3; Scale/2.00)
Cookie: NetflixId=...; SecureNetflixId=...; nfvdid=...
```
---
## 3. マニフェスト取得
MSL ゲートウェイ: `ios.prod.ftl.netflix.com` / `ios.prod.cloud.netflix.com`
### 提供されるコーデック (実測)
| 解像度 | プロファイル |
|--------|------------|
| 480x270 〜 1920x1080 | `playready-h264hpl22-dash` 〜 `playready-h264hpl40-dash` |
| 480x270 〜 1920x1080 | `hevc-main10-L30-dash-cenc-prk-do` 〜 `hevc-main10-L31-dash-cenc-prk-do` |
H.264 と HEVC の両方が提供される。
---
## 4. ライセンスチャレンジ (FairPlay)
**エンドポイント:** `POST /nq/iosplatform/pbo_license/~1.0.0/router`
### FairPlay フロー (Widevine とは異なる)
```
SPC (Server Playback Context) → Netflix サーバー → CKC (Content Key Context) → FairPlay CDM
```
1. マニフェストからコンテンツ ID を取得
2. FairPlay CDM に SPC (Server Playback Context) 生成を要求
3. SPC を MSL ペイロードに包んで `/pbo_license/router` に POST
4. レスポンスから CKC (Content Key Context) を取得
5. CKC を FairPlay CDM にインストール → コンテンツ鍵 ready
### ライセンスリクエストパラメータ
```json
{
"url": "/license?licenseType=standard&playbackContextId=...&esn=NFAPPL-02-...-PRV-...",
"params": {
"videoTrackName": "...",
"preferredlanguages": [...]
}
}
```
### FairPlay 固有の暗号化操作 (実測)
MSL ペイロード内で以下の暗号化操作が行われる:
- `aesCbcEncrypt`: リクエストペイロードの暗号化
- `aesCbcDecrypt`: レスポンスペイロードの復号
- `hmacSha256`: メッセージ認証コード生成
- `hmacVerify`: メッセージ認証コード検証
---
## 5. 再生方式
- **HLS** (HTTP Live Streaming) — DASH ではない
- **AVPlayer** で再生
- FairPlay DRM で保護されたセグメントを AVPlayer が透過的に復号
---
## 6. テレメトリ
| エンドポイント | 用途 |
|--------------|------|
| `/msl/playapi/ios/event` | 再生イベント (play, pause, stop, position) |
| `/msl/playapi/ios/logblob` | テレメトリデータ (品質指標、バッファリング等) |
---
## 7. MSL 復号パイプライン
詳細: [ios_msl_decrypt_pipeline.md](ios_msl_decrypt_pipeline.md)
### 7.1 鍵取得
Tweak `AppbootKeyExtract` が NFWebCrypto.framework の OpenSSL エクスポート関数をフックし、
DH 鍵交換 + セッション鍵をキャプチャする:
| フック対象 | 取得する鍵 |
|-----------|----------|
| `DH_generate_key` | DH 公開鍵・秘密鍵 (各 128 bytes) |
| `DH_compute_key` | DH 共有秘密 (128 bytes) |
| `AES_set_encrypt_key` (128-bit) | セッション暗号化鍵 (16 bytes) |
| `HMAC` (key_len=32) | セッション署名鍵 (32 bytes) |
### 7.2 CBOR MSL 固有の差異
| 項目 | JSON MSL (Chrome) | CBOR MSL (iOS) |
|------|--------------------|---------------|
| IV 格納 | `"iv"` フィールド (Base64, 16B) | ciphertext に prepend |
| 復号後圧縮 (リクエスト) | gzip (Base64 経由) | CBOR bstr フレーム + gzip |
| 復号後圧縮 (レスポンス) | gzip | raw deflate (`00 00` prefix) |
### 7.3 復号 CLI
```bash
python tools/decrypt_capture.py --keys raws/msl_keys.json --input capture.bin
```