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