OSDN Git Service

JsonWebKey support.
authorEdwin Wong <edwinwong@google.com>
Fri, 16 May 2014 00:24:57 +0000 (17:24 -0700)
committerFred Gylys-Colwell <fredgc@google.com>
Tue, 1 Jul 2014 03:27:03 +0000 (03:27 +0000)
Parses JSON Web Key Set in the response data; extracts and base64 decode
key id(s) and key(s).

bug: 12035506
Change-Id: Ib71bce942d6eca1e569dfad0a9adb6dee1cdf75e

drm/mediadrm/plugins/clearkey/JsonWebKey.cpp [new file with mode: 0644]
drm/mediadrm/plugins/clearkey/JsonWebKey.h [new file with mode: 0644]
drm/mediadrm/plugins/clearkey/tests/Android.mk [new file with mode: 0644]
drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp [new file with mode: 0644]
drm/mediadrm/plugins/clearkey/tests/unit-test.mk [new file with mode: 0644]

diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp b/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp
new file mode 100644 (file)
index 0000000..53ffae4
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "JsonWebKey"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/base64.h>
+#include <utils/Log.h>
+
+#include "JsonWebKey.h"
+
+namespace {
+const android::String8 kKeysTag("keys");
+const android::String8 kKeyTypeTag("kty");
+const android::String8 kSymmetricKeyValue("oct");
+const android::String8 kKeyTag("k");
+const android::String8 kKeyIdTag("kid");
+const android::String8 kBase64Padding("=");
+}
+
+namespace clearkeydrm {
+
+using android::ABuffer;
+using android::AString;
+
+JsonWebKey::JsonWebKey() {
+}
+
+JsonWebKey::~JsonWebKey() {
+}
+
+/*
+ * Parses a JSON Web Key Set string, initializes a KeyMap with key id:key
+ * pairs from the JSON Web Key Set. Both key ids and keys are base64url
+ * encoded. The KeyMap contains base64url decoded key id:key pairs.
+ *
+ * @return Returns false for errors, true for success.
+ */
+bool JsonWebKey::extractKeysFromJsonWebKeySet(const String8& jsonWebKeySet,
+        KeyMap* keys) {
+
+    keys->clear();
+    if (!parseJsonWebKeySet(jsonWebKeySet, &mJsonObjects)) {
+        return false;
+    }
+
+    // mJsonObjects[0] contains the entire JSON Web Key Set, including
+    // all the base64 encoded keys. Each key is also stored separately as
+    // a JSON object in mJsonObjects[1..n] where n is the total
+    // number of keys in the set.
+    if (!isJsonWebKeySet(mJsonObjects[0])) {
+        return false;
+    }
+
+    String8 encodedKey, encodedKeyId;
+    Vector<uint8_t> decodedKey, decodedKeyId;
+
+    // mJsonObjects[1] contains the first JSON Web Key in the set
+    for (size_t i = 1; i < mJsonObjects.size(); ++i) {
+        encodedKeyId.clear();
+        encodedKey.clear();
+
+        if (!parseJsonObject(mJsonObjects[i], &mTokens))
+            return false;
+
+        if (findKey(mJsonObjects[i], &encodedKeyId, &encodedKey)) {
+            if (encodedKeyId.isEmpty() || encodedKey.isEmpty()) {
+                ALOGE("Must have both key id and key in the JsonWebKey set.");
+                continue;
+            }
+
+            if (!decodeBase64String(encodedKeyId, &decodedKeyId)) {
+                ALOGE("Failed to decode key id(%s)", encodedKeyId.string());
+                continue;
+            }
+
+            if (!decodeBase64String(encodedKey, &decodedKey)) {
+                ALOGE("Failed to decode key(%s)", encodedKey.string());
+                continue;
+            }
+
+            keys->add(decodedKeyId, decodedKey);
+        }
+    }
+    return true;
+}
+
+bool JsonWebKey::decodeBase64String(const String8& encodedText,
+        Vector<uint8_t>* decodedText) {
+
+    decodedText->clear();
+
+    // encodedText should not contain padding characters as per EME spec.
+    if (encodedText.find(kBase64Padding) != -1) {
+        return false;
+    }
+
+    // Since android::decodeBase64() requires padding characters,
+    // add them so length of encodedText is exactly a multiple of 4.
+    int remainder = encodedText.length() % 4;
+    String8 paddedText(encodedText);
+    if (remainder > 0) {
+        for (int i = 0; i < 4 - remainder; ++i) {
+            paddedText.append(kBase64Padding);
+        }
+    }
+
+    android::sp<ABuffer> buffer =
+            android::decodeBase64(AString(paddedText.string()));
+    if (buffer == NULL) {
+        ALOGE("Malformed base64 encoded content found.");
+        return false;
+    }
+
+    decodedText->appendArray(buffer->base(), buffer->size());
+    return true;
+}
+
+bool JsonWebKey::findKey(const String8& jsonObject, String8* keyId,
+        String8* encodedKey) {
+
+    String8 key, value;
+
+    // Only allow symmetric key, i.e. "kty":"oct" pair.
+    if (jsonObject.find(kKeyTypeTag) >= 0) {
+        findValue(kKeyTypeTag, &value);
+        if (0 != value.compare(kSymmetricKeyValue))
+            return false;
+    }
+
+    if (jsonObject.find(kKeyIdTag) >= 0) {
+        findValue(kKeyIdTag, keyId);
+    }
+
+    if (jsonObject.find(kKeyTag) >= 0) {
+        findValue(kKeyTag, encodedKey);
+    }
+    return true;
+}
+
+void JsonWebKey::findValue(const String8 &key, String8* value) {
+    value->clear();
+    const char* valueToken;
+    for (Vector<String8>::const_iterator nextToken = mTokens.begin();
+        nextToken != mTokens.end(); ++nextToken) {
+        if (0 == (*nextToken).compare(key)) {
+            if (nextToken + 1 == mTokens.end())
+                break;
+            valueToken = (*(nextToken + 1)).string();
+            value->setTo(valueToken);
+            nextToken++;
+            break;
+        }
+    }
+}
+
+bool JsonWebKey::isJsonWebKeySet(const String8& jsonObject) const {
+    if (jsonObject.find(kKeysTag) == -1) {
+        ALOGE("JSON Web Key does not contain keys.");
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Parses a JSON objects string and initializes a vector of tokens.
+ *
+ * @return Returns false for errors, true for success.
+ */
+bool JsonWebKey::parseJsonObject(const String8& jsonObject,
+        Vector<String8>* tokens) {
+    jsmn_parser parser;
+
+    jsmn_init(&parser);
+    int numTokens = jsmn_parse(&parser,
+        jsonObject.string(), jsonObject.size(), NULL, 0);
+    if (numTokens < 0) {
+        ALOGE("Parser returns error code=%d", numTokens);
+        return false;
+    }
+
+    unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
+    mJsmnTokens.clear();
+    mJsmnTokens.setCapacity(jsmnTokensSize);
+
+    jsmn_init(&parser);
+    int status = jsmn_parse(&parser, jsonObject.string(),
+        jsonObject.size(), mJsmnTokens.editArray(), numTokens);
+    if (status < 0) {
+        ALOGE("Parser returns error code=%d", status);
+        return false;
+    }
+
+    tokens->clear();
+    String8 token;
+    const char *pjs;
+    for (int j = 0; j < numTokens; ++j) {
+        pjs = jsonObject.string() + mJsmnTokens[j].start;
+        if (mJsmnTokens[j].type == JSMN_STRING ||
+                mJsmnTokens[j].type == JSMN_PRIMITIVE) {
+            token.setTo(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start);
+            tokens->add(token);
+        }
+    }
+    return true;
+}
+
+/*
+ * Parses JSON Web Key Set string and initializes a vector of JSON objects.
+ *
+ * @return Returns false for errors, true for success.
+ */
+bool JsonWebKey::parseJsonWebKeySet(const String8& jsonWebKeySet,
+        Vector<String8>* jsonObjects) {
+    if (jsonWebKeySet.isEmpty()) {
+        ALOGE("Empty JSON Web Key");
+        return false;
+    }
+
+    // The jsmn parser only supports unicode encoding.
+    jsmn_parser parser;
+
+    // Computes number of tokens. A token marks the type, offset in
+    // the original string.
+    jsmn_init(&parser);
+    int numTokens = jsmn_parse(&parser,
+            jsonWebKeySet.string(), jsonWebKeySet.size(), NULL, 0);
+    if (numTokens < 0) {
+        ALOGE("Parser returns error code=%d", numTokens);
+        return false;
+    }
+
+    unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
+    mJsmnTokens.setCapacity(jsmnTokensSize);
+
+    jsmn_init(&parser);
+    int status = jsmn_parse(&parser, jsonWebKeySet.string(),
+            jsonWebKeySet.size(), mJsmnTokens.editArray(), numTokens);
+    if (status < 0) {
+        ALOGE("Parser returns error code=%d", status);
+        return false;
+    }
+
+    String8 token;
+    const char *pjs;
+    for (int i = 0; i < numTokens; ++i) {
+        pjs = jsonWebKeySet.string() + mJsmnTokens[i].start;
+        if (mJsmnTokens[i].type == JSMN_OBJECT) {
+            token.setTo(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start);
+            jsonObjects->add(token);
+        }
+    }
+    return true;
+}
+
+}  // clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.h b/drm/mediadrm/plugins/clearkey/JsonWebKey.h
new file mode 100644 (file)
index 0000000..6ae50ee
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CLEARKEY_JSON_WEB_KEY_H_
+#define CLEARKEY_JSON_WEB_KEY_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+
+#include "jsmn.h"
+#include "Utils.h"
+#include "ClearKeyTypes.h"
+
+namespace clearkeydrm {
+
+using android::KeyedVector;
+using android::sp;
+using android::String8;
+using android::Vector;
+
+class JsonWebKey {
+ public:
+    JsonWebKey();
+    virtual ~JsonWebKey();
+
+    bool extractKeysFromJsonWebKeySet(const String8& jsonWebKeySet,
+            KeyMap* keys);
+
+ private:
+    Vector<jsmntok_t> mJsmnTokens;
+    Vector<String8> mJsonObjects;
+    Vector<String8> mTokens;
+
+    bool decodeBase64String(const String8& encodedText,
+            Vector<uint8_t>* decodedText);
+    bool findKey(const String8& jsonObject, String8* keyId,
+            String8* encodedKey);
+    void findValue(const String8 &key, String8* value);
+    bool isJsonWebKeySet(const String8& jsonObject) const;
+    bool parseJsonObject(const String8& jsonObject, Vector<String8>* tokens);
+    bool parseJsonWebKeySet(const String8& jsonWebKeySet, Vector<String8>* jsonObjects);
+
+    DISALLOW_EVIL_CONSTRUCTORS(JsonWebKey);
+};
+
+}  // namespace clearkeydrm
+
+#endif  // CLEARKEY_JSON_WEB_KEY_H_
diff --git a/drm/mediadrm/plugins/clearkey/tests/Android.mk b/drm/mediadrm/plugins/clearkey/tests/Android.mk
new file mode 100644 (file)
index 0000000..c12cd9f
--- /dev/null
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ----------------------------------------------------------------
+# Builds ClearKey Drm Tests
+#
+LOCAL_PATH := $(call my-dir)
+
+test_name := JsonWebKeyUnittest
+test_src_dir := .
+include $(LOCAL_PATH)/unit-test.mk
+
+test_name :=
+test_src_dir :=
diff --git a/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp
new file mode 100644 (file)
index 0000000..c3b0d84
--- /dev/null
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <utils/Log.h>
+
+#include "JsonWebKey.h"
+
+#include "gtest/gtest.h"
+#include "Utils.h"
+
+namespace clearkeydrm {
+using android::String8;
+using android::Vector;
+
+class JsonWebKeyTest : public ::testing::Test {
+ protected:
+    JsonWebKey* jwk;
+
+    JsonWebKeyTest() {
+        jwk = new JsonWebKey;
+    }
+
+    virtual ~JsonWebKeyTest() {
+        if (jwk)
+            delete jwk;
+    }
+};
+
+void stringFromVector(const Vector<uint8_t>& input,
+        String8* converted) {
+    converted->clear();
+    if (input.isEmpty()) {
+        return;
+    }
+
+    for (size_t i = 0; i < input.size(); ++i) {
+        converted->appendFormat("%c", input.itemAt(i));
+    }
+}
+
+void verifyKeys(const KeyMap& keys, const String8* clearKeys) {
+    if (keys.isEmpty()) {
+        return;
+    }
+
+    String8 keyString;
+    for (size_t i = 0; i < keys.size(); ++i) {
+        stringFromVector(keys.valueAt(i), &keyString);
+        EXPECT_EQ(keyString, clearKeys[i]);
+    }
+}
+
+TEST_F(JsonWebKeyTest, NoSymmetricKey) {
+    const String8 js(
+            "{"
+                "[{"
+                    "\"kty\":\"rsa\","
+                    "\"alg\":\"A128KW1\","
+                    "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\","
+                    "\"k\":\"1-GawgguFyGrWKav7AX4VKUg\""
+                "}]"
+          "}");
+
+    KeyMap keys;
+    EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.isEmpty());
+}
+
+TEST_F(JsonWebKeyTest, NoKeysTag) {
+    const String8 js(
+            "{"
+                "[{"
+                    "\"kty\":\"oct\","
+                    "\"alg\":\"A128KW1\","
+                    "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\","
+                    "\"k\":\"1-GawgguFyGrWKav7AX4VKUg\""
+                "},"
+                "{"
+                    "\"kty\":\"oct\","
+                    "\"alg\":\"A128KW2\","
+                    "\"k\":\"R29vZCBkYXkh\","
+                    "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                "}]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.isEmpty());
+}
+
+TEST_F(JsonWebKeyTest, NoKeyId) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kISE=\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW2\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.size() == 1);
+
+    const String8 clearKeys("Good day!");
+    verifyKeys(keys, &clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, NoKey) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"kid\":\"`\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW2\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.size() == 1);
+
+    const String8 clearKeys("Good day!");
+    verifyKeys(keys, &clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, MalformedKey) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"GawgguFyGrWKav7AX4V???\""
+                        "\"kid\":\"67ef0gd8pvfd0=\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"GawgguFyGrWKav7AX4V???\""
+                        "\"kid\":"
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        ":\"GawgguFyGrWKav7AX4V???\""
+                        "\"kid\":\"67ef0gd8pvfd0=\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW3\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.size() == 1);
+
+    const String8 clearKeys("Good day!");
+    verifyKeys(keys, &clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, EmptyJsonWebKey) {
+    const String8 js;
+    KeyMap keys;
+    EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.isEmpty());
+}
+
+TEST_F(JsonWebKeyTest, MalformedJsonWebKey) {
+    // Missing begin array '['
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"GawgguFyGrWKav7AX4VKUg\""
+                        "\"kid\":\"67ef0gd8pvfd0=\""
+                    "}"
+            "]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.isEmpty());
+}
+
+TEST_F(JsonWebKeyTest, SameKeyId) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kISE\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kIQ\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW3\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    jwk->extractKeysFromJsonWebKeySet(js, &keys);
+    EXPECT_TRUE(keys.size() == 2);
+
+    const String8 clearKeys[] =
+            { String8("Hello Friend!"), String8("Good day!") };
+    verifyKeys(keys, clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, ExtractWellFormedKeys) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW2\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kIQ\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW3\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    jwk->extractKeysFromJsonWebKeySet(js, &keys);
+    EXPECT_TRUE(keys.size() == 2);
+
+    const String8 clearKeys[] =
+            { String8("Hello Friend!"), String8("Good day!") };
+    verifyKeys(keys, clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, ExtractKeys) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kISE\""
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW2\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kIQ\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                    "}"
+                    "{"
+                        "\"kty\":\"rsa\""
+                        "\"alg\":\"A128KW-rsa\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                        "\"kid\":\"rsa-67ef0gd8pvfd0=\""
+                    "}"
+                    "{"
+                        "\"alg\":\"A128KW3\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                        "\"kty\":\"oct\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    jwk->extractKeysFromJsonWebKeySet(js, &keys);
+    EXPECT_TRUE(keys.size() == 3);
+
+    const String8 clearKeys[] =
+            { String8("Hello Friend!!"), String8("Hello Friend!"),
+              String8("Good day!") };
+    verifyKeys(keys, clearKeys);
+}
+
+}  // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/tests/unit-test.mk b/drm/mediadrm/plugins/clearkey/tests/unit-test.mk
new file mode 100644 (file)
index 0000000..6afb5a3
--- /dev/null
@@ -0,0 +1,51 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------
+# Makes a unit or end to end test.
+# test_name must be passed in as the base filename(without the .cpp).
+#
+$(call assert-not-null,test_name)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := $(test_name)
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    $(test_src_dir)/$(test_name).cpp
+
+LOCAL_C_INCLUDES := \
+    bionic \
+    external/gtest/include \
+    external/jsmn \
+    external/openssl/include \
+    external/stlport/stlport \
+    frameworks/av/drm/mediadrm/plugins/clearkey \
+    frameworks/av/include \
+    frameworks/native/include \
+
+LOCAL_STATIC_LIBRARIES := \
+    libgtest \
+    libgtest_main \
+
+LOCAL_SHARED_LIBRARIES := \
+    libcrypto \
+    libdrmclearkeyplugin \
+    liblog \
+    libstlport \
+    libutils \
+
+include $(BUILD_EXECUTABLE)