Shaka Player Embedded
clearkey_implementation.cc
Go to the documentation of this file.
1 // Copyright 2017 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
16 
17 #include <algorithm>
18 #include <utility>
19 
20 #include "src/js/base_64.h"
21 #include "src/js/js_error.h"
23 #include "src/util/buffer_reader.h"
24 #include "src/util/utils.h"
25 
26 namespace shaka {
27 namespace eme {
28 
29 namespace {
30 
31 bool ParseKeyIds(const Data& data, std::vector<std::string>* base64_keyids) {
32  LocalVar<JsValue> data_val =
33  ParseJsonString(std::string(data.data(), data.data() + data.size()));
34  if (!IsObject(data_val)) {
35  LOG(ERROR) << "Init data is not valid JSON.";
36  return false;
37  }
38  LocalVar<JsObject> data_obj = UnsafeJsCast<JsObject>(data_val);
39  LocalVar<JsValue> kids = GetMemberRaw(data_obj, "kids");
41  LOG(ERROR) << "Init data doesn't have valid 'kids' member.";
42  return false;
43  }
44 
45  LocalVar<JsObject> kids_obj = UnsafeJsCast<JsObject>(kids);
46  const size_t kid_count = ArrayLength(kids_obj);
47  base64_keyids->reserve(kid_count);
48  for (size_t i = 0; i < kid_count; i++) {
49  LocalVar<JsValue> entry = GetArrayIndexRaw(kids_obj, i);
50  if (GetValueType(entry) != proto::ValueType::String) {
51  LOG(ERROR) << "Init data doesn't have valid 'kids' member.";
52  return false;
53  }
54  base64_keyids->emplace_back(ConvertToString(entry));
55  }
56  return true;
57 }
58 
59 bool ParsePssh(const Data& data, std::vector<std::string>* base64_keyids) {
60  // 4 box size
61  // 4 box type
62  // 4 version + flags
63  // 16 system-id
64  // if (version > 0)
65  // 4 key id count
66  // for (key id count)
67  // 16 key id
68  // 4 data size
69  // [data size] data
70  constexpr const size_t kSystemIdSize = 16;
71  constexpr const size_t kKeyIdSize = 16;
72  constexpr const uint8_t kCommonSystemId[] = {
73  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
74  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b};
75 
76  util::BufferReader reader(data.data(), data.size());
77  while (!reader.empty()) {
78  const size_t box_start_remaining = reader.BytesRemaining();
79  const uint32_t box_size = reader.ReadUint32();
80 
81  if (reader.ReadUint32() != 0x70737368) { // 'pssh'
82  LOG(ERROR) << "Init data is not a PSSH box";
83  return false;
84  }
85 
86  const uint8_t version = reader.ReadUint8();
87  reader.Skip(3);
88 
89  uint8_t system_id[kSystemIdSize];
90  if (reader.Read(system_id, kSystemIdSize) != kSystemIdSize) {
91  LOG(ERROR) << "Truncated init data";
92  return false;
93  }
94 
95  if (memcmp(system_id, kCommonSystemId, kSystemIdSize) != 0) {
96  VLOG(1) << "Ignoring non-common PSSH box";
97  const size_t bytes_read = box_start_remaining - reader.BytesRemaining();
98  reader.Skip(box_size - bytes_read);
99  continue;
100  }
101 
102  if (version == 0) {
103  LOG(ERROR) << "PSSH version 0 is not supported for clear-key";
104  return false;
105  }
106 
107  const uint32_t key_id_count = reader.ReadUint32();
108  if (reader.BytesRemaining() / kKeyIdSize < key_id_count) {
109  LOG(ERROR) << "Truncated init data";
110  return false;
111  }
112 
113  base64_keyids->reserve(key_id_count);
114  for (uint32_t i = 0; i < key_id_count; i++) {
115  uint8_t key_id[kKeyIdSize];
116  if (reader.Read(key_id, kKeyIdSize) != kKeyIdSize) {
117  LOG(ERROR) << "Truncated init data";
118  return false;
119  }
120  base64_keyids->emplace_back(
121  js::Base64::EncodeUrl(ByteString(key_id, key_id + kKeyIdSize)));
122  }
123  return true;
124  }
125 
126  LOG(ERROR) << "No PSSH box with common system ID found";
127  return false;
128 }
129 
130 bool ParseAndGenerateRequest(MediaKeyInitDataType type, const Data& data,
131  std::string* message) {
132  std::vector<std::string> key_ids;
133  switch (type) {
135  if (!ParseKeyIds(data, &key_ids))
136  return false;
137  break;
139  if (!ParsePssh(data, &key_ids))
140  return false;
141  break;
142 
143  default:
144  LOG(ERROR) << "Init data type not supported.";
145  return false;
146  }
147 
148  std::string ids_json;
149  for (const std::string& id : key_ids) {
150  if (ids_json.empty())
151  ids_json = '"' + id + '"';
152  else
153  ids_json += R"(,")" + id + '"';
154  }
155  *message = R"({"kids":[)" + ids_json + R"(],"type":"temporary"})";
156  return true;
157 }
158 
159 // This needs to be a template to access the private type |Session::Key|.
160 template <typename KeyType>
161 bool ParseResponse(const Data& data, std::list<KeyType>* keys) {
162  LocalVar<JsValue> data_val =
163  ParseJsonString(std::string(data.data(), data.data() + data.size()));
164  if (!IsObject(data_val)) {
165  LOG(ERROR) << "License response is not valid JSON.";
166  return false;
167  }
168  LocalVar<JsObject> data_obj = UnsafeJsCast<JsObject>(data_val);
169  LocalVar<JsValue> keys_val = GetMemberRaw(data_obj, "keys");
170  if (GetValueType(keys_val) != proto::ValueType::Array) {
171  LOG(ERROR) << "License response doesn't contain a valid 'keys' member.";
172  return false;
173  }
174 
175  LocalVar<JsObject> keys_array = UnsafeJsCast<JsObject>(keys_val);
176  const size_t key_count = ArrayLength(keys_array);
177  for (size_t i = 0; i < key_count; i++) {
178  LocalVar<JsValue> entry = GetArrayIndexRaw(keys_array, i);
179  if (!IsObject(entry)) {
180  LOG(ERROR) << "License response doesn't contain a valid 'keys' member.";
181  return false;
182  }
183  LocalVar<JsObject> entry_obj = UnsafeJsCast<JsObject>(entry);
184 
185  LocalVar<JsValue> k_val = GetMemberRaw(entry_obj, "k");
186  LocalVar<JsValue> kid_val = GetMemberRaw(entry_obj, "kid");
187  if (GetValueType(k_val) != proto::ValueType::String ||
189  LOG(ERROR) << "License response contains invalid key object.";
190  return false;
191  }
192 
193  ExceptionOr<ByteString> k = js::Base64::DecodeUrl(ConvertToString(k_val));
194  ExceptionOr<ByteString> kid =
196  if (holds_alternative<js::JsError>(k) ||
197  holds_alternative<js::JsError>(kid)) {
198  LOG(ERROR) << "License response contains invalid base-64 encoding.";
199  return false;
200  }
201  if (get<ByteString>(k).size() != 16 || get<ByteString>(kid).size() != 16) {
202  LOG(ERROR) << "Key or key ID is not correct size.";
203  return false;
204  }
205  keys->emplace_back(std::move(get<ByteString>(kid)),
206  std::move(get<ByteString>(k)));
207  }
208 
209  return true;
210 }
211 
212 } // namespace
213 
215  : helper_(helper), cur_session_id_(0) {}
217 
218 bool ClearKeyImplementation::GetExpiration(const std::string& session_id,
219  int64_t* expiration) const {
220  *expiration = -1;
221  return sessions_.count(session_id) != 0;
222 }
223 
225  const std::string& session_id, std::vector<KeyStatusInfo>* statuses) const {
226  std::unique_lock<std::mutex> lock(mutex_);
227  if (sessions_.count(session_id) == 0)
228  return false;
229 
230  statuses->clear();
231  for (auto& key : sessions_.at(session_id).keys)
232  statuses->emplace_back(key.key_id, MediaKeyStatus::Usable);
233  return true;
234 }
235 
237  Data /* cert */) {
238  promise.ResolveWith(false);
239 }
240 
242  EmePromise promise, std::function<void(const std::string&)> set_session_id,
243  MediaKeySessionType session_type, MediaKeyInitDataType init_data_type,
244  Data data) {
245  DCHECK(session_type == MediaKeySessionType::Temporary);
246 
247  std::unique_lock<std::mutex> lock(mutex_);
248 
249  const std::string session_id = std::to_string(++cur_session_id_);
250  // The indexer will create a new object since it doesn't exist.
251  Session* session = &sessions_[session_id];
252  DCHECK(!session->callable);
253 
254  std::string message;
255  if (!ParseAndGenerateRequest(init_data_type, data, &message)) {
257  "Invalid initialization data given.");
258  return;
259  }
260 
261  session->callable = true;
262  set_session_id(session_id);
263  helper_->OnMessage(session_id, MediaKeyMessageType::LicenseRequest,
264  reinterpret_cast<const uint8_t*>(message.c_str()),
265  message.size());
266  promise.Resolve();
267 }
268 
269 void ClearKeyImplementation::Load(const std::string& /* session_id */,
270  EmePromise promise) {
272  "Clear-key doesn't support persistent licences.");
273 }
274 
275 void ClearKeyImplementation::Update(const std::string& session_id,
276  EmePromise promise, Data data) {
277  std::unique_lock<std::mutex> lock(mutex_);
278 
279  if (sessions_.count(session_id) == 0) {
281  "Unable to find given session ID.");
282  return;
283  }
284  Session* session = &sessions_.at(session_id);
285 
286  if (!session->callable) {
287  promise.Reject(ExceptionType::InvalidState, "Not expecting an update.");
288  return;
289  }
290 
291  std::list<Session::Key> keys;
292  if (!ParseResponse(data, &keys)) {
293  promise.Reject(ExceptionType::InvalidState, "Invalid response data.");
294  return;
295  }
296 
297  session->callable = false;
298  // Move all keys into the session.
299  session->keys.splice(session->keys.end(), std::move(keys));
300  helper_->OnKeyStatusChange(session_id);
301  promise.Resolve();
302 }
303 
304 void ClearKeyImplementation::Close(const std::string& session_id,
305  EmePromise promise) {
306  std::unique_lock<std::mutex> lock(mutex_);
307  sessions_.erase(session_id);
308  promise.Resolve();
309 }
310 
311 void ClearKeyImplementation::Remove(const std::string& /* session_id */,
312  EmePromise promise) {
314  "Clear-key doesn't support persistent licences.");
315 }
316 
318  const uint8_t* data,
319  size_t data_size,
320  uint8_t* dest) const {
321  std::unique_lock<std::mutex> lock(mutex_);
322 
323  const std::vector<uint8_t>* key = nullptr;
324  for (auto& session_pair : sessions_) {
325  for (auto& cur_key : session_pair.second.keys) {
326  if (cur_key.key_id == info->key_id) {
327  key = &cur_key.key;
328  break;
329  }
330  }
331  }
332  if (!key) {
333  LOG(ERROR) << "Unable to find key ID: "
334  << util::ToHexString(info->key_id.data(), info->key_id.size());
336  }
337 
338  util::Decryptor decryptor(info->scheme, *key, info->iv);
339  if (info->subsamples.empty()) {
340  return DecryptBlock(info, data, data_size, 0, dest, &decryptor);
341  } else {
342  size_t block_offset = 0;
343  for (const auto& subsample : info->subsamples) {
344  if (data_size < subsample.clear_bytes ||
345  data_size - subsample.clear_bytes < subsample.protected_bytes) {
346  LOG(ERROR) << "Input data not large enough for subsamples";
348  }
349 
350  // The clear portion appears first.
351  memcpy(dest, data, subsample.clear_bytes);
352  data += subsample.clear_bytes;
353  dest += subsample.clear_bytes;
354  data_size -= subsample.clear_bytes;
355 
356  // Then the encrypted portion.
357  const auto ret = DecryptBlock(info, data, subsample.protected_bytes,
358  block_offset, dest, &decryptor);
359  if (ret != DecryptStatus::Success)
360  return ret;
361  data += subsample.protected_bytes;
362  dest += subsample.protected_bytes;
363  data_size -= subsample.protected_bytes;
364  block_offset =
365  (block_offset + subsample.protected_bytes) % AES_BLOCK_SIZE;
366 
367  // iv changing is handled by Decryptor.
368  }
369  if (data_size != 0) {
370  LOG(ERROR) << "Data remaining after subsample handling";
372  }
373  return DecryptStatus::Success;
374  }
375 }
376 
377 DecryptStatus ClearKeyImplementation::DecryptBlock(
378  const FrameEncryptionInfo* info, const uint8_t* data, size_t data_size,
379  size_t block_offset, uint8_t* dest, util::Decryptor* decryptor) const {
380  size_t num_bytes_read = 0;
381  if (block_offset != 0) {
382  if (info->pattern.clear_blocks != 0) {
383  LOG(ERROR) << "Cannot have block offset when using pattern encryption";
385  }
386 
387  num_bytes_read = std::min<size_t>(data_size, AES_BLOCK_SIZE - block_offset);
388  if (!decryptor->DecryptPartialBlock(data, num_bytes_read, block_offset,
389  dest)) {
391  }
392  }
393 
394  if (info->pattern.clear_blocks != 0) {
395  const size_t protected_size =
397  const size_t clear_size = AES_BLOCK_SIZE * info->pattern.clear_blocks;
398  const size_t pattern_size_in_blocks =
400  const size_t data_size_in_blocks = data_size / AES_BLOCK_SIZE;
401  for (size_t i = 0; i < data_size_in_blocks / pattern_size_in_blocks; i++) {
402  if (!decryptor->Decrypt(data + num_bytes_read, protected_size,
403  dest + num_bytes_read)) {
405  }
406  num_bytes_read += protected_size;
407 
408  memcpy(dest + num_bytes_read, data + num_bytes_read, clear_size);
409  num_bytes_read += clear_size;
410  }
411 
412  // If the last pattern block isn't big enough for the whole
413  // encrypted_blocks, then it is ignored.
414  if (data_size_in_blocks % pattern_size_in_blocks >=
415  info->pattern.encrypted_blocks) {
416  if (!decryptor->Decrypt(data + num_bytes_read, protected_size,
417  dest + num_bytes_read)) {
419  }
420  num_bytes_read += protected_size;
421  }
422 
423  memcpy(dest + num_bytes_read, data + num_bytes_read,
424  data_size - num_bytes_read);
425  } else {
426  if (!decryptor->Decrypt(data + num_bytes_read, data_size - num_bytes_read,
427  dest + num_bytes_read)) {
429  }
430  }
431 
432  return DecryptStatus::Success;
433 }
434 
435 void ClearKeyImplementation::LoadKeyForTesting(std::vector<uint8_t> key_id,
436  std::vector<uint8_t> key) {
437  std::unique_lock<std::mutex> lock(mutex_);
438  const std::string session_id = std::to_string(++cur_session_id_);
439  sessions_.emplace(session_id, Session());
440  sessions_.at(session_id).keys.emplace_back(std::move(key_id), std::move(key));
441 }
442 
443 ClearKeyImplementation::Session::Key::Key(std::vector<uint8_t> key_id,
444  std::vector<uint8_t> key)
445  : key_id(std::move(key_id)), key(std::move(key)) {}
447 
448 ClearKeyImplementation::Session::Session() {}
449 ClearKeyImplementation::Session::~Session() {}
450 
451 ClearKeyImplementation::Session::Session(Session&&) = default;
452 ClearKeyImplementation::Session& ClearKeyImplementation::Session::operator=(
453  Session&&) = default;
454 
455 } // namespace eme
456 } // namespace shaka
void CreateSessionAndGenerateRequest(EmePromise promise, std::function< void(const std::string &)> set_session_id, MediaKeySessionType session_type, MediaKeyInitDataType init_data_type, Data data) override
void Load(const std::string &session_id, EmePromise promise) override
ReturnVal< JsValue > ParseJsonString(const std::string &json)
Definition: js_wrappers.cc:256
const EncryptionPattern pattern
const char * dest
Definition: media_utils.cc:31
std::string ToHexString(const uint8_t *data, size_t data_size)
Definition: utils.cc:97
ClearKeyImplementation(ImplementationHelper *helper)
bool IsObject(Handle< JsValue > value)
Definition: js_wrappers.cc:315
ReturnVal< JsValue > GetArrayIndexRaw(Handle< JsObject > object, size_t index, LocalVar< JsValue > *exception=nullptr)
Definition: js_wrappers.cc:142
void SetServerCertificate(EmePromise promise, Data cert) override
static std::string EncodeUrl(ByteString input)
Definition: base_64.cc:152
static ExceptionOr< ByteString > DecodeUrl(const std::string &input)
Definition: base_64.cc:164
void Remove(const std::string &session_id, EmePromise promise) override
void Close(const std::string &session_id, EmePromise promise) override
virtual void OnMessage(const std::string &session_id, MediaKeyMessageType message_type, const uint8_t *data, size_t data_size) const =0
bool DecryptPartialBlock(const uint8_t *data, size_t data_size, uint32_t block_offset, uint8_t *dest)
std::string to_string(VideoReadyState state)
Definition: media_player.cc:32
ExceptionCode type
void Update(const std::string &session_id, EmePromise promise, Data data) override
DecryptStatus Decrypt(const FrameEncryptionInfo *info, const uint8_t *data, size_t data_size, uint8_t *dest) const override
proto::ValueType GetValueType(Handle< JsValue > value)
Definition: js_wrappers.cc:329
virtual void OnKeyStatusChange(const std::string &session_id) const =0
Key(std::vector< uint8_t > key_id, std::vector< uint8_t > key)
void ResolveWith(bool value)
Definition: eme_promise.cc:138
const std::vector< uint8_t > iv
std::string ConvertToString(Handle< JsValue > value)
Definition: js_wrappers.cc:203
const char * message
size_t ArrayLength(Handle< JsObject > value)
Definition: js_wrappers.h:418
bool Decrypt(const uint8_t *data, size_t data_size, uint8_t *dest)
void Reject(ExceptionType except_type, const std::string &message)
Definition: eme_promise.cc:142
bool GetKeyStatuses(const std::string &session_id, std::vector< KeyStatusInfo > *statuses) const override
const EncryptionScheme scheme
ReturnVal< JsValue > GetMemberRaw(Handle< JsObject > object, const std::string &name, LocalVar< JsValue > *exception=nullptr)
Definition: js_wrappers.cc:136
#define AES_BLOCK_SIZE
const std::vector< SubsampleInfo > subsamples
const std::vector< uint8_t > key_id
bool GetExpiration(const std::string &session_id, int64_t *expiration) const override