Shaka Packager SDK
playready_key_source.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_key_source.h>
8 
9 #include <algorithm>
10 #include <iterator>
11 
12 #include <absl/log/check.h>
13 #include <absl/log/log.h>
14 #include <absl/strings/escaping.h>
15 
16 #include <packager/macros/compiler.h>
17 #include <packager/macros/logging.h>
18 #include <packager/macros/status.h>
19 #include <packager/media/base/buffer_writer.h>
20 #include <packager/media/base/http_key_fetcher.h>
21 #include <packager/media/base/key_source.h>
22 #include <packager/media/base/protection_system_ids.h>
23 #include <packager/utils/hex_parser.h>
24 
25 namespace shaka {
26 namespace media {
27 
28 namespace {
29 
30 const int32_t kHttpFetchTimeout = 60; // In seconds
31 const std::string kAcquireLicenseRequest =
32  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
33  "<soap:Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" "
34  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
35  "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
36  "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
37  "<soap:Body>"
38  "<AcquirePackagingData "
39  "xmlns=\"http://schemas.microsoft.com/DRM/2007/03/protocols\">"
40  "<challenge "
41  "xmlns=\"http://schemas.microsoft.com/DRM"
42  "/2007/03/protocols/AcquirePackagingData/v1.0\">"
43  "<ProtectionSystems>"
44  "<ProtectionSystemId>9A04F079-9840-4286-AB92-E65BE0885F95"
45  "</ProtectionSystemId>"
46  "</ProtectionSystems>"
47  "<StreamProtectionRequests>"
48  "<StreamInformation>"
49  "<ProgramIdentifier>$0</ProgramIdentifier>"
50  "<OffsetFromProgramStart>P0S</OffsetFromProgramStart>"
51  "</StreamInformation>"
52  "</StreamProtectionRequests>"
53  "</challenge>"
54  "</AcquirePackagingData>"
55  "</soap:Body>"
56  "</soap:Envelope>";
57 
58 bool Base64StringToBytes(const std::string& base64_string,
59  std::vector<uint8_t>* bytes) {
60  DCHECK(bytes);
61  std::string str;
62  if (!absl::Base64Unescape(base64_string, &str))
63  return false;
64  bytes->assign(str.begin(), str.end());
65  return true;
66 }
67 }
68 
69 PlayReadyKeySource::PlayReadyKeySource(const std::string& server_url,
70  ProtectionSystem protection_systems)
71  // PlayReady PSSH is retrived from PlayReady server response.
72  : generate_playready_protection_system_(
73  // Generate PlayReady protection system if there are no other
74  // protection system specified.
75  protection_systems == ProtectionSystem::kNone ||
76  has_flag(protection_systems, ProtectionSystem::kPlayReady)),
77  encryption_key_(new EncryptionKey),
78  server_url_(server_url) {}
79 
80 PlayReadyKeySource::~PlayReadyKeySource() = default;
81 
82 Status RetrieveTextInXMLElement(const std::string& element,
83  const std::string& xml,
84  std::string* value) {
85  std::string start_tag = "<" + element + ">";
86  std::string end_tag = "</" + element + ">";
87  std::size_t start_pos = xml.find(start_tag);
88  if (start_pos == std::string::npos) {
89  return Status(error::SERVER_ERROR,
90  "Unable to find tag: " + start_tag);
91  }
92  start_pos += start_tag.size();
93  std::size_t end_pos = xml.find(end_tag);
94  if (end_pos == std::string::npos) {
95  return Status(error::SERVER_ERROR,
96  "Unable to find tag: " + end_tag);
97  }
98  if (start_pos > end_pos) {
99  return Status(error::SERVER_ERROR, "Invalid positions");
100  }
101  std::size_t segment_len = end_pos - start_pos;
102  *value = xml.substr(start_pos, segment_len);
103  return Status::OK;
104 }
105 
106 Status SetKeyInformationFromServerResponse(
107  const std::string& response,
108  bool generate_playready_protection_system,
109  EncryptionKey* encryption_key) {
110  // TODO(robinconnell): Currently all tracks are encrypted using the same
111  // key_id and key. Add the ability to retrieve multiple key_id/keys from
112  // the packager response and encrypt multiple tracks using differnt
113  // key_id/keys.
114  std::string key_id_hex;
115  RETURN_IF_ERROR(RetrieveTextInXMLElement("KeyId", response, &key_id_hex));
116  key_id_hex.erase(
117  std::remove(key_id_hex.begin(), key_id_hex.end(), '-'), key_id_hex.end());
118 
119  std::string key_id_raw;
120  if (!ValidHexStringToBytes(key_id_hex, &key_id_raw)) {
121  LOG(ERROR) << "Cannot parse key_id_hex, " << key_id_hex;
122  return Status(error::SERVER_ERROR, "Cannot parse key_id_hex.");
123  }
124  encryption_key->key_id.assign(key_id_raw.begin(), key_id_raw.end());
125 
126  std::string key_data_b64;
127  RETURN_IF_ERROR(RetrieveTextInXMLElement("KeyData", response, &key_data_b64));
128  if (!Base64StringToBytes(key_data_b64, &encryption_key->key)) {
129  LOG(ERROR) << "Cannot parse key, " << key_data_b64;
130  return Status(error::SERVER_ERROR, "Cannot parse key.");
131  }
132  encryption_key->key_ids.emplace_back(encryption_key->key_id);
133 
134  if (generate_playready_protection_system) {
135  std::string pssh_data_b64;
136  RETURN_IF_ERROR(RetrieveTextInXMLElement("Data", response, &pssh_data_b64));
137  std::vector<uint8_t> pssh_data;
138  if (!Base64StringToBytes(pssh_data_b64, &pssh_data)) {
139  LOG(ERROR) << "Cannot parse pssh data, " << pssh_data_b64;
140  return Status(error::SERVER_ERROR, "Cannot parse pssh.");
141  }
142 
143  PsshBoxBuilder pssh_builder;
144  pssh_builder.add_key_id(encryption_key->key_id);
145  pssh_builder.set_system_id(kPlayReadySystemId,
146  std::size(kPlayReadySystemId));
147  pssh_builder.set_pssh_data(pssh_data);
148  encryption_key->key_system_info.push_back(
149  {pssh_builder.system_id(), pssh_builder.CreateBox()});
150  }
151  return Status::OK;
152 }
153 
154 Status PlayReadyKeySource::FetchKeysWithProgramIdentifier(
155  const std::string& program_identifier) {
156  std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey);
157  HttpKeyFetcher key_fetcher(kHttpFetchTimeout);
158 
159  std::string acquire_license_request = kAcquireLicenseRequest;
160 
161  // Replace "$0" with |program_identifier|
162  size_t dollar_zero_location = acquire_license_request.find("$0");
163  if (dollar_zero_location != std::string::npos) {
164  acquire_license_request.replace(dollar_zero_location, /* len= */ 2,
165  program_identifier);
166  }
167 
168  std::string acquire_license_response;
169  Status status = key_fetcher.FetchKeys(server_url_, acquire_license_request,
170  &acquire_license_response);
171  VLOG(1) << "Server response: " << acquire_license_response;
172  RETURN_IF_ERROR(status);
173 
174  RETURN_IF_ERROR(SetKeyInformationFromServerResponse(
175  acquire_license_response, generate_playready_protection_system_,
176  encryption_key.get()));
177 
178  // PlayReady does not specify different streams.
179  encryption_key_ = std::move(encryption_key);
180  return Status::OK;
181 }
182 
183 Status PlayReadyKeySource::FetchKeys(EmeInitDataType init_data_type,
184  const std::vector<uint8_t>& init_data) {
185  UNUSED(init_data_type);
186  UNUSED(init_data);
187  // Do nothing for PlayReady encryption/decryption.
188  return Status::OK;
189 }
190 
191 Status PlayReadyKeySource::GetKey(const std::string& stream_label,
192  EncryptionKey* key) {
193  UNUSED(stream_label);
194  // TODO(robinconnell): Currently all tracks are encrypted using the same
195  // key_id and key. Add the ability to encrypt each stream_label using a
196  // different key_id and key.
197  DCHECK(key);
198  DCHECK(encryption_key_);
199  *key = *encryption_key_;
200  return Status::OK;
201 }
202 
203 Status PlayReadyKeySource::GetKey(const std::vector<uint8_t>& key_id,
204  EncryptionKey* key) {
205  UNUSED(key_id);
206  // TODO(robinconnell): Currently all tracks are encrypted using the same
207  // key_id and key. Add the ability to encrypt using multiple key_id/keys.
208  DCHECK(key);
209  DCHECK(encryption_key_);
210  *key = *encryption_key_;
211  return Status::OK;
212 }
213 
215  uint32_t crypto_period_index,
216  int32_t crypto_period_duration_in_seconds,
217  const std::string& stream_label,
218  EncryptionKey* key) {
219  UNUSED(crypto_period_index);
220  UNUSED(crypto_period_duration_in_seconds);
221  UNUSED(stream_label);
222  // TODO(robinconnell): Implement key rotation.
223  *key = *encryption_key_;
224  return Status::OK;
225 }
226 
227 } // namespace media
228 } // namespace shaka
Status GetKey(const std::string &stream_label, EncryptionKey *key) override
Status FetchKeys(EmeInitDataType init_data_type, const std::vector< uint8_t > &init_data) override
PlayReadyKeySource(const std::string &server_url, ProtectionSystem protection_systems)
Status GetCryptoPeriodKey(uint32_t crypto_period_index, int32_t crypto_period_duration_in_seconds, const std::string &stream_label, EncryptionKey *key) override
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66