| |
| /* |
| * Copyright (C) 2017 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_NDEBUG 0 |
| #define LOG_TAG "JsonAssetLoader" |
| |
| #include <media/stagefright/foundation/ABuffer.h> |
| #include <media/stagefright/foundation/AString.h> |
| #include <media/stagefright/foundation/base64.h> |
| #include <media/stagefright/MediaErrors.h> |
| #include <utils/Log.h> |
| |
| #include "JsonAssetLoader.h" |
| #include "protos/license_protos.pb.h" |
| |
| namespace android { |
| namespace clearkeycas { |
| |
| const String8 kIdTag("id"); |
| const String8 kNameTag("name"); |
| const String8 kLowerCaseOgranizationNameTag("lowercase_organization_name"); |
| const String8 kEncryptionKeyTag("encryption_key"); |
| const String8 kCasTypeTag("cas_type"); |
| const String8 kBase64Padding("="); |
| |
| JsonAssetLoader::JsonAssetLoader() { |
| } |
| |
| JsonAssetLoader::~JsonAssetLoader() { |
| } |
| |
| /* |
| * Extract a clear key asset from a JSON string. |
| * |
| * Returns OK if a clear key asset is extracted successfully, |
| * or ERROR_CAS_NO_LICENSE if the string doesn't contain a valid |
| * clear key asset. |
| */ |
| status_t JsonAssetLoader::extractAssetFromString( |
| const String8& jsonAssetString, Asset *asset) { |
| if (!parseJsonAssetString(jsonAssetString, &mJsonObjects)) { |
| return ERROR_CAS_NO_LICENSE; |
| } |
| |
| if (mJsonObjects.size() < 1) { |
| return ERROR_CAS_NO_LICENSE; |
| } |
| |
| if (!parseJsonObject(mJsonObjects[0], &mTokens)) |
| return ERROR_CAS_NO_LICENSE; |
| |
| if (!findKey(mJsonObjects[0], asset)) { |
| return ERROR_CAS_NO_LICENSE; |
| } |
| return OK; |
| } |
| |
| //static |
| sp<ABuffer> JsonAssetLoader::decodeBase64String(const String8& encodedText) { |
| // 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); |
| } |
| } |
| |
| return decodeBase64(AString(paddedText.c_str())); |
| } |
| |
| bool JsonAssetLoader::findKey(const String8& jsonObject, Asset *asset) { |
| |
| String8 value; |
| |
| if (jsonObject.find(kIdTag) < 0) { |
| return false; |
| } |
| findValue(kIdTag, &value); |
| ALOGV("found %s=%s", kIdTag.c_str(), value.c_str()); |
| asset->set_id(atoi(value.c_str())); |
| |
| if (jsonObject.find(kNameTag) < 0) { |
| return false; |
| } |
| findValue(kNameTag, &value); |
| ALOGV("found %s=%s", kNameTag.c_str(), value.c_str()); |
| asset->set_name(value.c_str()); |
| |
| if (jsonObject.find(kLowerCaseOgranizationNameTag) < 0) { |
| return false; |
| } |
| findValue(kLowerCaseOgranizationNameTag, &value); |
| ALOGV("found %s=%s", kLowerCaseOgranizationNameTag.c_str(), value.c_str()); |
| asset->set_lowercase_organization_name(value.c_str()); |
| |
| if (jsonObject.find(kCasTypeTag) < 0) { |
| return false; |
| } |
| findValue(kCasTypeTag, &value); |
| ALOGV("found %s=%s", kCasTypeTag.c_str(), value.c_str()); |
| // Asset_CasType_CLEARKEY_CAS = 1 |
| asset->set_cas_type((Asset_CasType)atoi(value.c_str())); |
| |
| return true; |
| } |
| |
| void JsonAssetLoader::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)).c_str(); |
| *value = valueToken; |
| nextToken++; |
| break; |
| } |
| } |
| } |
| |
| /* |
| * Parses a JSON objects string and initializes a vector of tokens. |
| * |
| * @return Returns false for errors, true for success. |
| */ |
| bool JsonAssetLoader::parseJsonObject(const String8& jsonObject, |
| Vector<String8>* tokens) { |
| jsmn_parser parser; |
| |
| jsmn_init(&parser); |
| int numTokens = jsmn_parse(&parser, |
| jsonObject.c_str(), 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.c_str(), |
| jsonObject.size(), mJsmnTokens.editArray(), numTokens); |
| if (status < 0) { |
| ALOGE("Parser returns error code=%d", status); |
| return false; |
| } |
| |
| tokens->clear(); |
| String8 token; |
| const char *pjs; |
| ALOGV("numTokens: %d", numTokens); |
| for (int j = 0; j < numTokens; ++j) { |
| pjs = jsonObject.c_str() + mJsmnTokens[j].start; |
| if (mJsmnTokens[j].type == JSMN_STRING || |
| mJsmnTokens[j].type == JSMN_PRIMITIVE) { |
| token = String8(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start); |
| tokens->add(token); |
| ALOGV("add token: %s", token.c_str()); |
| } |
| } |
| return true; |
| } |
| |
| /* |
| * Parses JSON asset string and initializes a vector of JSON objects. |
| * |
| * @return Returns false for errors, true for success. |
| */ |
| bool JsonAssetLoader::parseJsonAssetString(const String8& jsonAsset, |
| Vector<String8>* jsonObjects) { |
| if (jsonAsset.empty()) { |
| 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, |
| jsonAsset.c_str(), jsonAsset.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, jsonAsset.c_str(), |
| jsonAsset.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 = jsonAsset.c_str() + mJsmnTokens[i].start; |
| if (mJsmnTokens[i].type == JSMN_OBJECT) { |
| token = String8(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start); |
| jsonObjects->add(token); |
| } |
| } |
| return true; |
| } |
| |
| } // namespace clearkeycas |
| } // namespace android |