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  int protection_system_flags,
88  FourCC protection_scheme)
89  // Widevine PSSH is fetched from Widevine license server.
90  : KeySource(protection_system_flags & ~WIDEVINE_PROTECTION_SYSTEM_FLAG,
91  protection_scheme),
92  generate_widevine_protection_system_(
93  // Generate Widevine protection system if there are no other
94  // protection system specified.
95  protection_system_flags == NO_PROTECTION_SYSTEM_FLAG ||
96  protection_system_flags & WIDEVINE_PROTECTION_SYSTEM_FLAG),
97  key_production_thread_("KeyProductionThread",
98  base::Bind(&WidevineKeySource::FetchKeysTask,
99  base::Unretained(this))),
100  key_fetcher_(new HttpKeyFetcher(kKeyFetchTimeoutInSeconds)),
101  server_url_(server_url),
102  crypto_period_count_(kDefaultCryptoPeriodCount),
103  protection_scheme_(protection_scheme),
104  start_key_production_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
105  base::WaitableEvent::InitialState::NOT_SIGNALED) {
106  key_production_thread_.Start();
107 }
108 
109 WidevineKeySource::~WidevineKeySource() {
110  if (key_pool_)
111  key_pool_->Stop();
112  if (key_production_thread_.HasBeenStarted()) {
113  // Signal the production thread to start key production if it is not
114  // signaled yet so the thread can be joined.
115  start_key_production_.Signal();
116  key_production_thread_.Join();
117  }
118 }
119 
120 Status WidevineKeySource::FetchKeys(const std::vector<uint8_t>& content_id,
121  const std::string& policy) {
122  base::AutoLock scoped_lock(lock_);
123  common_encryption_request_.reset(new CommonEncryptionRequest);
124  common_encryption_request_->set_content_id(content_id.data(),
125  content_id.size());
126  common_encryption_request_->set_policy(policy);
127  common_encryption_request_->set_protection_scheme(
128  ToCommonEncryptionProtectionScheme(protection_scheme_));
129  if (enable_entitlement_license_)
130  common_encryption_request_->set_enable_entitlement_license(true);
131 
132  return FetchKeysInternal(!kEnableKeyRotation, 0, false);
133 }
134 
135 Status WidevineKeySource::FetchKeys(EmeInitDataType init_data_type,
136  const std::vector<uint8_t>& init_data) {
137  std::vector<uint8_t> pssh_data;
138  uint32_t asset_id = 0;
139  switch (init_data_type) {
140  case EmeInitDataType::CENC: {
141  const std::vector<uint8_t> widevine_system_id(
142  kWidevineSystemId, kWidevineSystemId + arraysize(kWidevineSystemId));
143  std::vector<ProtectionSystemSpecificInfo> protection_systems_info;
145  init_data.data(), init_data.size(), &protection_systems_info)) {
146  return Status(error::PARSER_FAILURE, "Error parsing the PSSH boxes.");
147  }
148  for (const auto& info : protection_systems_info) {
149  std::unique_ptr<PsshBoxBuilder> pssh_builder =
150  PsshBoxBuilder::ParseFromBox(info.psshs.data(), info.psshs.size());
151  if (!pssh_builder)
152  return Status(error::PARSER_FAILURE, "Error parsing the PSSH box.");
153  // Use Widevine PSSH if available otherwise construct a Widevine PSSH
154  // from the first available key ids.
155  if (info.system_id == widevine_system_id) {
156  pssh_data = pssh_builder->pssh_data();
157  break;
158  } else if (pssh_data.empty() && !pssh_builder->key_ids().empty()) {
159  pssh_data =
160  GenerateWidevinePsshDataFromKeyIds(pssh_builder->key_ids());
161  // Continue to see if there is any Widevine PSSH. The KeyId generated
162  // PSSH is only used if a Widevine PSSH could not be found.
163  continue;
164  }
165  }
166  if (pssh_data.empty())
167  return Status(error::INVALID_ARGUMENT, "No supported PSSHs found.");
168  break;
169  }
170  case EmeInitDataType::WEBM: {
171  pssh_data = GenerateWidevinePsshDataFromKeyIds({init_data});
172  break;
173  }
174  case EmeInitDataType::WIDEVINE_CLASSIC:
175  if (init_data.size() < sizeof(asset_id))
176  return Status(error::INVALID_ARGUMENT, "Invalid asset id.");
177  asset_id = ntohlFromBuffer(init_data.data());
178  break;
179  default:
180  LOG(ERROR) << "Init data type " << static_cast<int>(init_data_type)
181  << " not supported.";
182  return Status(error::INVALID_ARGUMENT, "Unsupported init data type.");
183  }
184  const bool widevine_classic =
185  init_data_type == EmeInitDataType::WIDEVINE_CLASSIC;
186  base::AutoLock scoped_lock(lock_);
187  common_encryption_request_.reset(new CommonEncryptionRequest);
188  if (widevine_classic) {
189  common_encryption_request_->set_asset_id(asset_id);
190  } else {
191  common_encryption_request_->set_pssh_data(pssh_data.data(),
192  pssh_data.size());
193  }
194  return FetchKeysInternal(!kEnableKeyRotation, 0, widevine_classic);
195 }
196 
197 Status WidevineKeySource::GetKey(const std::string& stream_label,
198  EncryptionKey* key) {
199  DCHECK(key);
200  if (encryption_key_map_.find(stream_label) == encryption_key_map_.end()) {
201  return Status(error::INTERNAL_ERROR,
202  "Cannot find key for '" + stream_label + "'.");
203  }
204  *key = *encryption_key_map_[stream_label];
205  return Status::OK;
206 }
207 
208 Status WidevineKeySource::GetKey(const std::vector<uint8_t>& key_id,
209  EncryptionKey* key) {
210  DCHECK(key);
211  for (const auto& pair : encryption_key_map_) {
212  if (pair.second->key_id == key_id) {
213  *key = *pair.second;
214  return Status::OK;
215  }
216  }
217  return Status(error::INTERNAL_ERROR,
218  "Cannot find key with specified key ID");
219 }
220 
221 Status WidevineKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
222  uint32_t crypto_period_duration_in_seconds,
223  const std::string& stream_label,
224  EncryptionKey* key) {
225  DCHECK(key_production_thread_.HasBeenStarted());
226  // TODO(kqyang): This is not elegant. Consider refactoring later.
227  {
228  base::AutoLock scoped_lock(lock_);
229  if (!key_production_started_) {
230  crypto_period_duration_in_seconds_ = crypto_period_duration_in_seconds;
231  // Another client may have a slightly smaller starting crypto period
232  // index. Set the initial value to account for that.
233  first_crypto_period_index_ =
234  crypto_period_index ? crypto_period_index - 1 : 0;
235  DCHECK(!key_pool_);
236  const size_t queue_size = crypto_period_count_ * 10;
237  key_pool_.reset(
238  new EncryptionKeyQueue(queue_size, first_crypto_period_index_));
239  start_key_production_.Signal();
240  key_production_started_ = true;
241  } else if (crypto_period_duration_in_seconds_ !=
242  crypto_period_duration_in_seconds) {
243  return Status(error::INVALID_ARGUMENT,
244  "Crypto period duration should not change.");
245  }
246  }
247  return GetKeyInternal(crypto_period_index, stream_label, key);
248 }
249 
250 void WidevineKeySource::set_signer(std::unique_ptr<RequestSigner> signer) {
251  signer_ = std::move(signer);
252 }
253 
255  std::unique_ptr<KeyFetcher> key_fetcher) {
256  key_fetcher_ = std::move(key_fetcher);
257 }
258 
259 Status WidevineKeySource::GetKeyInternal(uint32_t crypto_period_index,
260  const std::string& stream_label,
261  EncryptionKey* key) {
262  DCHECK(key_pool_);
263  DCHECK(key);
264 
265  std::shared_ptr<EncryptionKeyMap> encryption_key_map;
266  Status status = key_pool_->Peek(crypto_period_index, &encryption_key_map,
267  kGetKeyTimeoutInSeconds * 1000);
268  if (!status.ok()) {
269  if (status.error_code() == error::STOPPED) {
270  CHECK(!common_encryption_request_status_.ok());
271  return common_encryption_request_status_;
272  }
273  return status;
274  }
275 
276  if (encryption_key_map->find(stream_label) == encryption_key_map->end()) {
277  return Status(error::INTERNAL_ERROR,
278  "Cannot find key for '" + stream_label + "'.");
279  }
280  *key = *encryption_key_map->at(stream_label);
281  return Status::OK;
282 }
283 
284 void WidevineKeySource::FetchKeysTask() {
285  // Wait until key production is signaled.
286  start_key_production_.Wait();
287  if (!key_pool_ || key_pool_->Stopped())
288  return;
289 
290  Status status = FetchKeysInternal(kEnableKeyRotation,
291  first_crypto_period_index_,
292  false);
293  while (status.ok()) {
294  first_crypto_period_index_ += crypto_period_count_;
295  status = FetchKeysInternal(kEnableKeyRotation,
296  first_crypto_period_index_,
297  false);
298  }
299  common_encryption_request_status_ = status;
300  key_pool_->Stop();
301 }
302 
303 Status WidevineKeySource::FetchKeysInternal(bool enable_key_rotation,
304  uint32_t first_crypto_period_index,
305  bool widevine_classic) {
306  CommonEncryptionRequest request;
307  FillRequest(enable_key_rotation, first_crypto_period_index, &request);
308 
309  std::string message;
310  Status status = GenerateKeyMessage(request, &message);
311  if (!status.ok())
312  return status;
313  VLOG(1) << "Message: " << message;
314 
315  std::string raw_response;
316  int64_t sleep_duration = kFirstRetryDelayMilliseconds;
317 
318  // Perform client side retries if seeing server transient error to workaround
319  // server limitation.
320  for (int i = 0; i < kNumTransientErrorRetries; ++i) {
321  status = key_fetcher_->FetchKeys(server_url_, message, &raw_response);
322  if (status.ok()) {
323  VLOG(1) << "Retry [" << i << "] Response:" << raw_response;
324 
325  bool transient_error = false;
326  if (ExtractEncryptionKey(enable_key_rotation, widevine_classic,
327  raw_response, &transient_error))
328  return Status::OK;
329 
330  if (!transient_error) {
331  return Status(
332  error::SERVER_ERROR,
333  "Failed to extract encryption key from '" + raw_response + "'.");
334  }
335  } else if (status.error_code() != error::TIME_OUT) {
336  return status;
337  }
338 
339  // Exponential backoff.
340  if (i != kNumTransientErrorRetries - 1) {
341  base::PlatformThread::Sleep(
342  base::TimeDelta::FromMilliseconds(sleep_duration));
343  sleep_duration *= 2;
344  }
345  }
346  return Status(error::SERVER_ERROR,
347  "Failed to recover from server internal error.");
348 }
349 
350 void WidevineKeySource::FillRequest(bool enable_key_rotation,
351  uint32_t first_crypto_period_index,
352  CommonEncryptionRequest* request) {
353  DCHECK(common_encryption_request_);
354  DCHECK(request);
355  *request = *common_encryption_request_;
356 
357  request->add_tracks()->set_type("SD");
358  request->add_tracks()->set_type("HD");
359  request->add_tracks()->set_type("UHD1");
360  request->add_tracks()->set_type("UHD2");
361  request->add_tracks()->set_type("AUDIO");
362 
363  request->add_drm_types(ModularDrmType::WIDEVINE);
364 
365  if (enable_key_rotation) {
366  request->set_first_crypto_period_index(first_crypto_period_index);
367  request->set_crypto_period_count(crypto_period_count_);
368  request->set_crypto_period_seconds(crypto_period_duration_in_seconds_);
369  }
370 
371  if (!group_id_.empty())
372  request->set_group_id(group_id_.data(), group_id_.size());
373 
374  if (!FLAGS_video_feature.empty())
375  request->set_video_feature(FLAGS_video_feature);
376 }
377 
378 Status WidevineKeySource::GenerateKeyMessage(
379  const CommonEncryptionRequest& request,
380  std::string* message) {
381  DCHECK(message);
382 
383  SignedModularDrmRequest signed_request;
384  signed_request.set_request(MessageToJsonString(request));
385 
386  // Sign the request.
387  if (signer_) {
388  std::string signature;
389  if (!signer_->GenerateSignature(signed_request.request(), &signature))
390  return Status(error::INTERNAL_ERROR, "Signature generation failed.");
391 
392  signed_request.set_signature(signature);
393  signed_request.set_signer(signer_->signer_name());
394  }
395 
396  *message = MessageToJsonString(signed_request);
397  return Status::OK;
398 }
399 
400 bool WidevineKeySource::ExtractEncryptionKey(
401  bool enable_key_rotation,
402  bool widevine_classic,
403  const std::string& response,
404  bool* transient_error) {
405  DCHECK(transient_error);
406  *transient_error = false;
407 
408  SignedModularDrmResponse signed_response_proto;
409  if (!JsonStringToMessage(response, &signed_response_proto)) {
410  LOG(ERROR) << "Failed to convert JSON to proto: " << response;
411  return false;
412  }
413 
414  CommonEncryptionResponse response_proto;
415  if (!JsonStringToMessage(signed_response_proto.response(), &response_proto)) {
416  LOG(ERROR) << "Failed to convert JSON to proto: "
417  << signed_response_proto.response();
418  return false;
419  }
420 
421  if (response_proto.status() != CommonEncryptionResponse::OK) {
422  LOG(ERROR) << "Received non-OK license response: " << response;
423  // Server may return INTERNAL_ERROR intermittently, which is a transient
424  // error and the next client request may succeed without problem.
425  *transient_error =
426  (response_proto.status() == CommonEncryptionResponse::INTERNAL_ERROR);
427  return false;
428  }
429 
430  RCHECK(enable_key_rotation
431  ? response_proto.tracks_size() >= crypto_period_count_
432  : response_proto.tracks_size() >= 1);
433 
434  uint32_t current_crypto_period_index = first_crypto_period_index_;
435 
436  EncryptionKeyMap encryption_key_map;
437  for (const auto& track : response_proto.tracks()) {
438  VLOG(2) << "track " << track.ShortDebugString();
439 
440  if (enable_key_rotation) {
441  if (track.crypto_period_index() != current_crypto_period_index) {
442  if (track.crypto_period_index() != current_crypto_period_index + 1) {
443  LOG(ERROR) << "Expecting crypto period index "
444  << current_crypto_period_index << " or "
445  << current_crypto_period_index + 1 << "; Seen "
446  << track.crypto_period_index();
447  return false;
448  }
449  if (!PushToKeyPool(&encryption_key_map))
450  return false;
451  ++current_crypto_period_index;
452  }
453  }
454 
455  const std::string& stream_label = track.type();
456  RCHECK(encryption_key_map.find(stream_label) == encryption_key_map.end());
457 
458  std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
459  encryption_key->key.assign(track.key().begin(), track.key().end());
460 
461  // Get key ID and PSSH data for CENC content only.
462  if (!widevine_classic) {
463  encryption_key->key_id.assign(track.key_id().begin(),
464  track.key_id().end());
465  encryption_key->iv.assign(track.iv().begin(), track.iv().end());
466 
467  if (generate_widevine_protection_system_) {
468  if (track.pssh_size() != 1) {
469  LOG(ERROR) << "Expecting one and only one pssh, seeing "
470  << track.pssh_size();
471  return false;
472  }
473  encryption_key->key_system_info.push_back(
474  ProtectionSystemInfoFromPsshProto(track.pssh(0)));
475  }
476  }
477  encryption_key_map[stream_label] = std::move(encryption_key);
478  }
479 
480  if (!widevine_classic) {
481  if (!UpdateProtectionSystemInfo(&encryption_key_map).ok()) {
482  return false;
483  }
484  }
485 
486  DCHECK(!encryption_key_map.empty());
487  if (!enable_key_rotation) {
488  // Merge with previously requested keys.
489  for (auto& pair : encryption_key_map)
490  encryption_key_map_[pair.first] = std::move(pair.second);
491  return true;
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
All the methods that are virtual are virtual for mocking.
Status UpdateProtectionSystemInfo(EncryptionKeyMap *encryption_key_map)
Definition: key_source.cc:48
Status GetKey(const std::string &stream_label, EncryptionKey *key) override
void set_key_fetcher(std::unique_ptr< KeyFetcher > key_fetcher)
WidevineKeySource(const std::string &server_url, int protection_systems_flags, FourCC protection_scheme)
static std::unique_ptr< PsshBoxBuilder > ParseFromBox(const uint8_t *data, size_t data_size)
void set_signer(std::unique_ptr< RequestSigner > signer)
KeySource is responsible for encryption key acquisition.
Definition: key_source.h:48
Status FetchKeys(EmeInitDataType init_data_type, const std::vector< uint8_t > &init_data) override
static bool ParseBoxes(const uint8_t *data, size_t data_size, std::vector< ProtectionSystemSpecificInfo > *pssh_boxes)
Status GetCryptoPeriodKey(uint32_t crypto_period_index, uint32_t crypto_period_duration_in_seconds, const std::string &stream_label, EncryptionKey *key) override