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 (mJsonObjects.size() == 0 || !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