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}