Shaka Packager SDK
Loading...
Searching...
No Matches
widevine_key_source.cc
1// Copyright 2014 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/widevine_key_source.h>
8
9#include <chrono>
10#include <cstddef>
11#include <cstdint>
12#include <functional>
13#include <iterator>
14#include <memory>
15#include <string>
16#include <thread>
17#include <utility>
18#include <vector>
19
20#include <absl/base/internal/endian.h>
21#include <absl/flags/flag.h>
22#include <absl/log/check.h>
23#include <absl/log/log.h>
24#include <absl/synchronization/mutex.h>
25
26#include <packager/crypto_params.h>
27#include <packager/media/base/fourccs.h>
28#include <packager/media/base/http_key_fetcher.h>
29#include <packager/media/base/key_source.h>
30#include <packager/media/base/producer_consumer_queue.h>
31#include <packager/media/base/protection_system_ids.h>
32#include <packager/media/base/protection_system_specific_info.h>
33#include <packager/media/base/proto_json_util.h>
34#include <packager/media/base/pssh_generator_util.h>
35#include <packager/media/base/rcheck.h>
36#include <packager/media/base/request_signer.h>
37#include <packager/media/base/widevine_common_encryption.pb.h>
38#include <packager/status.h>
39
40ABSL_FLAG(std::string,
41 video_feature,
42 "",
43 "Specify the optional video feature, e.g. HDR.");
44
45namespace shaka {
46namespace media {
47namespace {
48
49const bool kEnableKeyRotation = true;
50
51// Number of times to retry requesting keys in case of a transient error from
52// the server.
53const int kNumTransientErrorRetries = 5;
54const int kFirstRetryDelayMilliseconds = 1000;
55
56// Default crypto period count, which is the number of keys to fetch on every
57// key rotation enabled request.
58const int kDefaultCryptoPeriodCount = 10;
59const int kGetKeyTimeoutInSeconds = 5 * 60; // 5 minutes.
60const int kKeyFetchTimeoutInSeconds = 60; // 1 minute.
61
62CommonEncryptionRequest::ProtectionScheme ToCommonEncryptionProtectionScheme(
63 FourCC protection_scheme) {
64 switch (protection_scheme) {
65 case FOURCC_cenc:
66 return CommonEncryptionRequest::CENC;
67 case FOURCC_cbcs:
68 case kAppleSampleAesProtectionScheme:
69 // Treat sample aes as a variant of cbcs.
70 return CommonEncryptionRequest::CBCS;
71 case FOURCC_cbc1:
72 return CommonEncryptionRequest::CBC1;
73 case FOURCC_cens:
74 return CommonEncryptionRequest::CENS;
75 default:
76 LOG(WARNING) << "Ignore unrecognized protection scheme "
77 << FourCCToString(protection_scheme);
78 return CommonEncryptionRequest::UNSPECIFIED;
79 }
80}
81
82ProtectionSystemSpecificInfo ProtectionSystemInfoFromPsshProto(
83 const CommonEncryptionResponse::Track::Pssh& pssh_proto) {
84 PsshBoxBuilder pssh_builder;
85 pssh_builder.set_system_id(kWidevineSystemId, std::size(kWidevineSystemId));
86
87 if (pssh_proto.has_boxes()) {
88 return {pssh_builder.system_id(),
89 std::vector<uint8_t>(pssh_proto.boxes().begin(),
90 pssh_proto.boxes().end())};
91 } else {
92 pssh_builder.set_pssh_box_version(0);
93 const std::vector<uint8_t> pssh_data(pssh_proto.data().begin(),
94 pssh_proto.data().end());
95 pssh_builder.set_pssh_data(pssh_data);
96 return {pssh_builder.system_id(), pssh_builder.CreateBox()};
97 }
98}
99
100} // namespace
101
102WidevineKeySource::WidevineKeySource(const std::string& server_url,
103 ProtectionSystem protection_systems,
104 FourCC protection_scheme)
105 // Widevine PSSH is fetched from Widevine license server.
106 : generate_widevine_protection_system_(
107 // Generate Widevine protection system if there are no other
108 // protection system specified.
109 protection_systems == ProtectionSystem::kNone ||
110 has_flag(protection_systems, ProtectionSystem::kWidevine)),
111 key_fetcher_(new HttpKeyFetcher(kKeyFetchTimeoutInSeconds)),
112 server_url_(server_url),
113 crypto_period_count_(kDefaultCryptoPeriodCount),
114 protection_scheme_(protection_scheme),
115 key_production_thread_(
116 std::bind(&WidevineKeySource::FetchKeysTask, this)) {}
117
118WidevineKeySource::~WidevineKeySource() {
119 if (key_pool_)
120 key_pool_->Stop();
121 // Signal the production thread to start key production if it is not
122 // signaled yet so the thread can be joined.
123 if (!start_key_production_.HasBeenNotified())
124 start_key_production_.Notify();
125 key_production_thread_.join();
126}
127
128Status WidevineKeySource::FetchKeys(const std::vector<uint8_t>& content_id,
129 const std::string& policy) {
130 absl::MutexLock scoped_lock(mutex_);
131 common_encryption_request_.reset(new CommonEncryptionRequest);
132 common_encryption_request_->set_content_id(content_id.data(),
133 content_id.size());
134 common_encryption_request_->set_policy(policy);
135 common_encryption_request_->set_protection_scheme(
136 ToCommonEncryptionProtectionScheme(protection_scheme_));
137 if (enable_entitlement_license_)
138 common_encryption_request_->set_enable_entitlement_license(true);
139
140 return FetchKeysInternal(!kEnableKeyRotation, 0, false);
141}
142
143Status WidevineKeySource::FetchKeys(EmeInitDataType init_data_type,
144 const std::vector<uint8_t>& init_data) {
145 std::vector<uint8_t> pssh_data;
146 uint32_t asset_id = 0;
147 switch (init_data_type) {
148 case EmeInitDataType::CENC: {
149 const std::vector<uint8_t> widevine_system_id(
150 kWidevineSystemId, kWidevineSystemId + std::size(kWidevineSystemId));
151 std::vector<ProtectionSystemSpecificInfo> protection_systems_info;
153 init_data.data(), init_data.size(), &protection_systems_info)) {
154 return Status(error::PARSER_FAILURE, "Error parsing the PSSH boxes.");
155 }
156 for (const auto& info : protection_systems_info) {
157 std::unique_ptr<PsshBoxBuilder> pssh_builder =
158 PsshBoxBuilder::ParseFromBox(info.psshs.data(), info.psshs.size());
159 if (!pssh_builder)
160 return Status(error::PARSER_FAILURE, "Error parsing the PSSH box.");
161 // Use Widevine PSSH if available otherwise construct a Widevine PSSH
162 // from the first available key ids.
163 if (info.system_id == widevine_system_id) {
164 pssh_data = pssh_builder->pssh_data();
165 break;
166 } else if (pssh_data.empty() && !pssh_builder->key_ids().empty()) {
167 pssh_data =
168 GenerateWidevinePsshDataFromKeyIds(pssh_builder->key_ids());
169 // Continue to see if there is any Widevine PSSH. The KeyId generated
170 // PSSH is only used if a Widevine PSSH could not be found.
171 continue;
172 }
173 }
174 if (pssh_data.empty())
175 return Status(error::INVALID_ARGUMENT, "No supported PSSHs found.");
176 break;
177 }
178 case EmeInitDataType::WEBM: {
179 pssh_data = GenerateWidevinePsshDataFromKeyIds({init_data});
180 break;
181 }
182 case EmeInitDataType::WIDEVINE_CLASSIC:
183 if (init_data.size() < sizeof(asset_id))
184 return Status(error::INVALID_ARGUMENT, "Invalid asset id.");
185 asset_id = absl::big_endian::Load32(init_data.data());
186 break;
187 default:
188 LOG(ERROR) << "Init data type " << static_cast<int>(init_data_type)
189 << " not supported.";
190 return Status(error::INVALID_ARGUMENT, "Unsupported init data type.");
191 }
192 const bool widevine_classic =
193 init_data_type == EmeInitDataType::WIDEVINE_CLASSIC;
194 absl::MutexLock scoped_lock(mutex_);
195 common_encryption_request_.reset(new CommonEncryptionRequest);
196 if (widevine_classic) {
197 common_encryption_request_->set_asset_id(asset_id);
198 } else {
199 common_encryption_request_->set_pssh_data(pssh_data.data(),
200 pssh_data.size());
201 }
202 return FetchKeysInternal(!kEnableKeyRotation, 0, widevine_classic);
203}
204
205Status WidevineKeySource::GetKey(const std::string& stream_label,
206 EncryptionKey* key) {
207 DCHECK(key);
208 if (encryption_key_map_.find(stream_label) == encryption_key_map_.end()) {
209 return Status(error::INTERNAL_ERROR,
210 "Cannot find key for '" + stream_label + "'.");
211 }
212 *key = *encryption_key_map_[stream_label];
213 return Status::OK;
214}
215
216Status WidevineKeySource::GetKey(const std::vector<uint8_t>& key_id,
217 EncryptionKey* key) {
218 DCHECK(key);
219 for (const auto& pair : encryption_key_map_) {
220 if (pair.second->key_id == key_id) {
221 *key = *pair.second;
222 return Status::OK;
223 }
224 }
225 return Status(error::INTERNAL_ERROR, "Cannot find key with specified key ID");
226}
227
229 uint32_t crypto_period_index,
230 int32_t crypto_period_duration_in_seconds,
231 const std::string& stream_label,
232 EncryptionKey* key) {
233 // TODO(kqyang): This is not elegant. Consider refactoring later.
234 {
235 absl::MutexLock scoped_lock(mutex_);
236 if (!key_production_started_) {
237 crypto_period_duration_in_seconds_ = crypto_period_duration_in_seconds;
238 // Another client may have a slightly smaller starting crypto period
239 // index. Set the initial value to account for that.
240 first_crypto_period_index_ =
241 crypto_period_index ? crypto_period_index - 1 : 0;
242 DCHECK(!key_pool_);
243 const size_t queue_size = crypto_period_count_ * 10;
244 key_pool_.reset(
245 new EncryptionKeyQueue(queue_size, first_crypto_period_index_));
246 start_key_production_.Notify();
247 key_production_started_ = true;
248 } else if (crypto_period_duration_in_seconds_ !=
249 crypto_period_duration_in_seconds) {
250 return Status(error::INVALID_ARGUMENT,
251 "Crypto period duration should not change.");
252 }
253 }
254 return GetKeyInternal(crypto_period_index, stream_label, key);
255}
256
257void WidevineKeySource::set_signer(std::unique_ptr<RequestSigner> signer) {
258 signer_ = std::move(signer);
259}
260
262 std::unique_ptr<KeyFetcher> key_fetcher) {
263 key_fetcher_ = std::move(key_fetcher);
264}
265
266Status WidevineKeySource::GetKeyInternal(uint32_t crypto_period_index,
267 const std::string& stream_label,
268 EncryptionKey* key) {
269 DCHECK(key_pool_);
270 DCHECK(key);
271
272 std::shared_ptr<EncryptionKeyMap> encryption_key_map;
273 Status status = key_pool_->Peek(crypto_period_index, &encryption_key_map,
274 kGetKeyTimeoutInSeconds * 1000);
275 if (!status.ok()) {
276 if (status.error_code() == error::STOPPED) {
277 CHECK(!common_encryption_request_status_.ok());
278 return common_encryption_request_status_;
279 }
280 return status;
281 }
282
283 if (encryption_key_map->find(stream_label) == encryption_key_map->end()) {
284 return Status(error::INTERNAL_ERROR,
285 "Cannot find key for '" + stream_label + "'.");
286 }
287 *key = *encryption_key_map->at(stream_label);
288 return Status::OK;
289}
290
291void WidevineKeySource::FetchKeysTask() {
292 // Wait until key production is signaled.
293 start_key_production_.WaitForNotification();
294 if (!key_pool_ || key_pool_->Stopped())
295 return;
296
297 Status status =
298 FetchKeysInternal(kEnableKeyRotation, first_crypto_period_index_, false);
299 while (status.ok()) {
300 first_crypto_period_index_ += crypto_period_count_;
301 status = FetchKeysInternal(kEnableKeyRotation, first_crypto_period_index_,
302 false);
303 }
304 common_encryption_request_status_ = status;
305 key_pool_->Stop();
306}
307
308Status WidevineKeySource::FetchKeysInternal(bool enable_key_rotation,
309 uint32_t first_crypto_period_index,
310 bool widevine_classic) {
311 CommonEncryptionRequest request;
312 FillRequest(enable_key_rotation, first_crypto_period_index, &request);
313
314 std::string message;
315 Status status = GenerateKeyMessage(request, &message);
316 if (!status.ok())
317 return status;
318 VLOG(1) << "Message: " << message;
319
320 std::string raw_response;
321 int64_t sleep_duration = kFirstRetryDelayMilliseconds;
322
323 // Perform client side retries if seeing server transient error to workaround
324 // server limitation.
325 for (int i = 0; i < kNumTransientErrorRetries; ++i) {
326 status = key_fetcher_->FetchKeys(server_url_, message, &raw_response);
327 if (status.ok()) {
328 VLOG(1) << "Retry [" << i << "] Response:" << raw_response;
329
330 bool transient_error = false;
331 if (ExtractEncryptionKey(enable_key_rotation, widevine_classic,
332 raw_response, &transient_error))
333 return Status::OK;
334
335 if (!transient_error) {
336 return Status(
337 error::SERVER_ERROR,
338 "Failed to extract encryption key from '" + raw_response + "'.");
339 }
340 } else if (status.error_code() != error::TIME_OUT) {
341 return status;
342 }
343
344 // Exponential backoff.
345 if (i != kNumTransientErrorRetries - 1) {
346 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_duration));
347 sleep_duration *= 2;
348 }
349 }
350 return Status(error::SERVER_ERROR,
351 "Failed to recover from server internal error.");
352}
353
354void WidevineKeySource::FillRequest(bool enable_key_rotation,
355 uint32_t first_crypto_period_index,
356 CommonEncryptionRequest* request) {
357 DCHECK(common_encryption_request_);
358 DCHECK(request);
359 *request = *common_encryption_request_;
360
361 request->add_tracks()->set_type("SD");
362 request->add_tracks()->set_type("HD");
363 request->add_tracks()->set_type("UHD1");
364 request->add_tracks()->set_type("UHD2");
365 request->add_tracks()->set_type("AUDIO");
366
367 request->add_drm_types(ModularDrmType::WIDEVINE);
368
369 if (enable_key_rotation) {
370 request->set_first_crypto_period_index(first_crypto_period_index);
371 request->set_crypto_period_count(crypto_period_count_);
372 request->set_crypto_period_seconds(crypto_period_duration_in_seconds_);
373 }
374
375 if (!group_id_.empty())
376 request->set_group_id(group_id_.data(), group_id_.size());
377
378 std::string video_feature = absl::GetFlag(FLAGS_video_feature);
379 if (!video_feature.empty())
380 request->set_video_feature(video_feature);
381}
382
383Status WidevineKeySource::GenerateKeyMessage(
384 const CommonEncryptionRequest& request,
385 std::string* message) {
386 DCHECK(message);
387
388 SignedModularDrmRequest signed_request;
389 signed_request.set_request(MessageToJsonString(request));
390
391 // Sign the request.
392 if (signer_) {
393 std::string signature;
394 if (!signer_->GenerateSignature(signed_request.request(), &signature))
395 return Status(error::INTERNAL_ERROR, "Signature generation failed.");
396
397 signed_request.set_signature(signature);
398 signed_request.set_signer(signer_->signer_name());
399 }
400
401 *message = MessageToJsonString(signed_request);
402 return Status::OK;
403}
404
405bool WidevineKeySource::ExtractEncryptionKey(bool enable_key_rotation,
406 bool widevine_classic,
407 const std::string& response,
408 bool* transient_error) {
409 DCHECK(transient_error);
410 *transient_error = false;
411
412 SignedModularDrmResponse signed_response_proto;
413 if (!JsonStringToMessage(response, &signed_response_proto)) {
414 LOG(ERROR) << "Failed to convert JSON to proto: " << response;
415 return false;
416 }
417
418 CommonEncryptionResponse response_proto;
419 if (!JsonStringToMessage(signed_response_proto.response(), &response_proto)) {
420 LOG(ERROR) << "Failed to convert JSON to proto: "
421 << signed_response_proto.response();
422 return false;
423 }
424
425 if (response_proto.status() != CommonEncryptionResponse::OK) {
426 LOG(ERROR) << "Received non-OK license response: " << response;
427 // Server may return INTERNAL_ERROR intermittently, which is a transient
428 // error and the next client request may succeed without problem.
429 *transient_error =
430 (response_proto.status() == CommonEncryptionResponse::INTERNAL_ERROR);
431 return false;
432 }
433
434 RCHECK(enable_key_rotation
435 ? response_proto.tracks_size() >= crypto_period_count_
436 : response_proto.tracks_size() >= 1);
437
438 uint32_t current_crypto_period_index = first_crypto_period_index_;
439
440 std::vector<std::vector<uint8_t>> key_ids;
441 for (const auto& track : response_proto.tracks()) {
442 if (!widevine_classic)
443 key_ids.emplace_back(track.key_id().begin(), track.key_id().end());
444 }
445
446 EncryptionKeyMap encryption_key_map;
447 for (const auto& track : response_proto.tracks()) {
448 VLOG(2) << "track " << track.ShortDebugString();
449
450 if (enable_key_rotation) {
451 if (track.crypto_period_index() != current_crypto_period_index) {
452 if (track.crypto_period_index() != current_crypto_period_index + 1) {
453 LOG(ERROR) << "Expecting crypto period index "
454 << current_crypto_period_index << " or "
455 << current_crypto_period_index + 1 << "; Seen "
456 << track.crypto_period_index();
457 return false;
458 }
459 if (!PushToKeyPool(&encryption_key_map))
460 return false;
461 ++current_crypto_period_index;
462 }
463 }
464
465 const std::string& stream_label = track.type();
466 RCHECK(encryption_key_map.find(stream_label) == encryption_key_map.end());
467
468 std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
469 encryption_key->key.assign(track.key().begin(), track.key().end());
470
471 // Get key ID and PSSH data for CENC content only.
472 if (!widevine_classic) {
473 encryption_key->key_id.assign(track.key_id().begin(),
474 track.key_id().end());
475 encryption_key->iv.assign(track.iv().begin(), track.iv().end());
476 encryption_key->key_ids = key_ids;
477
478 if (generate_widevine_protection_system_) {
479 if (track.pssh_size() != 1) {
480 LOG(ERROR) << "Expecting one and only one pssh, seeing "
481 << track.pssh_size();
482 return false;
483 }
484 encryption_key->key_system_info.push_back(
485 ProtectionSystemInfoFromPsshProto(track.pssh(0)));
486 }
487 }
488 encryption_key_map[stream_label] = std::move(encryption_key);
489 }
490
491 DCHECK(!encryption_key_map.empty());
492 if (!enable_key_rotation) {
493 // Merge with previously requested keys.
494 for (auto& pair : encryption_key_map)
495 encryption_key_map_[pair.first] = std::move(pair.second);
496 return true;
497 }
498
499 return PushToKeyPool(&encryption_key_map);
500}
501
502bool WidevineKeySource::PushToKeyPool(EncryptionKeyMap* encryption_key_map) {
503 DCHECK(key_pool_);
504 DCHECK(encryption_key_map);
505 auto encryption_key_map_shared = std::make_shared<EncryptionKeyMap>();
506 encryption_key_map_shared->swap(*encryption_key_map);
507 Status status = key_pool_->Push(encryption_key_map_shared, kInfiniteTimeout);
508 if (!status.ok()) {
509 DCHECK_EQ(error::STOPPED, status.error_code());
510 return false;
511 }
512 return true;
513}
514
515} // namespace media
516} // namespace shaka
static std::unique_ptr< PsshBoxBuilder > ParseFromBox(const uint8_t *data, size_t data_size)
Status GetCryptoPeriodKey(uint32_t crypto_period_index, int32_t crypto_period_duration_in_seconds, const std::string &stream_label, EncryptionKey *key) override
void set_signer(std::unique_ptr< RequestSigner > signer)
void set_key_fetcher(std::unique_ptr< KeyFetcher > key_fetcher)
WidevineKeySource(const std::string &server_url, ProtectionSystem protection_systems, FourCC protection_scheme)
Status GetKey(const std::string &stream_label, EncryptionKey *key) override
Status FetchKeys(EmeInitDataType init_data_type, const std::vector< uint8_t > &init_data) override
All the methods that are virtual are virtual for mocking.
static bool ParseBoxes(const uint8_t *data, size_t data_size, std::vector< ProtectionSystemSpecificInfo > *pssh_boxes)