1 // Copyright 2014 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.
5 #include "extensions/browser/api/cast_channel/cast_channel_api.h"
10 #include "base/json/json_writer.h"
11 #include "base/lazy_instance.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/time/default_tick_clock.h"
15 #include "base/values.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "extensions/browser/api/cast_channel/cast_auth_ica.h"
18 #include "extensions/browser/api/cast_channel/cast_message_util.h"
19 #include "extensions/browser/api/cast_channel/cast_socket.h"
20 #include "extensions/browser/api/cast_channel/keep_alive_delegate.h"
21 #include "extensions/browser/api/cast_channel/logger.h"
22 #include "extensions/browser/event_router.h"
23 #include "extensions/common/api/cast_channel/cast_channel.pb.h"
24 #include "extensions/common/api/cast_channel/logging.pb.h"
25 #include "net/base/ip_endpoint.h"
26 #include "net/base/net_errors.h"
27 #include "net/base/net_util.h"
30 // Default timeout interval for connection setup.
31 // Used if not otherwise specified at ConnectInfo::timeout.
32 const int kDefaultConnectTimeoutMillis
= 5000; // 5 seconds.
34 namespace extensions
{
36 namespace Close
= cast_channel::Close
;
37 namespace OnError
= cast_channel::OnError
;
38 namespace OnMessage
= cast_channel::OnMessage
;
39 namespace Open
= cast_channel::Open
;
40 namespace Send
= cast_channel::Send
;
41 using cast_channel::CastDeviceCapability
;
42 using cast_channel::CastMessage
;
43 using cast_channel::CastSocket
;
44 using cast_channel::ChannelAuthType
;
45 using cast_channel::ChannelError
;
46 using cast_channel::ChannelInfo
;
47 using cast_channel::ConnectInfo
;
48 using cast_channel::ErrorInfo
;
49 using cast_channel::LastErrors
;
50 using cast_channel::Logger
;
51 using cast_channel::MessageInfo
;
52 using cast_channel::ReadyState
;
53 using content::BrowserThread
;
57 // T is an extension dictionary (MessageInfo or ChannelInfo)
59 std::string
ParamToString(const T
& info
) {
60 scoped_ptr
<base::DictionaryValue
> dict
= info
.ToValue();
62 base::JSONWriter::Write(dict
.get(), &out
);
66 // Fills |channel_info| from the destination and state of |socket|.
67 void FillChannelInfo(const CastSocket
& socket
, ChannelInfo
* channel_info
) {
69 channel_info
->channel_id
= socket
.id();
70 channel_info
->url
= socket
.cast_url();
71 const net::IPEndPoint
& ip_endpoint
= socket
.ip_endpoint();
72 channel_info
->connect_info
.ip_address
= ip_endpoint
.ToStringWithoutPort();
73 channel_info
->connect_info
.port
= ip_endpoint
.port();
74 channel_info
->connect_info
.auth
= socket
.channel_auth();
75 channel_info
->ready_state
= socket
.ready_state();
76 channel_info
->error_state
= socket
.error_state();
77 channel_info
->keep_alive
= socket
.keep_alive();
80 // Fills |error_info| from |error_state| and |last_errors|.
81 void FillErrorInfo(ChannelError error_state
,
82 const LastErrors
& last_errors
,
83 ErrorInfo
* error_info
) {
84 error_info
->error_state
= error_state
;
85 if (last_errors
.event_type
!= cast_channel::proto::EVENT_TYPE_UNKNOWN
)
86 error_info
->event_type
.reset(new int(last_errors
.event_type
));
87 if (last_errors
.challenge_reply_error_type
!=
88 cast_channel::proto::CHALLENGE_REPLY_ERROR_NONE
) {
89 error_info
->challenge_reply_error_type
.reset(
90 new int(last_errors
.challenge_reply_error_type
));
92 if (last_errors
.net_return_value
<= 0)
93 error_info
->net_return_value
.reset(new int(last_errors
.net_return_value
));
94 if (last_errors
.nss_error_code
< 0)
95 error_info
->nss_error_code
.reset(new int(last_errors
.nss_error_code
));
98 bool IsValidConnectInfoPort(const ConnectInfo
& connect_info
) {
99 return connect_info
.port
> 0 && connect_info
.port
<
100 std::numeric_limits
<uint16_t>::max();
103 bool IsValidConnectInfoAuth(const ConnectInfo
& connect_info
) {
104 return connect_info
.auth
== cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED
||
105 connect_info
.auth
== cast_channel::CHANNEL_AUTH_TYPE_SSL
;
108 bool IsValidConnectInfoIpAddress(const ConnectInfo
& connect_info
) {
109 net::IPAddressNumber ip_address
;
110 return net::ParseIPLiteralToNumber(connect_info
.ip_address
, &ip_address
);
115 CastChannelAPI::CastChannelAPI(content::BrowserContext
* context
)
116 : browser_context_(context
),
118 new Logger(scoped_ptr
<base::TickClock
>(new base::DefaultTickClock
),
119 base::TimeTicks::UnixEpoch())) {
120 DCHECK(browser_context_
);
124 CastChannelAPI
* CastChannelAPI::Get(content::BrowserContext
* context
) {
125 return BrowserContextKeyedAPIFactory
<CastChannelAPI
>::Get(context
);
128 scoped_refptr
<Logger
> CastChannelAPI::GetLogger() {
132 void CastChannelAPI::SendEvent(const std::string
& extension_id
,
133 scoped_ptr
<Event
> event
) {
134 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
135 EventRouter
* event_router
= EventRouter::Get(GetBrowserContext());
137 event_router
->DispatchEventToExtension(extension_id
, event
.Pass());
141 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<CastChannelAPI
> >
142 g_factory
= LAZY_INSTANCE_INITIALIZER
;
145 BrowserContextKeyedAPIFactory
<CastChannelAPI
>*
146 CastChannelAPI::GetFactoryInstance() {
147 return g_factory
.Pointer();
150 void CastChannelAPI::SetSocketForTest(scoped_ptr
<CastSocket
> socket_for_test
) {
151 socket_for_test_
= socket_for_test
.Pass();
154 scoped_ptr
<CastSocket
> CastChannelAPI::GetSocketForTest() {
155 return socket_for_test_
.Pass();
158 content::BrowserContext
* CastChannelAPI::GetBrowserContext() const {
159 return browser_context_
;
162 void CastChannelAPI::SetPingTimeoutTimerForTest(scoped_ptr
<base::Timer
> timer
) {
163 injected_timeout_timer_
= timer
.Pass();
166 scoped_ptr
<base::Timer
> CastChannelAPI::GetInjectedTimeoutTimerForTest() {
167 return injected_timeout_timer_
.Pass();
170 CastChannelAPI::~CastChannelAPI() {}
172 CastChannelAsyncApiFunction::CastChannelAsyncApiFunction() : manager_(nullptr) {
175 CastChannelAsyncApiFunction::~CastChannelAsyncApiFunction() { }
177 bool CastChannelAsyncApiFunction::PrePrepare() {
178 manager_
= ApiResourceManager
<CastSocket
>::Get(browser_context());
182 bool CastChannelAsyncApiFunction::Respond() {
183 return GetError().empty();
186 CastSocket
* CastChannelAsyncApiFunction::GetSocketOrCompleteWithError(
188 CastSocket
* socket
= GetSocket(channel_id
);
190 SetResultFromError(channel_id
,
191 cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID
);
192 AsyncWorkCompleted();
197 int CastChannelAsyncApiFunction::AddSocket(CastSocket
* socket
) {
198 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
201 const int id
= manager_
->Add(socket
);
206 void CastChannelAsyncApiFunction::RemoveSocket(int channel_id
) {
207 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
209 manager_
->Remove(extension_
->id(), channel_id
);
212 void CastChannelAsyncApiFunction::SetResultFromSocket(
213 const CastSocket
& socket
) {
214 ChannelInfo channel_info
;
215 FillChannelInfo(socket
, &channel_info
);
216 ChannelError error
= socket
.error_state();
217 if (error
!= cast_channel::CHANNEL_ERROR_NONE
) {
218 SetError("Channel socket error = " + base::IntToString(error
));
220 SetResultFromChannelInfo(channel_info
);
223 void CastChannelAsyncApiFunction::SetResultFromError(int channel_id
,
224 ChannelError error
) {
225 ChannelInfo channel_info
;
226 channel_info
.channel_id
= channel_id
;
227 channel_info
.url
= "";
228 channel_info
.ready_state
= cast_channel::READY_STATE_CLOSED
;
229 channel_info
.error_state
= error
;
230 channel_info
.connect_info
.ip_address
= "";
231 channel_info
.connect_info
.port
= 0;
232 channel_info
.connect_info
.auth
= cast_channel::CHANNEL_AUTH_TYPE_SSL
;
233 SetResultFromChannelInfo(channel_info
);
234 SetError("Channel error = " + base::IntToString(error
));
237 CastSocket
* CastChannelAsyncApiFunction::GetSocket(int channel_id
) const {
238 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
240 return manager_
->Get(extension_
->id(), channel_id
);
243 void CastChannelAsyncApiFunction::SetResultFromChannelInfo(
244 const ChannelInfo
& channel_info
) {
245 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
246 SetResult(channel_info
.ToValue().release());
249 CastChannelOpenFunction::CastChannelOpenFunction()
250 : new_channel_id_(0) { }
252 CastChannelOpenFunction::~CastChannelOpenFunction() { }
254 // TODO(mfoltz): Remove URL parsing when clients have converted to use
257 // Allowed schemes for Cast device URLs.
258 const char kCastInsecureScheme
[] = "cast";
259 const char kCastSecureScheme
[] = "casts";
261 bool CastChannelOpenFunction::ParseChannelUrl(const GURL
& url
,
262 ConnectInfo
* connect_info
) {
263 DCHECK(connect_info
);
264 VLOG(2) << "ParseChannelUrl";
265 bool auth_required
= false;
266 if (url
.SchemeIs(kCastSecureScheme
)) {
267 auth_required
= true;
268 } else if (!url
.SchemeIs(kCastInsecureScheme
)) {
271 // TODO(mfoltz): Test for IPv6 addresses. Brackets or no brackets?
272 // TODO(mfoltz): Maybe enforce restriction to IPv4 private and IPv6
273 // link-local networks
274 const std::string
& path
= url
.path();
275 // Shortest possible: //A:B
276 if (path
.size() < 5) {
279 if (path
.find("//") != 0) {
282 size_t colon
= path
.find_last_of(':');
283 if (colon
== std::string::npos
|| colon
< 3 || colon
> path
.size() - 2) {
286 const std::string
& ip_address_str
= path
.substr(2, colon
- 2);
287 const std::string
& port_str
= path
.substr(colon
+ 1);
288 VLOG(2) << "IP: " << ip_address_str
<< " Port: " << port_str
;
290 if (!base::StringToInt(port_str
, &port
))
292 connect_info
->ip_address
= ip_address_str
;
293 connect_info
->port
= port
;
294 connect_info
->auth
= auth_required
?
295 cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED
:
296 cast_channel::CHANNEL_AUTH_TYPE_SSL
;
300 net::IPEndPoint
* CastChannelOpenFunction::ParseConnectInfo(
301 const ConnectInfo
& connect_info
) {
302 net::IPAddressNumber ip_address
;
303 CHECK(net::ParseIPLiteralToNumber(connect_info
.ip_address
, &ip_address
));
304 return new net::IPEndPoint(ip_address
,
305 static_cast<uint16
>(connect_info
.port
));
308 bool CastChannelOpenFunction::PrePrepare() {
309 api_
= CastChannelAPI::Get(browser_context());
310 return CastChannelAsyncApiFunction::PrePrepare();
313 bool CastChannelOpenFunction::Prepare() {
314 params_
= Open::Params::Create(*args_
);
315 EXTENSION_FUNCTION_VALIDATE(params_
.get());
316 // The connect_info parameter may be a string URL like cast:// or casts:// or
317 // a ConnectInfo object.
318 std::string cast_url
;
319 switch (params_
->connect_info
->GetType()) {
320 case base::Value::TYPE_STRING
:
321 CHECK(params_
->connect_info
->GetAsString(&cast_url
));
322 connect_info_
.reset(new ConnectInfo
);
323 if (!ParseChannelUrl(GURL(cast_url
), connect_info_
.get())) {
324 connect_info_
.reset();
325 SetError("Invalid connect_info (invalid Cast URL " + cast_url
+ ")");
328 case base::Value::TYPE_DICTIONARY
:
329 connect_info_
= ConnectInfo::FromValue(*(params_
->connect_info
));
330 if (!connect_info_
.get()) {
331 SetError("connect_info.auth is required");
335 SetError("Invalid connect_info (unknown type)");
338 if (!connect_info_
.get()) {
341 if (!IsValidConnectInfoPort(*connect_info_
)) {
342 SetError("Invalid connect_info (invalid port)");
343 } else if (!IsValidConnectInfoAuth(*connect_info_
)) {
344 SetError("Invalid connect_info (invalid auth)");
345 } else if (!IsValidConnectInfoIpAddress(*connect_info_
)) {
346 SetError("Invalid connect_info (invalid IP address)");
348 // Parse timeout parameters if they are set.
349 if (connect_info_
->liveness_timeout
) {
351 base::TimeDelta::FromMilliseconds(*connect_info_
->liveness_timeout
);
353 if (connect_info_
->ping_interval
) {
355 base::TimeDelta::FromMilliseconds(*connect_info_
->ping_interval
);
358 // Validate timeout parameters.
359 if (liveness_timeout_
< base::TimeDelta() ||
360 ping_interval_
< base::TimeDelta()) {
361 SetError("livenessTimeout and pingInterval must be greater than 0.");
362 } else if ((liveness_timeout_
> base::TimeDelta()) !=
363 (ping_interval_
> base::TimeDelta())) {
364 SetError("livenessTimeout and pingInterval must be set together.");
365 } else if (liveness_timeout_
< ping_interval_
) {
366 SetError("livenessTimeout must be longer than pingTimeout.");
370 if (!GetError().empty()) {
374 channel_auth_
= connect_info_
->auth
;
375 ip_endpoint_
.reset(ParseConnectInfo(*connect_info_
));
379 void CastChannelOpenFunction::AsyncWorkStart() {
381 DCHECK(ip_endpoint_
.get());
383 scoped_ptr
<CastSocket
> test_socket
= api_
->GetSocketForTest();
384 if (test_socket
.get()) {
385 socket
= test_socket
.release();
387 socket
= new cast_channel::CastSocketImpl(
388 extension_
->id(), *ip_endpoint_
, channel_auth_
,
389 ExtensionsBrowserClient::Get()->GetNetLog(),
390 base::TimeDelta::FromMilliseconds(connect_info_
->timeout
.get()
391 ? *connect_info_
->timeout
392 : kDefaultConnectTimeoutMillis
),
393 liveness_timeout_
> base::TimeDelta(), api_
->GetLogger(),
394 connect_info_
->capabilities
? *connect_info_
->capabilities
395 : CastDeviceCapability::NONE
);
397 new_channel_id_
= AddSocket(socket
);
398 api_
->GetLogger()->LogNewSocketEvent(*socket
);
400 // Construct read delegates.
401 scoped_ptr
<core_api::cast_channel::CastTransport::Delegate
> delegate(
402 make_scoped_ptr(new CastMessageHandler(
403 base::Bind(&CastChannelAPI::SendEvent
, api_
->AsWeakPtr()), socket
,
404 api_
->GetLogger())));
405 if (socket
->keep_alive()) {
406 // Wrap read delegate in a KeepAliveDelegate for timeout handling.
407 core_api::cast_channel::KeepAliveDelegate
* keep_alive
=
408 new core_api::cast_channel::KeepAliveDelegate(
409 socket
, api_
->GetLogger(), delegate
.Pass(), ping_interval_
,
411 scoped_ptr
<base::Timer
> injected_timer
=
412 api_
->GetInjectedTimeoutTimerForTest();
413 if (injected_timer
) {
414 keep_alive
->SetTimersForTest(
415 make_scoped_ptr(new base::Timer(false, false)),
416 injected_timer
.Pass());
418 delegate
.reset(keep_alive
);
421 api_
->GetLogger()->LogNewSocketEvent(*socket
);
422 socket
->Connect(delegate
.Pass(),
423 base::Bind(&CastChannelOpenFunction::OnOpen
, this));
426 void CastChannelOpenFunction::OnOpen(cast_channel::ChannelError result
) {
427 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
428 VLOG(1) << "Connect finished, OnOpen invoked.";
429 CastSocket
* socket
= GetSocket(new_channel_id_
);
431 SetResultFromError(new_channel_id_
, result
);
433 SetResultFromSocket(*socket
);
435 AsyncWorkCompleted();
438 CastChannelSendFunction::CastChannelSendFunction() { }
440 CastChannelSendFunction::~CastChannelSendFunction() { }
442 bool CastChannelSendFunction::Prepare() {
443 params_
= Send::Params::Create(*args_
);
444 EXTENSION_FUNCTION_VALIDATE(params_
.get());
445 if (params_
->message
.namespace_
.empty()) {
446 SetError("message_info.namespace_ is required");
449 if (params_
->message
.source_id
.empty()) {
450 SetError("message_info.source_id is required");
453 if (params_
->message
.destination_id
.empty()) {
454 SetError("message_info.destination_id is required");
457 switch (params_
->message
.data
->GetType()) {
458 case base::Value::TYPE_STRING
:
459 case base::Value::TYPE_BINARY
:
462 SetError("Invalid type of message_info.data");
468 void CastChannelSendFunction::AsyncWorkStart() {
469 CastSocket
* socket
= GetSocket(params_
->channel
.channel_id
);
471 SetResultFromError(params_
->channel
.channel_id
,
472 cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID
);
473 AsyncWorkCompleted();
476 CastMessage message_to_send
;
477 if (!MessageInfoToCastMessage(params_
->message
, &message_to_send
)) {
478 SetResultFromError(params_
->channel
.channel_id
,
479 cast_channel::CHANNEL_ERROR_INVALID_MESSAGE
);
480 AsyncWorkCompleted();
483 socket
->transport()->SendMessage(
484 message_to_send
, base::Bind(&CastChannelSendFunction::OnSend
, this));
487 void CastChannelSendFunction::OnSend(int result
) {
488 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
489 int channel_id
= params_
->channel
.channel_id
;
490 CastSocket
* socket
= GetSocket(channel_id
);
491 if (result
< 0 || !socket
) {
492 SetResultFromError(channel_id
,
493 cast_channel::CHANNEL_ERROR_SOCKET_ERROR
);
495 SetResultFromSocket(*socket
);
497 AsyncWorkCompleted();
500 CastChannelCloseFunction::CastChannelCloseFunction() { }
502 CastChannelCloseFunction::~CastChannelCloseFunction() { }
504 bool CastChannelCloseFunction::Prepare() {
505 params_
= Close::Params::Create(*args_
);
506 EXTENSION_FUNCTION_VALIDATE(params_
.get());
510 void CastChannelCloseFunction::AsyncWorkStart() {
511 CastSocket
* socket
= GetSocket(params_
->channel
.channel_id
);
513 SetResultFromError(params_
->channel
.channel_id
,
514 cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID
);
515 AsyncWorkCompleted();
517 socket
->Close(base::Bind(&CastChannelCloseFunction::OnClose
, this));
521 void CastChannelCloseFunction::OnClose(int result
) {
522 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
523 VLOG(1) << "CastChannelCloseFunction::OnClose result = " << result
;
524 int channel_id
= params_
->channel
.channel_id
;
525 CastSocket
* socket
= GetSocket(channel_id
);
526 if (result
< 0 || !socket
) {
527 SetResultFromError(channel_id
,
528 cast_channel::CHANNEL_ERROR_SOCKET_ERROR
);
530 SetResultFromSocket(*socket
);
531 // This will delete |socket|.
532 RemoveSocket(channel_id
);
534 AsyncWorkCompleted();
537 CastChannelGetLogsFunction::CastChannelGetLogsFunction() {
540 CastChannelGetLogsFunction::~CastChannelGetLogsFunction() {
543 bool CastChannelGetLogsFunction::PrePrepare() {
544 api_
= CastChannelAPI::Get(browser_context());
545 return CastChannelAsyncApiFunction::PrePrepare();
548 bool CastChannelGetLogsFunction::Prepare() {
552 void CastChannelGetLogsFunction::AsyncWorkStart() {
556 scoped_ptr
<char[]> out
= api_
->GetLogger()->GetLogs(&length
);
558 SetResult(new base::BinaryValue(out
.Pass(), length
));
560 SetError("Unable to get logs.");
563 api_
->GetLogger()->Reset();
565 AsyncWorkCompleted();
568 CastChannelOpenFunction::CastMessageHandler::CastMessageHandler(
569 const EventDispatchCallback
& ui_dispatch_cb
,
570 cast_channel::CastSocket
* socket
,
571 scoped_refptr
<Logger
> logger
)
572 : ui_dispatch_cb_(ui_dispatch_cb
), socket_(socket
), logger_(logger
) {
577 CastChannelOpenFunction::CastMessageHandler::~CastMessageHandler() {
580 void CastChannelOpenFunction::CastMessageHandler::OnError(
581 cast_channel::ChannelError error_state
) {
582 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
584 ChannelInfo channel_info
;
585 FillChannelInfo(*socket_
, &channel_info
);
586 channel_info
.error_state
= error_state
;
587 ErrorInfo error_info
;
588 FillErrorInfo(error_state
, logger_
->GetLastErrors(socket_
->id()),
591 scoped_ptr
<base::ListValue
> results
=
592 OnError::Create(channel_info
, error_info
);
593 scoped_ptr
<Event
> event(new Event(OnError::kEventName
, results
.Pass()));
594 BrowserThread::PostTask(
595 BrowserThread::UI
, FROM_HERE
,
596 base::Bind(ui_dispatch_cb_
, socket_
->owner_extension_id(),
597 base::Passed(event
.Pass())));
600 void CastChannelOpenFunction::CastMessageHandler::OnMessage(
601 const CastMessage
& message
) {
602 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
604 MessageInfo message_info
;
605 cast_channel::CastMessageToMessageInfo(message
, &message_info
);
606 ChannelInfo channel_info
;
607 FillChannelInfo(*socket_
, &channel_info
);
608 VLOG(1) << "Received message " << ParamToString(message_info
)
609 << " on channel " << ParamToString(channel_info
);
611 scoped_ptr
<base::ListValue
> results
=
612 OnMessage::Create(channel_info
, message_info
);
613 scoped_ptr
<Event
> event(new Event(OnMessage::kEventName
, results
.Pass()));
614 BrowserThread::PostTask(
615 BrowserThread::UI
, FROM_HERE
,
616 base::Bind(ui_dispatch_cb_
, socket_
->owner_extension_id(),
617 base::Passed(event
.Pass())));
620 void CastChannelOpenFunction::CastMessageHandler::Start() {
623 CastChannelSetAuthorityKeysFunction::CastChannelSetAuthorityKeysFunction() {
626 CastChannelSetAuthorityKeysFunction::~CastChannelSetAuthorityKeysFunction() {
629 bool CastChannelSetAuthorityKeysFunction::Prepare() {
630 params_
= cast_channel::SetAuthorityKeys::Params::Create(*args_
);
631 EXTENSION_FUNCTION_VALIDATE(params_
.get());
635 void CastChannelSetAuthorityKeysFunction::AsyncWorkStart() {
636 std::string
& keys
= params_
->keys
;
637 std::string
& signature
= params_
->signature
;
638 if (signature
.empty() || keys
.empty() ||
639 !cast_channel::SetTrustedCertificateAuthorities(keys
, signature
)) {
640 SetError("Unable to set authority keys.");
643 AsyncWorkCompleted();
646 } // namespace extensions