code wiki / _hdl_build / nx_cenc.nx
nx_cenc.nx source
↩ module page · 130 lines · 6186 B
1// nx_cenc.nx -- STANDARDS-BASED encrypted-media primitives for the Nishi browser's EME path:
2// (1) ClearKey key acquisition (W3C EME `org.w3.clearkey`) -- the license response hands over the content
3// keys IN THE CLEAR as JWK base64url; there is no crypto to break. Legitimate open standard (test
4// streams, self-hosted / internal content, DASH/HLS ClearKey deployments).
5// (2) CENC (ISO/IEC 23001-7) `cenc` scheme = AES-128-CTR SUBSAMPLE decryption GIVEN a content key. Just
6// AES (we already have it): decrypt the encrypted subsample byte-ranges, leave the clear ranges. Given
7// a key you legitimately hold (ClearKey, your own content, test vectors), this is a normal crypto op.
8// This is the CLEAN half of the DRM frontier -- it does NOT extract keys from a commercial license server
9// (that requires impersonating a provisioned Widevine CDM, a separate matter). Reuses nx_aes_ctr + base64.
10import "nx_syscalls.nx"
11import "nx_aes.nx"
12import "nx_aes_ctr.nx"
13import "base64.nx"
14
15func cenc_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
16
17// build the 16-byte AES-CTR initial counter from a CENC per-sample IV (8 or 16 bytes; an 8-byte IV occupies
18// the HIGH bytes, the low bytes are the block counter starting at 0). _aes_ctr_inc bumps the low 32 bits =
19// correct for any single sample (< 64 GiB / 2^32 blocks).
20func cenc_ctr_iv16(iv: *u8, ivlen: i64, out16: *u8) -> i64 {
21 var i: i64 = 0
22 while i < 16 { out16[i] = 0 as u8; i = i + 1 }
23 var n: i64 = ivlen; if n > 16 { n = 16 }
24 i = 0
25 while i < n { out16[i] = iv[i]; i = i + 1 }
26 return 0
27}
28
29// CENC `cenc` (AES-128-CTR) subsample decrypt. `data`/`dlen` = one encrypted sample. `subs` = nsub pairs
30// [clear_bytes, enc_bytes]; the CTR keystream runs CONTINUOUSLY over the ENCRYPTED bytes only (clear bytes
31// are copied verbatim and do NOT advance the keystream). nsub==0 => the whole sample is encrypted. Writes
32// the plaintext sample to `out` (length == dlen). `sched` = 176-byte expanded 16-byte content key.
33func cenc_decrypt_subsamples(sched: *u8, iv: *u8, ivlen: i64, data: *u8, dlen: i64, subs: *i64, nsub: i64, out: *u8) -> i64 {
34 let enc: *u8 = sys_mmap(dlen + 16) // gathered encrypted bytes (contiguous cipher stream)
35 var pos: i64 = 0
36 var ep: i64 = 0
37 if nsub == 0 {
38 var c0: i64 = 0
39 while c0 < dlen { enc[c0] = data[c0]; c0 = c0 + 1 }
40 ep = dlen
41 } else {
42 var s: i64 = 0
43 while s < nsub {
44 let clr: i64 = subs[s * 2 + 0]
45 let enl: i64 = subs[s * 2 + 1]
46 var a: i64 = 0
47 while a < clr { if (pos + a) < dlen { out[pos + a] = data[pos + a] } a = a + 1 } // clear verbatim
48 pos = pos + clr
49 var b: i64 = 0
50 while b < enl { if (pos + b) < dlen { enc[ep] = data[pos + b]; ep = ep + 1 } b = b + 1 } // gather enc
51 pos = pos + enl
52 s = s + 1
53 }
54 }
55 let icb: *u8 = sys_mmap(16)
56 cenc_ctr_iv16(iv, ivlen, icb)
57 let dec: *u8 = sys_mmap(ep + 16)
58 aes128_ctr_xor(sched, icb, enc, ep, dec) // continuous CTR over the gathered encrypted bytes
59 if nsub == 0 {
60 var c1: i64 = 0
61 while c1 < dlen { out[c1] = dec[c1]; c1 = c1 + 1 }
62 } else {
63 pos = 0; ep = 0
64 var s2: i64 = 0
65 while s2 < nsub {
66 let clr2: i64 = subs[s2 * 2 + 0]
67 let enl2: i64 = subs[s2 * 2 + 1]
68 pos = pos + clr2 // clear bytes already written
69 var d: i64 = 0
70 while d < enl2 { if (pos + d) < dlen { out[pos + d] = dec[ep]; ep = ep + 1 } d = d + 1 } // scatter
71 pos = pos + enl2
72 s2 = s2 + 1
73 }
74 }
75 return dlen
76}
77
78// base64url (RFC 4648 ยง5, unpadded) -> bytes: map -/_ back to +//, pad to a multiple of 4, decode. Returns n.
79func cenc_b64url_decode(s: *u8, n: i64, out: *u8) -> i64 {
80 let tmp: *u8 = sys_mmap(n + 8)
81 var i: i64 = 0
82 while i < n {
83 let c: i64 = s[i] & 0xff
84 if c == 45 { tmp[i] = 43 as u8 } // '-' -> '+'
85 else { if c == 95 { tmp[i] = 47 as u8 } // '_' -> '/'
86 else { tmp[i] = c as u8 } }
87 i = i + 1
88 }
89 var m: i64 = n
90 while (m % 4) != 0 { tmp[m] = 61 as u8; m = m + 1 } // pad '='
91 return b64_decode(tmp, m, out)
92}
93
94// find `"<field>":"<value>"` in a JSON blob; copy value into out; return value length or -1.
95func cenc_json_str(json: *u8, jlen: i64, field: *u8, out: *u8, ocap: i64) -> i64 {
96 let flen: i64 = cenc_slen(field)
97 var i: i64 = 0
98 while i < jlen {
99 var m: i64 = 1
100 if (i + 4 + flen) >= jlen { m = 0 }
101 if m == 1 { if (json[i] & 0xff) != 34 { m = 0 } } // opening quote
102 if m == 1 { var k: i64 = 0; while k < flen { if (json[i + 1 + k] & 0xff) != (field[k] & 0xff) { m = 0; k = flen } else { k = k + 1 } } }
103 if m == 1 { if (json[i + 1 + flen] & 0xff) != 34 { m = 0 } } // closing quote of field
104 if m == 1 { if (json[i + 2 + flen] & 0xff) != 58 { m = 0 } } // ':'
105 if m == 1 { if (json[i + 3 + flen] & 0xff) != 34 { m = 0 } } // opening quote of value
106 if m == 1 {
107 var vs: i64 = i + 4 + flen
108 var w: i64 = 0
109 var go: i64 = 1
110 while go == 1 {
111 if vs >= jlen { go = 0 }
112 else { if (json[vs] & 0xff) == 34 { go = 0 } else { if w < (ocap - 1) { out[w] = json[vs]; w = w + 1 } vs = vs + 1 } }
113 }
114 out[w] = 0 as u8
115 return w
116 }
117 i = i + 1
118 }
119 return 0 - 1
120}
121
122// ClearKey: pull the FIRST content key out of a license-response JSON ({"keys":[{"kty":"oct","k":"..","kid":".."}]})
123// -> out (16 bytes for AES-128). Returns key length or -1. The keys are handed over in the clear (that IS
124// ClearKey) -- no circumvention; this is the W3C standard key system.
125func clearkey_first_key(json: *u8, jlen: i64, out: *u8) -> i64 {
126 let b64: *u8 = sys_mmap(512)
127 let bl: i64 = cenc_json_str(json, jlen, "k\x00" as *u8, b64, 512)
128 if bl < 0 { return 0 - 1 }
129 return cenc_b64url_decode(b64, bl, out)
130}