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