widevine

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2023 License: GPL-3.0 Imports: 21 Imported by: 0

README

gowidevine

🐭 Go implementation of Google's Widevine DRM CDM (Content Decryption Module)

Features

  • Implementation of Widevine CDM/Device/Protobuf
  • Decrypt MP4 with Widevine key
  • Covered by realistic data tests
  • Few third-party dependencies
  • License proxy server/client

Getting Started

Install

You first need Go installed (version 1.18+ is required), then you can use the below Go command to install gowidevine:

go get -u github.com/iyear/gowidevine

Import

Import the package into your project:

import "github.com/iyear/gowidevine"

Usage

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"

    widevine "github.com/iyear/gowidevine"
    "github.com/iyear/gowidevine/widevinepb"
)

var (
    clientID   = []byte("foo")
    privateKey = []byte("bar")
    psshData   = []byte("baz")
)

func main() {
    keys, err := getKeys()
    if err != nil {
        panic(err)
    }
    if len(keys) == 0 {
        panic("no keys")
    }

    for _, key := range keys {
        fmt.Printf("type: %s, id: %x, key: %x\n", key.Type, key.ID, key.Key)
    }

    err = widevine.DecryptMP4(bytes.NewBufferString("encrypted data"),
        keys[0].Key, io.Discard)
    if err != nil {
        panic(err)
    }
}

func getKeys() ([]*widevine.Key, error) {
    // Create device from raw data or from wvd file
    device, err := widevine.NewDevice(
        widevine.FromRaw(clientID, privateKey),
        // widevine.FromWVD(bytes.NewReader([]byte("baz"))),
    )
    if err != nil {
        return nil, fmt.Errorf("create device: %w", err)
    }
    // Create CDM
    cdm := widevine.NewCDM(device)

    // Parse PSSH
    pssh, err := widevine.NewPSSH(psshData)
    if err != nil {
        return nil, fmt.Errorf("parse pssh: %w", err)
    }

    // Get license challenge
    challenge, parseLicense, err := cdm.GetLicenseChallenge(pssh, widevinepb.LicenseType_AUTOMATIC, false)
    if err != nil {
        return nil, fmt.Errorf("get license challenge: %w", err)
    }
    // Or use privacy mode
    cert, err := getServiceCert()
    if err != nil {
        return nil, fmt.Errorf("get service cert: %w", err)
    }
    challenge, parseLicense, err = cdm.GetLicenseChallenge(pssh, widevinepb.LicenseType_AUTOMATIC, true, cert)
    if err != nil {
        return nil, fmt.Errorf("get license challenge: %w", err)
    }

    // Send challenge to license server
    resp, err := http.DefaultClient.Do(&http.Request{Body: io.NopCloser(bytes.NewReader(challenge))})
    if err != nil {
        return nil, fmt.Errorf("request license: %w", err)
    }
    defer func() { _ = resp.Body.Close() }()

    license, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("read resp: %w", err)
    }

    // Parse license
    keys, err := parseLicense(license)
    if err != nil {
        return nil, fmt.Errorf("parse license: %w", err)
    }

    return keys, nil
}

func getServiceCert() (*widevinepb.DrmCertificate, error) {
    resp, err := http.DefaultClient.Do(&http.Request{Body: io.NopCloser(bytes.NewReader(widevine.ServiceCertificateRequest))})
    if err != nil {
        return nil, fmt.Errorf("request service cert: %w", err)
    }
    defer func() { _ = resp.Body.Close() }()

    serviceCert, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("read response: %w", err)
    }

    cert, err := widevine.ParseServiceCert(serviceCert)
    if err != nil {
        return nil, fmt.Errorf("parse service cert: %w", err)
    }

    return cert, nil
}

Thanks

Disclaimer

  • The project does not provide Google-provisioned private key and client id except for test purpose.
  • The project does not condone piracy or any action against the terms of the DRM systems.
  • The project is for study and research only, please do not use it for commercial purposes.

License

GPLv3 License

Documentation

Index

Constants

View Source
const WidevineSystemID = "edef8ba979d64acea3c827dcd51d21ed"

WidevineSystemID is the system ID of Widevine.

Variables

View Source
var ServiceCertificateRequest = []byte{0x08, 0x04}

ServiceCertificateRequest is the constant request for getting the service certificate from the Widevine license server.

Functions

func DecryptMP4

func DecryptMP4(r io.Reader, key []byte, w io.Writer) error

DecryptMP4 decrypts a fragmented MP4 file with the given key. Supports CENC and CBCS schemes.

func ParseServiceCert

func ParseServiceCert(serviceCert []byte) (*wvpb.DrmCertificate, error)

ParseServiceCert parses a service certificate which can be used in privacy mode.

Types

type CDM

type CDM struct {
	// contains filtered or unexported fields
}

CDM implements the Widevine CDM protocol.

func NewCDM

func NewCDM(device *Device, opts ...CDMOption) *CDM

NewCDM creates a new CDM.

Get device by calling NewDevice.

func (*CDM) GetLicenseChallenge

func (c *CDM) GetLicenseChallenge(pssh *PSSH, typ wvpb.LicenseType, privacyMode bool, serviceCert ...*wvpb.DrmCertificate) ([]byte, func(b []byte) ([]*Key, error), error)

GetLicenseChallenge returns the license challenge for the given PSSH.

Set privacyMode to true to enable privacy mode, and you must provide a service certificate.

type CDMOption

type CDMOption func(*CDM)

func WithNow

func WithNow(now func() time.Time) CDMOption

WithNow sets the time now source of the CDM.

func WithRandom

func WithRandom(source rand.Source) CDMOption

WithRandom sets the random source of the CDM.

type Device

type Device struct {
	// contains filtered or unexported fields
}

Device represents a Widevine device.

func NewDevice

func NewDevice(src DeviceSource) (*Device, error)

NewDevice creates a Device from a DeviceSource.

func (*Device) ClientID

func (d *Device) ClientID() *wvpb.ClientIdentification

ClientID returns the client ID of the device.

func (*Device) DrmCertificate

func (d *Device) DrmCertificate() *wvpb.DrmCertificate

DrmCertificate returns the DRM certificate of the device.

func (*Device) PrivateKey

func (d *Device) PrivateKey() *rsa.PrivateKey

PrivateKey returns the private key of the device.

type DeviceSource

type DeviceSource func() (*Device, error)

DeviceSource is a function that returns a Device.

func FromRaw

func FromRaw(clientID, privateKey []byte) DeviceSource

FromRaw creates a Device from raw client ID and private key data.

func FromWVD

func FromWVD(r io.Reader) DeviceSource

FromWVD creates a Device from a WVD file.

type Key

type Key struct {
	// Type is the type of key.
	Type wvpb.License_KeyContainer_KeyType
	// IV is the initialization vector of the key.
	IV []byte
	// ID is the ID of the key.
	ID []byte
	// Key is the key.
	Key []byte
}

type PSSH

type PSSH struct {
	// contains filtered or unexported fields
}

PSSH represents a PSSH box containing Widevine data.

func NewPSSH

func NewPSSH(b []byte) (*PSSH, error)

NewPSSH creates a PSSH from bytes

func (*PSSH) Data

func (p *PSSH) Data() *wvpb.WidevinePsshData

Data returns the parsed data of the PSSH box.

func (*PSSH) Flags

func (p *PSSH) Flags() uint32

Flags returns the flags of the PSSH box.

func (*PSSH) RawData

func (p *PSSH) RawData() []byte

RawData returns the data of the PSSH box.

func (*PSSH) Version

func (p *PSSH) Version() byte

Version returns the version of the PSSH box.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL