diff options
author | jrummell@chromium.org <jrummell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-11 04:31:03 +0000 |
---|---|---|
committer | jrummell@chromium.org <jrummell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-11 04:31:03 +0000 |
commit | bf2b82cd885f8ce07503c4565f2243edc74f5b80 (patch) | |
tree | c962a2091e27013dfd8e2452d3847a4acdd847d5 /media | |
parent | 52531bfad85eac5c0e6156e749ddafd7cb0d5e53 (diff) | |
download | chromium_src-bf2b82cd885f8ce07503c4565f2243edc74f5b80.zip chromium_src-bf2b82cd885f8ce07503c4565f2243edc74f5b80.tar.gz chromium_src-bf2b82cd885f8ce07503c4565f2243edc74f5b80.tar.bz2 |
Add EME support for JSON Web Keys
The current EME spec uses JSON Web Key (JWK) for the |key| parameter of
update() instead of a raw key like EME v0.1b. This change adds support for it.
BUG=267143
Review URL: https://chromiumcodereview.appspot.com/23533005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@222474 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/cdm/aes_decryptor.cc | 203 | ||||
-rw-r--r-- | media/cdm/aes_decryptor.h | 6 | ||||
-rw-r--r-- | media/cdm/aes_decryptor_unittest.cc | 249 |
3 files changed, 382 insertions, 76 deletions
diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc index da11442..b2dc48b 100644 --- a/media/cdm/aes_decryptor.cc +++ b/media/cdm/aes_decryptor.cc @@ -6,9 +6,13 @@ #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" @@ -26,6 +30,8 @@ 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, @@ -43,6 +49,105 @@ static void CopySubsamples(const std::vector<SubsampleEntry>& subsamples, } } +// 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. + // TODO(jrummell): The JWK spec and the EME spec don't say that 'kid' must be + // base64-encoded (they don't say anything at all). Verify with the EME spec. + std::string decoded_key_id; + std::string decoded_key; + if (!base::Base64Decode(encoded_key_id, &decoded_key_id) || + decoded_key_id.empty()) { + DVLOG(1) << "Invalid 'kid' value"; + return false; + } + if (!base::Base64Decode(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, @@ -170,41 +275,65 @@ void AesDecryptor::AddKey(const uint8* key, CHECK(key); CHECK_GT(key_length, 0); + // AddKey() is called from update(), where the key(s) are passed as a JSON + // Web Key (JWK) set. Each JWK needs to be a symmetric key ('kty' = "oct"), + // with 'kid' being the base64-encoded key id, and 'k' being the + // base64-encoded key. + // + // For backwards compatibility with v0.1b of the spec (where |key| is the raw + // key and |init_data| is the key id), if |key| is not valid JSON, then + // attempt to process it as a raw key. + // TODO(xhwang): Add |session_id| check after we figure out how: // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16550 - if (key_length != DecryptConfig::kDecryptionKeySize) { - DVLOG(1) << "Invalid key length: " << key_length; - key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); - return; - } - // TODO(xhwang): Fix the decryptor to accept no |init_data|. See - // http://crbug.com/123265. Until then, ensure a non-empty value is passed. - static const uint8 kDummyInitData[1] = { 0 }; - if (!init_data) { - init_data = kDummyInitData; - init_data_length = arraysize(kDummyInitData); - } + std::string key_string(reinterpret_cast<const char*>(key), key_length); + JWKKeys jwk_keys; + if (ExtractJWKKeys(key_string, &jwk_keys)) { + // Since |key| represents valid JSON, init_data must be empty. + DCHECK(!init_data); + DCHECK_EQ(init_data_length, 0); - // TODO(xhwang): For now, use |init_data| for key ID. Make this more spec - // compliant later (http://crbug.com/123262, http://crbug.com/123265). - std::string key_id_string(reinterpret_cast<const char*>(init_data), - init_data_length); - std::string key_string(reinterpret_cast<const char*>(key) , key_length); - scoped_ptr<DecryptionKey> decryption_key(new DecryptionKey(key_string)); - if (!decryption_key) { - DVLOG(1) << "Could not create key."; - key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); - return; - } + // Make sure that at least one key was extracted. + if (jwk_keys.empty()) { + key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + return; + } + for (JWKKeys::iterator it = jwk_keys.begin() ; it != jwk_keys.end(); ++it) { + if (!AddDecryptionKey(it->first, it->second)) { + key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + return; + } + } + } else { + // v0.1b backwards compatibility support. + // TODO(jrummell): Remove this code once v0.1b no longer supported. - if (!decryption_key->Init()) { - DVLOG(1) << "Could not initialize decryption key."; - key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); - return; - } + if (key_string.length() != + static_cast<size_t>(DecryptConfig::kDecryptionKeySize)) { + DVLOG(1) << "Invalid key length: " << key_string.length(); + key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + return; + } + + // TODO(xhwang): Fix the decryptor to accept no |init_data|. See + // http://crbug.com/123265. Until then, ensure a non-empty value is passed. + static const uint8 kDummyInitData[1] = {0}; + if (!init_data) { + init_data = kDummyInitData; + init_data_length = arraysize(kDummyInitData); + } - SetKey(key_id_string, decryption_key.Pass()); + // TODO(xhwang): For now, use |init_data| for key ID. Make this more spec + // compliant later (http://crbug.com/123262, http://crbug.com/123265). + std::string key_id_string(reinterpret_cast<const char*>(init_data), + init_data_length); + if (!AddDecryptionKey(key_id_string, key_string)) { + // Error logged in AddDecryptionKey() + key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + return; + } + } if (!new_audio_key_cb_.is_null()) new_audio_key_cb_.Run(); @@ -306,8 +435,19 @@ void AesDecryptor::DeinitializeDecoder(StreamType stream_type) { NOTREACHED() << "AesDecryptor does not support audio/video decoding"; } -void AesDecryptor::SetKey(const std::string& key_id, - scoped_ptr<DecryptionKey> decryption_key) { +bool AesDecryptor::AddDecryptionKey(const std::string& key_id, + const std::string& key_string) { + scoped_ptr<DecryptionKey> decryption_key(new DecryptionKey(key_string)); + if (!decryption_key) { + DVLOG(1) << "Could not create key."; + return false; + } + + if (!decryption_key->Init()) { + DVLOG(1) << "Could not initialize decryption key."; + return false; + } + base::AutoLock auto_lock(key_map_lock_); KeyMap::iterator found = key_map_.find(key_id); if (found != key_map_.end()) { @@ -315,6 +455,7 @@ void AesDecryptor::SetKey(const std::string& key_id, key_map_.erase(found); } key_map_[key_id] = decryption_key.release(); + return true; } AesDecryptor::DecryptionKey* AesDecryptor::GetKey( diff --git a/media/cdm/aes_decryptor.h b/media/cdm/aes_decryptor.h index fda5a0f..3ab4bc0 100644 --- a/media/cdm/aes_decryptor.h +++ b/media/cdm/aes_decryptor.h @@ -86,8 +86,10 @@ class MEDIA_EXPORT AesDecryptor : public MediaKeys, public Decryptor { DISALLOW_COPY_AND_ASSIGN(DecryptionKey); }; - // Sets |key| for |key_id|. The AesDecryptor takes the ownership of the |key|. - void SetKey(const std::string& key_id, scoped_ptr<DecryptionKey> key); + // Creates a DecryptionKey using |key_string| and associates it with |key_id|. + // Returns true if successful. + bool AddDecryptionKey(const std::string& key_id, + const std::string& key_string); // Gets a DecryptionKey associated with |key_id|. The AesDecryptor still owns // the key. Returns NULL if no key is associated with |key_id|. diff --git a/media/cdm/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc index b9c8005..a4b865c 100644 --- a/media/cdm/aes_decryptor_unittest.cc +++ b/media/cdm/aes_decryptor_unittest.cc @@ -31,17 +31,49 @@ static const char kClearKeySystem[] = "org.w3.clearkey"; static const uint8 kOriginalData[] = "Original subsample data."; static const int kOriginalDataSize = 24; -static const uint8 kKeyId[] = { 0x00, 0x01, 0x02, 0x03 }; +static const uint8 kKeyId[] = { + // base64 equivalent is AAECAw== + 0x00, 0x01, 0x02, 0x03 +}; static const uint8 kKey[] = { - 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, - 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13 + // base64 equivalent is BAUGBwgJCgsMDQ4PEBESEw== + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13 }; -static const uint8 kWrongKey[] = { - 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, - 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee -}; +static const char kKeyAsJWK[] = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAw==\"," + " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw==\"" + " }" + " ]" + "}"; + +static const char kWrongKeyAsJWK[] = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAw==\"," + " \"k\": \"7u7u7u7u7u7u7u7u7u7u7g==\"" + " }" + " ]" + "}"; + +static const char kWrongSizedKeyAsJWK[] = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAw==\"," + " \"k\": \"AAECAw==\"" + " }" + " ]" + "}"; static const uint8 kIv[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, @@ -72,15 +104,24 @@ static const uint8 kIv2[] = { }; static const uint8 kKeyId2[] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13 + // base64 equivalent is AAECAwQFBgcICQoLDA0ODxAREhM= + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13 }; -static const uint8 kKey2[] = { - 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, - 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 -}; +static const char kKey2AsJWK[] = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM=\"," + " \"k\": \"FBUWFxgZGhscHR4fICEiIw==\"" + " }" + " ]" + "}"; + +// 'k' in bytes is x14x15x16x17x18x19x1ax1bx1cx1dx1ex1fx20x21x22x23 static const uint8 kEncryptedData2[] = { 0x57, 0x66, 0xf4, 0x12, 0x1a, 0xed, 0xb5, 0x79, @@ -159,7 +200,6 @@ class AesDecryptorTest : public testing::Test { kSubsampleEncryptedData, kSubsampleEncryptedData + arraysize(kSubsampleEncryptedData)), key_id_(kKeyId, kKeyId + arraysize(kKeyId)), - key_(kKey, kKey + arraysize(kKey)), iv_(kIv, kIv + arraysize(kIv)), normal_subsample_entries_( kSubsampleEntriesNormal, @@ -180,9 +220,10 @@ class AesDecryptorTest : public testing::Test { KEY_ERROR }; - void AddKeyAndExpect(const std::vector<uint8>& key_id, - const std::vector<uint8>& key, - AddKeyExpectation result) { + void AddRawKeyAndExpect(const std::vector<uint8>& key_id, + const std::vector<uint8>& key, + AddKeyExpectation result) { + // TODO(jrummell): Remove once raw keys no longer supported. DCHECK(!key_id.empty()); DCHECK(!key.empty()); @@ -199,6 +240,23 @@ class AesDecryptorTest : public testing::Test { session_id_string_); } + void AddKeyAndExpect(const std::string& key, AddKeyExpectation result) { + DCHECK(!key.empty()); + + if (result == KEY_ADDED) { + EXPECT_CALL(*this, KeyAdded(session_id_string_)); + } else if (result == KEY_ERROR) { + EXPECT_CALL(*this, + KeyError(session_id_string_, MediaKeys::kUnknownError, 0)); + } else { + NOTREACHED(); + } + + decryptor_.AddKey(reinterpret_cast<const uint8*>(key.c_str()), key.length(), + NULL, 0, + session_id_string_); + } + MOCK_METHOD2(BufferDecrypted, void(Decryptor::Status, const scoped_refptr<DecoderBuffer>&)); @@ -263,7 +321,6 @@ class AesDecryptorTest : public testing::Test { const std::vector<uint8> encrypted_data_; const std::vector<uint8> subsample_encrypted_data_; const std::vector<uint8> key_id_; - const std::vector<uint8> key_; const std::vector<uint8> iv_; const std::vector<SubsampleEntry> normal_subsample_entries_; const std::vector<SubsampleEntry> no_subsample_entries_; @@ -276,7 +333,7 @@ TEST_F(AesDecryptorTest, GenerateKeyRequestWithNullInitData) { TEST_F(AesDecryptorTest, NormalDecryption) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, 0, no_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); @@ -284,7 +341,7 @@ TEST_F(AesDecryptorTest, NormalDecryption) { TEST_F(AesDecryptorTest, DecryptionWithOffset) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, 23, no_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); @@ -299,8 +356,7 @@ TEST_F(AesDecryptorTest, UnencryptedFrame) { TEST_F(AesDecryptorTest, WrongKey) { GenerateKeyRequest(key_id_); - std::vector<uint8> wrong_key(kWrongKey, kWrongKey + arraysize(kWrongKey)); - AddKeyAndExpect(key_id_, wrong_key, KEY_ADDED); + AddKeyAndExpect(kWrongKeyAsJWK, KEY_ADDED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, 0, no_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, DATA_MISMATCH); @@ -318,35 +374,33 @@ TEST_F(AesDecryptorTest, KeyReplacement) { scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, 0, no_subsample_entries_); - std::vector<uint8> wrong_key(kWrongKey, kWrongKey + arraysize(kWrongKey)); - AddKeyAndExpect(key_id_, wrong_key, KEY_ADDED); + AddKeyAndExpect(kWrongKeyAsJWK, KEY_ADDED); ASSERT_NO_FATAL_FAILURE(DecryptAndExpect( encrypted_buffer, original_data_, DATA_MISMATCH)); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); ASSERT_NO_FATAL_FAILURE( DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS)); } TEST_F(AesDecryptorTest, WrongSizedKey) { GenerateKeyRequest(key_id_); - // Use "-1" to create a wrong sized key. + AddKeyAndExpect(kWrongSizedKeyAsJWK, KEY_ERROR); + + // Repeat for a raw key. Use "-1" to create a wrong sized key. std::vector<uint8> wrong_sized_key(kKey, kKey + arraysize(kKey) - 1); - AddKeyAndExpect(key_id_, wrong_sized_key, KEY_ERROR); + AddRawKeyAndExpect(key_id_, wrong_sized_key, KEY_ERROR); } TEST_F(AesDecryptorTest, MultipleKeysAndFrames) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, 10, no_subsample_entries_); ASSERT_NO_FATAL_FAILURE( DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS)); - std::vector<uint8> key_id_2 = - std::vector<uint8>(kKeyId2, kKeyId2 + arraysize(kKeyId2)); - AddKeyAndExpect( - key_id_2, std::vector<uint8>(kKey2, kKey2 + arraysize(kKey2)), KEY_ADDED); + AddKeyAndExpect(kKey2AsJWK, KEY_ADDED); // The first key is still available after we added a second key. ASSERT_NO_FATAL_FAILURE( @@ -356,7 +410,7 @@ TEST_F(AesDecryptorTest, MultipleKeysAndFrames) { encrypted_buffer = CreateEncryptedBuffer( std::vector<uint8>(kEncryptedData2, kEncryptedData2 + arraysize(kEncryptedData2)), - key_id_2, + std::vector<uint8>(kKeyId2, kKeyId2 + arraysize(kKeyId2)), std::vector<uint8>(kIv2, kIv2 + arraysize(kIv2)), 30, no_subsample_entries_); @@ -369,7 +423,7 @@ TEST_F(AesDecryptorTest, MultipleKeysAndFrames) { TEST_F(AesDecryptorTest, CorruptedIv) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); std::vector<uint8> bad_iv = iv_; bad_iv[1]++; @@ -382,7 +436,7 @@ TEST_F(AesDecryptorTest, CorruptedIv) { TEST_F(AesDecryptorTest, CorruptedData) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); std::vector<uint8> bad_data = encrypted_data_; bad_data[1]++; @@ -394,7 +448,7 @@ TEST_F(AesDecryptorTest, CorruptedData) { TEST_F(AesDecryptorTest, EncryptedAsUnencryptedFailure) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, std::vector<uint8>(), 0, no_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, DATA_MISMATCH); @@ -402,7 +456,7 @@ TEST_F(AesDecryptorTest, EncryptedAsUnencryptedFailure) { TEST_F(AesDecryptorTest, SubsampleDecryption) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( subsample_encrypted_data_, key_id_, iv_, 0, normal_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); @@ -413,7 +467,7 @@ TEST_F(AesDecryptorTest, SubsampleDecryption) { // disallow such a configuration, it should be covered. TEST_F(AesDecryptorTest, SubsampleDecryptionWithOffset) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( subsample_encrypted_data_, key_id_, iv_, 23, normal_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); @@ -421,7 +475,7 @@ TEST_F(AesDecryptorTest, SubsampleDecryptionWithOffset) { TEST_F(AesDecryptorTest, SubsampleWrongSize) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); std::vector<SubsampleEntry> subsample_entries_wrong_size( kSubsampleEntriesWrongSize, @@ -434,7 +488,7 @@ TEST_F(AesDecryptorTest, SubsampleWrongSize) { TEST_F(AesDecryptorTest, SubsampleInvalidTotalSize) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); std::vector<SubsampleEntry> subsample_entries_invalid_total_size( kSubsampleEntriesInvalidTotalSize, @@ -450,7 +504,7 @@ TEST_F(AesDecryptorTest, SubsampleInvalidTotalSize) { // No cypher bytes in any of the subsamples. TEST_F(AesDecryptorTest, SubsampleClearBytesOnly) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); std::vector<SubsampleEntry> clear_only_subsample_entries( kSubsampleEntriesClearOnly, @@ -464,7 +518,7 @@ TEST_F(AesDecryptorTest, SubsampleClearBytesOnly) { // No clear bytes in any of the subsamples. TEST_F(AesDecryptorTest, SubsampleCypherBytesOnly) { GenerateKeyRequest(key_id_); - AddKeyAndExpect(key_id_, key_, KEY_ADDED); + AddKeyAndExpect(kKeyAsJWK, KEY_ADDED); std::vector<SubsampleEntry> cypher_only_subsample_entries( kSubsampleEntriesCypherOnly, @@ -475,4 +529,113 @@ TEST_F(AesDecryptorTest, SubsampleCypherBytesOnly) { DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); } +TEST_F(AesDecryptorTest, JWKKey) { + // Try a simple JWK key (i.e. not in a set) + const std::string key1 = + "{" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM=\"," + " \"k\": \"FBUWFxgZGhscHR4fICEiIw==\"" + "}"; + AddKeyAndExpect(key1, KEY_ERROR); + + // Try a key list with multiple entries. + const std::string key2 = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM=\"," + " \"k\": \"FBUWFxgZGhscHR4fICEiIw==\"" + " }," + " {" + " \"kty\": \"oct\"," + " \"kid\": \"JCUmJygpKissLS4vMA==\"," + " \"k\":\"MTIzNDU2Nzg5Ojs8PT4/QA==\"" + " }" + " ]" + "}"; + AddKeyAndExpect(key2, KEY_ADDED); + + // Try a key with no spaces and some \n plus additional fields. + const std::string key3 = + "\n\n{\"something\":1,\"keys\":[{\n\n\"kty\":\"oct\",\"alg\":\"A128KW\"," + "\"kid\":\"AAECAwQFBgcICQoLDA0ODxAREhM=\",\"k\":\"GawgguFyGrWKav7AX4VKUg=" + "=\",\"foo\":\"bar\"}]}\n\n"; + AddKeyAndExpect(key3, KEY_ADDED); + + // Try some non-ASCII characters. + AddKeyAndExpect("This is not ASCII due to \xff\xfe\xfd in it.", KEY_ERROR); + + // 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. + AddKeyAndExpect("This is not a JSON key.", KEY_ERROR); + + // Try passing some valid JSON that is not a dictionary at the top level. + AddKeyAndExpect("40", KEY_ERROR); + + // Try an empty dictionary. + AddKeyAndExpect("{ }", KEY_ERROR); + + // Try an empty 'keys' dictionary. + AddKeyAndExpect("{ \"keys\": [] }", KEY_ERROR); + + // Try with 'keys' not a dictionary. + AddKeyAndExpect("{ \"keys\":\"1\" }", KEY_ERROR); + + // Try with 'keys' a list of integers. + AddKeyAndExpect("{ \"keys\": [ 1, 2, 3 ] }", KEY_ERROR); + + // Try a key missing padding(=) at end of base64 string. + const std::string key4 = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAw==\"," + " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\"" + " }" + " ]" + "}"; + AddKeyAndExpect(key4, KEY_ERROR); + + // Try a key ID missing padding(=) at end of base64 string. + const std::string key5 = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"AAECAw\"," + " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw==\"" + " }" + " ]" + "}"; + AddKeyAndExpect(key5, KEY_ERROR); + + // Try a key with invalid base64 encoding. + const std::string key6 = + "{" + " \"keys\": [" + " {" + " \"kty\": \"oct\"," + " \"kid\": \"!@#$%^&*()==\"," + " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw==\"" + " }" + " ]" + "}"; + AddKeyAndExpect(key6, KEY_ERROR); +} + +TEST_F(AesDecryptorTest, RawKey) { + // Verify that v0.1b keys (raw key) is still supported. Raw keys are + // 16 bytes long. Use the undecoded value of |kKey|. + GenerateKeyRequest(key_id_); + AddRawKeyAndExpect( + key_id_, std::vector<uint8>(kKey, kKey + arraysize(kKey)), KEY_ADDED); + scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( + encrypted_data_, key_id_, iv_, 0, no_subsample_entries_); + DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); +} + } // namespace media |