summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjrummell@chromium.org <jrummell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-27 05:14:26 +0000
committerjrummell@chromium.org <jrummell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-27 05:14:26 +0000
commit202c0642785384a4db0c9eefbceda37b215fbb9d (patch)
tree3415adb462c9d23169ca600a0722895403e0cc16
parentc96d1b079e3da2fd9a4d2ec65f9f4b290a667a60 (diff)
downloadchromium_src-202c0642785384a4db0c9eefbceda37b215fbb9d.zip
chromium_src-202c0642785384a4db0c9eefbceda37b215fbb9d.tar.gz
chromium_src-202c0642785384a4db0c9eefbceda37b215fbb9d.tar.bz2
Move ExtractJWKKeys to json_web_key
Cleanup to move JWK functionality into one file. Also require that base64 padding characters are not present. BUG=303381 TEST=media_unittests pass Review URL: https://codereview.chromium.org/83593004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237517 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--media/cdm/aes_decryptor.cc131
-rw-r--r--media/cdm/aes_decryptor_unittest.cc44
-rw-r--r--media/cdm/json_web_key.cc132
-rw-r--r--media/cdm/json_web_key.h28
-rw-r--r--media/cdm/json_web_key_unittest.cc186
-rw-r--r--media/media.gyp1
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',