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 39 JsonWebKey::JsonWebKey() { 40 } 41 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 */ 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 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 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 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 170 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 */ 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 */ 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