Shaka Packager SDK
Loading...
Searching...
No Matches
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
25namespace shaka {
26namespace media {
27
28namespace {
29
30const int32_t kHttpFetchTimeout = 60; // In seconds
31const 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
58bool 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
69PlayReadyKeySource::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
80PlayReadyKeySource::~PlayReadyKeySource() = default;
81
82Status 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
106Status 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
154Status 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
183Status 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
191Status 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
203Status 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.