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 |