1 /*
2 * Copyright (C) 2021 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 "clearkey-JsonWebKey"
17
18 #include <utils/Log.h>
19
20 #include "JsonWebKey.h"
21
22 #include "Base64.h"
23
24 namespace {
25 const std::string kBase64Padding("=");
26 const std::string kKeysTag("keys");
27 const std::string kKeyTypeTag("kty");
28 const std::string kKeyTag("k");
29 const std::string kKeyIdTag("kid");
30 const std::string kSymmetricKeyValue("oct");
31 } // namespace
32
33 namespace clearkeydrm {
34
JsonWebKey()35 JsonWebKey::JsonWebKey() {}
36
~JsonWebKey()37 JsonWebKey::~JsonWebKey() {}
38
39 /*
40 * Parses a JSON Web Key Set string, initializes a KeyMap with key id:key
41 * pairs from the JSON Web Key Set. Both key ids and keys are base64url
42 * encoded. The KeyMap contains base64url decoded key id:key pairs.
43 *
44 * @return Returns false for errors, true for success.
45 */
extractKeysFromJsonWebKeySet(const std::string & jsonWebKeySet,KeyMap * keys)46 bool JsonWebKey::extractKeysFromJsonWebKeySet(const std::string& jsonWebKeySet, KeyMap* keys) {
47 keys->clear();
48
49 if (!parseJsonWebKeySet(jsonWebKeySet, &mJsonObjects)) {
50 return false;
51 }
52
53 // mJsonObjects[0] contains the entire JSON Web Key Set, including
54 // all the base64 encoded keys. Each key is also stored separately as
55 // a JSON object in mJsonObjects[1..n] where n is the total
56 // number of keys in the set.
57 if (mJsonObjects.size() == 0 || !isJsonWebKeySet(mJsonObjects[0])) {
58 return false;
59 }
60
61 std::string encodedKey, encodedKeyId;
62 std::vector<uint8_t> decodedKey, decodedKeyId;
63
64 // mJsonObjects[1] contains the first JSON Web Key in the set
65 for (size_t i = 1; i < mJsonObjects.size(); ++i) {
66 encodedKeyId.clear();
67 encodedKey.clear();
68
69 if (!parseJsonObject(mJsonObjects[i], &mTokens)) return false;
70
71 if (findKey(mJsonObjects[i], &encodedKeyId, &encodedKey)) {
72 if (encodedKeyId.empty() || encodedKey.empty()) {
73 ALOGE("Must have both key id and key in the JsonWebKey set.");
74 continue;
75 }
76
77 if (!decodeBase64String(encodedKeyId, &decodedKeyId)) {
78 ALOGE("Failed to decode key id(%s)", encodedKeyId.c_str());
79 continue;
80 }
81
82 if (!decodeBase64String(encodedKey, &decodedKey)) {
83 ALOGE("Failed to decode key(%s)", encodedKey.c_str());
84 continue;
85 }
86
87 keys->insert(std::pair<std::vector<uint8_t>, std::vector<uint8_t>>(decodedKeyId,
88 decodedKey));
89 }
90 }
91 return true;
92 }
93
decodeBase64String(const std::string & encodedText,std::vector<uint8_t> * decodedText)94 bool JsonWebKey::decodeBase64String(const std::string& encodedText,
95 std::vector<uint8_t>* decodedText) {
96 decodedText->clear();
97
98 // encodedText should not contain padding characters as per EME spec.
99 if (encodedText.find(kBase64Padding) != std::string::npos) {
100 return false;
101 }
102
103 // Since decodeBase64() requires padding characters,
104 // add them so length of encodedText is exactly a multiple of 4.
105 int remainder = encodedText.length() % 4;
106 std::string paddedText(encodedText);
107 if (remainder > 0) {
108 for (int i = 0; i < 4 - remainder; ++i) {
109 paddedText.append(kBase64Padding);
110 }
111 }
112
113 ::android::sp<Buffer> buffer = decodeBase64(paddedText);
114 if (buffer == nullptr) {
115 ALOGE("Malformed base64 encoded content found.");
116 return false;
117 }
118
119 decodedText->insert(decodedText->end(), buffer->base(), buffer->base() + buffer->size());
120 return true;
121 }
122
findKey(const std::string & jsonObject,std::string * keyId,std::string * encodedKey)123 bool JsonWebKey::findKey(const std::string& jsonObject, std::string* keyId,
124 std::string* encodedKey) {
125 std::string key, value;
126
127 // Only allow symmetric key, i.e. "kty":"oct" pair.
128 if (jsonObject.find(kKeyTypeTag) != std::string::npos) {
129 findValue(kKeyTypeTag, &value);
130 if (0 != value.compare(kSymmetricKeyValue)) return false;
131 }
132
133 if (jsonObject.find(kKeyIdTag) != std::string::npos) {
134 findValue(kKeyIdTag, keyId);
135 }
136
137 if (jsonObject.find(kKeyTag) != std::string::npos) {
138 findValue(kKeyTag, encodedKey);
139 }
140 return true;
141 }
142
findValue(const std::string & key,std::string * value)143 void JsonWebKey::findValue(const std::string& key, std::string* value) {
144 value->clear();
145 const char* valueToken;
146 for (std::vector<std::string>::const_iterator nextToken = mTokens.begin();
147 nextToken != mTokens.end(); ++nextToken) {
148 if (0 == (*nextToken).compare(key)) {
149 if (nextToken + 1 == mTokens.end()) break;
150 valueToken = (*(nextToken + 1)).c_str();
151 value->assign(valueToken);
152 nextToken++;
153 break;
154 }
155 }
156 }
157
isJsonWebKeySet(const std::string & jsonObject) const158 bool JsonWebKey::isJsonWebKeySet(const std::string& jsonObject) const {
159 if (jsonObject.find(kKeysTag) == std::string::npos) {
160 ALOGE("JSON Web Key does not contain keys.");
161 return false;
162 }
163 return true;
164 }
165
166 /*
167 * Parses a JSON objects string and initializes a vector of tokens.
168 *
169 * @return Returns false for errors, true for success.
170 */
parseJsonObject(const std::string & jsonObject,std::vector<std::string> * tokens)171 bool JsonWebKey::parseJsonObject(const std::string& jsonObject, std::vector<std::string>* tokens) {
172 jsmn_parser parser;
173
174 jsmn_init(&parser);
175 int numTokens = jsmn_parse(&parser, jsonObject.c_str(), jsonObject.size(), nullptr, 0);
176 if (numTokens < 0) {
177 ALOGE("Parser returns error code=%d", numTokens);
178 return false;
179 }
180
181 unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
182 mJsmnTokens.clear();
183 mJsmnTokens.resize(jsmnTokensSize);
184
185 jsmn_init(&parser);
186 int status = jsmn_parse(&parser, jsonObject.c_str(), jsonObject.size(), mJsmnTokens.data(),
187 numTokens);
188 if (status < 0) {
189 ALOGE("Parser returns error code=%d", status);
190 return false;
191 }
192
193 tokens->clear();
194 std::string token;
195 const char* pjs;
196 for (int j = 0; j < numTokens; ++j) {
197 pjs = jsonObject.c_str() + mJsmnTokens[j].start;
198 if (mJsmnTokens[j].type == JSMN_STRING || mJsmnTokens[j].type == JSMN_PRIMITIVE) {
199 token.assign(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start);
200 tokens->push_back(token);
201 }
202 }
203 return true;
204 }
205
206 /*
207 * Parses JSON Web Key Set string and initializes a vector of JSON objects.
208 *
209 * @return Returns false for errors, true for success.
210 */
parseJsonWebKeySet(const std::string & jsonWebKeySet,std::vector<std::string> * jsonObjects)211 bool JsonWebKey::parseJsonWebKeySet(const std::string& jsonWebKeySet,
212 std::vector<std::string>* jsonObjects) {
213 if (jsonWebKeySet.empty()) {
214 ALOGE("Empty JSON Web Key");
215 return false;
216 }
217
218 // The jsmn parser only supports unicode encoding.
219 jsmn_parser parser;
220
221 // Computes number of tokens. A token marks the type, offset in
222 // the original string.
223 jsmn_init(&parser);
224 int numTokens = jsmn_parse(&parser, jsonWebKeySet.c_str(), jsonWebKeySet.size(), nullptr, 0);
225 if (numTokens < 0) {
226 ALOGE("Parser returns error code=%d", numTokens);
227 return false;
228 }
229
230 unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
231 mJsmnTokens.resize(jsmnTokensSize);
232
233 jsmn_init(&parser);
234 int status = jsmn_parse(&parser, jsonWebKeySet.c_str(), jsonWebKeySet.size(),
235 mJsmnTokens.data(), numTokens);
236 if (status < 0) {
237 ALOGE("Parser returns error code=%d", status);
238 return false;
239 }
240
241 std::string token;
242 const char* pjs;
243 for (int i = 0; i < numTokens; ++i) {
244 pjs = jsonWebKeySet.c_str() + mJsmnTokens[i].start;
245 if (mJsmnTokens[i].type == JSMN_OBJECT) {
246 token.assign(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start);
247 jsonObjects->push_back(token);
248 }
249 }
250 return true;
251 }
252
253 } // namespace clearkeydrm
254