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 "content/renderer/media/crypto/proxy_decryptor.h"
6
7#include <cstring>
8
9#include "base/bind.h"
10#include "base/callback_helpers.h"
11#include "base/logging.h"
12#include "base/strings/string_util.h"
13#include "content/renderer/media/crypto/content_decryption_module_factory.h"
14#include "media/base/cdm_promise.h"
15#include "media/cdm/json_web_key.h"
16#include "media/cdm/key_system_names.h"
17
18#if defined(ENABLE_PEPPER_CDMS)
19#include "content/renderer/media/crypto/pepper_cdm_wrapper.h"
20#endif  // defined(ENABLE_PEPPER_CDMS)
21
22#if defined(ENABLE_BROWSER_CDMS)
23#include "content/renderer/media/crypto/renderer_cdm_manager.h"
24#endif  // defined(ENABLE_BROWSER_CDMS)
25
26namespace content {
27
28// Special system code to signal a closed persistent session in a SessionError()
29// call. This is needed because there is no SessionClosed() call in the prefixed
30// EME API.
31const int kSessionClosedSystemCode = 29127;
32
33ProxyDecryptor::ProxyDecryptor(
34#if defined(ENABLE_PEPPER_CDMS)
35    const CreatePepperCdmCB& create_pepper_cdm_cb,
36#elif defined(ENABLE_BROWSER_CDMS)
37    RendererCdmManager* manager,
38#endif  // defined(ENABLE_PEPPER_CDMS)
39    const KeyAddedCB& key_added_cb,
40    const KeyErrorCB& key_error_cb,
41    const KeyMessageCB& key_message_cb)
42    :
43#if defined(ENABLE_PEPPER_CDMS)
44      create_pepper_cdm_cb_(create_pepper_cdm_cb),
45#elif defined(ENABLE_BROWSER_CDMS)
46      manager_(manager),
47      cdm_id_(RendererCdmManager::kInvalidCdmId),
48#endif  // defined(ENABLE_PEPPER_CDMS)
49      key_added_cb_(key_added_cb),
50      key_error_cb_(key_error_cb),
51      key_message_cb_(key_message_cb),
52      is_clear_key_(false),
53      weak_ptr_factory_(this) {
54#if defined(ENABLE_PEPPER_CDMS)
55  DCHECK(!create_pepper_cdm_cb_.is_null());
56#endif  // defined(ENABLE_PEPPER_CDMS)
57  DCHECK(!key_added_cb_.is_null());
58  DCHECK(!key_error_cb_.is_null());
59  DCHECK(!key_message_cb_.is_null());
60}
61
62ProxyDecryptor::~ProxyDecryptor() {
63  // Destroy the decryptor explicitly before destroying the plugin.
64  media_keys_.reset();
65}
66
67media::Decryptor* ProxyDecryptor::GetDecryptor() {
68  return media_keys_ ? media_keys_->GetDecryptor() : NULL;
69}
70
71#if defined(ENABLE_BROWSER_CDMS)
72int ProxyDecryptor::GetCdmId() {
73  return cdm_id_;
74}
75#endif
76
77bool ProxyDecryptor::InitializeCDM(const std::string& key_system,
78                                   const GURL& security_origin) {
79  DVLOG(1) << "InitializeCDM: key_system = " << key_system;
80
81  DCHECK(!media_keys_);
82  media_keys_ = CreateMediaKeys(key_system, security_origin);
83  if (!media_keys_)
84    return false;
85
86  is_clear_key_ =
87      media::IsClearKey(key_system) || media::IsExternalClearKey(key_system);
88  return true;
89}
90
91// Returns true if |data| is prefixed with |header| and has data after the
92// |header|.
93bool HasHeader(const uint8* data, int data_length, const std::string& header) {
94  return static_cast<size_t>(data_length) > header.size() &&
95         std::equal(data, data + header.size(), header.begin());
96}
97
98bool ProxyDecryptor::GenerateKeyRequest(const std::string& content_type,
99                                        const uint8* init_data,
100                                        int init_data_length) {
101  DVLOG(1) << "GenerateKeyRequest()";
102  const char kPrefixedApiPersistentSessionHeader[] = "PERSISTENT|";
103  const char kPrefixedApiLoadSessionHeader[] = "LOAD_SESSION|";
104
105  SessionCreationType session_creation_type = TemporarySession;
106  if (HasHeader(init_data, init_data_length, kPrefixedApiLoadSessionHeader)) {
107    session_creation_type = LoadSession;
108  } else if (HasHeader(init_data,
109                       init_data_length,
110                       kPrefixedApiPersistentSessionHeader)) {
111    session_creation_type = PersistentSession;
112  }
113
114  scoped_ptr<media::NewSessionCdmPromise> promise(
115      new media::NewSessionCdmPromise(
116          base::Bind(&ProxyDecryptor::SetSessionId,
117                     weak_ptr_factory_.GetWeakPtr(),
118                     session_creation_type),
119          base::Bind(&ProxyDecryptor::OnSessionError,
120                     weak_ptr_factory_.GetWeakPtr(),
121                     std::string())));  // No session id until created.
122
123  if (session_creation_type == LoadSession) {
124    media_keys_->LoadSession(
125        std::string(reinterpret_cast<const char*>(
126                        init_data + strlen(kPrefixedApiLoadSessionHeader)),
127                    init_data_length - strlen(kPrefixedApiLoadSessionHeader)),
128        promise.Pass());
129    return true;
130  }
131
132  media::MediaKeys::SessionType session_type =
133      session_creation_type == PersistentSession
134          ? media::MediaKeys::PERSISTENT_SESSION
135          : media::MediaKeys::TEMPORARY_SESSION;
136
137  // Convert MIME types used in the prefixed implementation.
138  std::string init_data_type;
139  if (content_type == "audio/mp4" || content_type == "video/mp4") {
140    init_data_type = "cenc";
141  } else if (content_type == "audio/webm" || content_type == "video/webm") {
142    init_data_type = "webm";
143  } else {
144    NOTREACHED();
145    init_data_type = content_type;
146  }
147
148  media_keys_->CreateSession(init_data_type, init_data, init_data_length,
149                             session_type, promise.Pass());
150  return true;
151}
152
153void ProxyDecryptor::AddKey(const uint8* key,
154                            int key_length,
155                            const uint8* init_data,
156                            int init_data_length,
157                            const std::string& web_session_id) {
158  DVLOG(1) << "AddKey()";
159
160  // In the prefixed API, the session parameter provided to addKey() is
161  // optional, so use the single existing session if it exists.
162  // TODO(jrummell): remove when the prefixed API is removed.
163  std::string session_id(web_session_id);
164  if (session_id.empty()) {
165    if (active_sessions_.size() == 1) {
166      base::hash_map<std::string, bool>::iterator it = active_sessions_.begin();
167      session_id = it->first;
168    } else {
169      OnSessionError(std::string(),
170                     media::MediaKeys::NOT_SUPPORTED_ERROR,
171                     0,
172                     "SessionId not specified.");
173      return;
174    }
175  }
176
177  scoped_ptr<media::SimpleCdmPromise> promise(
178      new media::SimpleCdmPromise(base::Bind(&ProxyDecryptor::OnSessionReady,
179                                             weak_ptr_factory_.GetWeakPtr(),
180                                             web_session_id),
181                                  base::Bind(&ProxyDecryptor::OnSessionError,
182                                             weak_ptr_factory_.GetWeakPtr(),
183                                             web_session_id)));
184
185  // EME WD spec only supports a single array passed to the CDM. For
186  // Clear Key using v0.1b, both arrays are used (|init_data| is key_id).
187  // Since the EME WD spec supports the key as a JSON Web Key,
188  // convert the 2 arrays to a JWK and pass it as the single array.
189  if (is_clear_key_) {
190    // Decryptor doesn't support empty key ID (see http://crbug.com/123265).
191    // So ensure a non-empty value is passed.
192    if (!init_data) {
193      static const uint8 kDummyInitData[1] = {0};
194      init_data = kDummyInitData;
195      init_data_length = arraysize(kDummyInitData);
196    }
197
198    std::string jwk =
199        media::GenerateJWKSet(key, key_length, init_data, init_data_length);
200    DCHECK(!jwk.empty());
201    media_keys_->UpdateSession(session_id,
202                               reinterpret_cast<const uint8*>(jwk.data()),
203                               jwk.size(),
204                               promise.Pass());
205    return;
206  }
207
208  media_keys_->UpdateSession(session_id, key, key_length, promise.Pass());
209}
210
211void ProxyDecryptor::CancelKeyRequest(const std::string& web_session_id) {
212  DVLOG(1) << "CancelKeyRequest()";
213
214  scoped_ptr<media::SimpleCdmPromise> promise(
215      new media::SimpleCdmPromise(base::Bind(&ProxyDecryptor::OnSessionClosed,
216                                             weak_ptr_factory_.GetWeakPtr(),
217                                             web_session_id),
218                                  base::Bind(&ProxyDecryptor::OnSessionError,
219                                             weak_ptr_factory_.GetWeakPtr(),
220                                             web_session_id)));
221  media_keys_->RemoveSession(web_session_id, promise.Pass());
222}
223
224scoped_ptr<media::MediaKeys> ProxyDecryptor::CreateMediaKeys(
225    const std::string& key_system,
226    const GURL& security_origin) {
227  return ContentDecryptionModuleFactory::Create(
228      key_system,
229      security_origin,
230#if defined(ENABLE_PEPPER_CDMS)
231      create_pepper_cdm_cb_,
232#elif defined(ENABLE_BROWSER_CDMS)
233      manager_,
234      &cdm_id_,
235#endif  // defined(ENABLE_PEPPER_CDMS)
236      base::Bind(&ProxyDecryptor::OnSessionMessage,
237                 weak_ptr_factory_.GetWeakPtr()),
238      base::Bind(&ProxyDecryptor::OnSessionReady,
239                 weak_ptr_factory_.GetWeakPtr()),
240      base::Bind(&ProxyDecryptor::OnSessionClosed,
241                 weak_ptr_factory_.GetWeakPtr()),
242      base::Bind(&ProxyDecryptor::OnSessionError,
243                 weak_ptr_factory_.GetWeakPtr()),
244      base::Bind(&ProxyDecryptor::OnSessionKeysChange,
245                 weak_ptr_factory_.GetWeakPtr()),
246      base::Bind(&ProxyDecryptor::OnSessionExpirationUpdate,
247                 weak_ptr_factory_.GetWeakPtr()));
248}
249
250void ProxyDecryptor::OnSessionMessage(const std::string& web_session_id,
251                                      const std::vector<uint8>& message,
252                                      const GURL& destination_url) {
253  // Assumes that OnSessionCreated() has been called before this.
254
255  // For ClearKey, convert the message from JSON into just passing the key
256  // as the message. If unable to extract the key, return the message unchanged.
257  if (is_clear_key_) {
258    std::vector<uint8> key;
259    if (media::ExtractFirstKeyIdFromLicenseRequest(message, &key)) {
260      key_message_cb_.Run(web_session_id, key, destination_url);
261      return;
262    }
263  }
264
265  key_message_cb_.Run(web_session_id, message, destination_url);
266}
267
268void ProxyDecryptor::OnSessionKeysChange(const std::string& web_session_id,
269                                         bool has_additional_usable_key) {
270  // EME v0.1b doesn't support this event.
271}
272
273void ProxyDecryptor::OnSessionExpirationUpdate(
274    const std::string& web_session_id,
275    const base::Time& new_expiry_time) {
276  // EME v0.1b doesn't support this event.
277}
278
279void ProxyDecryptor::OnSessionReady(const std::string& web_session_id) {
280  key_added_cb_.Run(web_session_id);
281}
282
283void ProxyDecryptor::OnSessionClosed(const std::string& web_session_id) {
284  base::hash_map<std::string, bool>::iterator it =
285      active_sessions_.find(web_session_id);
286
287  // Latest EME spec separates closing a session ("allows an application to
288  // indicate that it no longer needs the session") and actually closing the
289  // session (done by the CDM at any point "such as in response to a close()
290  // call, when the session is no longer needed, or when system resources are
291  // lost.") Thus the CDM may cause 2 close() events -- one to resolve the
292  // close() promise, and a second to actually close the session. Prefixed EME
293  // only expects 1 close event, so drop the second (and subsequent) events.
294  // However, this means we can't tell if the CDM is generating spurious close()
295  // events.
296  if (it == active_sessions_.end())
297    return;
298
299  if (it->second) {
300    OnSessionError(web_session_id,
301                   media::MediaKeys::NOT_SUPPORTED_ERROR,
302                   kSessionClosedSystemCode,
303                   "Do not close persistent sessions.");
304  }
305  active_sessions_.erase(it);
306}
307
308void ProxyDecryptor::OnSessionError(const std::string& web_session_id,
309                                    media::MediaKeys::Exception exception_code,
310                                    uint32 system_code,
311                                    const std::string& error_message) {
312  // Convert |error_name| back to MediaKeys::KeyError if possible. Prefixed
313  // EME has different error message, so all the specific error events will
314  // get lost.
315  media::MediaKeys::KeyError error_code;
316  switch (exception_code) {
317    case media::MediaKeys::CLIENT_ERROR:
318      error_code = media::MediaKeys::kClientError;
319      break;
320    case media::MediaKeys::OUTPUT_ERROR:
321      error_code = media::MediaKeys::kOutputError;
322      break;
323    default:
324      // This will include all other CDM4 errors and any error generated
325      // by CDM5 or later.
326      error_code = media::MediaKeys::kUnknownError;
327      break;
328  }
329  key_error_cb_.Run(web_session_id, error_code, system_code);
330}
331
332void ProxyDecryptor::SetSessionId(SessionCreationType session_type,
333                                  const std::string& web_session_id) {
334  // Loaded sessions are considered persistent.
335  bool is_persistent =
336      session_type == PersistentSession || session_type == LoadSession;
337  active_sessions_.insert(std::make_pair(web_session_id, is_persistent));
338
339  // For LoadSession(), generate the SessionReady event.
340  if (session_type == LoadSession)
341    OnSessionReady(web_session_id);
342}
343
344}  // namespace content
345