add acc-lc dl

This commit is contained in:
itouakirai
2025-01-15 15:34:20 +08:00
parent 33502a5975
commit 90ba76ef96
11 changed files with 7066 additions and 49 deletions

274
utils/runv3/cdm/cdm.go Normal file
View File

@@ -0,0 +1,274 @@
package wv
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/pem"
"errors"
"github.com/aead/cmac"
"google.golang.org/protobuf/proto"
"lukechampine.com/frand"
"math"
"time"
)
type CDM struct {
privateKey *rsa.PrivateKey
clientID []byte
sessionID [32]byte
widevineCencHeader WidevineCencHeader
signedDeviceCertificate SignedDeviceCertificate
privacyMode bool
}
type Key struct {
ID []byte
Type License_KeyContainer_KeyType
Value []byte
}
// Creates a new CDM object with the specified device information.
func NewCDM(privateKey string, clientID []byte, initData []byte) (CDM, error) {
block, _ := pem.Decode([]byte(privateKey))
if block == nil || block.Type != "RSA PRIVATE KEY" {
return CDM{}, errors.New("failed to decode device private key")
}
keyParsed, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return CDM{}, err
}
var widevineCencHeader WidevineCencHeader
if len(initData) < 32 {
return CDM{}, errors.New("initData not long enough")
}
if err := proto.Unmarshal(initData[32:], &widevineCencHeader); err != nil {
return CDM{}, err
}
sessionID := func() (s [32]byte) {
c := []byte("ABCDEF0123456789")
for i := 0; i < 16; i++ {
s[i] = c[frand.Intn(len(c))]
}
s[16] = '0'
s[17] = '1'
for i := 18; i < 32; i++ {
s[i] = '0'
}
return s
}()
return CDM{
privateKey: keyParsed,
clientID: clientID,
widevineCencHeader: widevineCencHeader,
sessionID: sessionID,
}, nil
}
// Creates a new CDM object using the default device configuration.
func NewDefaultCDM(initData []byte) (CDM, error) {
return NewCDM(DefaultPrivateKey, DefaultClientID, initData)
}
// Sets a device certificate. This is makes generating the license request
// more complicated but is supported. This is usually not necessary for most
// Widevine applications.
func (c *CDM) SetServiceCertificate(certData []byte) error {
var message SignedMessage
if err := proto.Unmarshal(certData, &message); err != nil {
return err
}
if err := proto.Unmarshal(message.Msg, &c.signedDeviceCertificate); err != nil {
return err
}
c.privacyMode = true
return nil
}
func (c *CDM) GetServiceCertificate() *SignedDeviceCertificate {
return &c.signedDeviceCertificate
}
// Generates the license request data. This is sent to the license server via
// HTTP POST and the server in turn returns the license response.
func (c *CDM) GetLicenseRequest() ([]byte, error) {
var licenseRequest SignedLicenseRequest
licenseRequest.Msg = new(LicenseRequest)
licenseRequest.Msg.ContentId = new(LicenseRequest_ContentIdentification)
licenseRequest.Msg.ContentId.CencId = new(LicenseRequest_ContentIdentification_CENC)
// this is probably really bad for the GC but protobuf uses pointers for optional
// fields so it is necessary and this is not a long running program
{
v := SignedLicenseRequest_LICENSE_REQUEST
licenseRequest.Type = &v
}
licenseRequest.Msg.ContentId.CencId.Pssh = &c.widevineCencHeader
{
v := LicenseType_DEFAULT
licenseRequest.Msg.ContentId.CencId.LicenseType = &v
}
licenseRequest.Msg.ContentId.CencId.RequestId = c.sessionID[:]
{
v := LicenseRequest_NEW
licenseRequest.Msg.Type = &v
}
{
v := uint32(time.Now().Unix())
licenseRequest.Msg.RequestTime = &v
}
{
v := ProtocolVersion_CURRENT
licenseRequest.Msg.ProtocolVersion = &v
}
{
v := uint32(frand.Uint64n(math.MaxUint32))
licenseRequest.Msg.KeyControlNonce = &v
}
if c.privacyMode {
pad := func(data []byte, blockSize int) []byte {
padlen := blockSize - (len(data) % blockSize)
if padlen == 0 {
padlen = blockSize
}
return append(data, bytes.Repeat([]byte{byte(padlen)}, padlen)...)
}
const blockSize = 16
var cidKey, cidIV [blockSize]byte
frand.Read(cidKey[:])
frand.Read(cidIV[:])
block, err := aes.NewCipher(cidKey[:])
if err != nil {
return nil, err
}
paddedClientID := pad(c.clientID, blockSize)
encryptedClientID := make([]byte, len(paddedClientID))
cipher.NewCBCEncrypter(block, cidIV[:]).CryptBlocks(encryptedClientID, paddedClientID)
servicePublicKey, err := x509.ParsePKCS1PublicKey(c.signedDeviceCertificate.XDeviceCertificate.PublicKey)
if err != nil {
return nil, err
}
encryptedCIDKey, err := rsa.EncryptOAEP(sha1.New(), frand.Reader, servicePublicKey, cidKey[:], nil)
if err != nil {
return nil, err
}
licenseRequest.Msg.EncryptedClientId = new(EncryptedClientIdentification)
{
v := string(c.signedDeviceCertificate.XDeviceCertificate.ServiceId)
licenseRequest.Msg.EncryptedClientId.ServiceId = &v
}
licenseRequest.Msg.EncryptedClientId.ServiceCertificateSerialNumber = c.signedDeviceCertificate.XDeviceCertificate.SerialNumber
licenseRequest.Msg.EncryptedClientId.EncryptedClientId = encryptedClientID
licenseRequest.Msg.EncryptedClientId.EncryptedClientIdIv = cidIV[:]
licenseRequest.Msg.EncryptedClientId.EncryptedPrivacyKey = encryptedCIDKey
} else {
licenseRequest.Msg.ClientId = new(ClientIdentification)
if err := proto.Unmarshal(c.clientID, licenseRequest.Msg.ClientId); err != nil {
return nil, err
}
}
{
data, err := proto.Marshal(licenseRequest.Msg)
if err != nil {
return nil, err
}
hash := sha1.Sum(data)
if licenseRequest.Signature, err = rsa.SignPSS(frand.Reader, c.privateKey, crypto.SHA1, hash[:], &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}); err != nil {
return nil, err
}
}
return proto.Marshal(&licenseRequest)
}
// Retrieves the keys from the license response data. These keys can be
// used to decrypt the DASH-MP4.
func (c *CDM) GetLicenseKeys(licenseRequest []byte, licenseResponse []byte) (keys []Key, err error) {
var license SignedLicense
if err = proto.Unmarshal(licenseResponse, &license); err != nil {
return
}
var licenseRequestParsed SignedLicenseRequest
if err = proto.Unmarshal(licenseRequest, &licenseRequestParsed); err != nil {
return
}
licenseRequestMsg, err := proto.Marshal(licenseRequestParsed.Msg)
if err != nil {
return
}
sessionKey, err := rsa.DecryptOAEP(sha1.New(), frand.Reader, c.privateKey, license.SessionKey, nil)
if err != nil {
return
}
sessionKeyBlock, err := aes.NewCipher(sessionKey)
if err != nil {
return
}
encryptionKey := []byte{1, 'E', 'N', 'C', 'R', 'Y', 'P', 'T', 'I', 'O', 'N', 0}
encryptionKey = append(encryptionKey, licenseRequestMsg...)
encryptionKey = append(encryptionKey, []byte{0, 0, 0, 0x80}...)
encryptionKeyCmac, err := cmac.Sum(encryptionKey, sessionKeyBlock, sessionKeyBlock.BlockSize())
if err != nil {
return
}
encryptionKeyCipher, err := aes.NewCipher(encryptionKeyCmac)
if err != nil {
return
}
unpad := func(b []byte) []byte {
if len(b) == 0 {
return b
}
// pks padding is designed so that the value of all the padding bytes is
// the number of padding bytes repeated so to figure out how many
// padding bytes there are we can just look at the value of the last
// byte
// i.e if there are 6 padding bytes then it will look at like
// <data> 0x6 0x6 0x6 0x6 0x6 0x6
count := int(b[len(b)-1])
return b[0 : len(b)-count]
}
for _, key := range license.Msg.Key {
decrypter := cipher.NewCBCDecrypter(encryptionKeyCipher, key.Iv)
decryptedKey := make([]byte, len(key.Key))
decrypter.CryptBlocks(decryptedKey, key.Key)
keys = append(keys, Key{
ID: key.Id,
Type: *key.Type,
Value: unpad(decryptedKey),
})
}
return
}

