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
29namespace android {
30namespace clearkeycas {
31
32const String8 kIdTag("id");
33const String8 kNameTag("name");
34const String8 kLowerCaseOgranizationNameTag("lowercase_organization_name");
35const String8 kEncryptionKeyTag("encryption_key");
36const String8 kCasTypeTag("cas_type");
37const String8 kBase64Padding("=");
38
39const uint32_t kKeyLength = 16;
40
41JsonAssetLoader::JsonAssetLoader() {
42}
43
44JsonAssetLoader::~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 */
54status_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
74sp<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
88bool 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
124void 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 */
145bool 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 */
190bool 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