diff options
-rw-r--r-- | media/cdm/aes_decryptor.cc | 131 | ||||
-rw-r--r-- | media/cdm/aes_decryptor_unittest.cc | 44 | ||||
-rw-r--r-- | media/cdm/json_web_key.cc | 132 | ||||
-rw-r--r-- | media/cdm/json_web_key.h | 28 | ||||
-rw-r--r-- | media/cdm/json_web_key_unittest.cc | 186 | ||||
-rw-r--r-- | media/media.gyp | 1 |
6 files changed, 362 insertions, 160 deletions
diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc index 70a0a58..10f0499 100644 --- a/media/cdm/aes_decryptor.cc +++ b/media/cdm/aes_decryptor.cc @@ -6,13 +6,9 @@ #include <vector> -#include "base/base64.h" -#include "base/json/json_reader.h" #include "base/logging.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/string_util.h" -#include "base/values.h" #include "crypto/encryptor.h" #include "crypto/symmetric_key.h" #include "media/base/audio_decoder_config.h" @@ -20,6 +16,7 @@ #include "media/base/decrypt_config.h" #include "media/base/video_decoder_config.h" #include "media/base/video_frame.h" +#include "media/cdm/json_web_key.h" namespace media { @@ -30,8 +27,6 @@ enum ClearBytesBufferSel { kDstContainsClearBytes }; -typedef std::vector<std::pair<std::string, std::string> > JWKKeys; - static void CopySubsamples(const std::vector<SubsampleEntry>& subsamples, const ClearBytesBufferSel sel, const uint8* src, @@ -49,122 +44,6 @@ static void CopySubsamples(const std::vector<SubsampleEntry>& subsamples, } } -// Helper to decode a base64 string. EME spec doesn't allow padding characters, -// but base::Base64Decode() requires them. So check that they're not there, and -// then add them before calling base::Base64Decode(). -static bool DecodeBase64(std::string encoded_text, std::string* decoded_text) { - const char base64_padding = '='; - - // TODO(jrummell): Enable this after layout tests have been updated to not - // include trailing padding characters. - // if (encoded_text.back() == base64_padding) - // return false; - - // Add pad characters so length of |encoded_text| is exactly a multiple of 4. - size_t num_last_grouping_chars = encoded_text.length() % 4; - if (num_last_grouping_chars > 0) - encoded_text.append(4 - num_last_grouping_chars, base64_padding); - - return base::Base64Decode(encoded_text, decoded_text); -} - -// Processes a JSON Web Key to extract the key id and key value. Adds the -// id/value pair to |jwk_keys| and returns true on success. -static bool ProcessSymmetricKeyJWK(const DictionaryValue& jwk, - JWKKeys* jwk_keys) { - // A symmetric keys JWK looks like the following in JSON: - // { "kty":"oct", - // "kid":"AAECAwQFBgcICQoLDA0ODxAREhM", - // "k":"FBUWFxgZGhscHR4fICEiIw" } - // There may be other properties specified, but they are ignored. - // Ref: http://tools.ietf.org/html/draft-ietf-jose-json-web-key-14 - // and: - // http://tools.ietf.org/html/draft-jones-jose-json-private-and-symmetric-key-00 - - // Have found a JWK, start by checking that it is a symmetric key. - std::string type; - if (!jwk.GetString("kty", &type) || type != "oct") { - 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("kid", &encoded_key_id)) { - DVLOG(1) << "Missing 'kid' parameter"; - return false; - } - if (!jwk.GetString("k", &encoded_key)) { - DVLOG(1) << "Missing 'k' parameter"; - return false; - } - - // Key ID and key are base64-encoded strings, so decode them. - std::string decoded_key_id; - std::string decoded_key; - if (!DecodeBase64(encoded_key_id, &decoded_key_id) || - decoded_key_id.empty()) { - DVLOG(1) << "Invalid 'kid' value"; - return false; - } - if (!DecodeBase64(encoded_key, &decoded_key) || - decoded_key.length() != - static_cast<size_t>(DecryptConfig::kDecryptionKeySize)) { - DVLOG(1) << "Invalid length of 'k' " << decoded_key.length(); - return false; - } - - // Add the decoded key ID and the decoded key to the list. - jwk_keys->push_back(std::make_pair(decoded_key_id, decoded_key)); - return true; -} - -// Extracts the JSON Web Keys from a JSON Web Key Set. If |input| looks like -// a valid JWK Set, then true is returned and |jwk_keys| is updated to contain -// the list of keys found. Otherwise return false. -static bool ExtractJWKKeys(const std::string& input, JWKKeys* jwk_keys) { - // TODO(jrummell): The EME spec references a smaller set of allowed ASCII - // values. Verify with spec that the smaller character set is needed. - if (!IsStringASCII(input)) - return false; - - scoped_ptr<Value> root(base::JSONReader().ReadToValue(input)); - if (!root.get() || root->GetType() != Value::TYPE_DICTIONARY) - return false; - - // A JSON Web Key Set looks like the following in JSON: - // { "keys": [ JWK1, JWK2, ... ] } - // (See ProcessSymmetricKeyJWK() for description of JWK.) - // There may be other properties specified, but they are ignored. - // Locate the set from the dictionary. - DictionaryValue* dictionary = static_cast<DictionaryValue*>(root.get()); - ListValue* list_val = NULL; - if (!dictionary->GetList("keys", &list_val)) { - DVLOG(1) << "Missing 'keys' 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. - JWKKeys 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 'keys'[" << i << "] in JWK Set"; - return false; - } - if (!ProcessSymmetricKeyJWK(*jwk, &local_keys)) { - DVLOG(1) << "Error from 'keys'[" << i << "]"; - return false; - } - } - - // Successfully processed all JWKs in the set. - jwk_keys->swap(local_keys); - return true; -} - // Decrypts |input| using |key|. Returns a DecoderBuffer with the decrypted // data if decryption succeeded or NULL if decryption failed. static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input, @@ -308,19 +187,19 @@ void AesDecryptor::AddKey(uint32 reference_id, // with 'kid' being the base64-encoded key id, and 'k' being the // base64-encoded key. std::string key_string(reinterpret_cast<const char*>(key), key_length); - JWKKeys jwk_keys; - if (!ExtractJWKKeys(key_string, &jwk_keys)) { + KeyIdAndKeyPairs keys; + if (!ExtractKeysFromJWKSet(key_string, &keys)) { key_error_cb_.Run(reference_id, MediaKeys::kUnknownError, 0); return; } // Make sure that at least one key was extracted. - if (jwk_keys.empty()) { + if (keys.empty()) { key_error_cb_.Run(reference_id, MediaKeys::kUnknownError, 0); return; } - for (JWKKeys::iterator it = jwk_keys.begin() ; it != jwk_keys.end(); ++it) { + for (KeyIdAndKeyPairs::iterator it = keys.begin(); it != keys.end(); ++it) { if (it->second.length() != static_cast<size_t>(DecryptConfig::kDecryptionKeySize)) { DVLOG(1) << "Invalid key length: " << key_string.length(); diff --git a/media/cdm/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc index 5d41515..d9730a2 100644 --- a/media/cdm/aes_decryptor_unittest.cc +++ b/media/cdm/aes_decryptor_unittest.cc @@ -534,16 +534,16 @@ TEST_F(AesDecryptorTest, SubsampleCypherBytesOnly) { TEST_F(AesDecryptorTest, JWKKey) { // Try a simple JWK key (i.e. not in a set) - const std::string key1 = + const std::string kJwkSimple = "{" " \"kty\": \"oct\"," " \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\"," " \"k\": \"FBUWFxgZGhscHR4fICEiIw\"" "}"; - AddKeyAndExpect(key1, KEY_ERROR); + AddKeyAndExpect(kJwkSimple, KEY_ERROR); // Try a key list with multiple entries. - const std::string key2 = + const std::string kJwksMultipleEntries = "{" " \"keys\": [" " {" @@ -558,14 +558,14 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - AddKeyAndExpect(key2, KEY_ADDED); + AddKeyAndExpect(kJwksMultipleEntries, KEY_ADDED); // Try a key with no spaces and some \n plus additional fields. - const std::string key3 = + const std::string kJwksNoSpaces = "\n\n{\"something\":1,\"keys\":[{\n\n\"kty\":\"oct\",\"alg\":\"A128KW\"," "\"kid\":\"AAECAwQFBgcICQoLDA0ODxAREhM\",\"k\":\"GawgguFyGrWKav7AX4VKUg" "\",\"foo\":\"bar\"}]}\n\n"; - AddKeyAndExpect(key3, KEY_ADDED); + AddKeyAndExpect(kJwksNoSpaces, KEY_ADDED); // Try some non-ASCII characters. AddKeyAndExpect("This is not ASCII due to \xff\xfe\xfd in it.", KEY_ERROR); @@ -590,10 +590,8 @@ TEST_F(AesDecryptorTest, JWKKey) { // Try with 'keys' a list of integers. AddKeyAndExpect("{ \"keys\": [ 1, 2, 3 ] }", KEY_ERROR); - // TODO(jrummell): The next 2 tests should fail once checking for padding - // characters is enabled. - // Try a key with padding(=) at end of base64 string. - const std::string key4 = + // Try padding(=) at end of 'k' base64 string. + const std::string kJwksWithPaddedKey = "{" " \"keys\": [" " {" @@ -603,10 +601,10 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - AddKeyAndExpect(key4, KEY_ADDED); + AddKeyAndExpect(kJwksWithPaddedKey, KEY_ERROR); - // Try a key ID with padding(=) at end of base64 string. - const std::string key5 = + // Try padding(=) at end of 'kid' base64 string. + const std::string kJwksWithPaddedKeyId = "{" " \"keys\": [" " {" @@ -616,10 +614,10 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - AddKeyAndExpect(key5, KEY_ADDED); + AddKeyAndExpect(kJwksWithPaddedKeyId, KEY_ERROR); // Try a key with invalid base64 encoding. - const std::string key6 = + const std::string kJwksWithInvalidBase64 = "{" " \"keys\": [" " {" @@ -629,12 +627,12 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - AddKeyAndExpect(key6, KEY_ERROR); + AddKeyAndExpect(kJwksWithInvalidBase64, KEY_ERROR); - // Try a key where no padding is required. 'k' has to be 16 bytes, so it - // will always require padding. (Test above using |key2| has 2 'kid's that - // require 1 and 2 padding bytes). - const std::string key7 = + // Try a 3-byte 'kid' where no base64 padding is required. + // |kJwksMultipleEntries| above has 2 'kid's that require 1 and 2 padding + // bytes. Note that 'k' has to be 16 bytes, so it will always require padding. + const std::string kJwksWithNoPadding = "{" " \"keys\": [" " {" @@ -644,10 +642,10 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - AddKeyAndExpect(key7, KEY_ADDED); + AddKeyAndExpect(kJwksWithNoPadding, KEY_ADDED); // Empty key id. - const std::string key8 = + const std::string kJwksWithEmptyKeyId = "{" " \"keys\": [" " {" @@ -657,7 +655,7 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - AddKeyAndExpect(key8, KEY_ERROR); + AddKeyAndExpect(kJwksWithEmptyKeyId, KEY_ERROR); } } // namespace media diff --git a/media/cdm/json_web_key.cc b/media/cdm/json_web_key.cc index 8749b25..522f1c9 100644 --- a/media/cdm/json_web_key.cc +++ b/media/cdm/json_web_key.cc @@ -5,31 +5,62 @@ #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 { -// TODO(jrummell): Move JWK operations from aes_decryptor into this file. - 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<const char*>(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; - std::string key_id_base64; - base::Base64Encode( - std::string(reinterpret_cast<const char*>(key), key_length), - &key_base64); - base::Base64Encode( - std::string(reinterpret_cast<const char*>(key_id), key_id_length), - &key_id_base64); + 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<base::DictionaryValue> jwk(new base::DictionaryValue()); @@ -48,4 +79,85 @@ std::string GenerateJWKSet(const uint8* key, int key_length, 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<Value> 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<DictionaryValue*>(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 diff --git a/media/cdm/json_web_key.h b/media/cdm/json_web_key.h index 83fa222..cb483ae 100644 --- a/media/cdm/json_web_key.h +++ b/media/cdm/json_web_key.h @@ -6,16 +6,42 @@ #define MEDIA_CDM_JSON_WEB_KEY_H_ #include <string> +#include <utility> +#include <vector> #include "base/basictypes.h" #include "media/base/media_export.h" namespace media { -// Convert |key|, |key_id| to a JSON Web Key Set. +// A JSON Web Key Set looks like the following in JSON: +// { "keys": [ JWK1, JWK2, ... ] } +// A symmetric keys JWK looks like the following in JSON: +// { "kty":"oct", +// "kid":"AQIDBAUGBwgJCgsMDQ4PEA", +// "k":"FBUWFxgZGhscHR4fICEiIw" } +// There may be other properties specified, but they are ignored. +// Ref: http://tools.ietf.org/html/draft-ietf-jose-json-web-key and: +// http://tools.ietf.org/html/draft-jones-jose-json-private-and-symmetric-key +// +// For EME WD, both 'kid' and 'k' are base64 encoded strings, without trailing +// padding. + +// Vector of [key_id, key_value] pairs. Values are raw binary data, stored in +// strings for convenience. +typedef std::pair<std::string, std::string> KeyIdAndKeyPair; +typedef std::vector<KeyIdAndKeyPair> KeyIdAndKeyPairs; + +// Converts a single |key|, |key_id| pair to a JSON Web Key Set. MEDIA_EXPORT std::string GenerateJWKSet(const uint8* key, int key_length, const uint8* key_id, int key_id_length); +// Extracts the JSON Web Keys from a JSON Web Key Set. If |input| looks like +// a valid JWK Set, then true is returned and |keys| is updated to contain +// the list of keys found. Otherwise return false. +MEDIA_EXPORT bool ExtractKeysFromJWKSet(const std::string& jwk_set, + KeyIdAndKeyPairs* keys); + } // namespace media #endif // MEDIA_CDM_JSON_WEB_KEY_H_ diff --git a/media/cdm/json_web_key_unittest.cc b/media/cdm/json_web_key_unittest.cc new file mode 100644 index 0000000..1018d17 --- /dev/null +++ b/media/cdm/json_web_key_unittest.cc @@ -0,0 +1,186 @@ +// 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/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class JSONWebKeyTest : public testing::Test { + public: + JSONWebKeyTest() {} + + protected: + void ExtractJWKKeysAndExpect(const std::string& jwk, + bool expected_result, + size_t expected_number_of_keys) { + DCHECK(!jwk.empty()); + KeyIdAndKeyPairs keys; + EXPECT_EQ(expected_result, ExtractKeysFromJWKSet(jwk, &keys)); + EXPECT_EQ(expected_number_of_keys, keys.size()); + } +}; + +TEST_F(JSONWebKeyTest, GenerateJWKSet) { + const uint8 data1[] = { 0x01, 0x02 }; + const uint8 data2[] = { 0x01, 0x02, 0x03, 0x04 }; + const uint8 data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }; + + EXPECT_EQ("{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}]}", + GenerateJWKSet(data1, arraysize(data1), data1, arraysize(data1))); + EXPECT_EQ( + "{\"keys\":[{\"k\":\"AQIDBA\",\"kid\":\"AQIDBA\",\"kty\":\"oct\"}]}", + GenerateJWKSet(data2, arraysize(data2), data2, arraysize(data2))); + EXPECT_EQ("{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQIDBA\",\"kty\":\"oct\"}]}", + GenerateJWKSet(data1, arraysize(data1), data2, arraysize(data2))); + EXPECT_EQ("{\"keys\":[{\"k\":\"AQIDBA\",\"kid\":\"AQI\",\"kty\":\"oct\"}]}", + GenerateJWKSet(data2, arraysize(data2), data1, arraysize(data1))); + EXPECT_EQ( + "{\"keys\":[{\"k\":\"AQIDBAUGBwgJCgsMDQ4PEA\",\"kid\":" + "\"AQIDBAUGBwgJCgsMDQ4PEA\",\"kty\":\"oct\"}]}", + GenerateJWKSet(data3, arraysize(data3), data3, arraysize(data3))); +} + +TEST_F(JSONWebKeyTest, ExtractJWKKeys) { + // Try a simple JWK key (i.e. not in a set) + const std::string kJwkSimple = + "{" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\"," + " \"k\": \"FBUWFxgZGhscHR4fICEiIw\"" + "}"; + ExtractJWKKeysAndExpect(kJwkSimple, false, 0); + + // Try a key list with multiple entries. + const std::string kJwksMultipleEntries = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\"," + " \"k\": \"FBUWFxgZGhscHR4fICEiIw\"" + " }," + " {" + " \"kty\": \"oct\"," + " \"kid\": \"JCUmJygpKissLS4vMA\"," + " \"k\":\"MTIzNDU2Nzg5Ojs8PT4/QA\"" + " }" + " ]" + "}"; + ExtractJWKKeysAndExpect(kJwksMultipleEntries, true, 2); + + // Try a key with no spaces and some \n plus additional fields. + const std::string kJwksNoSpaces = + "\n\n{\"something\":1,\"keys\":[{\n\n\"kty\":\"oct\",\"alg\":\"A128KW\"," + "\"kid\":\"AAECAwQFBgcICQoLDA0ODxAREhM\",\"k\":\"GawgguFyGrWKav7AX4VKUg" + "\",\"foo\":\"bar\"}]}\n\n"; + ExtractJWKKeysAndExpect(kJwksNoSpaces, true, 1); + + // Try some non-ASCII characters. + ExtractJWKKeysAndExpect( + "This is not ASCII due to \xff\xfe\xfd in it.", false, 0); + + // Try some non-ASCII characters in an otherwise valid JWK. + const std::string kJwksInvalidCharacters = + "\n\n{\"something\":1,\"keys\":[{\n\n\"kty\":\"oct\",\"alg\":\"A128KW\"," + "\"kid\":\"AAECAwQFBgcICQoLDA0ODxAREhM\",\"k\":\"\xff\xfe\xfd" + "\",\"foo\":\"bar\"}]}\n\n"; + ExtractJWKKeysAndExpect(kJwksInvalidCharacters, false, 0); + + // Try a badly formatted key. Assume that the JSON parser is fully tested, + // so we won't try a lot of combinations. However, need a test to ensure + // that the code doesn't crash if invalid JSON received. + ExtractJWKKeysAndExpect("This is not a JSON key.", false, 0); + + // Try passing some valid JSON that is not a dictionary at the top level. + ExtractJWKKeysAndExpect("40", false, 0); + + // Try an empty dictionary. + ExtractJWKKeysAndExpect("{ }", false, 0); + + // Try an empty 'keys' dictionary. + ExtractJWKKeysAndExpect("{ \"keys\": [] }", true, 0); + + // Try with 'keys' not a dictionary. + ExtractJWKKeysAndExpect("{ \"keys\":\"1\" }", false, 0); + + // Try with 'keys' a list of integers. + ExtractJWKKeysAndExpect("{ \"keys\": [ 1, 2, 3 ] }", false, 0); + + // Try padding(=) at end of 'k' base64 string. + const std::string kJwksWithPaddedKey = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAw\"," + " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw==\"" + " }" + " ]" + "}"; + ExtractJWKKeysAndExpect(kJwksWithPaddedKey, false, 0); + + // Try padding(=) at end of 'kid' base64 string. + const std::string kJwksWithPaddedKeyId = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAw==\"," + " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\"" + " }" + " ]" + "}"; + ExtractJWKKeysAndExpect(kJwksWithPaddedKeyId, false, 0); + + // Try a key with invalid base64 encoding. + const std::string kJwksWithInvalidBase64 = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"!@#$%^&*()\"," + " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\"" + " }" + " ]" + "}"; + ExtractJWKKeysAndExpect(kJwksWithInvalidBase64, false, 0); + + // Empty key id. + const std::string kJwksWithEmptyKeyId = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"\"," + " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\"" + " }" + " ]" + "}"; + ExtractJWKKeysAndExpect(kJwksWithEmptyKeyId, false, 0); + + // Try a list with multiple keys with the same kid. + const std::string kJwksDuplicateKids = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"JCUmJygpKissLS4vMA\"," + " \"k\": \"FBUWFxgZGhscHR4fICEiIw\"" + " }," + " {" + " \"kty\": \"oct\"," + " \"kid\": \"JCUmJygpKissLS4vMA\"," + " \"k\":\"MTIzNDU2Nzg5Ojs8PT4/QA\"" + " }" + " ]" + "}"; + ExtractJWKKeysAndExpect(kJwksDuplicateKids, true, 2); +} + +} // namespace media + diff --git a/media/media.gyp b/media/media.gyp index 7754445..e2223c1 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -947,6 +947,7 @@ 'base/video_util_unittest.cc', 'base/yuv_convert_unittest.cc', 'cdm/aes_decryptor_unittest.cc', + 'cdm/json_web_key_unittest.cc', 'ffmpeg/ffmpeg_common_unittest.cc', 'filters/audio_decoder_selector_unittest.cc', 'filters/audio_file_reader_unittest.cc', |