Home | History | Annotate | Download | only in cdm
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "media/cdm/json_web_key.h"
      6 
      7 #include "base/base64.h"
      8 #include "base/json/json_reader.h"
      9 #include "base/json/json_string_value_serializer.h"
     10 #include "base/json/string_escape.h"
     11 #include "base/logging.h"
     12 #include "base/memory/scoped_ptr.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/values.h"
     15 
     16 namespace media {
     17 
     18 const char kKeysTag[] = "keys";
     19 const char kKeyTypeTag[] = "kty";
     20 const char kSymmetricKeyValue[] = "oct";
     21 const char kKeyTag[] = "k";
     22 const char kKeyIdTag[] = "kid";
     23 const char kKeyIdsTag[] = "kids";
     24 const char kBase64Padding = '=';
     25 const char kTypeTag[] = "type";
     26 const char kPersistentType[] = "persistent";
     27 const char kTemporaryType[] = "temporary";
     28 
     29 // Encodes |input| into a base64 string without padding.
     30 static std::string EncodeBase64(const uint8* input, int input_length) {
     31   std::string encoded_text;
     32   base::Base64Encode(
     33       std::string(reinterpret_cast<const char*>(input), input_length),
     34       &encoded_text);
     35 
     36   // Remove any padding characters added by Base64Encode().
     37   size_t found = encoded_text.find_last_not_of(kBase64Padding);
     38   if (found != std::string::npos)
     39     encoded_text.erase(found + 1);
     40 
     41   return encoded_text;
     42 }
     43 
     44 // Decodes an unpadded base64 string. Returns empty string on error.
     45 static std::string DecodeBase64(const std::string& encoded_text) {
     46   // EME spec doesn't allow padding characters.
     47   if (encoded_text.find_first_of(kBase64Padding) != std::string::npos)
     48     return std::string();
     49 
     50   // Since base::Base64Decode() requires padding characters, add them so length
     51   // of |encoded_text| is exactly a multiple of 4.
     52   size_t num_last_grouping_chars = encoded_text.length() % 4;
     53   std::string modified_text = encoded_text;
     54   if (num_last_grouping_chars > 0)
     55     modified_text.append(4 - num_last_grouping_chars, kBase64Padding);
     56 
     57   std::string decoded_text;
     58   if (!base::Base64Decode(modified_text, &decoded_text))
     59     return std::string();
     60 
     61   return decoded_text;
     62 }
     63 
     64 std::string GenerateJWKSet(const uint8* key, int key_length,
     65                            const uint8* key_id, int key_id_length) {
     66   // Both |key| and |key_id| need to be base64 encoded strings in the JWK.
     67   std::string key_base64 = EncodeBase64(key, key_length);
     68   std::string key_id_base64 = EncodeBase64(key_id, key_id_length);
     69 
     70   // Create the JWK, and wrap it into a JWK Set.
     71   scoped_ptr<base::DictionaryValue> jwk(new base::DictionaryValue());
     72   jwk->SetString(kKeyTypeTag, kSymmetricKeyValue);
     73   jwk->SetString(kKeyTag, key_base64);
     74   jwk->SetString(kKeyIdTag, key_id_base64);
     75   scoped_ptr<base::ListValue> list(new base::ListValue());
     76   list->Append(jwk.release());
     77   base::DictionaryValue jwk_set;
     78   jwk_set.Set(kKeysTag, list.release());
     79 
     80   // Finally serialize |jwk_set| into a string and return it.
     81   std::string serialized_jwk;
     82   JSONStringValueSerializer serializer(&serialized_jwk);
     83   serializer.Serialize(jwk_set);
     84   return serialized_jwk;
     85 }
     86 
     87 // Processes a JSON Web Key to extract the key id and key value. Sets |jwk_key|
     88 // to the id/value pair and returns true on success.
     89 static bool ConvertJwkToKeyPair(const base::DictionaryValue& jwk,
     90                                 KeyIdAndKeyPair* jwk_key) {
     91   // Have found a JWK, start by checking that it is a symmetric key.
     92   std::string type;
     93   if (!jwk.GetString(kKeyTypeTag, &type) || type != kSymmetricKeyValue) {
     94     DVLOG(1) << "JWK is not a symmetric key";
     95     return false;
     96   }
     97 
     98   // Get the key id and actual key parameters.
     99   std::string encoded_key_id;
    100   std::string encoded_key;
    101   if (!jwk.GetString(kKeyIdTag, &encoded_key_id)) {
    102     DVLOG(1) << "Missing '" << kKeyIdTag << "' parameter";
    103     return false;
    104   }
    105   if (!jwk.GetString(kKeyTag, &encoded_key)) {
    106     DVLOG(1) << "Missing '" << kKeyTag << "' parameter";
    107     return false;
    108   }
    109 
    110   // Key ID and key are base64-encoded strings, so decode them.
    111   std::string raw_key_id = DecodeBase64(encoded_key_id);
    112   if (raw_key_id.empty()) {
    113     DVLOG(1) << "Invalid '" << kKeyIdTag << "' value: " << encoded_key_id;
    114     return false;
    115   }
    116 
    117   std::string raw_key = DecodeBase64(encoded_key);
    118   if (raw_key.empty()) {
    119     DVLOG(1) << "Invalid '" << kKeyTag << "' value: " << encoded_key;
    120     return false;
    121   }
    122 
    123   // Add the decoded key ID and the decoded key to the list.
    124   *jwk_key = std::make_pair(raw_key_id, raw_key);
    125   return true;
    126 }
    127 
    128 bool ExtractKeysFromJWKSet(const std::string& jwk_set,
    129                            KeyIdAndKeyPairs* keys,
    130                            MediaKeys::SessionType* session_type) {
    131   if (!base::IsStringASCII(jwk_set))
    132     return false;
    133 
    134   scoped_ptr<base::Value> root(base::JSONReader().ReadToValue(jwk_set));
    135   if (!root.get() || root->GetType() != base::Value::TYPE_DICTIONARY)
    136     return false;
    137 
    138   // Locate the set from the dictionary.
    139   base::DictionaryValue* dictionary =
    140       static_cast<base::DictionaryValue*>(root.get());
    141   base::ListValue* list_val = NULL;
    142   if (!dictionary->GetList(kKeysTag, &list_val)) {
    143     DVLOG(1) << "Missing '" << kKeysTag
    144              << "' parameter or not a list in JWK Set";
    145     return false;
    146   }
    147 
    148   // Create a local list of keys, so that |jwk_keys| only gets updated on
    149   // success.
    150   KeyIdAndKeyPairs local_keys;
    151   for (size_t i = 0; i < list_val->GetSize(); ++i) {
    152     base::DictionaryValue* jwk = NULL;
    153     if (!list_val->GetDictionary(i, &jwk)) {
    154       DVLOG(1) << "Unable to access '" << kKeysTag << "'[" << i
    155                << "] in JWK Set";
    156       return false;
    157     }
    158     KeyIdAndKeyPair key_pair;
    159     if (!ConvertJwkToKeyPair(*jwk, &key_pair)) {
    160       DVLOG(1) << "Error from '" << kKeysTag << "'[" << i << "]";
    161       return false;
    162     }
    163     local_keys.push_back(key_pair);
    164   }
    165 
    166   // Successfully processed all JWKs in the set. Now check if "type" is
    167   // specified.
    168   base::Value* value = NULL;
    169   std::string type_id;
    170   if (!dictionary->Get(kTypeTag, &value)) {
    171     // Not specified, so use the default type.
    172     *session_type = MediaKeys::TEMPORARY_SESSION;
    173   } else if (!value->GetAsString(&type_id)) {
    174     DVLOG(1) << "Invalid '" << kTypeTag << "' value";
    175     return false;
    176   } else if (type_id == kPersistentType) {
    177     *session_type = MediaKeys::PERSISTENT_SESSION;
    178   } else if (type_id == kTemporaryType) {
    179     *session_type = MediaKeys::TEMPORARY_SESSION;
    180   } else {
    181     DVLOG(1) << "Invalid '" << kTypeTag << "' value: " << type_id;
    182     return false;
    183   }
    184 
    185   // All done.
    186   keys->swap(local_keys);
    187   return true;
    188 }
    189 
    190 void CreateLicenseRequest(const uint8* key_id,
    191                           int key_id_length,
    192                           MediaKeys::SessionType session_type,
    193                           std::vector<uint8>* license) {
    194   // Create the license request.
    195   scoped_ptr<base::DictionaryValue> request(new base::DictionaryValue());
    196   scoped_ptr<base::ListValue> list(new base::ListValue());
    197   list->AppendString(EncodeBase64(key_id, key_id_length));
    198   request->Set(kKeyIdsTag, list.release());
    199 
    200   switch (session_type) {
    201     case MediaKeys::TEMPORARY_SESSION:
    202       request->SetString(kTypeTag, kTemporaryType);
    203       break;
    204     case MediaKeys::PERSISTENT_SESSION:
    205       request->SetString(kTypeTag, kPersistentType);
    206       break;
    207   }
    208 
    209   // Serialize the license request as a string.
    210   std::string json;
    211   JSONStringValueSerializer serializer(&json);
    212   serializer.Serialize(*request);
    213 
    214   // Convert the serialized license request into std::vector and return it.
    215   std::vector<uint8> result(json.begin(), json.end());
    216   license->swap(result);
    217 }
    218 
    219 bool ExtractFirstKeyIdFromLicenseRequest(const std::vector<uint8>& license,
    220                                          std::vector<uint8>* first_key) {
    221   const std::string license_as_str(
    222       reinterpret_cast<const char*>(!license.empty() ? &license[0] : NULL),
    223       license.size());
    224   if (!base::IsStringASCII(license_as_str))
    225     return false;
    226 
    227   scoped_ptr<base::Value> root(base::JSONReader().ReadToValue(license_as_str));
    228   if (!root.get() || root->GetType() != base::Value::TYPE_DICTIONARY)
    229     return false;
    230 
    231   // Locate the set from the dictionary.
    232   base::DictionaryValue* dictionary =
    233       static_cast<base::DictionaryValue*>(root.get());
    234   base::ListValue* list_val = NULL;
    235   if (!dictionary->GetList(kKeyIdsTag, &list_val)) {
    236     DVLOG(1) << "Missing '" << kKeyIdsTag << "' parameter or not a list";
    237     return false;
    238   }
    239 
    240   // Get the first key.
    241   if (list_val->GetSize() < 1) {
    242     DVLOG(1) << "Empty '" << kKeyIdsTag << "' list";
    243     return false;
    244   }
    245 
    246   std::string encoded_key;
    247   if (!list_val->GetString(0, &encoded_key)) {
    248     DVLOG(1) << "First entry in '" << kKeyIdsTag << "' not a string";
    249     return false;
    250   }
    251 
    252   std::string decoded_string = DecodeBase64(encoded_key);
    253   if (decoded_string.empty()) {
    254     DVLOG(1) << "Invalid '" << kKeyIdsTag << "' value: " << encoded_key;
    255     return false;
    256   }
    257 
    258   std::vector<uint8> result(decoded_string.begin(), decoded_string.end());
    259   first_key->swap(result);
    260   return true;
    261 }
    262 
    263 }  // namespace media
    264