Shaka Packager SDK
Loading...
Searching...
No Matches
playready_pssh_generator.cc
1// Copyright 2017 Google LLC. 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#include <packager/media/base/playready_pssh_generator.h>
8
9#include <cstddef>
10#include <cstdint>
11#include <optional>
12#include <set>
13#include <string>
14#include <vector>
15
16#include <absl/log/check.h>
17#include <absl/log/log.h>
18#include <absl/strings/escaping.h>
19#include <mbedtls/cipher.h>
20
21#include <packager/macros/compiler.h>
22#include <packager/macros/crypto.h>
23#include <packager/macros/logging.h>
24#include <packager/media/base/buffer_writer.h>
25#include <packager/media/base/fourccs.h>
26#include <packager/media/base/protection_system_ids.h>
27#include <packager/media/base/pssh_generator.h>
28#include <packager/status.h>
29
30namespace shaka {
31namespace media {
32namespace {
33
34const uint8_t kPlayReadyPsshBoxVersion = 0;
35
36// For PlayReady clients 1.0+ that support CTR keys.
37const std::string kPlayHeaderObject_4_0 =
38 "<WRMHEADER "
39 "xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" "
40 "version=\"4.0.0.0\"><DATA>"
41 "<PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO>"
42 "<KID>$0</KID><CHECKSUM>$1</CHECKSUM>"
43 "$2</DATA></WRMHEADER>";
44
45// For PlayReady clients 4.0+ that support CBC keys.
46const std::string kPlayHeaderObject_4_3 =
47 "<WRMHEADER "
48 "xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" "
49 "version=\"4.3.0.0\"><DATA><PROTECTINFO><KIDS>"
50 "<KID ALGID=\"AESCBC\" VALUE=\"$0\"></KID>"
51 "</KIDS></PROTECTINFO>$1</DATA></WRMHEADER>";
52
53// Converts the key_id's endianness.
54std::vector<uint8_t> ConvertGuidEndianness(const std::vector<uint8_t>& input) {
55 std::vector<uint8_t> output = input;
56 if (output.size() > 7) { // Defensive check.
57 output[0] = input[3];
58 output[1] = input[2];
59 output[2] = input[1];
60 output[3] = input[0];
61 output[4] = input[5];
62 output[5] = input[4];
63 output[6] = input[7];
64 output[7] = input[6];
65 // 8-15 are an array of bytes with no endianness.
66 }
67 return output;
68}
69
70void ReplaceString(std::string* str,
71 const std::string& from,
72 const std::string& to) {
73 size_t location = str->find(from);
74 if (location != std::string::npos) {
75 str->replace(location, from.size(), to);
76 }
77}
78
79void AesEcbEncrypt(const std::vector<uint8_t>& key,
80 const std::vector<uint8_t>& plaintext,
81 std::vector<uint8_t>* ciphertext) {
82 CHECK_EQ(plaintext.size() % AES_BLOCK_SIZE, 0u);
83 ciphertext->resize(plaintext.size());
84
85 mbedtls_cipher_context_t ctx;
86 mbedtls_cipher_init(&ctx);
87
88 const mbedtls_cipher_info_t* cipher_info =
89 mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB);
90 CHECK(cipher_info);
91
92 CHECK_EQ(mbedtls_cipher_setup(&ctx, cipher_info), 0) << "Cipher setup failed";
93
94 CHECK_EQ(key.size(), 16u);
95 CHECK_EQ(
96 mbedtls_cipher_setkey(&ctx, key.data(), 8 * key.size(), MBEDTLS_ENCRYPT),
97 0)
98 << "Failed to set encryption key";
99
100 size_t output_size = 0;
101 CHECK_EQ(mbedtls_cipher_crypt(&ctx, /* iv= */ NULL, /* iv_len= */ 0,
102 plaintext.data(), plaintext.size(),
103 ciphertext->data(), &output_size),
104 0);
105
106 mbedtls_cipher_free(&ctx);
107}
108
109// Generates the data section of a PlayReady PSSH.
110// PlayReady PSSH Data is a PlayReady Header Object, which is described at
111// https://docs.microsoft.com/en-us/playready/specifications/playready-header-specification
112Status GeneratePlayReadyPsshData(const std::vector<uint8_t>& key_id,
113 const std::vector<uint8_t>& key,
114 const std::string& extra_header_data,
115 const FourCC protection_scheme,
116 std::vector<uint8_t>* output) {
117 CHECK(output);
118 std::vector<uint8_t> key_id_converted = ConvertGuidEndianness(key_id);
119
120 std::vector<uint8_t> encrypted_key_id;
121 AesEcbEncrypt(key, key_id_converted, &encrypted_key_id);
122
123 std::string checksum =
124 std::string(encrypted_key_id.begin(), encrypted_key_id.end())
125 .substr(0, 8);
126 std::string base64_checksum;
127 absl::Base64Escape(checksum, &base64_checksum);
128 std::string base64_key_id;
129 absl::Base64Escape(
130 std::string(key_id_converted.begin(), key_id_converted.end()),
131 &base64_key_id);
132
133 std::string playready_header;
134
135 switch (protection_scheme) {
136 case kAppleSampleAesProtectionScheme:
137 case FOURCC_cbc1:
138 case FOURCC_cbcs:
139 playready_header = kPlayHeaderObject_4_3;
140 ReplaceString(&playready_header, "$0", base64_key_id);
141 ReplaceString(&playready_header, "$1", extra_header_data);
142 break;
143
144 case FOURCC_cenc:
145 case FOURCC_cens:
146 playready_header = kPlayHeaderObject_4_0;
147 ReplaceString(&playready_header, "$0", base64_key_id);
148 ReplaceString(&playready_header, "$1", base64_checksum);
149 ReplaceString(&playready_header, "$2", extra_header_data);
150 break;
151
152 default:
153 return Status(error::INVALID_ARGUMENT,
154 "The provided protection scheme is not supported.");
155 }
156
157 // Create a PlayReady Record.
158 // Outline in section '2.PlayReady Records' of
159 // 'PlayReady Header Object' document. Note data is in little endian format.
160 std::vector<uint16_t> record_value =
161 std::vector<uint16_t>(playready_header.begin(), playready_header.end());
162 shaka::media::BufferWriter writer_pr_record;
163 uint16_t record_type =
164 1; // Indicates that the record contains a rights management header.
165 uint16_t record_length = record_value.size() * 2;
166 writer_pr_record.AppendInt(static_cast<uint8_t>(record_type & 0xff));
167 writer_pr_record.AppendInt(static_cast<uint8_t>((record_type >> 8) & 0xff));
168 writer_pr_record.AppendInt(static_cast<uint8_t>(record_length & 0xff));
169 writer_pr_record.AppendInt(static_cast<uint8_t>((record_length >> 8) & 0xff));
170 for (auto record_item : record_value) {
171 writer_pr_record.AppendInt(static_cast<uint8_t>(record_item & 0xff));
172 writer_pr_record.AppendInt(static_cast<uint8_t>((record_item >> 8) & 0xff));
173 }
174
175 // Create the PlayReady Header object.
176 // Outline in section '1.PlayReady Header Objects' of
177 // 'PlayReady Header Object' document. Note data is in little endian format.
178 shaka::media::BufferWriter writer_pr_header_object;
179 uint32_t playready_header_length = writer_pr_record.Size() + 4 + 2;
180 uint16_t record_count = 1;
181 writer_pr_header_object.AppendInt(
182 static_cast<uint8_t>(playready_header_length & 0xff));
183 writer_pr_header_object.AppendInt(
184 static_cast<uint8_t>((playready_header_length >> 8) & 0xff));
185 writer_pr_header_object.AppendInt(
186 static_cast<uint8_t>((playready_header_length >> 16) & 0xff));
187 writer_pr_header_object.AppendInt(
188 static_cast<uint8_t>((playready_header_length >> 24) & 0xff));
189 writer_pr_header_object.AppendInt(static_cast<uint8_t>(record_count & 0xff));
190 writer_pr_header_object.AppendInt(
191 static_cast<uint8_t>((record_count >> 8) & 0xff));
192 writer_pr_header_object.AppendBuffer(writer_pr_record);
193 *output = std::vector<uint8_t>(
194 writer_pr_header_object.Buffer(),
195 writer_pr_header_object.Buffer() + writer_pr_header_object.Size());
196 return Status::OK;
197}
198} // namespace
199
200PlayReadyPsshGenerator::PlayReadyPsshGenerator(
201 const std::string& extra_header_data,
202 FourCC protection_scheme)
203 : PsshGenerator(std::vector<uint8_t>(std::begin(kPlayReadySystemId),
204 std::end(kPlayReadySystemId)),
205 kPlayReadyPsshBoxVersion),
206 extra_header_data_(extra_header_data),
207 protection_scheme_(protection_scheme) {}
208
209PlayReadyPsshGenerator::~PlayReadyPsshGenerator() {}
210
211bool PlayReadyPsshGenerator::SupportMultipleKeys() {
212 return false;
213}
214
215std::optional<std::vector<uint8_t>>
216PlayReadyPsshGenerator::GeneratePsshDataFromKeyIdAndKey(
217 const std::vector<uint8_t>& key_id,
218 const std::vector<uint8_t>& key) const {
219 std::vector<uint8_t> pssh_data;
220 Status status = GeneratePlayReadyPsshData(key_id, key, extra_header_data_,
221 protection_scheme_, &pssh_data);
222 if (!status.ok()) {
223 LOG(ERROR) << status.ToString();
224 return std::nullopt;
225 }
226
227 return pssh_data;
228}
229
230std::optional<std::vector<uint8_t>>
231PlayReadyPsshGenerator::GeneratePsshDataFromKeyIds(
232 const std::vector<std::vector<uint8_t>>& key_ids) const {
233 UNUSED(key_ids);
234 NOTIMPLEMENTED();
235 return std::nullopt;
236}
237
238} // namespace media
239} // namespace shaka
const uint8_t * Buffer() const
All the methods that are virtual are virtual for mocking.