JsonWebKey.cpp revision 7bdf28d2b83e527f474e96b0984d6a3f5eb457f7
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
25namespace {
26const android::String8 kKeysTag("keys");
27const android::String8 kKeyTypeTag("kty");
28const android::String8 kSymmetricKeyValue("oct");
29const android::String8 kKeyTag("k");
30const android::String8 kKeyIdTag("kid");
31const android::String8 kBase64Padding("=");
32}
33
34namespace clearkeydrm {
35
36using android::ABuffer;
37using android::AString;
38
39JsonWebKey::JsonWebKey() {
40}
41
42JsonWebKey::~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 */
52bool 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
101bool 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
132bool 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
154void 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
170bool 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 */
183bool 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 */
226bool 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