Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(316)

Side by Side Diff: sdk/lib/io/tls_socket.dart

Issue 10916081: Add secure sockets to dart:io (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments. Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 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.
23 */
24 external static void setCertificateDatabase(String pkcertDirectory);
25 }
26
27
28 class _TlsSocket implements TlsSocket {
29 // Status states
30 static final int NOT_CONNECTED = 200;
31 static final int HANDSHAKE = 201;
32 static final int CONNECTED = 202;
33 static final int CLOSED = 203;
34
35 // Buffer identifiers.
36 // These must agree with those in the native C++ implementation.
37 static final int READ_PLAINTEXT = 0;
38 static final int WRITE_PLAINTEXT = 1;
39 static final int READ_ENCRYPTED = 2;
40 static final int WRITE_ENCRYPTED = 3;
41 static final int NUM_BUFFERS = 4;
42
43 int _count = 0;
44 // Constructs a new secure client socket.
45 _TlsSocket(String host, int port)
46 : _host = host,
47 _port = port,
48 _socket = new Socket(host, port),
49 _tlsFilter = new _TlsFilter() {
50 _socket.onConnect = _tlsConnectHandler;
51 _socket.onData = _tlsDataHandler;
52 _socket.onClosed = _tlsCloseHandler;
53 _tlsFilter.init();
54 _tlsFilter.registerHandshakeCallbacks(_tlsHandshakeStartHandler,
55 _tlsHandshakeFinishHandler);
56 }
57
58 InputStream get inputStream {
59 // TODO(6701): Implement stream interfaces on TlsSocket.
60 throw new UnimplementedError("TlsSocket.inputStream not implemented yet");
61 }
62
63 int get port => _socket.port;
64
65 String get remoteHost => _socket.remoteHost;
66
67 int get remotePort => _socket.remotePort;
68
69 void set onClosed(void callback()) {
70 _socketCloseHandler = callback;
71 }
72
73 void set onConnect(void callback()) {
74 _socketConnectHandler = callback;
75 }
76
77 void set onData(void callback()) {
78 _socketDataHandler = callback;
79 }
80
81 void set onWrite(void callback()) {
82 _socketWriteHandler = callback;
83 // Reset the one-shot onWrite handler.
84 _socket.onWrite = _tlsWriteHandler;
85 }
86
87 OutputStream get outputStream {
88 // TODO(6701): Implement stream interfaces on TlsSocket.
89 throw new UnimplementedError("TlsSocket.inputStream not implemented yet");
90 }
91
92 int available() {
93 throw new UnimplementedError("TlsSocket.available not implemented yet");
94 }
95
96 void close([bool halfClose]) {
97 _socket.close(halfClose);
98 }
99
100 List<int> read([int len]) {
101 var buffer = _tlsFilter.buffers[READ_PLAINTEXT];
102 _readEncryptedData();
103 int toRead = buffer.length;
104 if (len != null) {
105 if (len is! int || len < 0) {
106 throw new ArgumentError(
107 "Invalid len parameter in TlsSocket.read (len: $len)");
108 }
109 if (len < toRead) {
110 toRead = len;
111 }
112 }
Søren Gjesse 2012/11/14 08:18:59 Indentation
Bill Hesse 2012/11/14 13:33:30 Done.
113 List<int> result = buffer.data.getRange(buffer.start, toRead);
114 buffer.advanceStart(toRead);
115 _setHandlersAfterRead();
116 return result;
117 }
118
119 int readList(List<int> data, int offset, int bytes) {
120 if (offset < 0 || bytes < 0 || offset + bytes > data.length) {
121 throw new ArgumentError(
122 "Invalid offset or bytes in TlsSocket.readList");
123 }
124
125 int bytesRead = 0;
126 var buffer = _tlsFilter.buffers[READ_PLAINTEXT];
127 // TODO(whesse): Currently this fails if the if is turned into a while loop.
128 // Fix it so that it can loop and read more than one buffer's worth of data.
129 if (bytes > bytesRead) {
130 _readEncryptedData();
131 if (buffer.length > 0) {
132 int toRead = min(bytes - bytesRead, buffer.length);
133 data.setRange(offset, toRead, buffer.data, buffer.start);
134 buffer.advanceStart(toRead);
135 bytesRead += toRead;
136 offset += toRead;
137 }
138 }
139
140 _setHandlersAfterRead();
141 return bytesRead;
142 }
143
144 // Write the data to the socket, and flush it as much as possible
145 // until it blocks. If the write blocks, _writeEncryptedData sets
Søren Gjesse 2012/11/14 08:18:59 Please change block to "would block" (or something
Bill Hesse 2012/11/14 13:33:30 Done.
146 // up handlers to flush the pipeline when possible.
147 int writeList(List<int> data, int offset, int bytes) {
148 var buffer = _tlsFilter.buffers[WRITE_PLAINTEXT];
149 if (bytes > buffer.free) {
150 bytes = buffer.free;
151 }
152 if (bytes > 0) {
153 buffer.data.setRange(buffer.start + buffer.length, bytes, data, offset);
154 buffer.length += bytes;
155 }
156 _writeEncryptedData(); // Tries to flush all pipeline stages.
157 return bytes;
158 }
159
160 void _tlsConnectHandler() {
161 _connectPending = true;
162 _tlsFilter.connect(_host, _port);
163 _tlsHandshake();
164 }
165
166 void _tlsWriteHandler() {
167 if (_status == HANDSHAKE) {
168 _tlsHandshake();
169 } else if (_status == CONNECTED) {
170 if (_socketWriteHandler != null) {
171 _socketWriteHandler();
172 }
173 }
174 }
175
176 void _tlsDataHandler() {
177 if (_status == HANDSHAKE) {
178 _tlsHandshake();
179 } else {
180 _writeEncryptedData(); // TODO(whesse): Removing this causes a failure.
181 _readEncryptedData();
182 var buffer = _tlsFilter.buffers[READ_PLAINTEXT];
183 if (_filterEmpty) {
184 if (_fireCloseEventPending) {
185 _fireCloseEvent();
186 }
187 } else { // Filter is not empty.
188 if (scheduledDataEvent != null) {
189 scheduledDataEvent.cancel();
190 scheduledDataEvent = null;
191 }
192 if (_socketDataHandler != null) {
193 _socketDataHandler();
194 }
195 }
196 }
197 }
198
199 void _tlsCloseHandler() {
200 _socketClosed = true;
201 _status = CLOSED;
202 _socket.close();
203 if (_filterEmpty) {
204 _fireCloseEvent();
205 } else {
206 _fireCloseEventPending = true;
207 }
208 }
209
210 void _tlsHandshake() {
211 _readEncryptedData();
212 _tlsFilter.handshake();
213 _writeEncryptedData();
214 if (_tlsFilter.buffers[WRITE_ENCRYPTED].length > 0) {
215 _socket.onWrite = _tlsWriteHandler;
216 }
217 }
218
219 void _tlsHandshakeStartHandler() {
220 _status = HANDSHAKE;
221 }
222
223 void _tlsHandshakeFinishHandler() {
224 _status = CONNECTED;
225 if (_connectPending && _socketConnectHandler != null) {
226 _connectPending = false;
227 _socketConnectHandler();
228 }
229 }
230
231 void _fireCloseEvent() {
232 _fireCloseEventPending = false;
233 _tlsFilter.destroy();
234 _tlsFilter = null;
235 if (scheduledDataEvent != null) {
236 scheduledDataEvent.cancel();
237 }
238 if (_socketCloseHandler != null) {
239 _socketCloseHandler();
240 }
241 }
242
243 void _readEncryptedData() {
244 // Read from the socket, and push it through the filter as far as
245 // possible.
246 var encrypted = _tlsFilter.buffers[READ_ENCRYPTED];
247 var plaintext = _tlsFilter.buffers[READ_PLAINTEXT];
248 bool progress = true;
249 while (progress) {
250 progress = false;
251 // Do not try to read plaintext from the filter while handshaking.
252 if ((_status == CONNECTED || _status == CLOSED) && plaintext.free > 0) {
Søren Gjesse 2012/11/14 08:18:59 Why are we trying to process data when closed?
Bill Hesse 2012/11/14 13:33:30 The status == CLOSED is the status when the underl
253 int bytes = _tlsFilter.processBuffer(READ_PLAINTEXT);
254 if (bytes > 0) {
255 plaintext.length += bytes;
256 progress = true;
257 }
258 }
259 if (encrypted.length > 0) {
260 int bytes = _tlsFilter.processBuffer(READ_ENCRYPTED);
261 if (bytes > 0) {
262 encrypted.advanceStart(bytes);
263 progress = true;
264 }
265 }
266 if (!_socketClosed) {
267 int bytes = _socket.readList(encrypted.data,
268 encrypted.start + encrypted.length,
269 encrypted.free);
270 if (bytes > 0) {
271 encrypted.length += bytes;
272 progress = true;
273 }
274 }
275 }
276 // TODO(whesse): This can be incorrect if there is a partial
277 // encrypted block stuck in the tlsFilter, and no other data.
278 // Fix this - we do need to know when the filter is empty.
279 _filterEmpty = (plaintext.length == 0);
280 }
281
282 void _writeEncryptedData() {
283 // Write from the filter to the socket.
284 var buffer = _tlsFilter.buffers[WRITE_ENCRYPTED];
285 while (true) {
286 if (buffer.length > 0) {
287 int bytes = _socket.writeList(buffer.data, buffer.start, buffer.length);
288 if (bytes == 0) {
289 // The socket has blocked while we have data to write.
290 // We must be notified when it becomes unblocked.
291 _socket.onWrite = _tlsWriteHandler;
292 break;
293 }
294 buffer.advanceStart(bytes);
295 } else {
296 var plaintext = _tlsFilter.buffers[WRITE_PLAINTEXT];
297 if (plaintext.length > 0) {
298 int plaintext_bytes = _tlsFilter.processBuffer(WRITE_PLAINTEXT);
299 plaintext.advanceStart(plaintext_bytes);
300 }
301 int bytes = _tlsFilter.processBuffer(WRITE_ENCRYPTED);
302 if (bytes <= 0) break;
303 buffer.length += bytes;
304 }
305 }
306 }
307
308 /* After a read, the onData handler is enabled to fire again.
309 * We may also have a close event waiting for the TlsFilter to empty.
310 */
311 void _setHandlersAfterRead() {
312 // If the filter is empty, then we are guaranteed an event when it
313 // becomes unblocked.
314 // Otherwise, schedule a _tlsDataHandler call since there may data
315 // available, and this read call enables the data event.
316 if (!_filterEmpty && scheduledDataEvent == null) {
317 scheduledDataEvent = new Timer(0, (_) => _tlsDataHandler());
318 } else if (_filterEmpty && scheduledDataEvent != null) {
319 scheduledDataEvent.cancel();
320 scheduledDataEvent = null;
321 }
322 if (_filterEmpty && _fireCloseEventPending) {
323 _fireCloseEvent();
324 }
325 }
326
327 // _TlsSocket cannot extend _Socket and use _Socket's factory constructor.
328 Socket _socket;
329 String _host;
330 int _port;
331
332 var _status = NOT_CONNECTED;
333 bool _socketClosed = false;
334 bool _filterEmpty = false;
335 bool _connectPending = false;
336 bool _fireCloseEventPending = false;
337 Function _socketConnectHandler;
338 Function _socketWriteHandler;
339 Function _socketDataHandler;
340 Function _socketCloseHandler;
341 Timer scheduledDataEvent;
342
343 _TlsFilter _tlsFilter;
344 }
345
346
Søren Gjesse 2012/11/14 08:18:59 Could this maybe be made into a real circular buff
Bill Hesse 2012/11/14 13:33:30 Added as a TODO. I would not do this until we hav
347 class _TlsExternalBuffer {
348 static final int SIZE = 8 * 1024;
349 _TlsExternalBuffer() : start = 0, length = 0;
350
351 void advanceStart(int numBytes) {
352 start += numBytes;
353 length -= numBytes;
354 if (length == 0) {
355 start = 0;
356 }
357 }
358
359 int get free => SIZE - (start + length);
360
361 List data; // This will be a ExternalByteArray, backed by C allocated data.
362 int start;
363 int length;
364 }
365
366
367 abstract class _TlsFilter {
368 external factory _TlsFilter();
369
370 void connect(String hostName, int port);
371 void destroy();
372 void handshake();
373 void init();
374 int processBuffer(int bufferIndex);
375 void registerHandshakeCallbacks(Function startHandshakeHandler,
376 Function finishHandshakeHandler);
377 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698