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