Home | History | Annotate | Download | only in clearkey
      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