OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, 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 /** | |
6 * TlsSocket provides a secure (SSL or TLS) client connection to a server. | |
7 * The certificate provided by the server is checked | |
8 * using the certificate database provided in setCertificateDatabase. | |
9 */ | |
10 abstract class TlsSocket implements Socket { | |
11 /** | |
12 * Constructs a new secure client socket and connect it to the given | |
13 * host on the given port. The returned socket is not yet connected | |
14 * but ready for registration of callbacks. | |
15 */ | |
16 factory TlsSocket(String host, int port) => new _TlsSocket(host, port); | |
17 | |
18 /** | |
19 * Initializes the TLS library with the path to a certificate database | |
20 * containing root certificates for verifying certificate paths on | |
21 * client connections, and server certificates to provide on server | |
22 * connections. The password argument should be used when creating | |
23 * secure server sockets, to allow the private key of the server | |
24 * certificate to be fetched. | |
25 * | |
26 * The database should be an NSS certificate database directory | |
27 * containing a cert9.db file, not a cert8.db file. This version of | |
28 * the database can be created using the NSS certutil tool with "sql:" in | |
29 * front of the absolute path of the database directory, or setting the | |
30 * environment variable NSS_DEFAULT_DB_TYPE to "sql". | |
31 */ | |
32 external static void setCertificateDatabase(String certificateDatabase, | |
33 [String password]); | |
34 } | |
35 | |
36 | |
37 class _TlsSocket implements TlsSocket { | |
38 // Status states | |
39 static final int NOT_CONNECTED = 200; | |
40 static final int HANDSHAKE = 201; | |
41 static final int CONNECTED = 202; | |
42 static final int CLOSED = 203; | |
43 | |
44 // Buffer identifiers. | |
45 // These must agree with those in the native C++ implementation. | |
46 static final int READ_PLAINTEXT = 0; | |
47 static final int WRITE_PLAINTEXT = 1; | |
48 static final int READ_ENCRYPTED = 2; | |
49 static final int WRITE_ENCRYPTED = 3; | |
50 static final int NUM_BUFFERS = 4; | |
51 | |
52 int _count = 0; | |
53 // Constructs a new secure client socket. | |
54 factory _TlsSocket(String host, int port) => | |
55 new _TlsSocket.internal(host, port, false); | |
56 | |
57 // Constructs a new secure server socket, with the named server certificate. | |
58 factory _TlsSocket.server(String host, | |
59 int port, | |
60 Socket socket, | |
61 String certificateName) => | |
62 new _TlsSocket.internal(host, port, true, socket, certificateName); | |
63 | |
64 _TlsSocket.internal(String host, | |
65 int port, | |
66 bool is_server, | |
67 [Socket socket, | |
68 String certificateName]) | |
69 : _host = host, | |
70 _port = port, | |
71 _socket = socket, | |
72 _certificateName = certificateName, | |
73 _is_server = is_server, | |
74 _tlsFilter = new _TlsFilter() { | |
75 if (_socket == null) { | |
76 _socket = new Socket(host, port); | |
77 } | |
78 _socket.onConnect = _tlsConnectHandler; | |
79 _socket.onData = _tlsDataHandler; | |
80 _socket.onClosed = _tlsCloseHandler; | |
81 _tlsFilter.init(); | |
82 _tlsFilter.registerHandshakeCompleteCallback(_tlsHandshakeCompleteHandler); | |
83 } | |
84 | |
85 int get port => _socket.port; | |
86 | |
87 String get remoteHost => _socket.remoteHost; | |
88 | |
89 int get remotePort => _socket.remotePort; | |
90 | |
91 void set onClosed(void callback()) { | |
92 if (_inputStream != null) { | |
93 throw new StreamException( | |
94 "Cannot set close handler when input stream is used"); | |
95 } | |
96 _onClosed = callback; | |
97 } | |
98 | |
99 void set _onClosed(void callback()) { | |
100 _socketCloseHandler = callback; | |
101 } | |
102 | |
103 void set onConnect(void callback()) { | |
104 if (_outputStream != null) { | |
105 throw new StreamException( | |
106 "Cannot set connect handler when output stream is used"); | |
107 } | |
108 if (_status == CONNECTED || _status == CLOSED) { | |
109 throw new StreamException( | |
110 "Cannot set connect handler when already connected"); | |
111 } | |
112 _onConnect = callback; | |
113 } | |
114 | |
115 void set _onConnect(void callback()) { | |
116 _socketConnectHandler = callback; | |
117 } | |
118 | |
119 void set onData(void callback()) { | |
120 if (_outputStream != null) { | |
121 throw new StreamException( | |
122 "Cannot set data handler when input stream is used"); | |
123 } | |
124 _onData = callback; | |
125 } | |
126 | |
127 void set _onData(void callback()) { | |
128 _socketDataHandler = callback; | |
129 } | |
130 | |
131 void set onWrite(void callback()) { | |
132 if (_outputStream != null) { | |
133 throw new StreamException( | |
134 "Cannot set write handler when output stream is used"); | |
135 } | |
136 _onWrite = callback; | |
137 } | |
138 | |
139 void set _onWrite(void callback()) { | |
140 _socketWriteHandler = callback; | |
141 // Reset the one-shot onWrite handler. | |
142 _socket.onWrite = _tlsWriteHandler; | |
143 } | |
144 | |
145 InputStream get inputStream { | |
146 if (_inputStream == null) { | |
147 if (_socketDataHandler != null || _socketCloseHandler != null) { | |
148 throw new StreamException( | |
149 "Cannot get input stream when socket handlers are used"); | |
150 } | |
151 _inputStream = new _SocketInputStream(this); | |
152 } | |
153 return _inputStream; | |
154 } | |
155 | |
156 OutputStream get outputStream { | |
157 if (_outputStream == null) { | |
158 if (_socketConnectHandler != null || _socketWriteHandler != null) { | |
159 throw new StreamException( | |
160 "Cannot get output stream when socket handlers are used"); | |
161 } | |
162 _outputStream = new _SocketOutputStream(this); | |
163 } | |
164 return _outputStream; | |
165 } | |
166 | |
167 int available() { | |
168 throw new UnimplementedError("TlsSocket.available not implemented yet"); | |
169 } | |
170 | |
171 void close([bool halfClose]) { | |
172 if (halfClose) { | |
173 _closedWrite = true; | |
174 _writeEncryptedData(); | |
175 if (_filterWriteEmpty) { | |
176 _socket.close(true); | |
177 _socketClosedWrite = true; | |
178 } | |
179 } else { | |
180 _closedWrite = true; | |
181 _closedRead = true; | |
182 _socket.close(false); | |
183 _socketClosedWrite = true; | |
184 _socketClosedRead = true; | |
185 _tlsFilter.destroy(); | |
186 _tlsFilter = null; | |
187 if (scheduledDataEvent != null) { | |
188 scheduledDataEvent.cancel(); | |
189 } | |
190 _status = CLOSED; | |
191 } | |
192 } | |
193 | |
194 void _closeWrite() => close(true); | |
195 | |
196 List<int> read([int len]) { | |
197 if (_closedRead) { | |
198 throw new SocketException("Reading from a closed socket"); | |
199 } | |
200 if (_status != CONNECTED) { | |
201 return new List<int>(0); | |
202 } | |
203 var buffer = _tlsFilter.buffers[READ_PLAINTEXT]; | |
204 _readEncryptedData(); | |
205 int toRead = buffer.length; | |
206 if (len != null) { | |
207 if (len is! int || len < 0) { | |
208 throw new ArgumentError( | |
209 "Invalid len parameter in TlsSocket.read (len: $len)"); | |
210 } | |
211 if (len < toRead) { | |
212 toRead = len; | |
213 } | |
214 } | |
215 List<int> result = buffer.data.getRange(buffer.start, toRead); | |
216 buffer.advanceStart(toRead); | |
217 _setHandlersAfterRead(); | |
218 return result; | |
219 } | |
220 | |
221 int readList(List<int> data, int offset, int bytes) { | |
222 if (_closedRead) { | |
223 throw new SocketException("Reading from a closed socket"); | |
224 } | |
225 if (offset < 0 || bytes < 0 || offset + bytes > data.length) { | |
226 throw new ArgumentError( | |
227 "Invalid offset or bytes in TlsSocket.readList"); | |
228 } | |
229 if (_status != CONNECTED && _status != CLOSED) { | |
230 return 0; | |
231 } | |
232 | |
233 int bytesRead = 0; | |
234 var buffer = _tlsFilter.buffers[READ_PLAINTEXT]; | |
235 // TODO(whesse): Currently this fails if the if is turned into a while loop. | |
236 // Fix it so that it can loop and read more than one buffer's worth of data. | |
237 if (bytes > bytesRead) { | |
238 _readEncryptedData(); | |
239 if (buffer.length > 0) { | |
240 int toRead = min(bytes - bytesRead, buffer.length); | |
241 data.setRange(offset, toRead, buffer.data, buffer.start); | |
242 buffer.advanceStart(toRead); | |
243 bytesRead += toRead; | |
244 offset += toRead; | |
245 } | |
246 } | |
247 | |
248 _setHandlersAfterRead(); | |
249 return bytesRead; | |
250 } | |
251 | |
252 // Write the data to the socket, and flush it as much as possible | |
253 // until it would block. If the write would block, _writeEncryptedData sets | |
254 // up handlers to flush the pipeline when possible. | |
255 int writeList(List<int> data, int offset, int bytes) { | |
256 if (_closedWrite) { | |
257 throw new SocketException("Writing to a closed socket"); | |
258 } | |
259 if (_status != CONNECTED) return 0; | |
260 var buffer = _tlsFilter.buffers[WRITE_PLAINTEXT]; | |
261 if (bytes > buffer.free) { | |
262 bytes = buffer.free; | |
263 } | |
264 if (bytes > 0) { | |
265 buffer.data.setRange(buffer.start + buffer.length, bytes, data, offset); | |
266 buffer.length += bytes; | |
267 } | |
268 _writeEncryptedData(); // Tries to flush all pipeline stages. | |
269 return bytes; | |
270 } | |
271 | |
272 void _tlsConnectHandler() { | |
273 _connectPending = true; | |
274 _tlsFilter.connect(_host, _port, _is_server, _certificateName); | |
275 _status = HANDSHAKE; | |
276 _tlsHandshake(); | |
277 } | |
278 | |
279 void _tlsWriteHandler() { | |
280 _writeEncryptedData(); | |
281 if (_filterWriteEmpty && _closedWrite && !_socketClosedWrite) { | |
282 _socket.close(true); | |
283 _sockedClosedWrite = true; | |
284 } | |
285 if (_status == HANDSHAKE) { | |
286 _tlsHandshake(); | |
287 } else if (_status == CONNECTED && | |
288 _socketWriteHandler != null && | |
289 _tlsFilter.buffers[WRITE_PLAINTEXT].free > 0) { | |
290 // We must be able to set onWrite from the onWrite callback. | |
291 var handler = _socketWriteHandler; | |
292 // Reset the one-shot handler. | |
293 _socketWriteHandler = null; | |
294 handler(); | |
295 } | |
296 } | |
297 | |
298 void _tlsDataHandler() { | |
299 if (_status == HANDSHAKE) { | |
300 _tlsHandshake(); | |
301 } else { | |
302 _writeEncryptedData(); // TODO(whesse): Removing this causes a failure. | |
303 _readEncryptedData(); | |
304 if (!_filterReadEmpty) { | |
305 // Call the onData event. | |
306 if (scheduledDataEvent != null) { | |
307 scheduledDataEvent.cancel(); | |
308 scheduledDataEvent = null; | |
309 } | |
310 if (_socketDataHandler != null) { | |
311 _socketDataHandler(); | |
312 } | |
313 } | |
314 } | |
315 } | |
316 | |
317 void _tlsCloseHandler() { | |
318 _socketClosedRead = true; | |
319 if (_filterReadEmpty) { | |
320 _closedRead = true; | |
321 _fireCloseEvent(); | |
322 if (_socketClosedWrite) { | |
323 _tlsFilter.destroy(); | |
324 _tlsFilter = null; | |
325 _status = CLOSED; | |
326 } | |
327 } | |
328 } | |
329 | |
330 void _tlsHandshake() { | |
331 _readEncryptedData(); | |
332 _tlsFilter.handshake(); | |
333 _writeEncryptedData(); | |
334 if (_tlsFilter.buffers[WRITE_ENCRYPTED].length > 0) { | |
335 _socket.onWrite = _tlsWriteHandler; | |
336 } | |
337 } | |
338 | |
339 void _tlsHandshakeCompleteHandler() { | |
340 _status = CONNECTED; | |
341 if (_connectPending && _socketConnectHandler != null) { | |
342 _connectPending = false; | |
343 _socketConnectHandler(); | |
344 } | |
345 if (_socketWriteHandler != null) { | |
346 _socket.onWrite = _tlsWriteHandler; | |
347 } | |
348 } | |
349 | |
350 // True if the underlying socket is closed, the filter has been emptied of | |
351 // all data, and the close event has been fired. | |
352 get _closed => _socketClosed && !_fireCloseEventPending; | |
353 | |
354 void _fireCloseEvent() { | |
355 if (scheduledDataEvent != null) { | |
356 scheduledDataEvent.cancel(); | |
357 } | |
358 if (_socketCloseHandler != null) { | |
359 _socketCloseHandler(); | |
360 } | |
361 } | |
362 | |
363 void _readEncryptedData() { | |
364 // Read from the socket, and push it through the filter as far as | |
365 // possible. | |
366 var encrypted = _tlsFilter.buffers[READ_ENCRYPTED]; | |
367 var plaintext = _tlsFilter.buffers[READ_PLAINTEXT]; | |
368 bool progress = true; | |
369 while (progress) { | |
370 progress = false; | |
371 // Do not try to read plaintext from the filter while handshaking. | |
372 if ((_status == CONNECTED) && plaintext.free > 0) { | |
373 int bytes = _tlsFilter.processBuffer(READ_PLAINTEXT); | |
374 if (bytes > 0) { | |
375 plaintext.length += bytes; | |
376 progress = true; | |
377 } | |
378 } | |
379 if (encrypted.length > 0) { | |
380 int bytes = _tlsFilter.processBuffer(READ_ENCRYPTED); | |
381 if (bytes > 0) { | |
382 encrypted.advanceStart(bytes); | |
383 progress = true; | |
384 } | |
385 } | |
386 if (!_socketClosedRead) { | |
387 int bytes = _socket.readList(encrypted.data, | |
388 encrypted.start + encrypted.length, | |
389 encrypted.free); | |
390 if (bytes > 0) { | |
391 encrypted.length += bytes; | |
392 progress = true; | |
393 } | |
394 } | |
395 } | |
396 // If there is any data in any stages of the filter, there should | |
397 // be data in the plaintext buffer after this process. | |
398 // TODO(whesse): Verify that this is true, and there can be no | |
399 // partial encrypted block stuck in the tlsFilter. | |
400 _filterReadEmpty = (plaintext.length == 0); | |
401 } | |
402 | |
403 void _writeEncryptedData() { | |
404 if (_socketClosedWrite) return; | |
405 var encrypted = _tlsFilter.buffers[WRITE_ENCRYPTED]; | |
406 var plaintext = _tlsFilter.buffers[WRITE_PLAINTEXT]; | |
407 while (true) { | |
408 if (encrypted.length > 0) { | |
409 // Write from the filter to the socket. | |
410 int bytes = _socket.writeList(encrypted.data, | |
411 encrypted.start, | |
412 encrypted.length); | |
413 if (bytes == 0) { | |
414 // The socket has blocked while we have data to write. | |
415 // We must be notified when it becomes unblocked. | |
416 _socket.onWrite = _tlsWriteHandler; | |
417 _filterWriteEmpty = false; | |
418 break; | |
419 } | |
420 encrypted.advanceStart(bytes); | |
421 } else { | |
422 var plaintext = _tlsFilter.buffers[WRITE_PLAINTEXT]; | |
423 if (plaintext.length > 0) { | |
424 int plaintext_bytes = _tlsFilter.processBuffer(WRITE_PLAINTEXT); | |
425 plaintext.advanceStart(plaintext_bytes); | |
426 } | |
427 int bytes = _tlsFilter.processBuffer(WRITE_ENCRYPTED); | |
428 if (bytes <= 0) { | |
429 // We know the WRITE_ENCRYPTED buffer is empty, and the | |
430 // filter wrote zero bytes to it, so the filter must be empty. | |
431 // Also, the WRITE_PLAINTEXT buffer must have been empty, or | |
432 // it would have written to the filter. | |
433 // TODO(whesse): Verify that the filter works this way. | |
434 _filterWriteEmpty = true; | |
435 break; | |
436 } | |
437 encrypted.length += bytes; | |
438 } | |
439 } | |
440 } | |
441 | |
442 /* After a read, the onData handler is enabled to fire again. | |
443 * We may also have a close event waiting for the TlsFilter to empty. | |
444 */ | |
445 void _setHandlersAfterRead() { | |
446 // If the filter is empty, then we are guaranteed an event when it | |
447 // becomes unblocked. Cancel any _tlsDataHandler call. | |
448 // Otherwise, schedule a _tlsDataHandler call since there may data | |
449 // available, and this read call enables the data event. | |
450 if (_filterReadEmpty) { | |
451 if (scheduledDataEvent != null) { | |
452 scheduledDataEvent.cancel(); | |
453 scheduledDataEvent = null; | |
454 } | |
455 } else if (scheduledDataEvent == null) { | |
456 scheduledDataEvent = new Timer(0, (_) => _tlsDataHandler()); | |
457 } | |
458 | |
459 if (_socketClosedRead) { // An onClose event is pending. | |
460 // _closedRead is false, since we are in a read or readList call. | |
461 if (!_filterReadEmpty) { | |
462 // _filterReadEmpty may be out of date since read and readList empty | |
463 // the plaintext buffer after calling _readEncryptedData. | |
464 // TODO(whesse): Fix this as part of fixing read and readList. | |
465 _readEncryptedData(); | |
466 } | |
467 if (_filterReadEmpty) { | |
468 // This can't be an else clause: the value of _filterReadEmpty changes. | |
469 // This must be asynchronous, because we are in a read or readList call. | |
470 new Timer(0, (_) => _fireCloseEvent()); | |
471 } | |
472 } | |
473 } | |
474 | |
475 bool get _socketClosed => _closedRead; | |
476 | |
477 // _TlsSocket cannot extend _Socket and use _Socket's factory constructor. | |
478 Socket _socket; | |
479 String _host; | |
480 int _port; | |
481 bool _is_server; | |
482 String _certificateName; | |
483 | |
484 var _status = NOT_CONNECTED; | |
485 bool _socketClosedRead = false; // The network socket is closed for reading. | |
486 bool _socketClosedWrite = false; // The network socket is closed for writing. | |
487 bool _closedRead = false; // The secure socket has fired an onClosed event. | |
488 bool _closedWrite = false; // The secure socket has been closed for writing. | |
489 bool _filterReadEmpty = true; // There is no buffered data to read. | |
490 bool _filterWriteEmpty = true; // There is no buffered data to be written. | |
491 _SocketInputStream _inputStream; | |
492 _SocketOutputStream _outputStream; | |
493 bool _connectPending = false; | |
494 Function _socketConnectHandler; | |
495 Function _socketWriteHandler; | |
496 Function _socketDataHandler; | |
497 Function _socketCloseHandler; | |
498 Timer scheduledDataEvent; | |
499 | |
500 _TlsFilter _tlsFilter; | |
501 } | |
502 | |
503 | |
504 class _TlsExternalBuffer { | |
505 static final int SIZE = 8 * 1024; | |
506 _TlsExternalBuffer() : start = 0, length = 0; | |
507 | |
508 // TODO(whesse): Consider making this a circular buffer. Only if it helps. | |
509 void advanceStart(int numBytes) { | |
510 start += numBytes; | |
511 length -= numBytes; | |
512 if (length == 0) { | |
513 start = 0; | |
514 } | |
515 } | |
516 | |
517 int get free => SIZE - (start + length); | |
518 | |
519 List data; // This will be a ExternalByteArray, backed by C allocated data. | |
520 int start; | |
521 int length; | |
522 } | |
523 | |
524 | |
525 abstract class _TlsFilter { | |
526 external factory _TlsFilter(); | |
527 | |
528 void connect(String hostName, | |
529 int port, | |
530 bool is_server, | |
531 String certificateName); | |
532 void destroy(); | |
533 void handshake(); | |
534 void init(); | |
535 int processBuffer(int bufferIndex); | |
536 void registerHandshakeCompleteCallback(Function handshakeCompleteHandler); | |
537 | |
538 List<_TlsExternalBuffer> get buffers; | |
539 } | |
OLD | NEW |