diff options
Diffstat (limited to 'drm/mediadrm/plugins/clearkey/JsonWebKey.cpp')
-rw-r--r-- | drm/mediadrm/plugins/clearkey/JsonWebKey.cpp | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp b/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp new file mode 100644 index 0000000..53ffae4 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp @@ -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 |