DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator
playready_key_source.cc
1 // Copyright 2017 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 #include "packager/media/base/playready_key_source.h"
8 
9 #include <openssl/aes.h>
10 #include <algorithm>
11 #include "packager/base/base64.h"
12 #include "packager/base/logging.h"
13 #include "packager/base/strings/string_number_conversions.h"
14 #include "packager/base/strings/string_util.h"
15 #include "packager/media/base/buffer_writer.h"
16 #include "packager/media/base/http_key_fetcher.h"
17 
18 namespace shaka {
19 namespace media {
20 
21 namespace {
22 
23 const uint32_t kHttpFetchTimeout = 60; // In seconds
24 const std::string kPlayHeaderObject_4_1 = "<WRMHEADER "
25  "xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" "
26  "version=\"4.1.0.0\"><DATA><PROTECTINFO>"
27  "<KID VALUE=\"$0\" ALGID=\"AESCTR\" CHECKSUM=\"$1\"></KID></PROTECTINFO>"
28  "</DATA></WRMHEADER>";
29 const std::string kPlayHeaderObject_4_0 = "<WRMHEADER "
30  "xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" "
31  "version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN>"
32  "<ALGID>AESCTR</ALGID></PROTECTINFO><KID>$0</KID><CHECKSUM>$1</CHECKSUM>"
33  "</DATA></WRMHEADER>";
34 const std::string kAcquireLicenseRequest =
35  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
36  "<soap:Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" "
37  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
38  "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
39  "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
40  "<soap:Body>"
41  "<AcquirePackagingData "
42  "xmlns=\"http://schemas.microsoft.com/DRM/2007/03/protocols\">"
43  "<challenge "
44  "xmlns=\"http://schemas.microsoft.com/DRM"
45  "/2007/03/protocols/AcquirePackagingData/v1.0\">"
46  "<ProtectionSystems>"
47  "<ProtectionSystemId>9A04F079-9840-4286-AB92-E65BE0885F95"
48  "</ProtectionSystemId>"
49  "</ProtectionSystems>"
50  "<StreamProtectionRequests>"
51  "<StreamInformation>"
52  "<ProgramIdentifier>$0</ProgramIdentifier>"
53  "<OffsetFromProgramStart>P0S</OffsetFromProgramStart>"
54  "</StreamInformation>"
55  "</StreamProtectionRequests>"
56  "</challenge>"
57  "</AcquirePackagingData>"
58  "</soap:Body>"
59  "</soap:Envelope>";
60 
61 bool Base64StringToBytes(const std::string& base64_string,
62  std::vector<uint8_t>* bytes) {
63  DCHECK(bytes);
64  std::string str;
65  if (!base::Base64Decode(base64_string, &str))
66  return false;
67  bytes->assign(str.begin(), str.end());
68  return true;
69 }
70 
71 // Converts the key_id's endianness.
72 std::vector<uint8_t> ConvertGuidEndianness(const std::vector<uint8_t>& input) {
73  std::vector<uint8_t> output = input;
74  if (output.size() > 7) { // Defensive check.
75  output[0] = input[3];
76  output[1] = input[2];
77  output[2] = input[1];
78  output[3] = input[0];
79  output[4] = input[5];
80  output[5] = input[4];
81  output[6] = input[7];
82  output[7] = input[6];
83  // 8-15 are an array of bytes with no endianness.
84  }
85  return output;
86 }
87 
88 // Generates the data section of a PlayReady PSSH.
89 // PlayReady PSSH Data is a PlayReady Header Object.
90 // Format is outlined in the following document.
91 // http://download.microsoft.com/download/2/3/8/238F67D9-1B8B-48D3-AB83-9C00112268B2/PlayReady%20Header%20Object%202015-08-13-FINAL-CL.PDF
92 shaka::media::Status GeneratePlayReadyPsshData(const std::vector<uint8_t>& key_id,
93  const std::vector<uint8_t>& key,
94  std::vector<uint8_t>* output) {
95  CHECK(output);
96  std::vector<uint8_t> key_id_converted = ConvertGuidEndianness(key_id);
97  std::vector<uint8_t> encrypted_key_id(key_id_converted.size());
98  std::unique_ptr<AES_KEY> aes_key (new AES_KEY);
99  CHECK_EQ(AES_set_encrypt_key(key.data(), key.size() * 8, aes_key.get()), 0);
100  AES_ecb_encrypt(key_id_converted.data(), encrypted_key_id.data(),
101  aes_key.get(), AES_ENCRYPT);
102  std::string checksum = std::string(encrypted_key_id.begin(),
103  encrypted_key_id.end()).substr(0, 8);
104  std::string base64_checksum;
105  base::Base64Encode(checksum, &base64_checksum);
106  std::string base64_key_id;
107  base::Base64Encode(std::string(key_id_converted.begin(),
108  key_id_converted.end()),
109  &base64_key_id);
110  std::string playready_header = kPlayHeaderObject_4_0;
111  base::ReplaceFirstSubstringAfterOffset(
112  &playready_header, 0, "$0", base64_key_id);
113  base::ReplaceFirstSubstringAfterOffset(
114  &playready_header, 0, "$1", base64_checksum);
115 
116  // Create a PlayReady Record.
117  // Outline in section '2.PlayReady Records' of
118  // 'PlayReady Header Object' document. Note data is in little endian format.
119  std::vector<uint16_t> record_value =
120  std::vector<uint16_t>(playready_header.begin(), playready_header.end());
121  shaka::media::BufferWriter writer_pr_record;
122  uint16_t record_type = 1; // Indicates that the record contains a rights management header.
123  uint16_t record_length = record_value.size() * 2;
124  writer_pr_record.AppendInt(static_cast<uint8_t>(record_type & 0xff));
125  writer_pr_record.AppendInt(static_cast<uint8_t>((record_type >> 8) & 0xff));
126  writer_pr_record.AppendInt(static_cast<uint8_t>(record_length & 0xff));
127  writer_pr_record.AppendInt(static_cast<uint8_t>((record_length >> 8) & 0xff));
128  for (auto record_item: record_value) {
129  writer_pr_record.AppendInt(static_cast<uint8_t>(record_item & 0xff));
130  writer_pr_record.AppendInt(static_cast<uint8_t>((record_item >> 8) & 0xff));
131  }
132 
133  // Create the PlayReady Header object.
134  // Outline in section '1.PlayReady Header Objects' of
135  // 'PlayReady Header Object' document. Note data is in little endian format.
136  shaka::media::BufferWriter writer_pr_header_object;
137  uint32_t playready_header_length = writer_pr_record.Size() + 4 + 2;
138  uint16_t record_count = 1;
139  writer_pr_header_object.AppendInt(
140  static_cast<uint8_t>(playready_header_length & 0xff));
141  writer_pr_header_object.AppendInt(
142  static_cast<uint8_t>((playready_header_length >> 8) & 0xff));
143  writer_pr_header_object.AppendInt(
144  static_cast<uint8_t>((playready_header_length >> 16) & 0xff));
145  writer_pr_header_object.AppendInt(
146  static_cast<uint8_t>((playready_header_length >> 24) & 0xff));
147  writer_pr_header_object.AppendInt(
148  static_cast<uint8_t>(record_count & 0xff));
149  writer_pr_header_object.AppendInt(
150  static_cast<uint8_t>((record_count >> 8) & 0xff));
151  writer_pr_header_object.AppendBuffer(writer_pr_record);
152  *output = std::vector<uint8_t>(writer_pr_header_object.Buffer(),
153  writer_pr_header_object.Buffer() +
154  writer_pr_header_object.Size());
155  return shaka::media::Status::OK;
156 }
157 
158 } // namespace
159 
161  const std::string& server_url)
162  : encryption_key_(new EncryptionKey),
163  server_url_(server_url) {
164 }
165 
166 PlayReadyKeySource::PlayReadyKeySource(const std::string& server_url,
167  const std::string& client_cert_file,
168  const std::string& client_cert_private_key_file,
169  const std::string& client_cert_private_key_password)
170  : encryption_key_(new EncryptionKey),
171  server_url_(server_url),
172  client_cert_file_(client_cert_file),
173  client_cert_private_key_file_(client_cert_private_key_file),
174  client_cert_private_key_password_(client_cert_private_key_password) {
175 }
176 
178  std::unique_ptr<EncryptionKey> encryption_key)
179  : encryption_key_(std::move(encryption_key)) {
180 }
181 
182 PlayReadyKeySource::~PlayReadyKeySource() {}
183 
184 std::unique_ptr<PlayReadyKeySource> PlayReadyKeySource::CreateFromKeyAndKeyId(
185  const std::string& key_id_hex, const std::string& key_hex) {
186  std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey);
187  if (!base::HexStringToBytes(key_id_hex, &encryption_key->key_id)) {
188  LOG(ERROR) << "Cannot parse key_id_hex " << key_id_hex;
189  return std::unique_ptr<PlayReadyKeySource>();
190  }
191  std::vector<uint8_t> key;
192  if (!base::HexStringToBytes(key_hex, &encryption_key->key)) {
193  LOG(ERROR) << "Cannot parse key_hex " << key_hex;
194  return std::unique_ptr<PlayReadyKeySource>();
195  }
196  std::vector<uint8_t> pssh_data;
197  Status status = GeneratePlayReadyPsshData(
198  encryption_key->key_id, encryption_key->key, &pssh_data);
199  if (!status.ok()) {
200  LOG(ERROR) << status.ToString();
201  return std::unique_ptr<PlayReadyKeySource>();
202  }
204  info.add_key_id(encryption_key->key_id);
205  info.set_system_id(kPlayReadySystemId, arraysize(kPlayReadySystemId));
206  info.set_pssh_data(pssh_data);
207 
208  encryption_key->key_system_info.push_back(info);
209  return std::unique_ptr<PlayReadyKeySource>(
210  new PlayReadyKeySource(std::move(encryption_key)));
211 }
212 
213 Status RetrieveTextInXMLElement(const std::string& element,
214  const std::string& xml,
215  std::string* value) {
216  std::string start_tag = "<" + element + ">";
217  std::string end_tag = "</" + element + ">";
218  std::size_t start_pos = xml.find(start_tag);
219  if (start_pos == std::string::npos) {
220  return Status(error::SERVER_ERROR,
221  "Unable to find tag: " + start_tag);
222  }
223  start_pos += start_tag.size();
224  std::size_t end_pos = xml.find(end_tag);
225  if (end_pos == std::string::npos) {
226  return Status(error::SERVER_ERROR,
227  "Unable to find tag: " + end_tag);
228  }
229  if (start_pos > end_pos) {
230  return Status(error::SERVER_ERROR, "Invalid positions");
231  }
232  std::size_t segment_len = end_pos - start_pos;
233  *value = xml.substr(start_pos, segment_len);
234  return Status::OK;
235 }
236 
237 Status SetKeyInformationFromServerResponse(const std::string& response,
238  EncryptionKey* encryption_key) {
239  // TODO(robinconnell): Currently all tracks are encrypted using the same
240  // key_id and key. Add the ability to retrieve multiple key_id/keys from
241  // the packager response and encrypt multiple tracks using differnt
242  // key_id/keys.
243  std::string key_id_hex;
244  Status status = RetrieveTextInXMLElement("KeyId", response, &key_id_hex);
245  if (!status.ok()) {
246  return status;
247  }
248  key_id_hex.erase(
249  std::remove(key_id_hex.begin(), key_id_hex.end(), '-'), key_id_hex.end());
250  std::string key_data_b64;
251  status = RetrieveTextInXMLElement("KeyData", response, &key_data_b64);
252  if (!status.ok()) {
253  LOG(ERROR) << "Key retreiving KeyData";
254  return status;
255  }
256  std::string pssh_data_b64;
257  status = RetrieveTextInXMLElement("Data", response, &pssh_data_b64);
258  if (!status.ok()) {
259  LOG(ERROR) << "Key retreiving Data";
260  return status;
261  }
262  if (!base::HexStringToBytes(key_id_hex, &encryption_key->key_id)) {
263  LOG(ERROR) << "Cannot parse key_id_hex, " << key_id_hex;
264  return Status(error::SERVER_ERROR, "Cannot parse key_id_hex.");
265  }
266 
267  if (!Base64StringToBytes(key_data_b64, &encryption_key->key)) {
268  LOG(ERROR) << "Cannot parse key, " << key_data_b64;
269  return Status(error::SERVER_ERROR, "Cannot parse key.");
270  }
271  std::vector<uint8_t> pssh_data;
272  if (!Base64StringToBytes(pssh_data_b64, &pssh_data)) {
273  LOG(ERROR) << "Cannot parse pssh data, " << pssh_data_b64;
274  return Status(error::SERVER_ERROR, "Cannot parse pssh.");
275  }
276  ProtectionSystemSpecificInfo info;
277  info.add_key_id(encryption_key->key_id);
278  info.set_system_id(kPlayReadySystemId, arraysize(kPlayReadySystemId));
279  info.set_pssh_data(pssh_data);
280  encryption_key->key_system_info.push_back(info);
281  return Status::OK;
282 }
283 
284 Status PlayReadyKeySource::FetchKeysWithProgramIdentifier(
285  const std::string& program_identifier) {
286  std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey);
287  HttpKeyFetcher key_fetcher(kHttpFetchTimeout);
288  if (!client_cert_file_.empty() && !client_cert_private_key_file_.empty() &&
289  !client_cert_private_key_password_.empty()) {
290  key_fetcher.SetClientCertInfo(client_cert_file_,
291  client_cert_private_key_file_,
292  client_cert_private_key_password_);
293  }
294  if (!ca_file_.empty()) {
295  key_fetcher.SetCaFile(ca_file_);
296  }
297  std::string acquire_license_request = kAcquireLicenseRequest;
298  base::ReplaceFirstSubstringAfterOffset(
299  &acquire_license_request, 0, "$0", program_identifier);
300  std::string acquire_license_response;
301  Status status = key_fetcher.FetchKeys(server_url_, acquire_license_request,
302  &acquire_license_response);
303  if (!status.ok()) {
304  LOG(ERROR) << "Server response: " << acquire_license_response;
305  return status;
306  }
307  status = SetKeyInformationFromServerResponse(acquire_license_response,
308  encryption_key.get());
309  encryption_key_ = std::move(encryption_key);
310  return Status::OK;
311 }
312 
313 Status PlayReadyKeySource::FetchKeys(const std::vector<uint8_t>& pssh_box) {
314  // Does nothing for playready encryption/decryption.
315  return Status::OK;
316 }
317 
319  const std::vector<std::vector<uint8_t>>& key_ids) {
320  // Does nothing for playready encryption/decryption.
321  return Status::OK;
322 }
323 
325  // Does nothing for playready encryption/decryption.
326  return Status::OK;
327 }
328 
329 Status PlayReadyKeySource::GetKey(TrackType track_type, EncryptionKey* key) {
330  // TODO(robinconnell): Currently all tracks are encrypted using the same
331  // key_id and key. Add the ability to encrypt each track_type using a
332  // different key_id and key.
333  DCHECK(key);
334  DCHECK(encryption_key_);
335  *key = *encryption_key_;
336  return Status::OK;
337 }
338 
339 Status PlayReadyKeySource::GetKey(const std::vector<uint8_t>& key_id,
340  EncryptionKey* key) {
341  // TODO(robinconnell): Currently all tracks are encrypted using the same
342  // key_id and key. Add the ability to encrypt using multiple key_id/keys.
343  DCHECK(key);
344  DCHECK(encryption_key_);
345  *key = *encryption_key_;
346  return Status::OK;
347 }
348 
349 Status PlayReadyKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
350  TrackType track_type,
351  EncryptionKey* key) {
352  // TODO(robinconnell): Implement key rotation.
353  *key = *encryption_key_;
354  return Status::OK;
355 }
356 
357 } // namespace media
358 } // namespace shaka
std::string ToString() const
Definition: status.cc:68
PlayReadyKeySource(const std::string &server_url)
Status GetCryptoPeriodKey(uint32_t crypto_period_index, TrackType track_type, EncryptionKey *key) override
const uint8_t * Buffer() const
Definition: buffer_writer.h:59
Status GetKey(TrackType track_type, EncryptionKey *key) override
Status FetchKeys(const std::vector< uint8_t > &pssh_box) override
static std::unique_ptr< PlayReadyKeySource > CreateFromKeyAndKeyId(const std::string &key_id_hex, const std::string &key_hex)