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