// 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 "base/base64.h" #include "base/json/json_reader.h" #include "base/json/json_string_value_serializer.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" #include "base/values.h" namespace media { const char kKeysTag[] = "keys"; const char kKeyTypeTag[] = "kty"; const char kSymmetricKeyValue[] = "oct"; const char kKeyTag[] = "k"; const char kKeyIdTag[] = "kid"; const char kBase64Padding = '='; // Encodes |input| into a base64 string without padding. static std::string EncodeBase64(const uint8* input, int input_length) { std::string encoded_text; base::Base64Encode( std::string(reinterpret_cast(input), input_length), &encoded_text); // Remove any padding characters added by Base64Encode(). size_t found = encoded_text.find_last_not_of(kBase64Padding); if (found != std::string::npos) encoded_text.erase(found + 1); return encoded_text; } // Decodes an unpadded base64 string. Returns empty string on error. static std::string DecodeBase64(const std::string& encoded_text) { // EME spec doesn't allow padding characters. if (encoded_text.find_first_of(kBase64Padding) != std::string::npos) return std::string(); // Since base::Base64Decode() requires padding characters, add them so length // of |encoded_text| is exactly a multiple of 4. size_t num_last_grouping_chars = encoded_text.length() % 4; std::string modified_text = encoded_text; if (num_last_grouping_chars > 0) modified_text.append(4 - num_last_grouping_chars, kBase64Padding); std::string decoded_text; if (!base::Base64Decode(modified_text, &decoded_text)) return std::string(); return decoded_text; } std::string GenerateJWKSet(const uint8* key, int key_length, const uint8* key_id, int key_id_length) { // Both |key| and |key_id| need to be base64 encoded strings in the JWK. std::string key_base64 = EncodeBase64(key, key_length); std::string key_id_base64 = EncodeBase64(key_id, key_id_length); // Create the JWK, and wrap it into a JWK Set. scoped_ptr jwk(new base::DictionaryValue()); jwk->SetString(kKeyTypeTag, kSymmetricKeyValue); jwk->SetString(kKeyTag, key_base64); jwk->SetString(kKeyIdTag, key_id_base64); scoped_ptr list(new base::ListValue()); list->Append(jwk.release()); base::DictionaryValue jwk_set; jwk_set.Set(kKeysTag, list.release()); // 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 DictionaryValue& jwk, KeyIdAndKeyPair* jwk_key) { // Have found a JWK, start by checking that it is a symmetric key. std::string type; if (!jwk.GetString(kKeyTypeTag, &type) || type != kSymmetricKeyValue) { DVLOG(1) << "JWK is not a symmetric key"; 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 base64-encoded strings, so decode them. std::string raw_key_id = DecodeBase64(encoded_key_id); if (raw_key_id.empty()) { DVLOG(1) << "Invalid '" << kKeyIdTag << "' value: " << encoded_key_id; return false; } std::string raw_key = DecodeBase64(encoded_key); if (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) { if (!IsStringASCII(jwk_set)) return false; scoped_ptr root(base::JSONReader().ReadToValue(jwk_set)); if (!root.get() || root->GetType() != Value::TYPE_DICTIONARY) return false; // Locate the set from the dictionary. DictionaryValue* dictionary = static_cast(root.get()); 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) { 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. keys->swap(local_keys); return true; } } // namespace media