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 
JsonAssetLoader()39 JsonAssetLoader::JsonAssetLoader() {
40 }
41 
~JsonAssetLoader()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  */
extractAssetFromString(const String8 & jsonAssetString,Asset * asset)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
decodeBase64String(const String8 & encodedText)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 
findKey(const String8 & jsonObject,Asset * asset)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 
findValue(const String8 & key,String8 * value)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  */
parseJsonObject(const String8 & jsonObject,Vector<String8> * tokens)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  */
parseJsonAssetString(const String8 & jsonAsset,Vector<String8> * jsonObjects)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