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 const uint32_t kKeyLength = 16;
     40 
     41 JsonAssetLoader::JsonAssetLoader() {
     42 }
     43 
     44 JsonAssetLoader::~JsonAssetLoader() {
     45 }
     46 
     47 /*
     48  * Extract a clear key asset from a JSON string.
     49  *
     50  * Returns OK if a clear key asset is extracted successfully,
     51  * or ERROR_DRM_NO_LICENSE if the string doesn't contain a valid
     52  * clear key asset.
     53  */
     54 status_t JsonAssetLoader::extractAssetFromString(
     55         const String8& jsonAssetString, Asset *asset) {
     56     if (!parseJsonAssetString(jsonAssetString, &mJsonObjects)) {
     57         return ERROR_DRM_NO_LICENSE;
     58     }
     59 
     60     if (mJsonObjects.size() < 1) {
     61         return ERROR_DRM_NO_LICENSE;
     62     }
     63 
     64     if (!parseJsonObject(mJsonObjects[0], &mTokens))
     65         return ERROR_DRM_NO_LICENSE;
     66 
     67     if (!findKey(mJsonObjects[0], asset)) {
     68         return ERROR_DRM_NO_LICENSE;
     69     }
     70     return OK;
     71 }
     72 
     73 //static
     74 sp<ABuffer> JsonAssetLoader::decodeBase64String(const String8& encodedText) {
     75     // Since android::decodeBase64() requires padding characters,
     76     // add them so length of encodedText is exactly a multiple of 4.
     77     int remainder = encodedText.length() % 4;
     78     String8 paddedText(encodedText);
     79     if (remainder > 0) {
     80         for (int i = 0; i < 4 - remainder; ++i) {
     81             paddedText.append(kBase64Padding);
     82         }
     83     }
     84 
     85     return decodeBase64(AString(paddedText.string()));
     86 }
     87 
     88 bool JsonAssetLoader::findKey(const String8& jsonObject, Asset *asset) {
     89 
     90     String8 value;
     91 
     92     if (jsonObject.find(kIdTag) < 0) {
     93         return false;
     94     }
     95     findValue(kIdTag, &value);
     96     ALOGV("found %s=%s", kIdTag.string(), value.string());
     97     asset->set_id(atoi(value.string()));
     98 
     99     if (jsonObject.find(kNameTag) < 0) {
    100         return false;
    101     }
    102     findValue(kNameTag, &value);
    103     ALOGV("found %s=%s", kNameTag.string(), value.string());
    104     asset->set_name(value.string());
    105 
    106     if (jsonObject.find(kLowerCaseOgranizationNameTag) < 0) {
    107         return false;
    108     }
    109     findValue(kLowerCaseOgranizationNameTag, &value);
    110     ALOGV("found %s=%s", kLowerCaseOgranizationNameTag.string(), value.string());
    111     asset->set_lowercase_organization_name(value.string());
    112 
    113     if (jsonObject.find(kCasTypeTag) < 0) {
    114         return false;
    115     }
    116     findValue(kCasTypeTag, &value);
    117     ALOGV("found %s=%s", kCasTypeTag.string(), value.string());
    118     // Asset_CasType_CLEARKEY_CAS = 1
    119     asset->set_cas_type((Asset_CasType)atoi(value.string()));
    120 
    121     return true;
    122 }
    123 
    124 void JsonAssetLoader::findValue(const String8 &key, String8* value) {
    125     value->clear();
    126     const char* valueToken;
    127     for (Vector<String8>::const_iterator nextToken = mTokens.begin();
    128         nextToken != mTokens.end(); ++nextToken) {
    129         if (0 == (*nextToken).compare(key)) {
    130             if (nextToken + 1 == mTokens.end())
    131                 break;
    132             valueToken = (*(nextToken + 1)).string();
    133             value->setTo(valueToken);
    134             nextToken++;
    135             break;
    136         }
    137     }
    138 }
    139 
    140 /*
    141  * Parses a JSON objects string and initializes a vector of tokens.
    142  *
    143  * @return Returns false for errors, true for success.
    144  */
    145 bool JsonAssetLoader::parseJsonObject(const String8& jsonObject,
    146         Vector<String8>* tokens) {
    147     jsmn_parser parser;
    148 
    149     jsmn_init(&parser);
    150     int numTokens = jsmn_parse(&parser,
    151         jsonObject.string(), jsonObject.size(), NULL, 0);
    152     if (numTokens < 0) {
    153         ALOGE("Parser returns error code=%d", numTokens);
    154         return false;
    155     }
    156 
    157     unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
    158     mJsmnTokens.clear();
    159     mJsmnTokens.setCapacity(jsmnTokensSize);
    160 
    161     jsmn_init(&parser);
    162     int status = jsmn_parse(&parser, jsonObject.string(),
    163         jsonObject.size(), mJsmnTokens.editArray(), numTokens);
    164     if (status < 0) {
    165         ALOGE("Parser returns error code=%d", status);
    166         return false;
    167     }
    168 
    169     tokens->clear();
    170     String8 token;
    171     const char *pjs;
    172     ALOGV("numTokens: %d", numTokens);
    173     for (int j = 0; j < numTokens; ++j) {
    174         pjs = jsonObject.string() + mJsmnTokens[j].start;
    175         if (mJsmnTokens[j].type == JSMN_STRING ||
    176                 mJsmnTokens[j].type == JSMN_PRIMITIVE) {
    177             token.setTo(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start);
    178             tokens->add(token);
    179             ALOGV("add token: %s", token.string());
    180         }
    181     }
    182     return true;
    183 }
    184 
    185 /*
    186  * Parses JSON asset string and initializes a vector of JSON objects.
    187  *
    188  * @return Returns false for errors, true for success.
    189  */
    190 bool JsonAssetLoader::parseJsonAssetString(const String8& jsonAsset,
    191         Vector<String8>* jsonObjects) {
    192     if (jsonAsset.isEmpty()) {
    193         ALOGE("Empty JSON Web Key");
    194         return false;
    195     }
    196 
    197     // The jsmn parser only supports unicode encoding.
    198     jsmn_parser parser;
    199 
    200     // Computes number of tokens. A token marks the type, offset in
    201     // the original string.
    202     jsmn_init(&parser);
    203     int numTokens = jsmn_parse(&parser,
    204             jsonAsset.string(), jsonAsset.size(), NULL, 0);
    205     if (numTokens < 0) {
    206         ALOGE("Parser returns error code=%d", numTokens);
    207         return false;
    208     }
    209 
    210     unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
    211     mJsmnTokens.setCapacity(jsmnTokensSize);
    212 
    213     jsmn_init(&parser);
    214     int status = jsmn_parse(&parser, jsonAsset.string(),
    215             jsonAsset.size(), mJsmnTokens.editArray(), numTokens);
    216     if (status < 0) {
    217         ALOGE("Parser returns error code=%d", status);
    218         return false;
    219     }
    220 
    221     String8 token;
    222     const char *pjs;
    223     for (int i = 0; i < numTokens; ++i) {
    224         pjs = jsonAsset.string() + mJsmnTokens[i].start;
    225         if (mJsmnTokens[i].type == JSMN_OBJECT) {
    226             token.setTo(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start);
    227             jsonObjects->add(token);
    228         }
    229     }
    230     return true;
    231 }
    232 
    233 }  // namespace clearkeycas
    234 }  // namespace android
    235