summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjrummell <jrummell@chromium.org>2015-04-22 11:25:20 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-22 18:26:30 +0000
commit15426e1f537cb15a28396c6ba70644fd90605218 (patch)
tree223aa201301f23eece3ad2cec2121ae95201d10c
parentab42594ec3070953971f43b384f38f3c2b65d4ea (diff)
downloadchromium_src-15426e1f537cb15a28396c6ba70644fd90605218.zip
chromium_src-15426e1f537cb15a28396c6ba70644fd90605218.tar.gz
chromium_src-15426e1f537cb15a28396c6ba70644fd90605218.tar.bz2
Sanitize init_data before passing it on
BUG=456397 TEST=media_unittests and EME layout tests pass Review URL: https://codereview.chromium.org/1096203004 Cr-Commit-Position: refs/heads/master@{#326342}
-rw-r--r--content/browser/media/cdm/browser_cdm_manager.cc13
-rw-r--r--media/base/limits.h3
-rw-r--r--media/blink/webcontentdecryptionmodulesession_impl.cc110
-rw-r--r--media/blink/webcontentdecryptionmodulesession_impl.h6
-rw-r--r--media/cdm/cenc_utils.cc71
-rw-r--r--media/cdm/cenc_utils.h4
-rw-r--r--media/cdm/cenc_utils_unittest.cc13
-rw-r--r--media/cdm/json_web_key.cc19
-rw-r--r--media/cdm/json_web_key.h5
-rw-r--r--media/cdm/json_web_key_unittest.cc29
10 files changed, 220 insertions, 53 deletions
diff --git a/content/browser/media/cdm/browser_cdm_manager.cc b/content/browser/media/cdm/browser_cdm_manager.cc
index 3eb55f1..88cf526 100644
--- a/content/browser/media/cdm/browser_cdm_manager.cc
+++ b/content/browser/media/cdm/browser_cdm_manager.cc
@@ -36,13 +36,6 @@ using media::MediaKeys;
namespace {
-// Maximum lengths for various EME API parameters. These are checks to
-// prevent unnecessarily large parameters from being passed around, and the
-// lengths are somewhat arbitrary as the EME spec doesn't specify any limits.
-const size_t kMaxInitDataLength = 64 * 1024; // 64 KB
-const size_t kMaxSessionResponseLength = 64 * 1024; // 64 KB
-const size_t kMaxKeySystemLength = 256;
-
// The ID used in this class is a concatenation of |render_frame_id| and
// |cdm_id|, i.e. (render_frame_id << 32) + cdm_id.
@@ -300,7 +293,7 @@ void BrowserCdmManager::OnInitializeCdm(int render_frame_id,
int cdm_id,
const std::string& key_system,
const GURL& security_origin) {
- if (key_system.size() > kMaxKeySystemLength) {
+ if (key_system.size() > media::limits::kMaxKeySystemLength) {
// This failure will be discovered and reported by OnCreateSession()
// as GetCdm() will return null.
NOTREACHED() << "Invalid key system: " << key_system;
@@ -346,7 +339,7 @@ void BrowserCdmManager::OnCreateSessionAndGenerateRequest(
scoped_ptr<NewSessionPromise> promise(
new NewSessionPromise(this, render_frame_id, cdm_id, promise_id));
- if (init_data.size() > kMaxInitDataLength) {
+ if (init_data.size() > media::limits::kMaxInitDataLength) {
LOG(WARNING) << "InitData for ID: " << cdm_id
<< " too long: " << init_data.size();
promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Init data too long.");
@@ -400,7 +393,7 @@ void BrowserCdmManager::OnUpdateSession(int render_frame_id,
return;
}
- if (response.size() > kMaxSessionResponseLength) {
+ if (response.size() > media::limits::kMaxSessionResponseLength) {
LOG(WARNING) << "Response for ID " << cdm_id
<< " is too long: " << response.size();
promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Response too long.");
diff --git a/media/base/limits.h b/media/base/limits.h
index 69a5b63..c3cd677 100644
--- a/media/base/limits.h
+++ b/media/base/limits.h
@@ -52,6 +52,9 @@ enum {
kMinKeyIdLength = 1,
kMaxKeyIdLength = 512,
kMaxKeyIds = 128,
+ kMaxInitDataLength = 64 * 1024, // 64 KB
+ kMaxSessionResponseLength = 64 * 1024, // 64 KB
+ kMaxKeySystemLength = 256,
};
} // namespace limits
diff --git a/media/blink/webcontentdecryptionmodulesession_impl.cc b/media/blink/webcontentdecryptionmodulesession_impl.cc
index 643aa94..94afa0e 100644
--- a/media/blink/webcontentdecryptionmodulesession_impl.cc
+++ b/media/blink/webcontentdecryptionmodulesession_impl.cc
@@ -13,11 +13,14 @@
#include "media/base/cdm_key_information.h"
#include "media/base/cdm_promise.h"
#include "media/base/key_systems.h"
+#include "media/base/limits.h"
#include "media/base/media_keys.h"
#include "media/blink/cdm_result_promise.h"
#include "media/blink/cdm_session_adapter.h"
#include "media/blink/new_session_cdm_result_promise.h"
#include "media/blink/webmediaplayer_util.h"
+#include "media/cdm/cenc_utils.h"
+#include "media/cdm/json_web_key.h"
#include "third_party/WebKit/public/platform/WebData.h"
#include "third_party/WebKit/public/platform/WebEncryptedMediaKeyInformation.h"
#include "third_party/WebKit/public/platform/WebString.h"
@@ -91,6 +94,60 @@ static MediaKeys::SessionType convertSessionType(
return MediaKeys::TEMPORARY_SESSION;
}
+static bool SanitizeInitData(EmeInitDataType init_data_type,
+ const unsigned char* init_data,
+ size_t init_data_length,
+ std::vector<uint8>* sanitized_init_data,
+ std::string* error_message) {
+ if (init_data_length > limits::kMaxInitDataLength) {
+ error_message->assign("Initialization data too long.");
+ return false;
+ }
+
+ switch (init_data_type) {
+ case EmeInitDataType::WEBM:
+ sanitized_init_data->assign(init_data, init_data + init_data_length);
+ return true;
+
+ case EmeInitDataType::CENC:
+ if (!ValidatePsshInput(init_data, init_data_length)) {
+ error_message->assign("Initialization data for CENC is incorrect.");
+ return false;
+ }
+
+ sanitized_init_data->assign(init_data, init_data + init_data_length);
+ return true;
+
+ case EmeInitDataType::KEYIDS: {
+ // Extract the keys and then rebuild the message. This ensures that any
+ // extra data in the provided JSON is dropped.
+ std::string init_data_string(init_data, init_data + init_data_length);
+ KeyIdList key_ids;
+ if (!ExtractKeyIdsFromKeyIdsInitData(init_data_string, &key_ids,
+ error_message))
+ return false;
+
+ for (const auto& key_id : key_ids) {
+ if (key_id.size() < limits::kMinKeyIdLength ||
+ key_id.size() > limits::kMaxKeyIdLength) {
+ error_message->assign("Incorrect key size.");
+ return false;
+ }
+ }
+
+ CreateKeyIdsInitData(key_ids, sanitized_init_data);
+ return true;
+ }
+
+ case EmeInitDataType::UNKNOWN:
+ break;
+ }
+
+ NOTREACHED();
+ error_message->assign("Initialization data type is not supported.");
+ return false;
+}
+
WebContentDecryptionModuleSessionImpl::WebContentDecryptionModuleSessionImpl(
const scoped_refptr<CdmSessionAdapter>& adapter)
: adapter_(adapter), is_closed_(false), weak_ptr_factory_(this) {
@@ -118,7 +175,7 @@ void WebContentDecryptionModuleSessionImpl::initializeNewSession(
blink::WebContentDecryptionModuleResult result) {
DCHECK(session_id_.empty());
- // Step 5 from https://w3c.github.io/encrypted-media/#generateRequest.
+ // From https://w3c.github.io/encrypted-media/#generateRequest.
// 5. If the Key System implementation represented by this object's cdm
// implementation value does not support initDataType as an Initialization
// Data Type, return a promise rejected with a new DOMException whose name
@@ -134,9 +191,42 @@ void WebContentDecryptionModuleSessionImpl::initializeNewSession(
return;
}
+ // 9.1 If the init data is not valid for initDataType, reject promise with a
+ // new DOMException whose name is InvalidAccessError.
+ // 9.2 Let sanitized init data be a validated and sanitized version of init
+ // data. The user agent must thoroughly validate the Initialization Data
+ // before passing it to the CDM. This includes verifying that the length
+ // and values of fields are reasonable, verifying that values are within
+ // reasonable limits, and stripping irrelevant, unsupported, or unknown
+ // data or fields. It is recommended that user agents pre-parse, sanitize,
+ // and/or generate a fully sanitized version of the Initialization Data.
+ // If the Initialization Data format specified by initDataType support
+ // multiple entries, the user agent should remove entries that are not
+ // needed by the CDM.
+ // 9.3 If the previous step failed, reject promise with a new DOMException
+ // whose name is InvalidAccessError.
+ std::vector<uint8> sanitized_init_data;
+ std::string message;
+ if (!SanitizeInitData(eme_init_data_type, init_data, init_data_length,
+ &sanitized_init_data, &message)) {
+ result.completeWithError(
+ blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
+ blink::WebString::fromUTF8(message));
+ return;
+ }
+
+ // 9.4 Let session id be the empty string.
+ // (Done in constructor.)
+
+ // 9.5 Let message be null.
+ // (Done by CDM.)
+
+ // 9.6 Let cdm be the CDM instance represented by this object's cdm
+ // instance value.
+ // 9.7 Use the cdm to execute the following steps:
adapter_->InitializeNewSession(
- eme_init_data_type, init_data,
- base::saturated_cast<int>(init_data_length),
+ eme_init_data_type, vector_as_array(&sanitized_init_data),
+ base::saturated_cast<int>(sanitized_init_data.size()),
convertSessionType(session_type),
scoped_ptr<NewSessionCdmPromise>(new NewSessionCdmResultPromise(
result, adapter_->GetKeySystemUMAPrefix() + kGenerateRequestUMAName,
@@ -145,16 +235,6 @@ void WebContentDecryptionModuleSessionImpl::initializeNewSession(
base::Unretained(this)))));
}
-// TODO(jrummell): Remove this. http://crbug.com/418239.
-void WebContentDecryptionModuleSessionImpl::initializeNewSession(
- const blink::WebString& init_data_type,
- const uint8* init_data,
- size_t init_data_length,
- const blink::WebString& session_type,
- blink::WebContentDecryptionModuleResult result) {
- NOTREACHED();
-}
-
void WebContentDecryptionModuleSessionImpl::load(
const blink::WebString& session_id,
blink::WebContentDecryptionModuleResult result) {
@@ -207,8 +287,8 @@ void WebContentDecryptionModuleSessionImpl::OnSessionMessage(
MediaKeys::MessageType message_type,
const std::vector<uint8>& message) {
DCHECK(client_) << "Client not set before message event";
- client_->message(convertMessageType(message_type),
- message.empty() ? NULL : &message[0], message.size());
+ client_->message(convertMessageType(message_type), vector_as_array(&message),
+ message.size());
}
void WebContentDecryptionModuleSessionImpl::OnSessionKeysChange(
diff --git a/media/blink/webcontentdecryptionmodulesession_impl.h b/media/blink/webcontentdecryptionmodulesession_impl.h
index 881c07e..2f00ff5 100644
--- a/media/blink/webcontentdecryptionmodulesession_impl.h
+++ b/media/blink/webcontentdecryptionmodulesession_impl.h
@@ -38,12 +38,6 @@ class WebContentDecryptionModuleSessionImpl
size_t initDataLength,
blink::WebEncryptedMediaSessionType session_type,
blink::WebContentDecryptionModuleResult result);
- virtual void initializeNewSession(
- const blink::WebString& init_data_type,
- const uint8* init_data,
- size_t init_data_length,
- const blink::WebString& session_type,
- blink::WebContentDecryptionModuleResult result);
virtual void load(const blink::WebString& session_id,
blink::WebContentDecryptionModuleResult result);
virtual void update(const uint8* response,
diff --git a/media/cdm/cenc_utils.cc b/media/cdm/cenc_utils.cc
index 2eeafd7..a59c19c 100644
--- a/media/cdm/cenc_utils.cc
+++ b/media/cdm/cenc_utils.cc
@@ -70,6 +70,52 @@ static bool IsCommonSystemID(BitReader* reader) {
return true;
}
+// Checks that |reader| contains a valid 'ppsh' box header. |reader| is updated
+// to point to the content immediately following the box header. Returns true
+// if the header looks valid and |reader| contains enough data for the size of
+// header. |size| is updated as the computed size of the box header. Otherwise
+// false is returned.
+static bool ValidBoxHeader(BitReader* reader, uint32* size) {
+ // Enough data for a miniumum size 'pssh' box?
+ uint32 available_bytes = reader->bits_available() / 8;
+ RCHECK(available_bytes >= kMinimumBoxSizeInBytes);
+
+ *size = ReadBits(reader, 32);
+
+ // Must be a 'pssh' box or else fail.
+ RCHECK(ReadBits(reader, 8) == 'p');
+ RCHECK(ReadBits(reader, 8) == 's');
+ RCHECK(ReadBits(reader, 8) == 's');
+ RCHECK(ReadBits(reader, 8) == 'h');
+
+ if (*size == 1) {
+ // If largesize > 2**32 it is too big.
+ RCHECK(ReadBits(reader, 32) == 0);
+ *size = ReadBits(reader, 32);
+ } else if (*size == 0) {
+ *size = available_bytes;
+ }
+
+ // Check that the buffer contains at least size bytes.
+ return available_bytes >= *size;
+}
+
+bool ValidatePsshInput(const uint8* input, size_t input_length) {
+ size_t offset = 0;
+ while (offset < input_length) {
+ // Create a BitReader over the remaining part of the buffer.
+ BitReader reader(input + offset, input_length - offset);
+ uint32 size;
+ RCHECK(ValidBoxHeader(&reader, &size));
+
+ // Update offset to point at the next 'pssh' box (may not be one).
+ offset += size;
+ }
+
+ // Only valid if this contains 0 or more 'pssh' boxes.
+ return offset == input_length;
+}
+
bool GetKeyIdsForCommonSystemId(const uint8* input,
int input_length,
std::vector<std::vector<uint8>>* key_ids) {
@@ -77,29 +123,10 @@ bool GetKeyIdsForCommonSystemId(const uint8* input,
std::vector<std::vector<uint8>> result;
while (offset < input_length) {
+ // Create a BitReader over the remaining part of the buffer.
BitReader reader(input + offset, input_length - offset);
-
- // Enough data for a miniumum size 'pssh' box?
- RCHECK(reader.bits_available() >= kMinimumBoxSizeInBytes * 8);
-
- uint32 size = ReadBits(&reader, 32);
-
- // Must be a 'pssh' box or else fail.
- RCHECK(ReadBits(&reader, 8) == 'p');
- RCHECK(ReadBits(&reader, 8) == 's');
- RCHECK(ReadBits(&reader, 8) == 's');
- RCHECK(ReadBits(&reader, 8) == 'h');
-
- if (size == 1) {
- // If largesize > 2**32 it is too big.
- RCHECK(ReadBits(&reader, 32) == 0);
- size = ReadBits(&reader, 32);
- } else if (size == 0) {
- size = input_length - offset;
- }
-
- // Check that the buffer contains at least size bytes.
- RCHECK(static_cast<uint32>(input_length - offset) >= size);
+ uint32 size;
+ RCHECK(ValidBoxHeader(&reader, &size));
// Update offset to point at the next 'pssh' box (may not be one).
offset += size;
diff --git a/media/cdm/cenc_utils.h b/media/cdm/cenc_utils.h
index c659742..6fd9952 100644
--- a/media/cdm/cenc_utils.h
+++ b/media/cdm/cenc_utils.h
@@ -12,6 +12,10 @@
namespace media {
+// Validate that |input| is a set of concatenated 'pssh' boxes and the sizes
+// match. Returns true if |input| looks valid, false otherwise.
+MEDIA_EXPORT bool ValidatePsshInput(const uint8* input, size_t input_length);
+
// Gets the Key Ids from a 'pssh' box for the Common SystemID among one or
// more concatenated 'pssh' boxes. If |input| looks valid, then true is
// returned and |key_ids| is updated to contain the values found. Otherwise
diff --git a/media/cdm/cenc_utils_unittest.cc b/media/cdm/cenc_utils_unittest.cc
index ae75817..fd481da 100644
--- a/media/cdm/cenc_utils_unittest.cc
+++ b/media/cdm/cenc_utils_unittest.cc
@@ -168,6 +168,7 @@ class CencUtilsTest : public testing::Test {
TEST_F(CencUtilsTest, EmptyPSSH) {
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(nullptr, 0));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(nullptr, 0, &key_ids));
EXPECT_EQ(0u, key_ids.size());
}
@@ -175,6 +176,7 @@ TEST_F(CencUtilsTest, EmptyPSSH) {
TEST_F(CencUtilsTest, PSSHVersion0) {
std::vector<uint8> box = MakePSSHBox(0);
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(&box[0], box.size()));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
EXPECT_EQ(0u, key_ids.size());
}
@@ -182,6 +184,7 @@ TEST_F(CencUtilsTest, PSSHVersion0) {
TEST_F(CencUtilsTest, PSSHVersion1WithNoKeys) {
std::vector<uint8> box = MakePSSHBox(1);
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(&box[0], box.size()));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
EXPECT_EQ(0u, key_ids.size());
}
@@ -189,6 +192,7 @@ TEST_F(CencUtilsTest, PSSHVersion1WithNoKeys) {
TEST_F(CencUtilsTest, PSSHVersion1WithOneKey) {
std::vector<uint8> box = MakePSSHBox(1, Key1());
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(&box[0], box.size()));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
EXPECT_EQ(1u, key_ids.size());
EXPECT_EQ(key_ids[0], Key1());
@@ -197,6 +201,7 @@ TEST_F(CencUtilsTest, PSSHVersion1WithOneKey) {
TEST_F(CencUtilsTest, PSSHVersion1WithTwoKeys) {
std::vector<uint8> box = MakePSSHBox(1, Key1(), Key2());
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(&box[0], box.size()));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
EXPECT_EQ(2u, key_ids.size());
EXPECT_EQ(key_ids[0], Key1());
@@ -212,6 +217,7 @@ TEST_F(CencUtilsTest, PSSHVersion0Plus1) {
box0.push_back(value);
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(&box0[0], box0.size()));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box0[0], box0.size(), &key_ids));
EXPECT_EQ(1u, key_ids.size());
EXPECT_EQ(key_ids[0], Key1());
@@ -226,6 +232,7 @@ TEST_F(CencUtilsTest, PSSHVersion1Plus0) {
box1.push_back(value);
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(&box1[0], box1.size()));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box1[0], box1.size(), &key_ids));
EXPECT_EQ(1u, key_ids.size());
EXPECT_EQ(key_ids[0], Key1());
@@ -244,6 +251,7 @@ TEST_F(CencUtilsTest, MultiplePSSHVersion1) {
box.push_back(value);
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(&box[0], box.size()));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
EXPECT_EQ(4u, key_ids.size());
EXPECT_EQ(key_ids[0], Key1());
@@ -257,8 +265,10 @@ TEST_F(CencUtilsTest, InvalidPSSH) {
std::vector<std::vector<uint8>> key_ids;
for (uint32 i = 1; i < box.size(); ++i) {
// Modify size of data passed to be less than real size.
+ EXPECT_FALSE(ValidatePsshInput(&box[0], i));
EXPECT_FALSE(GetKeyIdsForCommonSystemId(&box[0], i, &key_ids));
// Modify starting point.
+ EXPECT_FALSE(ValidatePsshInput(&box[i], box.size() - i));
EXPECT_FALSE(GetKeyIdsForCommonSystemId(&box[i], box.size() - i, &key_ids));
}
}
@@ -304,6 +314,7 @@ TEST_F(CencUtilsTest, LongSize) {
};
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(data, arraysize(data)));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(data, arraysize(data), &key_ids));
EXPECT_EQ(2u, key_ids.size());
}
@@ -325,6 +336,7 @@ TEST_F(CencUtilsTest, NoSize) {
};
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(ValidatePsshInput(data, arraysize(data)));
EXPECT_TRUE(GetKeyIdsForCommonSystemId(data, arraysize(data), &key_ids));
EXPECT_EQ(2u, key_ids.size());
}
@@ -347,6 +359,7 @@ TEST_F(CencUtilsTest, HugeSize) {
};
std::vector<std::vector<uint8>> key_ids;
+ EXPECT_FALSE(ValidatePsshInput(data, arraysize(data)));
EXPECT_FALSE(GetKeyIdsForCommonSystemId(data, arraysize(data), &key_ids));
}
diff --git a/media/cdm/json_web_key.cc b/media/cdm/json_web_key.cc
index 43533f0..f60c6f5 100644
--- a/media/cdm/json_web_key.cc
+++ b/media/cdm/json_web_key.cc
@@ -329,6 +329,25 @@ void CreateLicenseRequest(const KeyIdList& key_ids,
license->swap(result);
}
+void CreateKeyIdsInitData(const KeyIdList& key_ids,
+ std::vector<uint8>* init_data) {
+ // Create the init_data.
+ scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
+ scoped_ptr<base::ListValue> list(new base::ListValue());
+ for (const auto& key_id : key_ids)
+ list->AppendString(EncodeBase64Url(&key_id[0], key_id.size()));
+ dictionary->Set(kKeyIdsTag, list.release());
+
+ // 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> result(json.begin(), json.end());
+ init_data->swap(result);
+}
+
bool ExtractFirstKeyIdFromLicenseRequest(const std::vector<uint8>& license,
std::vector<uint8>* first_key) {
const std::string license_as_str(
diff --git a/media/cdm/json_web_key.h b/media/cdm/json_web_key.h
index 7be4473..c77a814 100644
--- a/media/cdm/json_web_key.h
+++ b/media/cdm/json_web_key.h
@@ -81,6 +81,11 @@ MEDIA_EXPORT void CreateLicenseRequest(const KeyIdList& key_ids,
MediaKeys::SessionType session_type,
std::vector<uint8>* license);
+// Creates a keyIDs init_data message for the |key_ids| specified.
+// |key_ids_init_data| is updated to contain the resulting JSON string.
+MEDIA_EXPORT void CreateKeyIdsInitData(const KeyIdList& key_ids,
+ std::vector<uint8>* key_ids_init_data);
+
// Extract the first key from the license request message. Returns true if
// |license| is a valid license request and contains at least one key,
// otherwise false and |first_key| is not touched.
diff --git a/media/cdm/json_web_key_unittest.cc b/media/cdm/json_web_key_unittest.cc
index ecd9d2a..82b3bb0 100644
--- a/media/cdm/json_web_key_unittest.cc
+++ b/media/cdm/json_web_key_unittest.cc
@@ -576,5 +576,34 @@ TEST_F(JSONWebKeyTest, ExtractKeyIds) {
"'kids'[1] is not valid base64url encoded. Value: AQI/");
}
+TEST_F(JSONWebKeyTest, CreateInitData) {
+ 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 };
+
+ KeyIdList key_ids;
+ std::string error_message;
+
+ key_ids.push_back(std::vector<uint8>(data1, data1 + arraysize(data1)));
+ std::vector<uint8> init_data1;
+ CreateKeyIdsInitData(key_ids, &init_data1);
+ std::string result1(init_data1.begin(), init_data1.end());
+ EXPECT_EQ(result1, "{\"kids\":[\"AQI\"]}");
+
+ key_ids.push_back(std::vector<uint8>(data2, data2 + arraysize(data2)));
+ std::vector<uint8> init_data2;
+ CreateKeyIdsInitData(key_ids, &init_data2);
+ std::string result2(init_data2.begin(), init_data2.end());
+ EXPECT_EQ(result2, "{\"kids\":[\"AQI\",\"AQIDBA\"]}");
+
+ key_ids.push_back(std::vector<uint8>(data3, data3 + arraysize(data3)));
+ std::vector<uint8> init_data3;
+ CreateKeyIdsInitData(key_ids, &init_data3);
+ std::string result3(init_data3.begin(), init_data3.end());
+ EXPECT_EQ(result3,
+ "{\"kids\":[\"AQI\",\"AQIDBA\",\"AQIDBAUGBwgJCgsMDQ4PEA\"]}");
+}
+
} // namespace media