blob: 3b3ba1c978563c0f6d0c87cb7b96c08f6f11de1f [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/cdm/json_web_key.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "base/base64url.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "media/base/content_decryption_module.h"
namespace media {
const char kKeysTag[] = "keys";
const char kKeyTypeTag[] = "kty";
const char kKeyTypeOct[] = "oct"; // Octet sequence.
const char kKeyTag[] = "k";
const char kKeyIdTag[] = "kid";
const char kKeyIdsTag[] = "kids";
const char kTypeTag[] = "type";
const char kTemporarySession[] = "temporary";
const char kPersistentLicenseSession[] = "persistent-license";
const char kPersistentUsageRecordSession[] = "persistent-usage-record";
static std::string ShortenTo64Characters(const std::string& input) {
// Convert |input| into a string with escaped characters replacing any
// non-ASCII characters. Limiting |input| to the first 65 characters so
// we don't waste time converting a potentially long string and then
// throwing away the excess.
std::string escaped_str =
base::EscapeBytesAsInvalidJSONString(input.substr(0, 65), false);
if (escaped_str.length() <= 64u)
return escaped_str;
// This may end up truncating an escaped character, but the first part of
// the string should provide enough information.
return escaped_str.substr(0, 61).append("...");
}
static std::unique_ptr<base::DictionaryValue> CreateJSONDictionary(
const uint8_t* key,
int key_length,
const uint8_t* key_id,
int key_id_length) {
std::string key_string, key_id_string;
base::Base64UrlEncode(
base::StringPiece(reinterpret_cast<const char*>(key), key_length),
base::Base64UrlEncodePolicy::OMIT_PADDING, &key_string);
base::Base64UrlEncode(
base::StringPiece(reinterpret_cast<const char*>(key_id), key_id_length),
base::Base64UrlEncodePolicy::OMIT_PADDING, &key_id_string);
auto jwk = std::make_unique<base::DictionaryValue>();
jwk->SetString(kKeyTypeTag, kKeyTypeOct);
jwk->SetString(kKeyTag, key_string);
jwk->SetString(kKeyIdTag, key_id_string);
return jwk;
}
std::string GenerateJWKSet(const uint8_t* key,
int key_length,
const uint8_t* key_id,
int key_id_length) {
// Create the JWK, and wrap it into a JWK Set.
auto list = std::make_unique<base::ListValue>();
list->Append(CreateJSONDictionary(key, key_length, key_id, key_id_length));
base::DictionaryValue jwk_set;
jwk_set.Set(kKeysTag, std::move(list));
// Finally serialize |jwk_set| into a string and return it.
std::string serialized_jwk;
JSONStringValueSerializer serializer(&serialized_jwk);
serializer.Serialize(jwk_set);
return serialized_jwk;
}
std::string GenerateJWKSet(const KeyIdAndKeyPairs& keys,
CdmSessionType session_type) {
auto list = std::make_unique<base::ListValue>();
for (const auto& key_pair : keys) {
list->Append(CreateJSONDictionary(
reinterpret_cast<const uint8_t*>(key_pair.second.data()),
key_pair.second.length(),
reinterpret_cast<const uint8_t*>(key_pair.first.data()),
key_pair.first.length()));
}
base::DictionaryValue jwk_set;
jwk_set.Set(kKeysTag, std::move(list));
switch (session_type) {
case CdmSessionType::kTemporary:
jwk_set.SetString(kTypeTag, kTemporarySession);
break;
case CdmSessionType::kPersistentLicense:
jwk_set.SetString(kTypeTag, kPersistentLicenseSession);
break;
case CdmSessionType::kPersistentUsageRecord:
jwk_set.SetString(kTypeTag, kPersistentUsageRecordSession);
break;
}
// Finally serialize |jwk_set| into a string and return it.
std::string serialized_jwk;
JSONStringValueSerializer serializer(&serialized_jwk);
serializer.Serialize(jwk_set);
return serialized_jwk;
}
// Processes a JSON Web Key to extract the key id and key value. Sets |jwk_key|
// to the id/value pair and returns true on success.
static bool ConvertJwkToKeyPair(const base::DictionaryValue& jwk,
KeyIdAndKeyPair* jwk_key) {
std::string type;
if (!jwk.GetString(kKeyTypeTag, &type) || type != kKeyTypeOct) {
DVLOG(1) << "Missing or invalid '" << kKeyTypeTag << "': " << type;
return false;
}
// Get the key id and actual key parameters.
std::string encoded_key_id;
std::string encoded_key;
if (!jwk.GetString(kKeyIdTag, &encoded_key_id)) {
DVLOG(1) << "Missing '" << kKeyIdTag << "' parameter";
return false;
}
if (!jwk.GetString(kKeyTag, &encoded_key)) {
DVLOG(1) << "Missing '" << kKeyTag << "' parameter";
return false;
}
// Key ID and key are base64url-encoded strings, so decode them.
std::string raw_key_id;
if (!base::Base64UrlDecode(encoded_key_id,
base::Base64UrlDecodePolicy::DISALLOW_PADDING,
&raw_key_id) ||
raw_key_id.empty()) {
DVLOG(1) << "Invalid '" << kKeyIdTag << "' value: " << encoded_key_id;
return false;
}
std::string raw_key;
if (!base::Base64UrlDecode(encoded_key,
base::Base64UrlDecodePolicy::DISALLOW_PADDING,
&raw_key) ||
raw_key.empty()) {
DVLOG(1) << "Invalid '" << kKeyTag << "' value: " << encoded_key;
return false;
}
// Add the decoded key ID and the decoded key to the list.
*jwk_key = std::make_pair(raw_key_id, raw_key);
return true;
}
bool ExtractKeysFromJWKSet(const std::string& jwk_set,
KeyIdAndKeyPairs* keys,
CdmSessionType* session_type) {
if (!base::IsStringASCII(jwk_set)) {
DVLOG(1) << "Non ASCII JWK Set: " << jwk_set;
return false;
}
std::unique_ptr<base::Value> root(
base::JSONReader().ReadToValueDeprecated(jwk_set));
if (!root.get() || root->type() != base::Value::Type::DICTIONARY) {
DVLOG(1) << "Not valid JSON: " << jwk_set << ", root: " << root.get();
return false;
}
// Locate the set from the dictionary.
base::DictionaryValue* dictionary =
static_cast<base::DictionaryValue*>(root.get());
base::ListValue* list_val = NULL;
if (!dictionary->GetList(kKeysTag, &list_val)) {
DVLOG(1) << "Missing '" << kKeysTag
<< "' parameter or not a list in JWK Set";
return false;
}
// Create a local list of keys, so that |jwk_keys| only gets updated on
// success.
KeyIdAndKeyPairs local_keys;
for (size_t i = 0; i < list_val->GetSize(); ++i) {
base::DictionaryValue* jwk = NULL;
if (!list_val->GetDictionary(i, &jwk)) {
DVLOG(1) << "Unable to access '" << kKeysTag << "'[" << i
<< "] in JWK Set";
return false;
}
KeyIdAndKeyPair key_pair;
if (!ConvertJwkToKeyPair(*jwk, &key_pair)) {
DVLOG(1) << "Error from '" << kKeysTag << "'[" << i << "]";
return false;
}
local_keys.push_back(key_pair);
}
// Successfully processed all JWKs in the set. Now check if "type" is
// specified.
base::Value* value = NULL;
std::string session_type_id;
if (!dictionary->Get(kTypeTag, &value)) {
// Not specified, so use the default type.
*session_type = CdmSessionType::kTemporary;
} else if (!value->GetAsString(&session_type_id)) {
DVLOG(1) << "Invalid '" << kTypeTag << "' value";
return false;
} else if (session_type_id == kTemporarySession) {
*session_type = CdmSessionType::kTemporary;
} else if (session_type_id == kPersistentLicenseSession) {
*session_type = CdmSessionType::kPersistentLicense;
} else if (session_type_id == kPersistentUsageRecordSession) {
*session_type = CdmSessionType::kPersistentUsageRecord;
} else {
DVLOG(1) << "Invalid '" << kTypeTag << "' value: " << session_type_id;
return false;
}
// All done.
keys->swap(local_keys);
return true;
}
bool ExtractKeyIdsFromKeyIdsInitData(const std::string& input,
KeyIdList* key_ids,
std::string* error_message) {
if (!base::IsStringASCII(input)) {
error_message->assign("Non ASCII: ");
error_message->append(ShortenTo64Characters(input));
return false;
}
std::unique_ptr<base::Value> root(
base::JSONReader().ReadToValueDeprecated(input));
if (!root.get() || root->type() != base::Value::Type::DICTIONARY) {
error_message->assign("Not valid JSON: ");
error_message->append(ShortenTo64Characters(input));
return false;
}
// Locate the set from the dictionary.
base::DictionaryValue* dictionary =
static_cast<base::DictionaryValue*>(root.get());
base::ListValue* list_val = NULL;
if (!dictionary->GetList(kKeyIdsTag, &list_val)) {
error_message->assign("Missing '");
error_message->append(kKeyIdsTag);
error_message->append("' parameter or not a list");
return false;
}
// Create a local list of key ids, so that |key_ids| only gets updated on
// success.
KeyIdList local_key_ids;
for (size_t i = 0; i < list_val->GetSize(); ++i) {
std::string encoded_key_id;
if (!list_val->GetString(i, &encoded_key_id)) {
error_message->assign("'");
error_message->append(kKeyIdsTag);
error_message->append("'[");
error_message->append(base::NumberToString(i));
error_message->append("] is not string.");
return false;
}
// Key ID is a base64url-encoded string, so decode it.
std::string raw_key_id;
if (!base::Base64UrlDecode(encoded_key_id,
base::Base64UrlDecodePolicy::DISALLOW_PADDING,
&raw_key_id) ||
raw_key_id.empty()) {
error_message->assign("'");
error_message->append(kKeyIdsTag);
error_message->append("'[");
error_message->append(base::NumberToString(i));
error_message->append("] is not valid base64url encoded. Value: ");
error_message->append(ShortenTo64Characters(encoded_key_id));
return false;
}
// Add the decoded key ID to the list.
local_key_ids.push_back(std::vector<uint8_t>(
raw_key_id.data(), raw_key_id.data() + raw_key_id.length()));
}
// All done.
key_ids->swap(local_key_ids);
error_message->clear();
return true;
}
void CreateLicenseRequest(const KeyIdList& key_ids,
CdmSessionType session_type,
std::vector<uint8_t>* license) {
// Create the license request.
auto request = std::make_unique<base::DictionaryValue>();
auto list = std::make_unique<base::ListValue>();
for (const auto& key_id : key_ids) {
std::string key_id_string;
base::Base64UrlEncode(
base::StringPiece(reinterpret_cast<const char*>(key_id.data()),
key_id.size()),
base::Base64UrlEncodePolicy::OMIT_PADDING, &key_id_string);
list->AppendString(key_id_string);
}
request->Set(kKeyIdsTag, std::move(list));
switch (session_type) {
case CdmSessionType::kTemporary:
request->SetString(kTypeTag, kTemporarySession);
break;
case CdmSessionType::kPersistentLicense:
request->SetString(kTypeTag, kPersistentLicenseSession);
break;
case CdmSessionType::kPersistentUsageRecord:
request->SetString(kTypeTag, kPersistentUsageRecordSession);
break;
}
// Serialize the license request as a string.
std::string json;
JSONStringValueSerializer serializer(&json);
serializer.Serialize(*request);
// Convert the serialized license request into std::vector and return it.
std::vector<uint8_t> result(json.begin(), json.end());
license->swap(result);
}
void CreateKeyIdsInitData(const KeyIdList& key_ids,
std::vector<uint8_t>* init_data) {
// Create the init_data.
auto dictionary = std::make_unique<base::DictionaryValue>();
auto list = std::make_unique<base::ListValue>();
for (const auto& key_id : key_ids) {
std::string key_id_string;
base::Base64UrlEncode(
base::StringPiece(reinterpret_cast<const char*>(key_id.data()),
key_id.size()),
base::Base64UrlEncodePolicy::OMIT_PADDING, &key_id_string);
list->AppendString(key_id_string);
}
dictionary->Set(kKeyIdsTag, std::move(list));
// Serialize the dictionary as a string.
std::string json;
JSONStringValueSerializer serializer(&json);
serializer.Serialize(*dictionary);
// Convert the serialized data into std::vector and return it.
std::vector<uint8_t> result(json.begin(), json.end());
init_data->swap(result);
}
bool ExtractFirstKeyIdFromLicenseRequest(const std::vector<uint8_t>& license,
std::vector<uint8_t>* first_key) {
const std::string license_as_str(
reinterpret_cast<const char*>(!license.empty() ? &license[0] : NULL),
license.size());
if (!base::IsStringASCII(license_as_str)) {
DVLOG(1) << "Non ASCII license: " << license_as_str;
return false;
}
std::unique_ptr<base::Value> root(
base::JSONReader().ReadToValueDeprecated(license_as_str));
if (!root.get() || root->type() != base::Value::Type::DICTIONARY) {
DVLOG(1) << "Not valid JSON: " << license_as_str;
return false;
}
// Locate the set from the dictionary.
base::DictionaryValue* dictionary =
static_cast<base::DictionaryValue*>(root.get());
base::ListValue* list_val = NULL;
if (!dictionary->GetList(kKeyIdsTag, &list_val)) {
DVLOG(1) << "Missing '" << kKeyIdsTag << "' parameter or not a list";
return false;
}
// Get the first key.
if (list_val->GetSize() < 1) {
DVLOG(1) << "Empty '" << kKeyIdsTag << "' list";
return false;
}
std::string encoded_key;
if (!list_val->GetString(0, &encoded_key)) {
DVLOG(1) << "First entry in '" << kKeyIdsTag << "' not a string";
return false;
}
std::string decoded_string;
if (!base::Base64UrlDecode(encoded_key,
base::Base64UrlDecodePolicy::DISALLOW_PADDING,
&decoded_string) ||
decoded_string.empty()) {
DVLOG(1) << "Invalid '" << kKeyIdsTag << "' value: " << encoded_key;
return false;
}
std::vector<uint8_t> result(decoded_string.begin(), decoded_string.end());
first_key->swap(result);
return true;
}
} // namespace media