Shaka Packager SDK
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 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  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  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 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::vector<uint8_t>& key_id,
186  const std::vector<uint8_t>& key) {
187  std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey);
188  encryption_key->key_id = key_id;
189  encryption_key->key = key;
190  std::vector<uint8_t> pssh_data;
191  Status status = GeneratePlayReadyPsshData(
192  encryption_key->key_id, encryption_key->key, &pssh_data);
193  if (!status.ok()) {
194  LOG(ERROR) << status.ToString();
195  return std::unique_ptr<PlayReadyKeySource>();
196  }
198  info.add_key_id(encryption_key->key_id);
199  info.set_system_id(kPlayReadySystemId, arraysize(kPlayReadySystemId));
200  info.set_pssh_data(pssh_data);
201 
202  encryption_key->key_system_info.push_back(info);
203  return std::unique_ptr<PlayReadyKeySource>(
204  new PlayReadyKeySource(std::move(encryption_key)));
205 }
206 
207 Status RetrieveTextInXMLElement(const std::string& element,
208  const std::string& xml,
209  std::string* value) {
210  std::string start_tag = "<" + element + ">";
211  std::string end_tag = "</" + element + ">";
212  std::size_t start_pos = xml.find(start_tag);
213  if (start_pos == std::string::npos) {
214  return Status(error::SERVER_ERROR,
215  "Unable to find tag: " + start_tag);
216  }
217  start_pos += start_tag.size();
218  std::size_t end_pos = xml.find(end_tag);
219  if (end_pos == std::string::npos) {
220  return Status(error::SERVER_ERROR,
221  "Unable to find tag: " + end_tag);
222  }
223  if (start_pos > end_pos) {
224  return Status(error::SERVER_ERROR, "Invalid positions");
225  }
226  std::size_t segment_len = end_pos - start_pos;
227  *value = xml.substr(start_pos, segment_len);
228  return Status::OK;
229 }
230 
231 Status SetKeyInformationFromServerResponse(const std::string& response,
232  EncryptionKey* encryption_key) {
233  // TODO(robinconnell): Currently all tracks are encrypted using the same
234  // key_id and key. Add the ability to retrieve multiple key_id/keys from
235  // the packager response and encrypt multiple tracks using differnt
236  // key_id/keys.
237  std::string key_id_hex;
238  Status status = RetrieveTextInXMLElement("KeyId", response, &key_id_hex);
239  if (!status.ok()) {
240  return status;
241  }
242  key_id_hex.erase(
243  std::remove(key_id_hex.begin(), key_id_hex.end(), '-'), key_id_hex.end());
244  std::string key_data_b64;
245  status = RetrieveTextInXMLElement("KeyData", response, &key_data_b64);
246  if (!status.ok()) {
247  LOG(ERROR) << "Key retreiving KeyData";
248  return status;
249  }
250  std::string pssh_data_b64;
251  status = RetrieveTextInXMLElement("Data", response, &pssh_data_b64);
252  if (!status.ok()) {
253  LOG(ERROR) << "Key retreiving Data";
254  return status;
255  }
256  if (!base::HexStringToBytes(key_id_hex, &encryption_key->key_id)) {
257  LOG(ERROR) << "Cannot parse key_id_hex, " << key_id_hex;
258  return Status(error::SERVER_ERROR, "Cannot parse key_id_hex.");
259  }
260 
261  if (!Base64StringToBytes(key_data_b64, &encryption_key->key)) {
262  LOG(ERROR) << "Cannot parse key, " << key_data_b64;
263  return Status(error::SERVER_ERROR, "Cannot parse key.");
264  }
265  std::vector<uint8_t> pssh_data;
266  if (!Base64StringToBytes(pssh_data_b64, &pssh_data)) {
267  LOG(ERROR) << "Cannot parse pssh data, " << pssh_data_b64;
268  return Status(error::SERVER_ERROR, "Cannot parse pssh.");
269  }
271  info.add_key_id(encryption_key->key_id);
272  info.set_system_id(kPlayReadySystemId, arraysize(kPlayReadySystemId));
273  info.set_pssh_data(pssh_data);
274  encryption_key->key_system_info.push_back(info);
275  return Status::OK;
276 }
277 
278 Status PlayReadyKeySource::FetchKeysWithProgramIdentifier(
279  const std::string& program_identifier) {
280  std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey);
281  HttpKeyFetcher key_fetcher(kHttpFetchTimeout);
282  if (!client_cert_file_.empty() && !client_cert_private_key_file_.empty()) {
283  key_fetcher.SetClientCertInfo(client_cert_file_,
284  client_cert_private_key_file_,
285  client_cert_private_key_password_);
286  }
287  if (!ca_file_.empty()) {
288  key_fetcher.SetCaFile(ca_file_);
289  }
290  std::string acquire_license_request = kAcquireLicenseRequest;
291  base::ReplaceFirstSubstringAfterOffset(
292  &acquire_license_request, 0, "$0", program_identifier);
293  std::string acquire_license_response;
294  Status status = key_fetcher.FetchKeys(server_url_, acquire_license_request,
295  &acquire_license_response);
296  if (!status.ok()) {
297  LOG(ERROR) << "Server response: " << acquire_license_response;
298  return status;
299  }
300  status = SetKeyInformationFromServerResponse(acquire_license_response,
301  encryption_key.get());
302  encryption_key_ = std::move(encryption_key);
303  return Status::OK;
304 }
305 
306 Status PlayReadyKeySource::FetchKeys(EmeInitDataType init_data_type,
307  const std::vector<uint8_t>& init_data) {
308  // Do nothing for playready encryption/decryption.
309  return Status::OK;
310 }
311 
312 Status PlayReadyKeySource::GetKey(const std::string& stream_label,
313  EncryptionKey* key) {
314  // TODO(robinconnell): Currently all tracks are encrypted using the same
315  // key_id and key. Add the ability to encrypt each stream_label using a
316  // different key_id and key.
317  DCHECK(key);
318  DCHECK(encryption_key_);
319  *key = *encryption_key_;
320  return Status::OK;
321 }
322 
323 Status PlayReadyKeySource::GetKey(const std::vector<uint8_t>& key_id,
324  EncryptionKey* key) {
325  // TODO(robinconnell): Currently all tracks are encrypted using the same
326  // key_id and key. Add the ability to encrypt using multiple key_id/keys.
327  DCHECK(key);
328  DCHECK(encryption_key_);
329  *key = *encryption_key_;
330  return Status::OK;
331 }
332 
333 Status PlayReadyKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
334  const std::string& stream_label,
335  EncryptionKey* key) {
336  // TODO(robinconnell): Implement key rotation.
337  *key = *encryption_key_;
338  return Status::OK;
339 }
340 
341 } // namespace media
342 } // namespace shaka
Status GetKey(const std::string &stream_label, EncryptionKey *key) override
std::string ToString() const
Definition: status.cc:81
void SetCaFile(const std::string &ca_file)
PlayReadyKeySource(const std::string &server_url)
All the methods that are virtual are virtual for mocking.
Status GetCryptoPeriodKey(uint32_t crypto_period_index, const std::string &stream_label, EncryptionKey *key) override
void SetClientCertInfo(const std::string &cert_file, const std::string &private_key_file, const std::string &private_key_password)
Status FetchKeys(EmeInitDataType init_data_type, const std::vector< uint8_t > &init_data) override
Status FetchKeys(const std::string &url, const std::string &request, std::string *response) override
static std::unique_ptr< PlayReadyKeySource > CreateFromKeyAndKeyId(const std::vector< uint8_t > &key_id, const std::vector< uint8_t > &key)