OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of dart.io; | |
6 | |
7 /** | |
8 * A high-level class for communicating securely over a TCP socket, using | |
9 * TLS and SSL. The [SecureSocket] exposes both a [Stream] and an | |
10 * [IOSink] interface, making it ideal for using together with | |
11 * other [Stream]s. | |
12 */ | |
13 abstract class SecureSocket implements Socket { | |
14 external factory SecureSocket._(RawSecureSocket rawSocket); | |
15 | |
16 /** | |
17 * Constructs a new secure client socket and connects it to the given | |
18 * [host] on port [port]. The returned Future will complete with a | |
19 * [SecureSocket] that is connected and ready for subscription. | |
20 * | |
21 * The certificate provided by the server is checked | |
22 * using the trusted certificates set in the SecurityContext object. | |
23 * The default SecurityContext object contains a built-in set of trusted | |
24 * root certificates for well-known certificate authorities. | |
25 * | |
26 * [onBadCertificate] is an optional handler for unverifiable certificates. | |
27 * The handler receives the [X509Certificate], and can inspect it and | |
28 * decide (or let the user decide) whether to accept | |
29 * the connection or not. The handler should return true | |
30 * to continue the [SecureSocket] connection. | |
31 */ | |
32 static Future<SecureSocket> connect( | |
33 host, | |
34 int port, | |
35 {SecurityContext context, | |
36 bool onBadCertificate(X509Certificate certificate), | |
37 List<String> supportedProtocols}) { | |
38 return RawSecureSocket.connect(host, | |
39 port, | |
40 context: context, | |
41 onBadCertificate: onBadCertificate, | |
42 supportedProtocols: supportedProtocols) | |
43 .then((rawSocket) => new SecureSocket._(rawSocket)); | |
44 } | |
45 | |
46 /** | |
47 * Takes an already connected [socket] and starts client side TLS | |
48 * handshake to make the communication secure. When the returned | |
49 * future completes the [SecureSocket] has completed the TLS | |
50 * handshake. Using this function requires that the other end of the | |
51 * connection is prepared for TLS handshake. | |
52 * | |
53 * If the [socket] already has a subscription, this subscription | |
54 * will no longer receive and events. In most cases calling | |
55 * `pause` on this subscription before starting TLS handshake is | |
56 * the right thing to do. | |
57 * | |
58 * If the [host] argument is passed it will be used as the host name | |
59 * for the TLS handshake. If [host] is not passed the host name from | |
60 * the [socket] will be used. The [host] can be either a [String] or | |
61 * an [InternetAddress]. | |
62 * | |
63 * Calling this function will _not_ cause a DNS host lookup. If the | |
64 * [host] passed is a [String] the [InternetAddress] for the | |
65 * resulting [SecureSocket] will have the passed in [host] as its | |
66 * host value and the internet address of the already connected | |
67 * socket as its address value. | |
68 * | |
69 * See [connect] for more information on the arguments. | |
70 * | |
71 */ | |
72 static Future<SecureSocket> secure( | |
73 Socket socket, | |
74 {host, | |
75 SecurityContext context, | |
76 bool onBadCertificate(X509Certificate certificate)}) { | |
77 var completer = new Completer(); | |
78 (socket as dynamic)._detachRaw() | |
79 .then((detachedRaw) { | |
80 return RawSecureSocket.secure( | |
81 detachedRaw[0], | |
82 subscription: detachedRaw[1], | |
83 host: host, | |
84 context: context, | |
85 onBadCertificate: onBadCertificate); | |
86 }) | |
87 .then((raw) { | |
88 completer.complete(new SecureSocket._(raw)); | |
89 }); | |
90 return completer.future; | |
91 } | |
92 | |
93 /** | |
94 * Takes an already connected [socket] and starts server side TLS | |
95 * handshake to make the communication secure. When the returned | |
96 * future completes the [SecureSocket] has completed the TLS | |
97 * handshake. Using this function requires that the other end of the | |
98 * connection is going to start the TLS handshake. | |
99 * | |
100 * If the [socket] already has a subscription, this subscription | |
101 * will no longer receive and events. In most cases calling | |
102 * [:pause:] on this subscription before starting TLS handshake is | |
103 * the right thing to do. | |
104 * | |
105 * If some of the data of the TLS handshake has already been read | |
106 * from the socket this data can be passed in the [bufferedData] | |
107 * parameter. This data will be processed before any other data | |
108 * available on the socket. | |
109 * | |
110 * See [SecureServerSocket.bind] for more information on the | |
111 * arguments. | |
112 * | |
113 */ | |
114 static Future<SecureSocket> secureServer( | |
115 Socket socket, | |
116 SecurityContext context, | |
117 {List<int> bufferedData, | |
118 bool requestClientCertificate: false, | |
119 bool requireClientCertificate: false, | |
120 List<String> supportedProtocols}) { | |
121 var completer = new Completer(); | |
122 (socket as dynamic)._detachRaw() | |
123 .then((detachedRaw) { | |
124 return RawSecureSocket.secureServer( | |
125 detachedRaw[0], | |
126 context, | |
127 subscription: detachedRaw[1], | |
128 bufferedData: bufferedData, | |
129 requestClientCertificate: requestClientCertificate, | |
130 requireClientCertificate: requireClientCertificate, | |
131 supportedProtocols: supportedProtocols); | |
132 }) | |
133 .then((raw) { | |
134 completer.complete(new SecureSocket._(raw)); | |
135 }); | |
136 return completer.future; | |
137 } | |
138 | |
139 /** | |
140 * Get the peer certificate for a connected SecureSocket. If this | |
141 * SecureSocket is the server end of a secure socket connection, | |
142 * [peerCertificate] will return the client certificate, or null, if no | |
143 * client certificate was received. If it is the client end, | |
144 * [peerCertificate] will return the server's certificate. | |
145 */ | |
146 X509Certificate get peerCertificate; | |
147 | |
148 /** | |
149 * Get the protocol which was selected during protocol negotiation. | |
150 */ | |
151 String get selectedProtocol; | |
152 | |
153 /** | |
154 * Renegotiate an existing secure connection, renewing the session keys | |
155 * and possibly changing the connection properties. | |
156 * | |
157 * This repeats the SSL or TLS handshake, with options that allow clearing | |
158 * the session cache and requesting a client certificate. | |
159 */ | |
160 void renegotiate({bool useSessionCache: true, | |
161 bool requestClientCertificate: false, | |
162 bool requireClientCertificate: false}); | |
163 } | |
164 | |
165 | |
166 /** | |
167 * RawSecureSocket provides a secure (SSL or TLS) network connection. | |
168 * Client connections to a server are provided by calling | |
169 * RawSecureSocket.connect. A secure server, created with | |
170 * [RawSecureServerSocket], also returns RawSecureSocket objects representing | |
171 * the server end of a secure connection. | |
172 * The certificate provided by the server is checked | |
173 * using the trusted certificates set in the SecurityContext object. | |
174 * The default [SecurityContext] object contains a built-in set of trusted | |
175 * root certificates for well-known certificate authorities. | |
176 */ | |
177 abstract class RawSecureSocket implements RawSocket { | |
178 /** | |
179 * Constructs a new secure client socket and connect it to the given | |
180 * host on the given port. The returned [Future] is completed with the | |
181 * RawSecureSocket when it is connected and ready for subscription. | |
182 * | |
183 * The certificate provided by the server is checked using the trusted | |
184 * certificates set in the SecurityContext object If a certificate and key are | |
185 * set on the client, using [SecurityContext.useCertificateChain] and | |
186 * [SecurityContext.usePrivateKey], and the server asks for a client | |
187 * certificate, then that client certificate is sent to the server. | |
188 * | |
189 * [onBadCertificate] is an optional handler for unverifiable certificates. | |
190 * The handler receives the [X509Certificate], and can inspect it and | |
191 * decide (or let the user decide) whether to accept | |
192 * the connection or not. The handler should return true | |
193 * to continue the [RawSecureSocket] connection. | |
194 */ | |
195 static Future<RawSecureSocket> connect( | |
196 host, | |
197 int port, | |
198 {SecurityContext context, | |
199 bool onBadCertificate(X509Certificate certificate), | |
200 List<String> supportedProtocols}) { | |
201 _RawSecureSocket._verifyFields( | |
202 host, | |
203 port, | |
204 false, | |
205 false, | |
206 false, | |
207 onBadCertificate); | |
208 return RawSocket.connect(host, port) | |
209 .then((socket) { | |
210 return secure(socket, | |
211 context: context, | |
212 onBadCertificate: onBadCertificate, | |
213 supportedProtocols: supportedProtocols); | |
214 }); | |
215 } | |
216 | |
217 /** | |
218 * Takes an already connected [socket] and starts client side TLS | |
219 * handshake to make the communication secure. When the returned | |
220 * future completes the [RawSecureSocket] has completed the TLS | |
221 * handshake. Using this function requires that the other end of the | |
222 * connection is prepared for TLS handshake. | |
223 * | |
224 * If the [socket] already has a subscription, pass the existing | |
225 * subscription in the [subscription] parameter. The [secure] | |
226 * operation will take over the subscription by replacing the | |
227 * handlers with it own secure processing. The caller must not touch | |
228 * this subscription anymore. Passing a paused subscription is an | |
229 * error. | |
230 * | |
231 * If the [host] argument is passed it will be used as the host name | |
232 * for the TLS handshake. If [host] is not passed the host name from | |
233 * the [socket] will be used. The [host] can be either a [String] or | |
234 * an [InternetAddress]. | |
235 * | |
236 * Calling this function will _not_ cause a DNS host lookup. If the | |
237 * [host] passed is a [String] the [InternetAddress] for the | |
238 * resulting [SecureSocket] will have this passed in [host] as its | |
239 * host value and the internet address of the already connected | |
240 * socket as its address value. | |
241 * | |
242 * See [connect] for more information on the arguments. | |
243 * | |
244 */ | |
245 static Future<RawSecureSocket> secure( | |
246 RawSocket socket, | |
247 {StreamSubscription subscription, | |
248 host, | |
249 SecurityContext context, | |
250 bool onBadCertificate(X509Certificate certificate), | |
251 List<String> supportedProtocols}) { | |
252 socket.readEventsEnabled = false; | |
253 socket.writeEventsEnabled = false; | |
254 return _RawSecureSocket.connect( | |
255 host != null ? host : socket.address.host, | |
256 socket.port, | |
257 is_server: false, | |
258 socket: socket, | |
259 subscription: subscription, | |
260 context: context, | |
261 onBadCertificate: onBadCertificate, | |
262 supportedProtocols: supportedProtocols); | |
263 } | |
264 | |
265 /** | |
266 * Takes an already connected [socket] and starts server side TLS | |
267 * handshake to make the communication secure. When the returned | |
268 * future completes the [RawSecureSocket] has completed the TLS | |
269 * handshake. Using this function requires that the other end of the | |
270 * connection is going to start the TLS handshake. | |
271 * | |
272 * If the [socket] already has a subscription, pass the existing | |
273 * subscription in the [subscription] parameter. The [secureServer] | |
274 * operation will take over the subscription by replacing the | |
275 * handlers with it own secure processing. The caller must not touch | |
276 * this subscription anymore. Passing a paused subscription is an | |
277 * error. | |
278 * | |
279 * If some of the data of the TLS handshake has already been read | |
280 * from the socket this data can be passed in the [bufferedData] | |
281 * parameter. This data will be processed before any other data | |
282 * available on the socket. | |
283 * | |
284 * See [RawSecureServerSocket.bind] for more information on the | |
285 * arguments. | |
286 * | |
287 */ | |
288 static Future<RawSecureSocket> secureServer( | |
289 RawSocket socket, | |
290 SecurityContext context, | |
291 {StreamSubscription subscription, | |
292 List<int> bufferedData, | |
293 bool requestClientCertificate: false, | |
294 bool requireClientCertificate: false, | |
295 List<String> supportedProtocols}) { | |
296 socket.readEventsEnabled = false; | |
297 socket.writeEventsEnabled = false; | |
298 return _RawSecureSocket.connect( | |
299 socket.address, | |
300 socket.remotePort, | |
301 context: context, | |
302 is_server: true, | |
303 socket: socket, | |
304 subscription: subscription, | |
305 bufferedData: bufferedData, | |
306 requestClientCertificate: requestClientCertificate, | |
307 requireClientCertificate: requireClientCertificate, | |
308 supportedProtocols: supportedProtocols); | |
309 } | |
310 | |
311 /** | |
312 * Renegotiate an existing secure connection, renewing the session keys | |
313 * and possibly changing the connection properties. | |
314 * | |
315 * This repeats the SSL or TLS handshake, with options that allow clearing | |
316 * the session cache and requesting a client certificate. | |
317 */ | |
318 void renegotiate({bool useSessionCache: true, | |
319 bool requestClientCertificate: false, | |
320 bool requireClientCertificate: false}); | |
321 | |
322 /** | |
323 * Get the peer certificate for a connected RawSecureSocket. If this | |
324 * RawSecureSocket is the server end of a secure socket connection, | |
325 * [peerCertificate] will return the client certificate, or null, if no | |
326 * client certificate was received. If it is the client end, | |
327 * [peerCertificate] will return the server's certificate. | |
328 */ | |
329 X509Certificate get peerCertificate; | |
330 | |
331 /** | |
332 * Get the protocol which was selected during protocol negotiation. | |
333 */ | |
334 String get selectedProtocol; | |
335 } | |
336 | |
337 | |
338 /** | |
339 * X509Certificate represents an SSL certificate, with accessors to | |
340 * get the fields of the certificate. | |
341 */ | |
342 abstract class X509Certificate { | |
343 external factory X509Certificate._(); | |
344 | |
345 String get subject; | |
346 String get issuer; | |
347 DateTime get startValidity; | |
348 DateTime get endValidity; | |
349 } | |
350 | |
351 | |
352 class _FilterStatus { | |
353 bool progress = false; // The filter read or wrote data to the buffers. | |
354 bool readEmpty = true; // The read buffers and decryption filter are empty. | |
355 bool writeEmpty = true; // The write buffers and encryption filter are empty. | |
356 // These are set if a buffer changes state from empty or full. | |
357 bool readPlaintextNoLongerEmpty = false; | |
358 bool writePlaintextNoLongerFull = false; | |
359 bool readEncryptedNoLongerFull = false; | |
360 bool writeEncryptedNoLongerEmpty = false; | |
361 | |
362 _FilterStatus(); | |
363 } | |
364 | |
365 | |
366 class _RawSecureSocket extends Stream<RawSocketEvent> | |
367 implements RawSecureSocket { | |
368 // Status states | |
369 static final int HANDSHAKE = 201; | |
370 static final int CONNECTED = 202; | |
371 static final int CLOSED = 203; | |
372 | |
373 // Buffer identifiers. | |
374 // These must agree with those in the native C++ implementation. | |
375 static final int READ_PLAINTEXT = 0; | |
376 static final int WRITE_PLAINTEXT = 1; | |
377 static final int READ_ENCRYPTED = 2; | |
378 static final int WRITE_ENCRYPTED = 3; | |
379 static final int NUM_BUFFERS = 4; | |
380 | |
381 // Is a buffer identifier for an encrypted buffer? | |
382 static bool _isBufferEncrypted(int identifier) => identifier >= READ_ENCRYPTED
; | |
383 | |
384 RawSocket _socket; | |
385 final Completer<_RawSecureSocket> _handshakeComplete = | |
386 new Completer<_RawSecureSocket>(); | |
387 StreamController<RawSocketEvent> _controller; | |
388 Stream<RawSocketEvent> _stream; | |
389 StreamSubscription<RawSocketEvent> _socketSubscription; | |
390 List<int> _bufferedData; | |
391 int _bufferedDataIndex = 0; | |
392 final InternetAddress address; | |
393 final bool is_server; | |
394 SecurityContext context; | |
395 final bool requestClientCertificate; | |
396 final bool requireClientCertificate; | |
397 final Function onBadCertificate; | |
398 | |
399 var _status = HANDSHAKE; | |
400 bool _writeEventsEnabled = true; | |
401 bool _readEventsEnabled = true; | |
402 int _pauseCount = 0; | |
403 bool _pendingReadEvent = false; | |
404 bool _socketClosedRead = false; // The network socket is closed for reading. | |
405 bool _socketClosedWrite = false; // The network socket is closed for writing. | |
406 bool _closedRead = false; // The secure socket has fired an onClosed event. | |
407 bool _closedWrite = false; // The secure socket has been closed for writing. | |
408 Completer _closeCompleter = new Completer(); // The network socket is gone. | |
409 _FilterStatus _filterStatus = new _FilterStatus(); | |
410 bool _connectPending = true; | |
411 bool _filterPending = false; | |
412 bool _filterActive = false; | |
413 | |
414 _SecureFilter _secureFilter = new _SecureFilter(); | |
415 String _selectedProtocol; | |
416 | |
417 static Future<_RawSecureSocket> connect( | |
418 dynamic/*String|InternetAddress*/ host, | |
419 int requestedPort, | |
420 {bool is_server, | |
421 SecurityContext context, | |
422 RawSocket socket, | |
423 StreamSubscription subscription, | |
424 List<int> bufferedData, | |
425 bool requestClientCertificate: false, | |
426 bool requireClientCertificate: false, | |
427 bool onBadCertificate(X509Certificate certificate), | |
428 List<String> supportedProtocols}) { | |
429 _verifyFields(host, requestedPort, is_server, | |
430 requestClientCertificate, requireClientCertificate, | |
431 onBadCertificate); | |
432 if (host is InternetAddress) host = host.host; | |
433 InternetAddress address = socket.address; | |
434 if (host != null) { | |
435 address = InternetAddress._cloneWithNewHost(address, host); | |
436 } | |
437 return new _RawSecureSocket(address, | |
438 requestedPort, | |
439 is_server, | |
440 context, | |
441 socket, | |
442 subscription, | |
443 bufferedData, | |
444 requestClientCertificate, | |
445 requireClientCertificate, | |
446 onBadCertificate, | |
447 supportedProtocols) | |
448 ._handshakeComplete.future; | |
449 } | |
450 | |
451 _RawSecureSocket( | |
452 this.address, | |
453 int requestedPort, | |
454 this.is_server, | |
455 this.context, | |
456 RawSocket this._socket, | |
457 this._socketSubscription, | |
458 this._bufferedData, | |
459 this.requestClientCertificate, | |
460 this.requireClientCertificate, | |
461 this.onBadCertificate(X509Certificate certificate), | |
462 List<String> supportedProtocols) { | |
463 if (context == null) { | |
464 context = SecurityContext.defaultContext; | |
465 } | |
466 _controller = new StreamController<RawSocketEvent>( | |
467 sync: true, | |
468 onListen: _onSubscriptionStateChange, | |
469 onPause: _onPauseStateChange, | |
470 onResume: _onPauseStateChange, | |
471 onCancel: _onSubscriptionStateChange); | |
472 _stream = _controller.stream; | |
473 // Throw an ArgumentError if any field is invalid. After this, all | |
474 // errors will be reported through the future or the stream. | |
475 _secureFilter.init(); | |
476 _secureFilter.registerHandshakeCompleteCallback( | |
477 _secureHandshakeCompleteHandler); | |
478 if (onBadCertificate != null) { | |
479 _secureFilter.registerBadCertificateCallback(_onBadCertificateWrapper); | |
480 } | |
481 _socket.readEventsEnabled = true; | |
482 _socket.writeEventsEnabled = false; | |
483 if (_socketSubscription == null) { | |
484 // If a current subscription is provided use this otherwise | |
485 // create a new one. | |
486 _socketSubscription = _socket.listen(_eventDispatcher, | |
487 onError: _reportError, | |
488 onDone: _doneHandler); | |
489 } else { | |
490 if (_socketSubscription.isPaused) { | |
491 _socket.close(); | |
492 throw new ArgumentError( | |
493 "Subscription passed to TLS upgrade is paused"); | |
494 } | |
495 // If we are upgrading a socket that is already closed for read, | |
496 // report an error as if we received READ_CLOSED during the handshake. | |
497 dynamic s = _socket; // Cast to dynamic to avoid warning. | |
498 if (s._socket.closedReadEventSent) { | |
499 _eventDispatcher(RawSocketEvent.READ_CLOSED); | |
500 } | |
501 _socketSubscription | |
502 ..onData(_eventDispatcher) | |
503 ..onError(_reportError) | |
504 ..onDone(_doneHandler); | |
505 } | |
506 try { | |
507 var encodedProtocols = | |
508 SecurityContext._protocolsToLengthEncoding(supportedProtocols); | |
509 _secureFilter.connect(address.host, | |
510 context, | |
511 is_server, | |
512 requestClientCertificate || | |
513 requireClientCertificate, | |
514 requireClientCertificate, | |
515 encodedProtocols); | |
516 _secureHandshake(); | |
517 } catch (e, s) { | |
518 _reportError(e, s); | |
519 } | |
520 } | |
521 | |
522 StreamSubscription<RawSocketEvent> listen(void onData(RawSocketEvent data), | |
523 {Function onError, | |
524 void onDone(), | |
525 bool cancelOnError}) { | |
526 _sendWriteEvent(); | |
527 return _stream.listen(onData, | |
528 onError: onError, | |
529 onDone: onDone, | |
530 cancelOnError: cancelOnError); | |
531 } | |
532 | |
533 static void _verifyFields(host, | |
534 int requestedPort, | |
535 bool is_server, | |
536 bool requestClientCertificate, | |
537 bool requireClientCertificate, | |
538 Function onBadCertificate) { | |
539 if (host is! String && host is! InternetAddress) { | |
540 throw new ArgumentError("host is not a String or an InternetAddress"); | |
541 } | |
542 if (requestedPort is! int) { | |
543 throw new ArgumentError("requestedPort is not an int"); | |
544 } | |
545 if (requestedPort < 0 || requestedPort > 65535) { | |
546 throw new ArgumentError("requestedPort is not in the range 0..65535"); | |
547 } | |
548 if (requestClientCertificate is! bool) { | |
549 throw new ArgumentError("requestClientCertificate is not a bool"); | |
550 } | |
551 if (requireClientCertificate is! bool) { | |
552 throw new ArgumentError("requireClientCertificate is not a bool"); | |
553 } | |
554 if (onBadCertificate != null && onBadCertificate is! Function) { | |
555 throw new ArgumentError("onBadCertificate is not null or a Function"); | |
556 } | |
557 } | |
558 | |
559 int get port => _socket.port; | |
560 | |
561 InternetAddress get remoteAddress => _socket.remoteAddress; | |
562 | |
563 int get remotePort => _socket.remotePort; | |
564 | |
565 void set _owner(owner) { | |
566 (_socket as dynamic)._owner = owner; | |
567 } | |
568 | |
569 int available() { | |
570 return _status != CONNECTED ? 0 | |
571 : _secureFilter.buffers[READ_PLAINTEXT].length; | |
572 } | |
573 | |
574 Future<RawSecureSocket> close() { | |
575 shutdown(SocketDirection.BOTH); | |
576 return _closeCompleter.future; | |
577 } | |
578 | |
579 void _completeCloseCompleter([dummy]) { | |
580 if (!_closeCompleter.isCompleted) _closeCompleter.complete(this); | |
581 } | |
582 | |
583 void _close() { | |
584 _closedWrite = true; | |
585 _closedRead = true; | |
586 if (_socket != null) { | |
587 _socket.close().then(_completeCloseCompleter); | |
588 } else { | |
589 _completeCloseCompleter(); | |
590 } | |
591 _socketClosedWrite = true; | |
592 _socketClosedRead = true; | |
593 if (!_filterActive && _secureFilter != null) { | |
594 _secureFilter.destroy(); | |
595 _secureFilter = null; | |
596 } | |
597 if (_socketSubscription != null) { | |
598 _socketSubscription.cancel(); | |
599 } | |
600 _controller.close(); | |
601 _status = CLOSED; | |
602 } | |
603 | |
604 void shutdown(SocketDirection direction) { | |
605 if (direction == SocketDirection.SEND || | |
606 direction == SocketDirection.BOTH) { | |
607 _closedWrite = true; | |
608 if (_filterStatus.writeEmpty) { | |
609 _socket.shutdown(SocketDirection.SEND); | |
610 _socketClosedWrite = true; | |
611 if (_closedRead) { | |
612 _close(); | |
613 } | |
614 } | |
615 } | |
616 if (direction == SocketDirection.RECEIVE || | |
617 direction == SocketDirection.BOTH) { | |
618 _closedRead = true; | |
619 _socketClosedRead = true; | |
620 _socket.shutdown(SocketDirection.RECEIVE); | |
621 if (_socketClosedWrite) { | |
622 _close(); | |
623 } | |
624 } | |
625 } | |
626 | |
627 bool get writeEventsEnabled => _writeEventsEnabled; | |
628 | |
629 void set writeEventsEnabled(bool value) { | |
630 _writeEventsEnabled = value; | |
631 if (value) { | |
632 Timer.run(() => _sendWriteEvent()); | |
633 } | |
634 } | |
635 | |
636 bool get readEventsEnabled => _readEventsEnabled; | |
637 | |
638 void set readEventsEnabled(bool value) { | |
639 _readEventsEnabled = value; | |
640 _scheduleReadEvent(); | |
641 } | |
642 | |
643 List<int> read([int length]) { | |
644 if (length != null && (length is! int || length < 0)) { | |
645 throw new ArgumentError( | |
646 "Invalid length parameter in SecureSocket.read (length: $length)"); | |
647 } | |
648 if (_closedRead) { | |
649 throw new SocketException("Reading from a closed socket"); | |
650 } | |
651 if (_status != CONNECTED) { | |
652 return null; | |
653 } | |
654 var result = _secureFilter.buffers[READ_PLAINTEXT].read(length); | |
655 _scheduleFilter(); | |
656 return result; | |
657 } | |
658 | |
659 // Write the data to the socket, and schedule the filter to encrypt it. | |
660 int write(List<int> data, [int offset, int bytes]) { | |
661 if (bytes != null && (bytes is! int || bytes < 0)) { | |
662 throw new ArgumentError( | |
663 "Invalid bytes parameter in SecureSocket.read (bytes: $bytes)"); | |
664 } | |
665 if (offset != null && (offset is! int || offset < 0)) { | |
666 throw new ArgumentError( | |
667 "Invalid offset parameter in SecureSocket.read (offset: $offset)"); | |
668 } | |
669 if (_closedWrite) { | |
670 _controller.addError(new SocketException("Writing to a closed socket")); | |
671 return 0; | |
672 } | |
673 if (_status != CONNECTED) return 0; | |
674 if (offset == null) offset = 0; | |
675 if (bytes == null) bytes = data.length - offset; | |
676 | |
677 int written = | |
678 _secureFilter.buffers[WRITE_PLAINTEXT].write(data, offset, bytes); | |
679 if (written > 0) { | |
680 _filterStatus.writeEmpty = false; | |
681 } | |
682 _scheduleFilter(); | |
683 return written; | |
684 } | |
685 | |
686 X509Certificate get peerCertificate => _secureFilter.peerCertificate; | |
687 | |
688 String get selectedProtocol => _selectedProtocol; | |
689 | |
690 bool _onBadCertificateWrapper(X509Certificate certificate) { | |
691 if (onBadCertificate == null) return false; | |
692 var result = onBadCertificate(certificate); | |
693 if (result is bool) return result; | |
694 throw new HandshakeException( | |
695 "onBadCertificate callback returned non-boolean $result"); | |
696 } | |
697 | |
698 bool setOption(SocketOption option, bool enabled) { | |
699 if (_socket == null) return false; | |
700 return _socket.setOption(option, enabled); | |
701 } | |
702 | |
703 void _eventDispatcher(RawSocketEvent event) { | |
704 try { | |
705 if (event == RawSocketEvent.READ) { | |
706 _readHandler(); | |
707 } else if (event == RawSocketEvent.WRITE) { | |
708 _writeHandler(); | |
709 } else if (event == RawSocketEvent.READ_CLOSED) { | |
710 _closeHandler(); | |
711 } | |
712 } catch (e, stackTrace) { | |
713 _reportError(e, stackTrace); | |
714 } | |
715 } | |
716 | |
717 void _readHandler() { | |
718 _readSocket(); | |
719 _scheduleFilter(); | |
720 } | |
721 | |
722 void _writeHandler() { | |
723 _writeSocket(); | |
724 _scheduleFilter(); | |
725 } | |
726 | |
727 void _doneHandler() { | |
728 if (_filterStatus.readEmpty) { | |
729 _close(); | |
730 } | |
731 } | |
732 | |
733 void _reportError(e, [StackTrace stackTrace]) { | |
734 if (_status == CLOSED) { | |
735 return; | |
736 } else if (_connectPending) { | |
737 // _connectPending is true until the handshake has completed, and the | |
738 // _handshakeComplete future returned from SecureSocket.connect has | |
739 // completed. Before this point, we must complete it with an error. | |
740 _handshakeComplete.completeError(e, stackTrace); | |
741 } else { | |
742 _controller.addError(e, stackTrace); | |
743 } | |
744 _close(); | |
745 } | |
746 | |
747 void _closeHandler() { | |
748 if (_status == CONNECTED) { | |
749 if (_closedRead) return; | |
750 _socketClosedRead = true; | |
751 if (_filterStatus.readEmpty) { | |
752 _closedRead = true; | |
753 _controller.add(RawSocketEvent.READ_CLOSED); | |
754 if (_socketClosedWrite) { | |
755 _close(); | |
756 } | |
757 } else { | |
758 _scheduleFilter(); | |
759 } | |
760 } else if (_status == HANDSHAKE) { | |
761 _socketClosedRead = true; | |
762 if (_filterStatus.readEmpty) { | |
763 _reportError( | |
764 new HandshakeException('Connection terminated during handshake'), | |
765 null); | |
766 } else { | |
767 _secureHandshake(); | |
768 } | |
769 } | |
770 } | |
771 | |
772 void _secureHandshake() { | |
773 try { | |
774 _secureFilter.handshake(); | |
775 _filterStatus.writeEmpty = false; | |
776 _readSocket(); | |
777 _writeSocket(); | |
778 _scheduleFilter(); | |
779 } catch (e, stackTrace) { | |
780 _reportError(e, stackTrace); | |
781 } | |
782 } | |
783 | |
784 void renegotiate({bool useSessionCache: true, | |
785 bool requestClientCertificate: false, | |
786 bool requireClientCertificate: false}) { | |
787 if (_status != CONNECTED) { | |
788 throw new HandshakeException( | |
789 "Called renegotiate on a non-connected socket"); | |
790 } | |
791 _secureFilter.renegotiate(useSessionCache, | |
792 requestClientCertificate, | |
793 requireClientCertificate); | |
794 _status = HANDSHAKE; | |
795 _filterStatus.writeEmpty = false; | |
796 _scheduleFilter(); | |
797 } | |
798 | |
799 void _secureHandshakeCompleteHandler() { | |
800 _status = CONNECTED; | |
801 if (_connectPending) { | |
802 _connectPending = false; | |
803 try { | |
804 _selectedProtocol = _secureFilter.selectedProtocol(); | |
805 // We don't want user code to run synchronously in this callback. | |
806 Timer.run(() => _handshakeComplete.complete(this)); | |
807 } catch (error, stack) { | |
808 _handshakeComplete.completeError(error, stack); | |
809 } | |
810 } | |
811 } | |
812 | |
813 void _onPauseStateChange() { | |
814 if (_controller.isPaused) { | |
815 _pauseCount++; | |
816 } else { | |
817 _pauseCount--; | |
818 if (_pauseCount == 0) { | |
819 _scheduleReadEvent(); | |
820 _sendWriteEvent(); // Can send event synchronously. | |
821 } | |
822 } | |
823 | |
824 if (!_socketClosedRead || !_socketClosedWrite) { | |
825 if (_controller.isPaused) { | |
826 _socketSubscription.pause(); | |
827 } else { | |
828 _socketSubscription.resume(); | |
829 } | |
830 } | |
831 } | |
832 | |
833 void _onSubscriptionStateChange() { | |
834 if (_controller.hasListener) { | |
835 // TODO(ajohnsen): Do something here? | |
836 } | |
837 } | |
838 | |
839 void _scheduleFilter() { | |
840 _filterPending = true; | |
841 _tryFilter(); | |
842 } | |
843 | |
844 void _tryFilter() { | |
845 if (_status == CLOSED) { | |
846 return; | |
847 } | |
848 if (_filterPending && !_filterActive) { | |
849 _filterActive = true; | |
850 _filterPending = false; | |
851 _pushAllFilterStages().then((status) { | |
852 _filterStatus = status; | |
853 _filterActive = false; | |
854 if (_status == CLOSED) { | |
855 _secureFilter.destroy(); | |
856 _secureFilter = null; | |
857 return; | |
858 } | |
859 _socket.readEventsEnabled = true; | |
860 if (_filterStatus.writeEmpty && _closedWrite && !_socketClosedWrite) { | |
861 // Checks for and handles all cases of partially closed sockets. | |
862 shutdown(SocketDirection.SEND); | |
863 if (_status == CLOSED) { | |
864 return; | |
865 } | |
866 } | |
867 if (_filterStatus.readEmpty && _socketClosedRead && !_closedRead) { | |
868 if (_status == HANDSHAKE) { | |
869 _secureFilter.handshake(); | |
870 if (_status == HANDSHAKE) { | |
871 throw new HandshakeException( | |
872 'Connection terminated during handshake'); | |
873 } | |
874 } | |
875 _closeHandler(); | |
876 } | |
877 if (_status == CLOSED) { | |
878 return; | |
879 } | |
880 if (_filterStatus.progress) { | |
881 _filterPending = true; | |
882 if (_filterStatus.writeEncryptedNoLongerEmpty) { | |
883 _writeSocket(); | |
884 } | |
885 if (_filterStatus.writePlaintextNoLongerFull) { | |
886 _sendWriteEvent(); | |
887 } | |
888 if (_filterStatus.readEncryptedNoLongerFull) { | |
889 _readSocket(); | |
890 } | |
891 if (_filterStatus.readPlaintextNoLongerEmpty) { | |
892 _scheduleReadEvent(); | |
893 } | |
894 if (_status == HANDSHAKE) { | |
895 _secureHandshake(); | |
896 } | |
897 } | |
898 _tryFilter(); | |
899 }).catchError(_reportError); | |
900 } | |
901 } | |
902 | |
903 List<int> _readSocketOrBufferedData(int bytes) { | |
904 if (_bufferedData != null) { | |
905 if (bytes > _bufferedData.length - _bufferedDataIndex) { | |
906 bytes = _bufferedData.length - _bufferedDataIndex; | |
907 } | |
908 var result = _bufferedData.sublist(_bufferedDataIndex, | |
909 _bufferedDataIndex + bytes); | |
910 _bufferedDataIndex += bytes; | |
911 if (_bufferedData.length == _bufferedDataIndex) { | |
912 _bufferedData = null; | |
913 } | |
914 return result; | |
915 } else if (!_socketClosedRead) { | |
916 return _socket.read(bytes); | |
917 } else { | |
918 return null; | |
919 } | |
920 } | |
921 | |
922 void _readSocket() { | |
923 if (_status == CLOSED) return; | |
924 var buffer = _secureFilter.buffers[READ_ENCRYPTED]; | |
925 if (buffer.writeFromSource(_readSocketOrBufferedData) > 0) { | |
926 _filterStatus.readEmpty = false; | |
927 } else { | |
928 _socket.readEventsEnabled = false; | |
929 } | |
930 } | |
931 | |
932 void _writeSocket() { | |
933 if (_socketClosedWrite) return; | |
934 var buffer = _secureFilter.buffers[WRITE_ENCRYPTED]; | |
935 if (buffer.readToSocket(_socket)) { // Returns true if blocked | |
936 _socket.writeEventsEnabled = true; | |
937 } | |
938 } | |
939 | |
940 // If a read event should be sent, add it to the controller. | |
941 _scheduleReadEvent() { | |
942 if (!_pendingReadEvent && | |
943 _readEventsEnabled && | |
944 _pauseCount == 0 && | |
945 _secureFilter != null && | |
946 !_secureFilter.buffers[READ_PLAINTEXT].isEmpty) { | |
947 _pendingReadEvent = true; | |
948 Timer.run(_sendReadEvent); | |
949 } | |
950 } | |
951 | |
952 _sendReadEvent() { | |
953 _pendingReadEvent = false; | |
954 if (_status != CLOSED && | |
955 _readEventsEnabled && | |
956 _pauseCount == 0 && | |
957 _secureFilter != null && | |
958 !_secureFilter.buffers[READ_PLAINTEXT].isEmpty) { | |
959 _controller.add(RawSocketEvent.READ); | |
960 _scheduleReadEvent(); | |
961 } | |
962 } | |
963 | |
964 // If a write event should be sent, add it to the controller. | |
965 _sendWriteEvent() { | |
966 if (!_closedWrite && | |
967 _writeEventsEnabled && | |
968 _pauseCount == 0 && | |
969 _secureFilter != null && | |
970 _secureFilter.buffers[WRITE_PLAINTEXT].free > 0) { | |
971 _writeEventsEnabled = false; | |
972 _controller.add(RawSocketEvent.WRITE); | |
973 } | |
974 } | |
975 | |
976 Future<_FilterStatus> _pushAllFilterStages() { | |
977 bool wasInHandshake = _status != CONNECTED; | |
978 List args = new List(2 + NUM_BUFFERS * 2); | |
979 args[0] = _secureFilter._pointer(); | |
980 args[1] = wasInHandshake; | |
981 var bufs = _secureFilter.buffers; | |
982 for (var i = 0; i < NUM_BUFFERS; ++i) { | |
983 args[2 * i + 2] = bufs[i].start; | |
984 args[2 * i + 3] = bufs[i].end; | |
985 } | |
986 | |
987 return _IOService._dispatch(_SSL_PROCESS_FILTER, args).then((response) { | |
988 if (response.length == 2) { | |
989 if (wasInHandshake) { | |
990 // If we're in handshake, throw a handshake error. | |
991 _reportError( | |
992 new HandshakeException('${response[1]} error ${response[0]}'), | |
993 null); | |
994 } else { | |
995 // If we're connected, throw a TLS error. | |
996 _reportError(new TlsException('${response[1]} error ${response[0]}'), | |
997 null); | |
998 } | |
999 } | |
1000 int start(int index) => response[2 * index]; | |
1001 int end(int index) => response[2 * index + 1]; | |
1002 | |
1003 _FilterStatus status = new _FilterStatus(); | |
1004 // Compute writeEmpty as "write plaintext buffer and write encrypted | |
1005 // buffer were empty when we started and are empty now". | |
1006 status.writeEmpty = bufs[WRITE_PLAINTEXT].isEmpty && | |
1007 start(WRITE_ENCRYPTED) == end(WRITE_ENCRYPTED); | |
1008 // If we were in handshake when this started, _writeEmpty may be false | |
1009 // because the handshake wrote data after we checked. | |
1010 if (wasInHandshake) status.writeEmpty = false; | |
1011 | |
1012 // Compute readEmpty as "both read buffers were empty when we started | |
1013 // and are empty now". | |
1014 status.readEmpty = bufs[READ_ENCRYPTED].isEmpty && | |
1015 start(READ_PLAINTEXT) == end(READ_PLAINTEXT); | |
1016 | |
1017 _ExternalBuffer buffer = bufs[WRITE_PLAINTEXT]; | |
1018 int new_start = start(WRITE_PLAINTEXT); | |
1019 if (new_start != buffer.start) { | |
1020 status.progress = true; | |
1021 if (buffer.free == 0) { | |
1022 status.writePlaintextNoLongerFull = true; | |
1023 } | |
1024 buffer.start = new_start; | |
1025 } | |
1026 buffer = bufs[READ_ENCRYPTED]; | |
1027 new_start = start(READ_ENCRYPTED); | |
1028 if (new_start != buffer.start) { | |
1029 status.progress = true; | |
1030 if (buffer.free == 0) { | |
1031 status.readEncryptedNoLongerFull = true; | |
1032 } | |
1033 buffer.start = new_start; | |
1034 } | |
1035 buffer = bufs[WRITE_ENCRYPTED]; | |
1036 int new_end = end(WRITE_ENCRYPTED); | |
1037 if (new_end != buffer.end) { | |
1038 status.progress = true; | |
1039 if (buffer.length == 0) { | |
1040 status.writeEncryptedNoLongerEmpty = true; | |
1041 } | |
1042 buffer.end = new_end; | |
1043 } | |
1044 buffer = bufs[READ_PLAINTEXT]; | |
1045 new_end = end(READ_PLAINTEXT); | |
1046 if (new_end != buffer.end) { | |
1047 status.progress = true; | |
1048 if (buffer.length == 0) { | |
1049 status.readPlaintextNoLongerEmpty = true; | |
1050 } | |
1051 buffer.end = new_end; | |
1052 } | |
1053 return status; | |
1054 }); | |
1055 } | |
1056 } | |
1057 | |
1058 | |
1059 /** | |
1060 * A circular buffer backed by an external byte array. Accessed from | |
1061 * both C++ and Dart code in an unsynchronized way, with one reading | |
1062 * and one writing. All updates to start and end are done by Dart code. | |
1063 */ | |
1064 class _ExternalBuffer { | |
1065 List data; // This will be a ExternalByteArray, backed by C allocated data. | |
1066 int start; | |
1067 int end; | |
1068 final size; | |
1069 | |
1070 _ExternalBuffer(this.size) { | |
1071 start = end = size ~/ 2; | |
1072 } | |
1073 | |
1074 void advanceStart(int bytes) { | |
1075 assert(start > end || start + bytes <= end); | |
1076 start += bytes; | |
1077 if (start >= size) { | |
1078 start -= size; | |
1079 assert(start <= end); | |
1080 assert(start < size); | |
1081 } | |
1082 } | |
1083 | |
1084 void advanceEnd(int bytes) { | |
1085 assert(start <= end || start > end + bytes); | |
1086 end += bytes; | |
1087 if (end >= size) { | |
1088 end -= size; | |
1089 assert(end < start); | |
1090 assert(end < size); | |
1091 } | |
1092 } | |
1093 | |
1094 bool get isEmpty => end == start; | |
1095 | |
1096 int get length => | |
1097 start > end ? size + end - start : end - start; | |
1098 | |
1099 int get linearLength => | |
1100 start > end ? size - start : end - start; | |
1101 | |
1102 int get free => | |
1103 start > end ? start - end - 1 : size + start - end - 1; | |
1104 | |
1105 int get linearFree { | |
1106 if (start > end) return start - end - 1; | |
1107 if (start == 0) return size - end - 1; | |
1108 return size - end; | |
1109 } | |
1110 | |
1111 List<int> read(int bytes) { | |
1112 if (bytes == null) { | |
1113 bytes = length; | |
1114 } else { | |
1115 bytes = min(bytes, length); | |
1116 } | |
1117 if (bytes == 0) return null; | |
1118 List<int> result = new Uint8List(bytes); | |
1119 int bytesRead = 0; | |
1120 // Loop over zero, one, or two linear data ranges. | |
1121 while (bytesRead < bytes) { | |
1122 int toRead = min(bytes - bytesRead, linearLength); | |
1123 result.setRange(bytesRead, | |
1124 bytesRead + toRead, | |
1125 data, | |
1126 start); | |
1127 advanceStart(toRead); | |
1128 bytesRead += toRead; | |
1129 } | |
1130 return result; | |
1131 } | |
1132 | |
1133 int write(List<int> inputData, int offset, int bytes) { | |
1134 if (bytes > free) { | |
1135 bytes = free; | |
1136 } | |
1137 int written = 0; | |
1138 int toWrite = min(bytes, linearFree); | |
1139 // Loop over zero, one, or two linear data ranges. | |
1140 while (toWrite > 0) { | |
1141 data.setRange(end, end + toWrite, inputData, offset); | |
1142 advanceEnd(toWrite); | |
1143 offset += toWrite; | |
1144 written += toWrite; | |
1145 toWrite = min(bytes - written, linearFree); | |
1146 } | |
1147 return written; | |
1148 } | |
1149 | |
1150 int writeFromSource(List<int> getData(int requested)) { | |
1151 int written = 0; | |
1152 int toWrite = linearFree; | |
1153 // Loop over zero, one, or two linear data ranges. | |
1154 while (toWrite > 0) { | |
1155 // Source returns at most toWrite bytes, and it returns null when empty. | |
1156 var inputData = getData(toWrite); | |
1157 if (inputData == null || inputData.length == 0) break; | |
1158 var len = inputData.length; | |
1159 data.setRange(end, end + len, inputData); | |
1160 advanceEnd(len); | |
1161 written += len; | |
1162 toWrite = linearFree; | |
1163 } | |
1164 return written; | |
1165 } | |
1166 | |
1167 bool readToSocket(RawSocket socket) { | |
1168 // Loop over zero, one, or two linear data ranges. | |
1169 while (true) { | |
1170 var toWrite = linearLength; | |
1171 if (toWrite == 0) return false; | |
1172 int bytes = socket.write(data, start, toWrite); | |
1173 advanceStart(bytes); | |
1174 if (bytes < toWrite) { | |
1175 // The socket has blocked while we have data to write. | |
1176 return true; | |
1177 } | |
1178 } | |
1179 } | |
1180 } | |
1181 | |
1182 | |
1183 abstract class _SecureFilter { | |
1184 external factory _SecureFilter(); | |
1185 | |
1186 void connect(String hostName, | |
1187 SecurityContext context, | |
1188 bool is_server, | |
1189 bool requestClientCertificate, | |
1190 bool requireClientCertificate, | |
1191 Uint8List protocols); | |
1192 void destroy(); | |
1193 void handshake(); | |
1194 String selectedProtocol(); | |
1195 void rehandshake(); | |
1196 void renegotiate(bool useSessionCache, | |
1197 bool requestClientCertificate, | |
1198 bool requireClientCertificate); | |
1199 void init(); | |
1200 X509Certificate get peerCertificate; | |
1201 int processBuffer(int bufferIndex); | |
1202 void registerBadCertificateCallback(Function callback); | |
1203 void registerHandshakeCompleteCallback(Function handshakeCompleteHandler); | |
1204 | |
1205 // This call may cause a reference counted pointer in the native | |
1206 // implementation to be retained. It should only be called when the resulting | |
1207 // value is passed to the IO service through a call to dispatch(). | |
1208 int _pointer(); | |
1209 | |
1210 List<_ExternalBuffer> get buffers; | |
1211 } | |
1212 | |
1213 /** A secure networking exception caused by a failure in the | |
1214 * TLS/SSL protocol. | |
1215 */ | |
1216 class TlsException implements IOException { | |
1217 final String type; | |
1218 final String message; | |
1219 final OSError osError; | |
1220 | |
1221 const TlsException([String message = "", | |
1222 OSError osError = null]) | |
1223 : this._("TlsException", message, osError); | |
1224 | |
1225 const TlsException._(this.type, this.message, this.osError); | |
1226 | |
1227 String toString() { | |
1228 StringBuffer sb = new StringBuffer(); | |
1229 sb.write(type); | |
1230 if (!message.isEmpty) { | |
1231 sb.write(": $message"); | |
1232 if (osError != null) { | |
1233 sb.write(" ($osError)"); | |
1234 } | |
1235 } else if (osError != null) { | |
1236 sb.write(": $osError"); | |
1237 } | |
1238 return sb.toString(); | |
1239 } | |
1240 } | |
1241 | |
1242 | |
1243 /** | |
1244 * An exception that happens in the handshake phase of establishing | |
1245 * a secure network connection. | |
1246 */ | |
1247 class HandshakeException extends TlsException { | |
1248 const HandshakeException([String message = "", | |
1249 OSError osError = null]) | |
1250 : super._("HandshakeException", message, osError); | |
1251 } | |
1252 | |
1253 | |
1254 /** | |
1255 * An exception that happens in the handshake phase of establishing | |
1256 * a secure network connection, when looking up or verifying a | |
1257 * certificate. | |
1258 */ | |
1259 class CertificateException extends TlsException { | |
1260 const CertificateException([String message = "", | |
1261 OSError osError = null]) | |
1262 : super._("CertificateException", message, osError); | |
1263 } | |
OLD | NEW |