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/playready_pssh_generator.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  int protection_system_flags,
67  FourCC protection_scheme)
68  // PlayReady PSSH is retrived from PlayReady server response.
69  : KeySource(protection_system_flags & ~PLAYREADY_PROTECTION_SYSTEM_FLAG,
70  protection_scheme),
71  generate_playready_protection_system_(
72  // Generate PlayReady protection system if there are no other
73  // protection system specified.
74  protection_system_flags == NO_PROTECTION_SYSTEM_FLAG ||
75  protection_system_flags & PLAYREADY_PROTECTION_SYSTEM_FLAG),
76  encryption_key_(new EncryptionKey),
77  server_url_(server_url) {}
78 
80  const std::string& server_url,
81  const std::string& client_cert_file,
82  const std::string& client_cert_private_key_file,
83  const std::string& client_cert_private_key_password,
84  int protection_system_flags,
85  FourCC protection_scheme)
86  // PlayReady PSSH is retrived from PlayReady server response.
87  : KeySource(protection_system_flags & ~PLAYREADY_PROTECTION_SYSTEM_FLAG,
88  protection_scheme),
89  encryption_key_(new EncryptionKey),
90  server_url_(server_url),
91  client_cert_file_(client_cert_file),
92  client_cert_private_key_file_(client_cert_private_key_file),
93  client_cert_private_key_password_(client_cert_private_key_password) {}
94 
95 PlayReadyKeySource::~PlayReadyKeySource() = default;
96 
97 Status RetrieveTextInXMLElement(const std::string& element,
98  const std::string& xml,
99  std::string* value) {
100  std::string start_tag = "<" + element + ">";
101  std::string end_tag = "</" + element + ">";
102  std::size_t start_pos = xml.find(start_tag);
103  if (start_pos == std::string::npos) {
104  return Status(error::SERVER_ERROR,
105  "Unable to find tag: " + start_tag);
106  }
107  start_pos += start_tag.size();
108  std::size_t end_pos = xml.find(end_tag);
109  if (end_pos == std::string::npos) {
110  return Status(error::SERVER_ERROR,
111  "Unable to find tag: " + end_tag);
112  }
113  if (start_pos > end_pos) {
114  return Status(error::SERVER_ERROR, "Invalid positions");
115  }
116  std::size_t segment_len = end_pos - start_pos;
117  *value = xml.substr(start_pos, segment_len);
118  return Status::OK;
119 }
120 
121 Status SetKeyInformationFromServerResponse(
122  const std::string& response,
123  bool generate_playready_protection_system,
124  EncryptionKey* encryption_key) {
125  // TODO(robinconnell): Currently all tracks are encrypted using the same
126  // key_id and key. Add the ability to retrieve multiple key_id/keys from
127  // the packager response and encrypt multiple tracks using differnt
128  // key_id/keys.
129  std::string key_id_hex;
130  RETURN_IF_ERROR(RetrieveTextInXMLElement("KeyId", response, &key_id_hex));
131  key_id_hex.erase(
132  std::remove(key_id_hex.begin(), key_id_hex.end(), '-'), key_id_hex.end());
133  if (!base::HexStringToBytes(key_id_hex, &encryption_key->key_id)) {
134  LOG(ERROR) << "Cannot parse key_id_hex, " << key_id_hex;
135  return Status(error::SERVER_ERROR, "Cannot parse key_id_hex.");
136  }
137 
138  std::string key_data_b64;
139  RETURN_IF_ERROR(RetrieveTextInXMLElement("KeyData", response, &key_data_b64));
140  if (!Base64StringToBytes(key_data_b64, &encryption_key->key)) {
141  LOG(ERROR) << "Cannot parse key, " << key_data_b64;
142  return Status(error::SERVER_ERROR, "Cannot parse key.");
143  }
144 
145  if (generate_playready_protection_system) {
146  std::string pssh_data_b64;
147  RETURN_IF_ERROR(RetrieveTextInXMLElement("Data", response, &pssh_data_b64));
148  std::vector<uint8_t> pssh_data;
149  if (!Base64StringToBytes(pssh_data_b64, &pssh_data)) {
150  LOG(ERROR) << "Cannot parse pssh data, " << pssh_data_b64;
151  return Status(error::SERVER_ERROR, "Cannot parse pssh.");
152  }
153 
154  PsshBoxBuilder pssh_builder;
155  pssh_builder.add_key_id(encryption_key->key_id);
156  pssh_builder.set_system_id(kPlayReadySystemId,
157  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  }
162  return Status::OK;
163 }
164 
165 Status PlayReadyKeySource::FetchKeysWithProgramIdentifier(
166  const std::string& program_identifier) {
167  std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey);
168  HttpKeyFetcher key_fetcher(kHttpFetchTimeout);
169  if (!client_cert_file_.empty() && !client_cert_private_key_file_.empty()) {
170  key_fetcher.SetClientCertInfo(client_cert_file_,
171  client_cert_private_key_file_,
172  client_cert_private_key_password_);
173  }
174  if (!ca_file_.empty()) {
175  key_fetcher.SetCaFile(ca_file_);
176  }
177 
178  std::string acquire_license_request = kAcquireLicenseRequest;
179  base::ReplaceFirstSubstringAfterOffset(
180  &acquire_license_request, 0, "$0", program_identifier);
181  std::string acquire_license_response;
182  Status status = key_fetcher.FetchKeys(server_url_, acquire_license_request,
183  &acquire_license_response);
184  VLOG(1) << "Server response: " << acquire_license_response;
185  RETURN_IF_ERROR(status);
186 
187  RETURN_IF_ERROR(SetKeyInformationFromServerResponse(
188  acquire_license_response, generate_playready_protection_system_,
189  encryption_key.get()));
190 
191  // PlayReady does not specify different streams.
192  const char kEmptyDrmLabel[] = "";
193  EncryptionKeyMap encryption_key_map;
194  encryption_key_map[kEmptyDrmLabel] = std::move(encryption_key);
195  RETURN_IF_ERROR(UpdateProtectionSystemInfo(&encryption_key_map));
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:39
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:48
PlayReadyKeySource(const std::string &server_url, int protection_systems_flags, FourCC protection_scheme)