24
utils/runv3/cdm/consts.go Normal file
View File

@@ -0,0 +1,24 @@
package wv
import "encoding/base64"
var DefaultPrivateKey string
var DefaultClientID []byte
func InitConstants() {
DefaultPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA2bO3yvFwNnIHsbDl3MTjKdDsiBWsuZWOGVxInFWAVMp+nffG\nYlquTKpJurEry95yprcRB3hYhvA5ghsACidcWPDEPVqqRZ7YXLevyUA+Sn2Jxpvt\nOcwyFHbSwruNxprWOkHCT774O4L/wJUt5x2C4iFCrJByjw0omN8u+EHdavvH7ZPn\nb3/EZp/cpZa9/+HOkutvBHBvaPp18F8JQhzUQ9MwLuDFTr+QLDB5+Y57Je2tNYDK\nxD1K+Ed5Ja0A4OKhPKIwPwPre0nt5scjLba3LSAKtKxiGqFtWO4U7Tf1YrdjJv2o\n9o8Sf8qcnbpzvQ4KwFqehuJnB7+W7mdJJw12PQIDAQABAoIBACE32wOMc6LbI3Fp\nnKljIYZv6qeZJxHqUBRukGXKZhqKC2fvNsYrMA1irn1eK2CgQL5PkLmjE18DqMLB\ne/AQsXagxlDWVMTqx/jdzmTW+KpFHZDAmiIHllypBN/R3oA/gBDDl/KzIQ1zn7Kz\nEJ4DUsVObe4G3HQXfepVo8Udx7tbB7X6wHe2kEgFyY3lPdvubik0C4t4ipSD79y7\nSfW7XVA5XUQmqN4U2kWM0uSwzd4BA7hqyScJsygf6KgpMWPS2xFZEZQRUpYcBH48\nE7YqNrrlYP3yaQ+9Jx56kKS0mvv3vUXS7AfUbU8CiHwD9I3BGwswEUueOGGVeXbx\ntFF8s8ECgYEA97BDcL/bt+r3qJF0dxtMB5ZngJbFx9RdsblYepVpblr2UfxnFttO\nPoNSKa4W36HuDsun49dkaoABJWdtZs2Hy6q+xvEgozvhMaBVE3spnWnzCT1yTMYL\nG02uDEl0dPiTg116bVElaswtqMXvnnpbOTMTe7Ig9sWiUW/GH9RM+N8CgYEA4QHb\n+OA0BfczbVQP9B+plt4mAuu4BDm4GPwq1yXOWo3Ct8Ik+HeY1hqOObpfyQMAza+E\ne/kP6W8vXpiElGrmiUbTXK4Rzmf+yYeOrvl3D80bFq4GtDNAIQD3jpj6zjlT+Gzw\nI501gRx5iPl4fSccRSdpoeri7F9ANtc6EEGFyGMCgYEAjMznWYXHGkL47BtbkIW0\n769BQSj0X4dKh8gsEusylugglDSeSbD7RrASGd175T7A/CorU2rTC3OesyubVlBJ\n/K4gaykRe5mDh1l0Y3GlE3XyEXObsSb3k1rSMOvkxsWz3X5bJR923MIaxpFWiMlX\naCmvzqZQ9NceUZrvjpJ5+xMCgYAJa8KCESEcftUwZqykVA8Nug9tX+E8jA4hPa2t\nhG+3augUOZTCsn87t7Dsydjo2a9W7Vpmtm7sHzOkik5CyJcOeGCxKLimI8SPO5XF\nzbwmdTgFIxQ0x1CQETJMTityJwRVCnqjgxmSZlbQXWGmG9UbMCNEHEmUDAjsQuaz\nd4racQKBgQDR1Y2kalvleYGrhwcA8LTnIh0rYEfAt9YxNmTi5qDKf5QPvUP2v+WO\nfSB5coUqR8LBweHE5V8JgFt74fdLBqZV/k2z/dI0r+EQWmpZ2uPEC0Khk/Sb9iRD\nfH7at3PMusrkwZCGZ8beFEAr6icXclV08nPCNGB6WckacfzpAj8Azg==\n-----END RSA PRIVATE KEY-----"
DefaultClientdIDBase64 := "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"
DefaultClientID, _ = base64.StdEncoding.DecodeString(DefaultClientdIDBase64)
// DefaultPrivateKeyBuffer, err := ioutil.ReadFile("device_private_key")
// if err != nil {
// panic(err)
// }
// DefaultPrivateKey = string(DefaultPrivateKeyBuffer)
//
// DefaultClientID, err = ioutil.ReadFile("device_client_id_blob")
// if err != nil {
// panic(err)
// }
}

16
utils/runv3/cdm/pssh.go Normal file
View File

@@ -0,0 +1,16 @@
package wv
import (
"bytes"
"io"
"net/http"
)
func GetCertData(client *http.Client, licenseURL string) ([]byte, error) {
response, err := client.Post(licenseURL, "application/x-www-form-urlencoded", bytes.NewReader([]byte{0x08, 0x04}))
if err != nil {
return nil, err
}
defer response.Body.Close()
return io.ReadAll(response.Body)
}

File diff suppressed because it is too large Load Diff