├── .github └── main.workflow ├── README.md ├── fileHandling └── read.go ├── playready └── parse.go ├── psshutil.go └── widevine ├── parse.go ├── widevine_pssh_data.pb.go └── widevine_pssh_data.proto /.github/main.workflow: -------------------------------------------------------------------------------- 1 | workflow "Build" { 2 | on = "release" 3 | resolves = [ 4 | "release darwin/amd64", 5 | "release windows/amd64", 6 | "release linux/amd64", 7 | ] 8 | } 9 | 10 | action "release darwin/amd64" { 11 | uses = "ngs/go-release.action@v1.0.0" 12 | env = { 13 | GOOS = "darwin" 14 | GOARCH = "amd64" 15 | } 16 | secrets = ["GITHUB_TOKEN"] 17 | } 18 | 19 | action "release windows/amd64" { 20 | uses = "ngs/go-release.action@v1.0.0" 21 | env = { 22 | GOOS = "windows" 23 | GOARCH = "amd64" 24 | } 25 | secrets = ["GITHUB_TOKEN"] 26 | } 27 | 28 | action "release linux/amd64" { 29 | uses = "ngs/go-release.action@v1.0.0" 30 | env = { 31 | GOOS = "linux" 32 | GOARCH = "amd64" 33 | } 34 | secrets = ["GITHUB_TOKEN"] 35 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # psshutil 2 | 3 | A simple library for parsing PSSH headers from **ISOBMFF** boxes 4 | 5 | Status 6 | ------ 7 | Parses basic information for PlayReady and Widevine, including KeyIDs. 8 | Will signal presence for a number of DRM systems. 9 | 10 | Build and install 11 | ----------------- 12 | Make sure you have a working **go** environment 13 | 14 | go get github.com/colde/psshutil 15 | go install github.com/colde/psshutil 16 | 17 | Usage 18 | ----- 19 | psshutil -i video.mp4 20 | -------------------------------------------------------------------------------- /fileHandling/read.go: -------------------------------------------------------------------------------- 1 | package fileHandling 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | func ReadFromFile(f *os.File, size int64) ([]byte, error) { 9 | buf := make([]byte, size) 10 | _, err := f.Read(buf) 11 | 12 | if err != nil { 13 | log.Fatalln(err.Error()) 14 | return nil, err 15 | } 16 | 17 | return buf, nil 18 | } 19 | 20 | func ReadHeader(f *os.File) ([]byte, []byte, error) { 21 | buf, err := ReadFromFile(f, 8) 22 | if err != nil { 23 | log.Fatalln(err.Error()) 24 | return nil, nil, err 25 | } 26 | 27 | return buf[0:4], buf[4:8], nil 28 | } 29 | -------------------------------------------------------------------------------- /playready/parse.go: -------------------------------------------------------------------------------- 1 | package playready 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/binary" 7 | "encoding/xml" 8 | "fmt" 9 | "github.com/colde/psshutil/fileHandling" 10 | "log" 11 | "os" 12 | "unicode/utf16" 13 | "unicode/utf8" 14 | ) 15 | 16 | type WRMHeader struct { 17 | XMLName xml.Name `xml:"WRMHEADER"` 18 | Version string `xml:"version,attr"` 19 | Data []Data `xml:"DATA"` 20 | } 21 | type Data struct { 22 | XMLName xml.Name `xml:"DATA"` 23 | ProtectInfo []ProtectInfo `xml:"PROTECTINFO"` 24 | KeyID string `xml:"KID"` 25 | Checksum string `xml:"CHECKSUM"` 26 | LicenseUrl string `xml:"LA_URL"` 27 | } 28 | type ProtectInfo struct { 29 | XMLName xml.Name `xml:"PROTECTINFO"` 30 | KeyLength string `xml:"KEYLEN"` 31 | AlgorithmID string `xml:"ALGID"` 32 | } 33 | 34 | func Parse(f *os.File, size int64) { 35 | dataSize, err := fileHandling.ReadFromFile(f, 4) 36 | if err != nil { 37 | log.Fatalln(err.Error()) 38 | return 39 | } 40 | 41 | sizeInt := int64(binary.BigEndian.Uint32(dataSize)) 42 | 43 | // Read PlayReady Header Length (identical to previous length, but little endian) 44 | _, err = fileHandling.ReadFromFile(f, 4) 45 | if err != nil { 46 | log.Fatalln(err.Error()) 47 | return 48 | } 49 | 50 | // Read record count 51 | _, err = fileHandling.ReadFromFile(f, 2) 52 | if err != nil { 53 | log.Fatalln(err.Error()) 54 | return 55 | } 56 | 57 | // Read rest of data 58 | buf, err := fileHandling.ReadFromFile(f, sizeInt-6) 59 | if err != nil { 60 | log.Fatalln(err.Error()) 61 | return 62 | } 63 | 64 | // Assume just 1 record and slice of the record type and record length 65 | header, err := DecodeUTF16(buf[4:]) 66 | if err != nil { 67 | log.Fatalln(err.Error()) 68 | return 69 | } 70 | 71 | xmlheader := WRMHeader{} 72 | err = xml.Unmarshal([]byte(header), &xmlheader) 73 | if err != nil { 74 | log.Fatalln(err.Error()) 75 | return 76 | } 77 | 78 | for _, data := range xmlheader.Data { 79 | key_bytes, _ := base64.StdEncoding.DecodeString(data.KeyID) 80 | 81 | tenc_bytes := make([]byte, 16) 82 | tenc_bytes[0] = key_bytes[3] 83 | tenc_bytes[1] = key_bytes[2] 84 | tenc_bytes[2] = key_bytes[1] 85 | tenc_bytes[3] = key_bytes[4] 86 | tenc_bytes[4] = key_bytes[3] 87 | tenc_bytes[5] = key_bytes[6] 88 | tenc_bytes[6] = key_bytes[5] 89 | copy(tenc_bytes[7:], key_bytes[7:]) 90 | 91 | tenc_keyid := base64.StdEncoding.EncodeToString(tenc_bytes) 92 | 93 | fmt.Println("PlayReady Header/License KID:", data.KeyID) 94 | fmt.Println("PlayReady tenc KID:", tenc_keyid) 95 | fmt.Println("PlayReady LA_URL:", data.LicenseUrl) 96 | } 97 | } 98 | 99 | func DecodeUTF16(b []byte) (string, error) { 100 | 101 | if len(b)%2 != 0 { 102 | return "", fmt.Errorf("Must have even length byte slice") 103 | } 104 | 105 | u16s := make([]uint16, 1) 106 | 107 | ret := &bytes.Buffer{} 108 | 109 | b8buf := make([]byte, 4) 110 | 111 | lb := len(b) 112 | for i := 0; i < lb; i += 2 { 113 | u16s[0] = uint16(b[i]) + (uint16(b[i+1]) << 8) 114 | r := utf16.Decode(u16s) 115 | n := utf8.EncodeRune(b8buf, r[0]) 116 | ret.Write(b8buf[:n]) 117 | } 118 | 119 | return ret.String(), nil 120 | } 121 | -------------------------------------------------------------------------------- /psshutil.go: -------------------------------------------------------------------------------- 1 | /* 2 | psshutil implements function to manipulate and use pssh boxes in isobmff files 3 | */ 4 | package main 5 | 6 | import ( 7 | "encoding/binary" 8 | "flag" 9 | "fmt" 10 | "github.com/colde/psshutil/fileHandling" 11 | "github.com/colde/psshutil/playready" 12 | "github.com/colde/psshutil/widevine" 13 | "github.com/nu7hatch/gouuid" 14 | "log" 15 | "os" 16 | ) 17 | 18 | func main() { 19 | var fileName = flag.String("i", "", "Input file for reading/parsing") 20 | flag.Parse() 21 | 22 | if *fileName == "" { 23 | fmt.Println("Usage: psshutil -i ") 24 | os.Exit(0) 25 | } 26 | 27 | var totalSize int64 28 | 29 | f, e := os.Open(*fileName) 30 | if e != nil { 31 | log.Fatalf(e.Error()) 32 | } 33 | defer f.Close() 34 | 35 | fi, err := f.Stat() 36 | if err != nil { 37 | log.Fatalln(err.Error()) 38 | } 39 | totalSize = fi.Size() 40 | 41 | loopAtoms(f, totalSize, 0) 42 | } 43 | 44 | func parsePssh(f *os.File, box string, size int64) { 45 | 46 | // Full box header 47 | _, err := fileHandling.ReadFromFile(f, 4) 48 | if err != nil { 49 | log.Fatalln(err.Error()) 50 | return 51 | } 52 | 53 | systemID, err := fileHandling.ReadFromFile(f, 16) 54 | if err != nil { 55 | log.Fatalln(err.Error()) 56 | return 57 | } 58 | 59 | systemUUID, err := uuid.Parse(systemID) 60 | if err != nil { 61 | log.Fatalln(err.Error()) 62 | return 63 | } 64 | switch systemUUID.String() { 65 | case "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed": 66 | fmt.Println("Found Widevine", systemUUID) 67 | // Size determined to be size - 8 (box header), 4 (fullbox header), 16 (systemid) 68 | widevine.Parse(f, size-28) 69 | case "9a04f079-9840-4286-ab92-e65be0885f95": 70 | fmt.Println("Found Microsoft PlayReady", systemUUID) 71 | // Size determined to be size - 8 (box header), 4 (fullbox header), 16 (systemid) 72 | playready.Parse(f, size-28) 73 | case "f239e769-efa3-4850-9c16-a903c6932efb": 74 | fmt.Println("Found Adobe Primetime DRM, version 4", systemUUID) 75 | case "5e629af5-38da-4063-8977-97ffbd9902d4": 76 | fmt.Println("Found Marlin DRM") 77 | case "adb41c24-2dbf-4a6d-958b-4457c0d27b95": 78 | fmt.Println("Found Nagra MediaAccess PRM 3.0") 79 | case "a68129d3-575b-4f1a-9cba-3223846cf7c3": 80 | fmt.Println("Cisco/NDS VideoGuard Everywhere DRM") 81 | case "9a27dd82-fde2-4725-8cbc-4234aa06ec09": 82 | fmt.Println("Found Verimatrix VCAS") 83 | case "1f83e1e8-6ee9-4f0d-ba2f-5ec4e3ed1a66": 84 | fmt.Println("Found Arris SecureMedia") 85 | case "644fe7b5-260f-4fad-949a-0762ffb054b4": 86 | fmt.Println("Found CMLA (OMA DRM)") 87 | case "6a99532d-869f-5922-9a91-113ab7b1e2f3": 88 | fmt.Println("Found MobiTV DRM") 89 | case "35bf197b-530e-42d7-8b65-1b4bf415070f": 90 | fmt.Println("Found DivX DRM Series 5") 91 | case "b4413586-c58c-ffb0-94a5-d4896c1af6c3": 92 | fmt.Println("Found Viaccess-Orca DRM") 93 | case "80a6be7e-1448-4c37-9e70-d5aebe04c8d2": 94 | fmt.Println("Found Irdeto Content Protection for DASH") 95 | case "dcf4e3e3-62f1-5818-7ba6-0a6fe33ff3dd": 96 | fmt.Println("Found DigiCAP SmartXess for DASH") 97 | case "45d481cb-8fe0-49c0-ada9-ab2d2455b2f2": 98 | fmt.Println("Found CoreCrypt (CoreTrust)") 99 | case "616c7469-6361-7374-2d50-726f74656374": 100 | fmt.Println("Found Alticast altiProtect") 101 | case "992c46e6-c437-4899-b6a0-50fa91ad0e39": 102 | fmt.Println("Found SecureMedia SteelKnot") 103 | case "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b": 104 | // W3C standard: https://w3c.github.io/encrypted-media/format-registry/initdata/cenc.html 105 | fmt.Println("Found Common PSSH Box Format") 106 | case "e2719d58-a985-b3c9-781a-b030af78d30e": 107 | fmt.Println("Found ClearKey signaling (this should not be in the PSSH box)") 108 | case "94ce86fb-07ff-4f43-adb8-93d2fa968ca2": 109 | fmt.Println("Found Apple FairPlay") 110 | case "279fe473-512c-48fe-ade8-d176fee6b40f": 111 | fmt.Println("Found Arris Titanium") 112 | case "aa11967f-cc01-4a4a-8e99-c5d3dddfea2d": 113 | fmt.Println("Found Unitend DRM") 114 | default: 115 | fmt.Println("Found unknown DRM system", systemUUID) 116 | } 117 | fmt.Println() 118 | } 119 | 120 | func loopAtoms(f *os.File, totalSize int64, offset int64) { 121 | var pos int64 122 | 123 | for totalSize > pos { 124 | size, box, err := fileHandling.ReadHeader(f) 125 | if err != nil { 126 | log.Fatalln(err.Error()) 127 | } 128 | 129 | sizeInt := int64(binary.BigEndian.Uint32(size)) 130 | 131 | if string(box) == "moov" { 132 | loopAtoms(f, sizeInt-8, pos+8) 133 | pos += sizeInt 134 | } else { 135 | if string(box) == "pssh" { 136 | parsePssh(f, string(box), sizeInt) 137 | } 138 | pos += sizeInt 139 | seek := pos + offset 140 | f.Seek(seek, 0) 141 | } 142 | } 143 | return 144 | } 145 | -------------------------------------------------------------------------------- /widevine/parse.go: -------------------------------------------------------------------------------- 1 | package widevine 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/binary" 6 | "fmt" 7 | "github.com/colde/psshutil/fileHandling" 8 | "github.com/golang/protobuf/proto" 9 | "log" 10 | "os" 11 | ) 12 | 13 | func Parse(f *os.File, size int64) { 14 | dataSize, err := fileHandling.ReadFromFile(f, 4) 15 | if err != nil { 16 | log.Fatalln(err.Error()) 17 | return 18 | } 19 | 20 | sizeInt := int64(binary.BigEndian.Uint32(dataSize)) 21 | 22 | buf, err := fileHandling.ReadFromFile(f, sizeInt) 23 | if err != nil { 24 | log.Fatalln(err.Error()) 25 | return 26 | } 27 | 28 | widevineHeader := &WidevinePsshData{} 29 | err = proto.Unmarshal(buf, widevineHeader) 30 | if err != nil { 31 | log.Fatal("unmarshaling error: ", err) 32 | } 33 | 34 | key_ids := widevineHeader.GetKeyId() 35 | 36 | fmt.Println("Widevine Content ID:", string(widevineHeader.GetContentId())) 37 | for _, key_id := range key_ids { 38 | fmt.Println("Widevine Key ID:", base64.StdEncoding.EncodeToString(key_id)) 39 | } 40 | fmt.Println("Widevine provider ID:", string(widevineHeader.GetProvider())) 41 | } 42 | -------------------------------------------------------------------------------- /widevine/widevine_pssh_data.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: widevine_pssh_data.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package widevine_pssh_data is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | widevine_pssh_data.proto 10 | 11 | It has these top-level messages: 12 | WidevinePsshData 13 | */ 14 | package widevine 15 | 16 | import "github.com/golang/protobuf/proto" 17 | import "fmt" 18 | import "math" 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = fmt.Errorf 23 | var _ = math.Inf 24 | 25 | // This is a compile-time assertion to ensure that this generated file 26 | // is compatible with the proto package it is being compiled against. 27 | const _ = proto.ProtoPackageIsVersion1 28 | 29 | type WidevinePsshData_Algorithm int32 30 | 31 | const ( 32 | WidevinePsshData_UNENCRYPTED WidevinePsshData_Algorithm = 0 33 | WidevinePsshData_AESCTR WidevinePsshData_Algorithm = 1 34 | ) 35 | 36 | var WidevinePsshData_Algorithm_name = map[int32]string{ 37 | 0: "UNENCRYPTED", 38 | 1: "AESCTR", 39 | } 40 | var WidevinePsshData_Algorithm_value = map[string]int32{ 41 | "UNENCRYPTED": 0, 42 | "AESCTR": 1, 43 | } 44 | 45 | func (x WidevinePsshData_Algorithm) Enum() *WidevinePsshData_Algorithm { 46 | p := new(WidevinePsshData_Algorithm) 47 | *p = x 48 | return p 49 | } 50 | func (x WidevinePsshData_Algorithm) String() string { 51 | return proto.EnumName(WidevinePsshData_Algorithm_name, int32(x)) 52 | } 53 | func (x *WidevinePsshData_Algorithm) UnmarshalJSON(data []byte) error { 54 | value, err := proto.UnmarshalJSONEnum(WidevinePsshData_Algorithm_value, data, "WidevinePsshData_Algorithm") 55 | if err != nil { 56 | return err 57 | } 58 | *x = WidevinePsshData_Algorithm(value) 59 | return nil 60 | } 61 | func (WidevinePsshData_Algorithm) EnumDescriptor() ([]byte, []int) { 62 | return fileDescriptor0, []int{0, 0} 63 | } 64 | 65 | type WidevinePsshData struct { 66 | Algorithm *WidevinePsshData_Algorithm `protobuf:"varint,1,opt,name=algorithm,enum=WidevinePsshData_Algorithm" json:"algorithm,omitempty"` 67 | KeyId [][]byte `protobuf:"bytes,2,rep,name=key_id" json:"key_id,omitempty"` 68 | // Content provider name. 69 | Provider *string `protobuf:"bytes,3,opt,name=provider" json:"provider,omitempty"` 70 | // A content identifier, specified by content provider. 71 | ContentId []byte `protobuf:"bytes,4,opt,name=content_id" json:"content_id,omitempty"` 72 | // The name of a registered policy to be used for this asset. 73 | Policy *string `protobuf:"bytes,6,opt,name=policy" json:"policy,omitempty"` 74 | // Crypto period index, for media using key rotation. 75 | CryptoPeriodIndex *uint32 `protobuf:"varint,7,opt,name=crypto_period_index" json:"crypto_period_index,omitempty"` 76 | // Optional protected context for group content. The grouped_license is a 77 | // serialized SignedMessage. 78 | GroupedLicense []byte `protobuf:"bytes,8,opt,name=grouped_license" json:"grouped_license,omitempty"` 79 | XXX_unrecognized []byte `json:"-"` 80 | } 81 | 82 | func (m *WidevinePsshData) Reset() { *m = WidevinePsshData{} } 83 | func (m *WidevinePsshData) String() string { return proto.CompactTextString(m) } 84 | func (*WidevinePsshData) ProtoMessage() {} 85 | func (*WidevinePsshData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 86 | 87 | func (m *WidevinePsshData) GetAlgorithm() WidevinePsshData_Algorithm { 88 | if m != nil && m.Algorithm != nil { 89 | return *m.Algorithm 90 | } 91 | return WidevinePsshData_UNENCRYPTED 92 | } 93 | 94 | func (m *WidevinePsshData) GetKeyId() [][]byte { 95 | if m != nil { 96 | return m.KeyId 97 | } 98 | return nil 99 | } 100 | 101 | func (m *WidevinePsshData) GetProvider() string { 102 | if m != nil && m.Provider != nil { 103 | return *m.Provider 104 | } 105 | return "" 106 | } 107 | 108 | func (m *WidevinePsshData) GetContentId() []byte { 109 | if m != nil { 110 | return m.ContentId 111 | } 112 | return nil 113 | } 114 | 115 | func (m *WidevinePsshData) GetPolicy() string { 116 | if m != nil && m.Policy != nil { 117 | return *m.Policy 118 | } 119 | return "" 120 | } 121 | 122 | func (m *WidevinePsshData) GetCryptoPeriodIndex() uint32 { 123 | if m != nil && m.CryptoPeriodIndex != nil { 124 | return *m.CryptoPeriodIndex 125 | } 126 | return 0 127 | } 128 | 129 | func (m *WidevinePsshData) GetGroupedLicense() []byte { 130 | if m != nil { 131 | return m.GroupedLicense 132 | } 133 | return nil 134 | } 135 | 136 | func init() { 137 | proto.RegisterType((*WidevinePsshData)(nil), "WidevinePsshData") 138 | proto.RegisterEnum("WidevinePsshData_Algorithm", WidevinePsshData_Algorithm_name, WidevinePsshData_Algorithm_value) 139 | } 140 | 141 | var fileDescriptor0 = []byte{ 142 | // 229 bytes of a gzipped FileDescriptorProto 143 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x5c, 0x8e, 0xc1, 0x4e, 0x02, 0x31, 144 | 0x10, 0x86, 0x5d, 0x31, 0x2b, 0x3b, 0x22, 0x34, 0xf5, 0x60, 0x13, 0x2e, 0x84, 0xd3, 0x9e, 0x7a, 145 | 0xf0, 0x0d, 0x08, 0xec, 0x95, 0x10, 0xc4, 0x18, 0x4f, 0xcd, 0x66, 0x3b, 0x81, 0x46, 0xec, 0x34, 146 | 0xdd, 0x8a, 0xee, 0x1b, 0xfb, 0x18, 0xce, 0x1a, 0x8d, 0x09, 0xd7, 0x7e, 0xdf, 0xf7, 0x77, 0x40, 147 | 0x7d, 0x38, 0x8b, 0x27, 0xe7, 0xd1, 0x84, 0xb6, 0x3d, 0x18, 0x5b, 0xa7, 0x5a, 0x87, 0x48, 0x89, 148 | 0xe6, 0x5f, 0x19, 0x88, 0xe7, 0x5f, 0xb8, 0x61, 0xb6, 0x62, 0x24, 0x35, 0x14, 0xf5, 0x71, 0x4f, 149 | 0xd1, 0xa5, 0xc3, 0x9b, 0xca, 0x66, 0x59, 0x39, 0x7e, 0x98, 0xea, 0x73, 0x4b, 0x2f, 0xfe, 0x14, 150 | 0x39, 0x86, 0xfc, 0x15, 0x3b, 0xe3, 0xac, 0xba, 0x9c, 0x0d, 0xca, 0x91, 0x14, 0x30, 0xe4, 0xf5, 151 | 0x13, 0x07, 0x51, 0x0d, 0x38, 0x2f, 0xa4, 0x04, 0x68, 0xc8, 0x27, 0xf4, 0xa9, 0xb7, 0xae, 0xf8, 152 | 0x6d, 0xd4, 0x57, 0x81, 0x8e, 0xae, 0xe9, 0x54, 0xfe, 0xe3, 0x4c, 0xe1, 0xae, 0x89, 0x5d, 0x48, 153 | 0x64, 0x02, 0x46, 0x47, 0xd6, 0x38, 0x6f, 0xf1, 0x53, 0x5d, 0x33, 0xbc, 0x95, 0xf7, 0x30, 0xd9, 154 | 0x47, 0x7a, 0x0f, 0x68, 0x0d, 0x27, 0xe8, 0x5b, 0x54, 0xc3, 0x7e, 0x65, 0x5e, 0x42, 0xf1, 0x7f, 155 | 0xc8, 0x04, 0x6e, 0x9e, 0xd6, 0xd5, 0x7a, 0xb9, 0x7d, 0xd9, 0xec, 0xaa, 0x95, 0xb8, 0xe0, 0x6f, 156 | 0xf3, 0x45, 0xf5, 0xb8, 0xdc, 0x6d, 0x45, 0xf6, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x56, 0x48, 0x8b, 157 | 0xcd, 0x05, 0x01, 0x00, 0x00, 158 | } 159 | -------------------------------------------------------------------------------- /widevine/widevine_pssh_data.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | // 7 | // This file defines Widevine Pssh Data proto format. 8 | 9 | syntax = "proto2"; 10 | 11 | message WidevinePsshData { 12 | enum Algorithm { 13 | UNENCRYPTED = 0; 14 | AESCTR = 1; 15 | }; 16 | optional Algorithm algorithm = 1; 17 | repeated bytes key_id = 2; 18 | 19 | // Content provider name. 20 | optional string provider = 3; 21 | 22 | // A content identifier, specified by content provider. 23 | optional bytes content_id = 4; 24 | 25 | // The name of a registered policy to be used for this asset. 26 | optional string policy = 6; 27 | 28 | // Crypto period index, for media using key rotation. 29 | optional uint32 crypto_period_index = 7; 30 | 31 | // Optional protected context for group content. The grouped_license is a 32 | // serialized SignedMessage. 33 | optional bytes grouped_license = 8; 34 | } 35 | --------------------------------------------------------------------------------