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

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, remove HandshakeStartHandler. 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
« no previous file with comments | « sdk/lib/io/iolib_sources.gypi ('k') | tests/standalone/io/pkcert/cert9.db » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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.registerHandshakeCompleteCallback(_tlsHandshakeCompleteHandler);
55 }
56
57 InputStream get inputStream {
58 // TODO(6701): Implement stream interfaces on TlsSocket.
59 throw new UnimplementedError("TlsSocket.inputStream not implemented yet");
60 }
61
62 int get port => _socket.port;
63
64 String get remoteHost => _socket.remoteHost;
65
66 int get remotePort => _socket.remotePort;
67
68 void set onClosed(void callback()) {
69 _socketCloseHandler = callback;
70 }
71
72 void set onConnect(void callback()) {
73 _socketConnectHandler = callback;
74 }
75
76 void set onData(void callback()) {
77 _socketDataHandler = callback;
78 }
79
80 void set onWrite(void callback()) {
81 _socketWriteHandler = callback;
82 // Reset the one-shot onWrite handler.
83 _socket.onWrite = _tlsWriteHandler;
84 }
85
86 OutputStream get outputStream {
87 // TODO(6701): Implement stream interfaces on TlsSocket.
88 throw new UnimplementedError("TlsSocket.inputStream not implemented yet");
89 }
90
91 int available() {
92 throw new UnimplementedError("TlsSocket.available not implemented yet");
93 }
94
95 void close([bool halfClose]) {
96 _socket.close(halfClose);
97 }
98
99 List<int> read([int len]) {
100 var buffer = _tlsFilter.buffers[READ_PLAINTEXT];
101 _readEncryptedData();
102 int toRead = buffer.length;
103 if (len != null) {
104 if (len is! int || len < 0) {
105 throw new ArgumentError(
106 "Invalid len parameter in TlsSocket.read (len: $len)");
107 }
108 if (len < toRead) {
109 toRead = len;
110 }
111 }
112 List<int> result = buffer.data.getRange(buffer.start, toRead);
113 buffer.advanceStart(toRead);
114 _setHandlersAfterRead();
115 return result;
116 }
117
118 int readList(List<int> data, int offset, int bytes) {
119 if (offset < 0 || bytes < 0 || offset + bytes > data.length) {
120 throw new ArgumentError(
121 "Invalid offset or bytes in TlsSocket.readList");
122 }
123
124 int bytesRead = 0;
125 var buffer = _tlsFilter.buffers[READ_PLAINTEXT];
126 // TODO(whesse): Currently this fails if the if is turned into a while loop.
127 // Fix it so that it can loop and read more than one buffer's worth of data.
128 if (bytes > bytesRead) {
129 _readEncryptedData();
130 if (buffer.length > 0) {
131 int toRead = min(bytes - bytesRead, buffer.length);
132 data.setRange(offset, toRead, buffer.data, buffer.start);
133 buffer.advanceStart(toRead);
134 bytesRead += toRead;
135 offset += toRead;
136 }
137 }
138
139 _setHandlersAfterRead();
140 return bytesRead;
141 }
142
143 // Write the data to the socket, and flush it as much as possible
144 // until it would block. If the write would block, _writeEncryptedData sets
145 // up handlers to flush the pipeline when possible.
146 int writeList(List<int> data, int offset, int bytes) {
147 var buffer = _tlsFilter.buffers[WRITE_PLAINTEXT];
148 if (bytes > buffer.free) {
149 bytes = buffer.free;
150 }
151 if (bytes > 0) {
152 buffer.data.setRange(buffer.start + buffer.length, bytes, data, offset);
153 buffer.length += bytes;
154 }
155 _writeEncryptedData(); // Tries to flush all pipeline stages.
156 return bytes;
157 }
158
159 void _tlsConnectHandler() {
160 _connectPending = true;
161 _tlsFilter.connect(_host, _port);
162 _status = HANDSHAKE;
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 _tlsHandshakeCompleteHandler() {
220 _status = CONNECTED;
221 if (_connectPending && _socketConnectHandler != null) {
222 _connectPending = false;
223 _socketConnectHandler();
224 }
225 }
226
227 void _fireCloseEvent() {
228 _fireCloseEventPending = false;
229 _tlsFilter.destroy();
230 _tlsFilter = null;
231 if (scheduledDataEvent != null) {
232 scheduledDataEvent.cancel();
233 }
234 if (_socketCloseHandler != null) {
235 _socketCloseHandler();
236 }
237 }
238
239 void _readEncryptedData() {
240 // Read from the socket, and push it through the filter as far as
241 // possible.
242 var encrypted = _tlsFilter.buffers[READ_ENCRYPTED];
243 var plaintext = _tlsFilter.buffers[READ_PLAINTEXT];
244 bool progress = true;
245 while (progress) {
246 progress = false;
247 // Do not try to read plaintext from the filter while handshaking.
248 if ((_status == CONNECTED || _status == CLOSED) && plaintext.free > 0) {
249 int bytes = _tlsFilter.processBuffer(READ_PLAINTEXT);
250 if (bytes > 0) {
251 plaintext.length += bytes;
252 progress = true;
253 }
254 }
255 if (encrypted.length > 0) {
256 int bytes = _tlsFilter.processBuffer(READ_ENCRYPTED);
257 if (bytes > 0) {
258 encrypted.advanceStart(bytes);
259 progress = true;
260 }
261 }
262 if (!_socketClosed) {
263 int bytes = _socket.readList(encrypted.data,
264 encrypted.start + encrypted.length,
265 encrypted.free);
266 if (bytes > 0) {
267 encrypted.length += bytes;
268 progress = true;
269 }
270 }
271 }
272 // TODO(whesse): This can be incorrect if there is a partial
273 // encrypted block stuck in the tlsFilter, and no other data.
274 // Fix this - we do need to know when the filter is empty.
275 _filterEmpty = (plaintext.length == 0);
276 }
277
278 void _writeEncryptedData() {
279 // Write from the filter to the socket.
280 var buffer = _tlsFilter.buffers[WRITE_ENCRYPTED];
281 while (true) {
282 if (buffer.length > 0) {
283 int bytes = _socket.writeList(buffer.data, buffer.start, buffer.length);
284 if (bytes == 0) {
285 // The socket has blocked while we have data to write.
286 // We must be notified when it becomes unblocked.
287 _socket.onWrite = _tlsWriteHandler;
288 break;
289 }
290 buffer.advanceStart(bytes);
291 } else {
292 var plaintext = _tlsFilter.buffers[WRITE_PLAINTEXT];
293 if (plaintext.length > 0) {
294 int plaintext_bytes = _tlsFilter.processBuffer(WRITE_PLAINTEXT);
295 plaintext.advanceStart(plaintext_bytes);
296 }
297 int bytes = _tlsFilter.processBuffer(WRITE_ENCRYPTED);
298 if (bytes <= 0) break;
299 buffer.length += bytes;
300 }
301 }
302 }
303
304 /* After a read, the onData handler is enabled to fire again.
305 * We may also have a close event waiting for the TlsFilter to empty.
306 */
307 void _setHandlersAfterRead() {
308 // If the filter is empty, then we are guaranteed an event when it
309 // becomes unblocked.
310 // Otherwise, schedule a _tlsDataHandler call since there may data
311 // available, and this read call enables the data event.
312 if (!_filterEmpty && scheduledDataEvent == null) {
313 scheduledDataEvent = new Timer(0, (_) => _tlsDataHandler());
314 } else if (_filterEmpty && scheduledDataEvent != null) {
315 scheduledDataEvent.cancel();
316 scheduledDataEvent = null;
317 }
318 if (_filterEmpty && _fireCloseEventPending) {
319 _fireCloseEvent();
320 }
321 }
322
323 // _TlsSocket cannot extend _Socket and use _Socket's factory constructor.
324 Socket _socket;
325 String _host;
326 int _port;
327
328 var _status = NOT_CONNECTED;
329 bool _socketClosed = false;
330 bool _filterEmpty = false;
331 bool _connectPending = false;
332 bool _fireCloseEventPending = false;
333 Function _socketConnectHandler;
334 Function _socketWriteHandler;
335 Function _socketDataHandler;
336 Function _socketCloseHandler;
337 Timer scheduledDataEvent;
338
339 _TlsFilter _tlsFilter;
340 }
341
342
343 class _TlsExternalBuffer {
344 static final int SIZE = 8 * 1024;
345 _TlsExternalBuffer() : start = 0, length = 0;
346
347 // TODO(whesse): Consider making this a circular buffer. Only if it helps.
348 void advanceStart(int numBytes) {
349 start += numBytes;
350 length -= numBytes;
351 if (length == 0) {
352 start = 0;
353 }
354 }
355
356 int get free => SIZE - (start + length);
357
358 List data; // This will be a ExternalByteArray, backed by C allocated data.
359 int start;
360 int length;
361 }
362
363
364 abstract class _TlsFilter {
365 external factory _TlsFilter();
366
367 void connect(String hostName, int port);
368 void destroy();
369 void handshake();
370 void init();
371 int processBuffer(int bufferIndex);
372 void registerHandshakeCompleteCallback(Function handshakeCompleteHandler);
373 }
OLDNEW
« no previous file with comments | « sdk/lib/io/iolib_sources.gypi ('k') | tests/standalone/io/pkcert/cert9.db » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698