1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #define LOG_TAG "JsonWebKey"
17 
18 #include <media/stagefright/foundation/ABuffer.h>
19 #include <media/stagefright/foundation/AString.h>
20 #include <media/stagefright/foundation/base64.h>
21 #include <utils/Log.h>
22 
23 #include "JsonWebKey.h"
24 
25 namespace {
26 const android::String8 kKeysTag("keys");
27 const android::String8 kKeyTypeTag("kty");
28 const android::String8 kSymmetricKeyValue("oct");
29 const android::String8 kKeyTag("k");
30 const android::String8 kKeyIdTag("kid");
31 const android::String8 kBase64Padding("=");
32 }
33 
34 namespace clearkeydrm {
35 
36 using android::ABuffer;
37 using android::AString;
38 
JsonWebKey()39 JsonWebKey::JsonWebKey() {
40 }
41 
~JsonWebKey()42 JsonWebKey::~JsonWebKey() {
43 }
44 
45 /*
46  * Parses a JSON Web Key Set string, initializes a KeyMap with key id:key
47  * pairs from the JSON Web Key Set. Both key ids and keys are base64url
48  * encoded. The KeyMap contains base64url decoded key id:key pairs.
49  *
50  * @return Returns false for errors, true for success.
51  */
extractKeysFromJsonWebKeySet(const String8 & jsonWebKeySet,KeyMap * keys)52 bool JsonWebKey::extractKeysFromJsonWebKeySet(const String8& jsonWebKeySet,
53         KeyMap* keys) {
54 
55     keys->clear();
56     if (!parseJsonWebKeySet(jsonWebKeySet, &mJsonObjects)) {
57         return false;
58     }
59 
60     // mJsonObjects[0] contains the entire JSON Web Key Set, including
61     // all the base64 encoded keys. Each key is also stored separately as
62     // a JSON object in mJsonObjects[1..n] where n is the total
63     // number of keys in the set.
64     if (!isJsonWebKeySet(mJsonObjects[0])) {
65         return false;
66     }
67 
68     String8 encodedKey, encodedKeyId;
69     Vector<uint8_t> decodedKey, decodedKeyId;
70 
71     // mJsonObjects[1] contains the first JSON Web Key in the set
72     for (size_t i = 1; i < mJsonObjects.size(); ++i) {
73         encodedKeyId.clear();
74         encodedKey.clear();
75 
76         if (!parseJsonObject(mJsonObjects[i], &mTokens))
77             return false;
78 
79         if (findKey(mJsonObjects[i], &encodedKeyId, &encodedKey)) {
80             if (encodedKeyId.isEmpty() || encodedKey.isEmpty()) {
81                 ALOGE("Must have both key id and key in the JsonWebKey set.");
82                 continue;
83             }
84 
85             if (!decodeBase64String(encodedKeyId, &decodedKeyId)) {
86                 ALOGE("Failed to decode key id(%s)", encodedKeyId.string());
87                 continue;
88             }
89 
90             if (!decodeBase64String(encodedKey, &decodedKey)) {
91                 ALOGE("Failed to decode key(%s)", encodedKey.string());
92                 continue;
93             }
94 
95             keys->add(decodedKeyId, decodedKey);
96         }
97     }
98     return true;
99 }
100 
decodeBase64String(const String8 & encodedText,Vector<uint8_t> * decodedText)101 bool JsonWebKey::decodeBase64String(const String8& encodedText,
102         Vector<uint8_t>* decodedText) {
103 
104     decodedText->clear();
105 
106     // encodedText should not contain padding characters as per EME spec.
107     if (encodedText.find(kBase64Padding) != -1) {
108         return false;
109     }
110 
111     // Since android::decodeBase64() requires padding characters,
112     // add them so length of encodedText is exactly a multiple of 4.
113     int remainder = encodedText.length() % 4;
114     String8 paddedText(encodedText);
115     if (remainder > 0) {
116         for (int i = 0; i < 4 - remainder; ++i) {
117             paddedText.append(kBase64Padding);
118         }
119     }
120 
121     android::sp<ABuffer> buffer =
122             android::decodeBase64(AString(paddedText.string()));
123     if (buffer == NULL) {
124         ALOGE("Malformed base64 encoded content found.");
125         return false;
126     }
127 
128     decodedText->appendArray(buffer->base(), buffer->size());
129     return true;
130 }
131 
findKey(const String8 & jsonObject,String8 * keyId,String8 * encodedKey)132 bool JsonWebKey::findKey(const String8& jsonObject, String8* keyId,
133         String8* encodedKey) {
134 
135     String8 key, value;
136 
137     // Only allow symmetric key, i.e. "kty":"oct" pair.
138     if (jsonObject.find(kKeyTypeTag) >= 0) {
139         findValue(kKeyTypeTag, &value);
140         if (0 != value.compare(kSymmetricKeyValue))
141             return false;
142     }
143 
144     if (jsonObject.find(kKeyIdTag) >= 0) {
145         findValue(kKeyIdTag, keyId);
146     }
147 
148     if (jsonObject.find(kKeyTag) >= 0) {
149         findValue(kKeyTag, encodedKey);
150     }
151     return true;
152 }
153 
findValue(const String8 & key,String8 * value)154 void JsonWebKey::findValue(const String8 &key, String8* value) {
155     value->clear();
156     const char* valueToken;
157     for (Vector<String8>::const_iterator nextToken = mTokens.begin();
158         nextToken != mTokens.end(); ++nextToken) {
159         if (0 == (*nextToken).compare(key)) {
160             if (nextToken + 1 == mTokens.end())
161                 break;
162             valueToken = (*(nextToken + 1)).string();
163             value->setTo(valueToken);
164             nextToken++;
165             break;
166         }
167     }
168 }
169 
isJsonWebKeySet(const String8 & jsonObject) const170 bool JsonWebKey::isJsonWebKeySet(const String8& jsonObject) const {
171     if (jsonObject.find(kKeysTag) == -1) {
172         ALOGE("JSON Web Key does not contain keys.");
173         return false;
174     }
175     return true;
176 }
177 
178 /*
179  * Parses a JSON objects string and initializes a vector of tokens.
180  *
181  * @return Returns false for errors, true for success.
182  */
parseJsonObject(const String8 & jsonObject,Vector<String8> * tokens)183 bool JsonWebKey::parseJsonObject(const String8& jsonObject,
184         Vector<String8>* tokens) {
185     jsmn_parser parser;
186 
187     jsmn_init(&parser);
188     int numTokens = jsmn_parse(&parser,
189         jsonObject.string(), jsonObject.size(), NULL, 0);
190     if (numTokens < 0) {
191         ALOGE("Parser returns error code=%d", numTokens);
192         return false;
193     }
194 
195     unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
196     mJsmnTokens.clear();
197     mJsmnTokens.setCapacity(jsmnTokensSize);
198 
199     jsmn_init(&parser);
200     int status = jsmn_parse(&parser, jsonObject.string(),
201         jsonObject.size(), mJsmnTokens.editArray(), numTokens);
202     if (status < 0) {
203         ALOGE("Parser returns error code=%d", status);
204         return false;
205     }
206 
207     tokens->clear();
208     String8 token;
209     const char *pjs;
210     for (int j = 0; j < numTokens; ++j) {
211         pjs = jsonObject.string() + mJsmnTokens[j].start;
212         if (mJsmnTokens[j].type == JSMN_STRING ||
213                 mJsmnTokens[j].type == JSMN_PRIMITIVE) {
214             token.setTo(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start);
215             tokens->add(token);
216         }
217     }
218     return true;
219 }
220 
221 /*
222  * Parses JSON Web Key Set string and initializes a vector of JSON objects.
223  *
224  * @return Returns false for errors, true for success.
225  */
parseJsonWebKeySet(const String8 & jsonWebKeySet,Vector<String8> * jsonObjects)226 bool JsonWebKey::parseJsonWebKeySet(const String8& jsonWebKeySet,
227         Vector<String8>* jsonObjects) {
228     if (jsonWebKeySet.isEmpty()) {
229         ALOGE("Empty JSON Web Key");
230         return false;
231     }
232 
233     // The jsmn parser only supports unicode encoding.
234     jsmn_parser parser;
235 
236     // Computes number of tokens. A token marks the type, offset in
237     // the original string.
238     jsmn_init(&parser);
239     int numTokens = jsmn_parse(&parser,
240             jsonWebKeySet.string(), jsonWebKeySet.size(), NULL, 0);
241     if (numTokens < 0) {
242         ALOGE("Parser returns error code=%d", numTokens);
243         return false;
244     }
245 
246     unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
247     mJsmnTokens.setCapacity(jsmnTokensSize);
248 
249     jsmn_init(&parser);
250     int status = jsmn_parse(&parser, jsonWebKeySet.string(),
251             jsonWebKeySet.size(), mJsmnTokens.editArray(), numTokens);
252     if (status < 0) {
253         ALOGE("Parser returns error code=%d", status);
254         return false;
255     }
256 
257     String8 token;
258     const char *pjs;
259     for (int i = 0; i < numTokens; ++i) {
260         pjs = jsonWebKeySet.string() + mJsmnTokens[i].start;
261         if (mJsmnTokens[i].type == JSMN_OBJECT) {
262             token.setTo(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start);
263             jsonObjects->add(token);
264         }
265     }
266     return true;
267 }
268 
269 }  // clearkeydrm
270