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