Home | History | Annotate | Download | only in clearkey
      1 
      2 /*
      3  * Copyright (C) 2017 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 //#define LOG_NDEBUG 0
     18 #define LOG_TAG "JsonAssetLoader"
     19 
     20 #include <media/stagefright/foundation/ABuffer.h>
     21 #include <media/stagefright/foundation/AString.h>
     22 #include <media/stagefright/foundation/base64.h>
     23 #include <media/stagefright/MediaErrors.h>
     24 #include <utils/Log.h>
     25 
     26 #include "JsonAssetLoader.h"
     27 #include "protos/license_protos.pb.h"
     28 
     29 namespace android {
     30 namespace clearkeycas {
     31 
     32 const String8 kIdTag("id");
     33 const String8 kNameTag("name");
     34 const String8 kLowerCaseOgranizationNameTag("lowercase_organization_name");
     35 const String8 kEncryptionKeyTag("encryption_key");
     36 const String8 kCasTypeTag("cas_type");
     37 const String8 kBase64Padding("=");
     38 
     39 JsonAssetLoader::JsonAssetLoader() {
     40 }
     41 
     42 JsonAssetLoader::~JsonAssetLoader() {
     43 }
     44 
     45 /*
     46  * Extract a clear key asset from a JSON string.
     47  *
     48  * Returns OK if a clear key asset is extracted successfully,
     49  * or ERROR_CAS_NO_LICENSE if the string doesn't contain a valid
     50  * clear key asset.
     51  */
     52 status_t JsonAssetLoader::extractAssetFromString(
     53         const String8& jsonAssetString, Asset *asset) {
     54     if (!parseJsonAssetString(jsonAssetString, &mJsonObjects)) {
     55         return ERROR_CAS_NO_LICENSE;
     56     }
     57 
     58     if (mJsonObjects.size() < 1) {
     59         return ERROR_CAS_NO_LICENSE;
     60     }
     61 
     62     if (!parseJsonObject(mJsonObjects[0], &mTokens))
     63         return ERROR_CAS_NO_LICENSE;
     64 
     65     if (!findKey(mJsonObjects[0], asset)) {
     66         return ERROR_CAS_NO_LICENSE;
     67     }
     68     return OK;
     69 }
     70 
     71 //static
     72 sp<ABuffer> JsonAssetLoader::decodeBase64String(const String8& encodedText) {
     73     // Since android::decodeBase64() requires padding characters,
     74     // add them so length of encodedText is exactly a multiple of 4.
     75     int remainder = encodedText.length() % 4;
     76     String8 paddedText(encodedText);
     77     if (remainder > 0) {
     78         for (int i = 0; i < 4 - remainder; ++i) {
     79             paddedText.append(kBase64Padding);
     80         }
     81     }
     82 
     83     return decodeBase64(AString(paddedText.string()));
     84 }
     85 
     86 bool JsonAssetLoader::findKey(const String8& jsonObject, Asset *asset) {
     87 
     88     String8 value;
     89 
     90     if (jsonObject.find(kIdTag) < 0) {
     91         return false;
     92     }
     93     findValue(kIdTag, &value);
     94     ALOGV("found %s=%s", kIdTag.string(), value.string());
     95     asset->set_id(atoi(value.string()));
     96 
     97     if (jsonObject.find(kNameTag) < 0) {
     98         return false;
     99     }
    100     findValue(kNameTag, &value);
    101     ALOGV("found %s=%s", kNameTag.string(), value.string());
    102     asset->set_name(value.string());
    103 
    104     if (jsonObject.find(kLowerCaseOgranizationNameTag) < 0) {
    105         return false;
    106     }
    107     findValue(kLowerCaseOgranizationNameTag, &value);
    108     ALOGV("found %s=%s", kLowerCaseOgranizationNameTag.string(), value.string());
    109     asset->set_lowercase_organization_name(value.string());
    110 
    111     if (jsonObject.find(kCasTypeTag) < 0) {
    112         return false;
    113     }
    114     findValue(kCasTypeTag, &value);
    115     ALOGV("found %s=%s", kCasTypeTag.string(), value.string());
    116     // Asset_CasType_CLEARKEY_CAS = 1
    117     asset->set_cas_type((Asset_CasType)atoi(value.string()));
    118 
    119     return true;
    120 }
    121 
    122 void JsonAssetLoader::findValue(const String8 &key, String8* value) {
    123     value->clear();
    124     const char* valueToken;
    125     for (Vector<String8>::const_iterator nextToken = mTokens.begin();
    126         nextToken != mTokens.end(); ++nextToken) {
    127         if (0 == (*nextToken).compare(key)) {
    128             if (nextToken + 1 == mTokens.end())
    129                 break;
    130             valueToken = (*(nextToken + 1)).string();
    131             value->setTo(valueToken);
    132             nextToken++;
    133             break;
    134         }
    135     }
    136 }
    137 
    138 /*
    139  * Parses a JSON objects string and initializes a vector of tokens.
    140  *
    141  * @return Returns false for errors, true for success.
    142  */
    143 bool JsonAssetLoader::parseJsonObject(const String8& jsonObject,
    144         Vector<String8>* tokens) {
    145     jsmn_parser parser;
    146 
    147     jsmn_init(&parser);
    148     int numTokens = jsmn_parse(&parser,
    149         jsonObject.string(), jsonObject.size(), NULL, 0);
    150     if (numTokens < 0) {
    151         ALOGE("Parser returns error code=%d", numTokens);
    152         return false;
    153     }
    154 
    155     unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
    156     mJsmnTokens.clear();
    157     mJsmnTokens.setCapacity(jsmnTokensSize);
    158 
    159     jsmn_init(&parser);
    160     int status = jsmn_parse(&parser, jsonObject.string(),
    161         jsonObject.size(), mJsmnTokens.editArray(), numTokens);
    162     if (status < 0) {
    163         ALOGE("Parser returns error code=%d", status);
    164         return false;
    165     }
    166 
    167     tokens->clear();
    168     String8 token;
    169     const char *pjs;
    170     ALOGV("numTokens: %d", numTokens);
    171     for (int j = 0; j < numTokens; ++j) {
    172         pjs = jsonObject.string() + mJsmnTokens[j].start;
    173         if (mJsmnTokens[j].type == JSMN_STRING ||
    174                 mJsmnTokens[j].type == JSMN_PRIMITIVE) {
    175             token.setTo(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start);
    176             tokens->add(token);
    177             ALOGV("add token: %s", token.string());
    178         }
    179     }
    180     return true;
    181 }
    182 
    183 /*
    184  * Parses JSON asset string and initializes a vector of JSON objects.
    185  *
    186  * @return Returns false for errors, true for success.
    187  */
    188 bool JsonAssetLoader::parseJsonAssetString(const String8& jsonAsset,
    189         Vector<String8>* jsonObjects) {
    190     if (jsonAsset.isEmpty()) {
    191         ALOGE("Empty JSON Web Key");
    192         return false;
    193     }
    194 
    195     // The jsmn parser only supports unicode encoding.
    196     jsmn_parser parser;
    197 
    198     // Computes number of tokens. A token marks the type, offset in
    199     // the original string.
    200     jsmn_init(&parser);
    201     int numTokens = jsmn_parse(&parser,
    202             jsonAsset.string(), jsonAsset.size(), NULL, 0);
    203     if (numTokens < 0) {
    204         ALOGE("Parser returns error code=%d", numTokens);
    205         return false;
    206     }
    207 
    208     unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
    209     mJsmnTokens.setCapacity(jsmnTokensSize);
    210 
    211     jsmn_init(&parser);
    212     int status = jsmn_parse(&parser, jsonAsset.string(),
    213             jsonAsset.size(), mJsmnTokens.editArray(), numTokens);
    214     if (status < 0) {
    215         ALOGE("Parser returns error code=%d", status);
    216         return false;
    217     }
    218 
    219     String8 token;
    220     const char *pjs;
    221     for (int i = 0; i < numTokens; ++i) {
    222         pjs = jsonAsset.string() + mJsmnTokens[i].start;
    223         if (mJsmnTokens[i].type == JSMN_OBJECT) {
    224             token.setTo(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start);
    225             jsonObjects->add(token);
    226         }
    227     }
    228     return true;
    229 }
    230 
    231 }  // namespace clearkeycas
    232 }  // namespace android
    233