303 lines
11 KiB
Go
303 lines
11 KiB
Go
package provider
|
||||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/binary"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Edge-DRM: Sec-MS-GEC v2. Microsoft verlangt seit der Protokoll-Änderung
|
|||
|
|
// (edge-tts-Referenz, Stand 2026) einen Token der Form
|
|||
|
|
// „<16 Hex CS64>-<64 Hex SHA256-Variante>" über den 5-Minuten-gerundeten
|
|||
|
|
// Windows-Filetime-Ticks + Trusted-Client-Token. Beide Digests sind
|
|||
|
|
// absichtlich verschleierte Varianten bekannter Algorithmen — die
|
|||
|
|
// Konstanten und der Kontrollfluss unten sind eine wörtliche Portierung
|
|||
|
|
// der Referenz (edge-tts src/edge_tts/drm.py, MIT) und gegen deren
|
|||
|
|
// Ausgaben tabellengeprüft (TestEdgeDRMReferenzvektoren). Ändert Microsoft
|
|||
|
|
// das Schema erneut, liefert der Edge-Provider klare Fehler („Protokoll
|
|||
|
|
// geändert?"), statt still zu versagen.
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
drmWinEpoch = 11644473600 // Sekunden zwischen 1601-01-01 und 1970-01-01
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// drmIV ist der Standard-SHA-256-Initialvektor.
|
|||
|
|
var drmIV = [8]uint32{
|
|||
|
|
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
|
|||
|
|
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmMidState ist der Arbeitsregister-Stand der Referenz für den
|
|||
|
|
// Mid-Block-Einstieg (Runde 14 des ersten Blocks).
|
|||
|
|
var drmMidState = [8]uint32{
|
|||
|
|
0xFD9FBEA2, 0x91243CE7, 0xBE499094, 0xCD2E1E0A,
|
|||
|
|
0x6C006F49, 0x04BD35AB, 0xFEB0A7E4, 0x03EFFBD2,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmSchedule sind vorberechnete Schedule-Anteile des ersten Blocks.
|
|||
|
|
var drmSchedule = [14]uint32{
|
|||
|
|
0xEB24BF9E, 0x1558691F, 0xA0C09EE2, 0x0CFA32BA,
|
|||
|
|
0xA622EE8C, 0xD1DD9491, 0xC0DD25A0, 0xF9E70AB6,
|
|||
|
|
0xF0C31573, 0x7058CE4E, 0x7188E606, 0x00E5AB33,
|
|||
|
|
0xD258B060, 0x72766564,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmAdjustment ersetzt die Rundungskonstante K[14] im Mid-Block-Pfad.
|
|||
|
|
const (
|
|||
|
|
drmAdjustment uint32 = 0xC9DC06A7
|
|||
|
|
drmSigma0Adj uint32 = 0x059C0B80
|
|||
|
|
drmSigma1Adj uint32 = 0x000B92C0
|
|||
|
|
drmCS64Factor uint32 = 0x82893933
|
|||
|
|
drmCS64Adj0 uint32 = 0xF2F90B1A
|
|||
|
|
drmCS64Adj1 uint32 = 0x0B1A0000
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// drmK sind die Standard-SHA-256-Rundungskonstanten.
|
|||
|
|
var drmK = [64]uint32{
|
|||
|
|
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|||
|
|
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|||
|
|
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|||
|
|
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|||
|
|
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|||
|
|
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|||
|
|
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|||
|
|
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmCS64Coefficients: (Term-, Offset-Koeffizient) je Zeile.
|
|||
|
|
var drmCS64Coefficients = [14][2]uint32{
|
|||
|
|
{0xE82E91B1, 0xB32700D0}, {0x44DE24C6, 0x88DFF51D},
|
|||
|
|
{0x3BEB65BC, 0x29514038}, {0xC89C5933, 0xB328CBDF},
|
|||
|
|
{0x8D1EC211, 0xD405B4D5}, {0x314EF5D5, 0x26E55F3C},
|
|||
|
|
{0xE92491B1, 0x607801C6}, {0xF0A5B9D5, 0x82B2C1FB},
|
|||
|
|
{0x06064CF3, 0xFA51A3DA}, {0xDA79FA60, 0x207CC464},
|
|||
|
|
{0xBED20560, 0x3C3C74F3}, {0x89F9C9F8, 0xD2D14B61},
|
|||
|
|
{0x35FA38C6, 0xBB04A2C1}, {0xCB49E3C6, 0xC5626167},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmSigma0 ist die (verschleiert notierte) SHA-256-σ0-Mischfunktion.
|
|||
|
|
func drmSigma0(v uint32) uint32 {
|
|||
|
|
return v>>7 ^ v<<25 ^ v>>18 ^ v<<14 ^ v>>3
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmSigma1 ist die SHA-256-σ1-Mischfunktion.
|
|||
|
|
func drmSigma1(v uint32) uint32 {
|
|||
|
|
return v>>17 ^ v<<15 ^ v>>19 ^ v<<13 ^ v>>10
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmRol16 rotiert 32 Bit um 16.
|
|||
|
|
func drmRol16(v uint32) uint32 { return v<<16 | v>>16 }
|
|||
|
|
|
|||
|
|
// drmRotR rotiert 32 Bit rechts um n.
|
|||
|
|
func drmRotR(v uint32, n uint) uint32 { return v>>n | v<<(32-n) }
|
|||
|
|
|
|||
|
|
// drmExpand erweitert die Message-Schedule ab from auf 64 Wörter.
|
|||
|
|
func drmExpand(words []uint32, from int) []uint32 {
|
|||
|
|
for i := from; i < 64; i++ {
|
|||
|
|
x, y := words[i-2], words[i-15]
|
|||
|
|
words = append(words, drmSigma1(x)+words[i-7]+drmSigma0(y)+words[i-16])
|
|||
|
|
}
|
|||
|
|
return words
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmRounds führt die SHA-256-Runden aus — wörtlich nach Referenz,
|
|||
|
|
// inklusive des Mid-Block-Einstiegs (firstRound 14 mit ersetzter
|
|||
|
|
// Rundungskonstante) und der 8er-Rollen-Rotation der Register.
|
|||
|
|
func drmRounds(state [8]uint32, words []uint32, firstRound int) [8]uint32 {
|
|||
|
|
a, b, c, d, e, f, g, h := state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7]
|
|||
|
|
midBlock := firstRound == 14
|
|||
|
|
k := func(i int) uint32 {
|
|||
|
|
if midBlock && i == 14 {
|
|||
|
|
return drmAdjustment
|
|||
|
|
}
|
|||
|
|
return drmK[i]
|
|||
|
|
}
|
|||
|
|
aligned := ((firstRound + 7) / 8) * 8
|
|||
|
|
if aligned > 64 {
|
|||
|
|
aligned = 64
|
|||
|
|
}
|
|||
|
|
for i := firstRound; i < aligned; i++ {
|
|||
|
|
t1 := h + (drmRotR(e, 6) ^ drmRotR(e, 11) ^ drmRotR(e, 25)) + (g ^ (e & (f ^ g))) + k(i) + words[i]
|
|||
|
|
t2 := (drmRotR(a, 2) ^ drmRotR(a, 13) ^ drmRotR(a, 22)) + ((a & b) | (c & (a | b)))
|
|||
|
|
a, b, c, d, e, f, g, h = t1+t2, a, b, c, d+t1, e, f, g
|
|||
|
|
}
|
|||
|
|
for i := aligned; i < 64; i += 8 {
|
|||
|
|
// Runde i
|
|||
|
|
t1 := h + (drmRotR(e, 6) ^ drmRotR(e, 11) ^ drmRotR(e, 25)) + (g ^ (e & (f ^ g))) + k(i) + words[i]
|
|||
|
|
d += t1
|
|||
|
|
h = t1 + (drmRotR(a, 2) ^ drmRotR(a, 13) ^ drmRotR(a, 22)) + ((a & b) | (c & (a | b)))
|
|||
|
|
// Runde i+1 (Rollen rotiert)
|
|||
|
|
t1 = g + (drmRotR(d, 6) ^ drmRotR(d, 11) ^ drmRotR(d, 25)) + (f ^ (d & (e ^ f))) + k(i+1) + words[i+1]
|
|||
|
|
c += t1
|
|||
|
|
g = t1 + (drmRotR(h, 2) ^ drmRotR(h, 13) ^ drmRotR(h, 22)) + ((h & a) | (b & (h | a)))
|
|||
|
|
// Runde i+2
|
|||
|
|
t1 = f + (drmRotR(c, 6) ^ drmRotR(c, 11) ^ drmRotR(c, 25)) + (e ^ (c & (d ^ e))) + k(i+2) + words[i+2]
|
|||
|
|
b += t1
|
|||
|
|
f = t1 + (drmRotR(g, 2) ^ drmRotR(g, 13) ^ drmRotR(g, 22)) + ((g & h) | (a & (g | h)))
|
|||
|
|
// Runde i+3
|
|||
|
|
t1 = e + (drmRotR(b, 6) ^ drmRotR(b, 11) ^ drmRotR(b, 25)) + (d ^ (b & (c ^ d))) + k(i+3) + words[i+3]
|
|||
|
|
a += t1
|
|||
|
|
e = t1 + (drmRotR(f, 2) ^ drmRotR(f, 13) ^ drmRotR(f, 22)) + ((f & g) | (h & (f | g)))
|
|||
|
|
// Runde i+4
|
|||
|
|
t1 = d + (drmRotR(a, 6) ^ drmRotR(a, 11) ^ drmRotR(a, 25)) + (c ^ (a & (b ^ c))) + k(i+4) + words[i+4]
|
|||
|
|
h += t1
|
|||
|
|
d = t1 + (drmRotR(e, 2) ^ drmRotR(e, 13) ^ drmRotR(e, 22)) + ((e & f) | (g & (e | f)))
|
|||
|
|
// Runde i+5
|
|||
|
|
t1 = c + (drmRotR(h, 6) ^ drmRotR(h, 11) ^ drmRotR(h, 25)) + (b ^ (h & (a ^ b))) + k(i+5) + words[i+5]
|
|||
|
|
g += t1
|
|||
|
|
c = t1 + (drmRotR(d, 2) ^ drmRotR(d, 13) ^ drmRotR(d, 22)) + ((d & e) | (f & (d | e)))
|
|||
|
|
// Runde i+6
|
|||
|
|
t1 = b + (drmRotR(g, 6) ^ drmRotR(g, 11) ^ drmRotR(g, 25)) + (a ^ (g & (h ^ a))) + k(i+6) + words[i+6]
|
|||
|
|
f += t1
|
|||
|
|
b = t1 + (drmRotR(c, 2) ^ drmRotR(c, 13) ^ drmRotR(c, 22)) + ((c & d) | (e & (c | d)))
|
|||
|
|
// Runde i+7
|
|||
|
|
t1 = a + (drmRotR(f, 6) ^ drmRotR(f, 11) ^ drmRotR(f, 25)) + (h ^ (f & (g ^ h))) + k(i+7) + words[i+7]
|
|||
|
|
e += t1
|
|||
|
|
a = t1 + (drmRotR(b, 2) ^ drmRotR(b, 13) ^ drmRotR(b, 22)) + ((b & c) | (d & (b | c)))
|
|||
|
|
}
|
|||
|
|
return [8]uint32{a, b, c, d, e, f, g, h}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmSHA256Init berechnet den Zustand nach dem (verschleiert notierten)
|
|||
|
|
// ersten Block: 14 Nullwörter + die ersten 7 Bytes von data als
|
|||
|
|
// 24-Bit-/32-Bit-Big-Endian-Wörter, Schedule mit Korrekturen, Runden ab 14.
|
|||
|
|
func drmSHA256Init(data []byte) [8]uint32 {
|
|||
|
|
words := make([]uint32, 30, 64)
|
|||
|
|
words[14] = uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2])
|
|||
|
|
words[15] = binary.BigEndian.Uint32(data[3:7])
|
|||
|
|
for i := 16; i < 30; i++ {
|
|||
|
|
v := drmSchedule[i-16]
|
|||
|
|
// σ1-Anteil von W[i-2] (Index 16 liest W[14], dazu die Korrektur).
|
|||
|
|
s1 := drmSigma1(words[i-2])
|
|||
|
|
if i == 16 {
|
|||
|
|
s1 ^= drmSigma1Adj
|
|||
|
|
}
|
|||
|
|
v += s1
|
|||
|
|
if i-7 >= 14 {
|
|||
|
|
v += words[i-7]
|
|||
|
|
}
|
|||
|
|
if i-15 >= 14 { // nur i == 29, liest W[14], dazu die Korrektur
|
|||
|
|
s0 := drmSigma0(words[i-15])
|
|||
|
|
if i == 29 {
|
|||
|
|
s0 ^= drmSigma0Adj
|
|||
|
|
}
|
|||
|
|
v += s0
|
|||
|
|
}
|
|||
|
|
words[i] = v
|
|||
|
|
}
|
|||
|
|
words = append(words, drmSigma1(words[28])+words[23]+drmSigma0(words[15])+words[14]+drmAdjustment-drmK[14])
|
|||
|
|
words = drmExpand(words, 31)
|
|||
|
|
working := drmRounds(drmMidState, words, 14)
|
|||
|
|
var out [8]uint32
|
|||
|
|
for i := range out {
|
|||
|
|
out[i] = drmIV[i] + working[i]
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmSHA256Digest ist die verschleierte SHA-256-Variante der Referenz:
|
|||
|
|
// Block 1 steckt in drmSHA256Init (erste 7 Bytes), danach data[7:] mit
|
|||
|
|
// Referenz-Längenfeld (57+len(data)) und Standard-Runden.
|
|||
|
|
func drmSHA256Digest(data []byte) [8]uint32 {
|
|||
|
|
length := 57 + len(data)
|
|||
|
|
padded := append([]byte(nil), data[7:]...)
|
|||
|
|
padded = append(padded, 0x80)
|
|||
|
|
zeros := (55 - length) % 64
|
|||
|
|
if zeros < 0 {
|
|||
|
|
zeros += 64
|
|||
|
|
}
|
|||
|
|
padded = append(padded, make([]byte, zeros)...)
|
|||
|
|
var lenBuf [8]byte
|
|||
|
|
binary.BigEndian.PutUint64(lenBuf[:], uint64(length)*8)
|
|||
|
|
padded = append(padded, lenBuf[:]...)
|
|||
|
|
|
|||
|
|
state := drmSHA256Init(data[:7])
|
|||
|
|
for off := 0; off < len(padded); off += 64 {
|
|||
|
|
words := make([]uint32, 16, 64)
|
|||
|
|
for i := range words {
|
|||
|
|
words[i] = binary.BigEndian.Uint32(padded[off+i*4:])
|
|||
|
|
}
|
|||
|
|
words = drmExpand(words, 16)
|
|||
|
|
working := drmRounds(state, words, 0)
|
|||
|
|
for i := range state {
|
|||
|
|
state[i] += working[i]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return state
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmWordsLE4Even zerlegt data in little-endian u32-Wörter (auf 4 Bytes
|
|||
|
|
// null-aufgefüllt, Wortzahl gerade).
|
|||
|
|
func drmWordsLE4Even(data []byte) []uint32 {
|
|||
|
|
padded := make([]byte, len(data))
|
|||
|
|
copy(padded, data)
|
|||
|
|
if r := len(padded) % 4; r != 0 {
|
|||
|
|
padded = append(padded, make([]byte, 4-r)...)
|
|||
|
|
}
|
|||
|
|
words := make([]uint32, len(padded)/4)
|
|||
|
|
for i := range words {
|
|||
|
|
words[i] = binary.LittleEndian.Uint32(padded[i*4:])
|
|||
|
|
}
|
|||
|
|
if len(words)%2 == 1 {
|
|||
|
|
words = append(words, 0)
|
|||
|
|
}
|
|||
|
|
return words
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// drmCS64 berechnet die beiden CS64-Prüfsummen-Wörter (wörtliche
|
|||
|
|
// Portierung; Schlüssel aus den ersten 8 Digest-Bytes little-endian).
|
|||
|
|
func drmCS64(data []byte, digest [8]uint32) (a, b uint32) {
|
|||
|
|
var digestBytes [32]byte
|
|||
|
|
for i, w := range digest {
|
|||
|
|
binary.BigEndian.PutUint32(digestBytes[i*4:], w)
|
|||
|
|
}
|
|||
|
|
keyA := binary.LittleEndian.Uint32(digestBytes[0:4]) | 1
|
|||
|
|
keyB := binary.LittleEndian.Uint32(digestBytes[4:8]) | 1
|
|||
|
|
factors := [2]uint32{keyA * drmCS64Factor, keyB * drmCS64Factor}
|
|||
|
|
|
|||
|
|
terms := make([]uint32, 0, 14+8)
|
|||
|
|
offsets := make([]uint32, 0, 14+8)
|
|||
|
|
for i := range drmCS64Coefficients {
|
|||
|
|
terms = append(terms, drmCS64Coefficients[i][0]*factors[i%2])
|
|||
|
|
offsets = append(offsets, drmCS64Coefficients[i][1])
|
|||
|
|
}
|
|||
|
|
for i, word := range drmWordsLE4Even(append([]byte{0}, data...)) {
|
|||
|
|
var term, offset uint32
|
|||
|
|
if i%2 == 0 {
|
|||
|
|
term = word * keyA
|
|||
|
|
offset = drmRol16(word) * 0xEF0569FB
|
|||
|
|
} else {
|
|||
|
|
term = word * keyB
|
|||
|
|
offset = drmRol16(word) * 0xC31713DB
|
|||
|
|
}
|
|||
|
|
if i == 0 {
|
|||
|
|
term += drmCS64Adj0 * factors[0]
|
|||
|
|
offset += drmCS64Adj1
|
|||
|
|
}
|
|||
|
|
terms = append(terms, term)
|
|||
|
|
offsets = append(offsets, offset)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var swapAcc, swapLast, st, acc uint32
|
|||
|
|
for i := 0; i < len(terms); i += 2 {
|
|||
|
|
x, y := terms[i], terms[i+1]
|
|||
|
|
t := drmRol16(offsets[i] + x)
|
|||
|
|
q := t * 0x76F50F34
|
|||
|
|
swapAcc += drmRol16(q)*0xC3EFEA97 + q
|
|||
|
|
t = drmRol16(offsets[i+1] + y)
|
|||
|
|
q = t * 0x1881CE3C
|
|||
|
|
swapLast = drmRol16(q)*0x35BD1EC9 + q
|
|||
|
|
swapAcc += swapLast
|
|||
|
|
|
|||
|
|
st = st*keyA + x
|
|||
|
|
st = drmRol16(st) * 0xCF98B111
|
|||
|
|
st = drmRol16(st) * 0x87085B9F
|
|||
|
|
st = drmRol16(st) * 0x12CEB96D
|
|||
|
|
st = drmRol16(st) * 0x257E1D83
|
|||
|
|
acc += st
|
|||
|
|
t = st*keyB + y
|
|||
|
|
t = drmRol16(t) * 0xA27416F5
|
|||
|
|
t = drmRol16(t) * 0xD38396FF
|
|||
|
|
st = drmRol16(drmRol16(t)*0x7C932B89) * 0xBFA49F69
|
|||
|
|
acc += st
|
|||
|
|
}
|
|||
|
|
return swapLast ^ st, swapAcc ^ acc
|
|||
|
|
}
|