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

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: Add Socket.read and pass port number to NSS. 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 }
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
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();
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 // TODO(whesse): Set the write handler only when there is filter data
215 // to be written to the socket. Currently Windows stalls if we do this.
216 // The condition should be _tlsFilter.buffers[kWriteEncrypted].length > 0.
217 if (_tlsFilter.buffers[WRITE_ENCRYPTED].length > 0) {
218 _socket.onWrite = _tlsWriteHandler;
219 }
220 }
221
222 void _tlsHandshakeStartHandler() {
223 _status = HANDSHAKE;
224 }
225
226 void _tlsHandshakeFinishHandler() {
227 _status = CONNECTED;
228 if (_connectPending && _socketConnectHandler != null) {
229 _connectPending = false;
230 _socketConnectHandler();
231 }
232 }
233
234 void _fireCloseEvent() {
235 _fireCloseEventPending = false;
236 _tlsFilter.destroy();
237 _tlsFilter = null;
238 if (scheduledDataEvent != null) {
239 scheduledDataEvent.cancel();
240 }
241 if (_socketCloseHandler != null) {
242 _socketCloseHandler();
243 }
244 }
245
246 void _readEncryptedData() {
247 // Read from the socket, and push it through the filter as far as
248 // possible.
249 var encrypted = _tlsFilter.buffers[READ_ENCRYPTED];
250 var plaintext = _tlsFilter.buffers[READ_PLAINTEXT];
251 bool progress = true;
252 while (progress) {
253 progress = false;
254 // Do not try to read plaintext from the filter while handshaking.
255 if ((_status == CONNECTED || _status == CLOSED) && plaintext.free > 0) {
256 int bytes = _tlsFilter.processBuffer(READ_PLAINTEXT);
257 if (bytes > 0) {
258 plaintext.length += bytes;
259 progress = true;
260 }
261 }
262 if (encrypted.length > 0) {
263 int bytes = _tlsFilter.processBuffer(READ_ENCRYPTED);
264 if (bytes > 0) {
265 encrypted.advanceStart(bytes);
266 progress = true;
267 }
268 }
269 if (!_socketClosed) {
270 int bytes = _socket.readList(encrypted.data,
271 encrypted.start + encrypted.length,
272 encrypted.free);
273 if (bytes > 0) {
274 encrypted.length += bytes;
275 progress = true;
276 }
277 }
278 }
279 // TODO(whesse): This can be incorrect if there is a partial
280 // encrypted block stuck in the tlsFilter, and no other data.
281 // Fix this - we do need to know when the filter is empty.
282 _filterEmpty = (plaintext.length == 0);
283 }
284
285 void _writeEncryptedData() {
286 // Write from the filter to the socket.
287 var buffer = _tlsFilter.buffers[WRITE_ENCRYPTED];
288 while (true) {
289 if (buffer.length > 0) {
290 int bytes = _socket.writeList(buffer.data, buffer.start, buffer.length);
291 if (bytes == 0) {
292 // The socket has blocked while we have data to write.
293 // We must be notified when it becomes unblocked.
294 _socket.onWrite = _tlsWriteHandler;
295 break;
296 }
297 buffer.advanceStart(bytes);
298 } else {
299 var plaintext = _tlsFilter.buffers[WRITE_PLAINTEXT];
300 if (plaintext.length > 0) {
301 int plaintext_bytes = _tlsFilter.processBuffer(WRITE_PLAINTEXT);
302 plaintext.advanceStart(plaintext_bytes);
303 }
304 int bytes = _tlsFilter.processBuffer(WRITE_ENCRYPTED);
305 if (bytes <= 0) break;
306 buffer.length += bytes;
307 }
308 }
309 }
310
311 /* After a read, the onData handler is enabled to fire again.
312 * We may also have a close event waiting for the TlsFilter to empty.
313 */
314 void _setHandlersAfterRead() {
315 // If the filter is empty, then we are guaranteed an event when it
316 // becomes unblocked.
317 // Otherwise, schedule a _tlsDataHandler call since there may data
318 // available, and this read call enables the data event.
319 if (!_filterEmpty && scheduledDataEvent == null) {
320 scheduledDataEvent = new Timer(0, (_) => _tlsDataHandler());
321 } else if (_filterEmpty && scheduledDataEvent != null) {
322 scheduledDataEvent.cancel();
323 scheduledDataEvent = null;
324 }
325 if (_filterEmpty && _fireCloseEventPending) {
326 _fireCloseEvent();
327 }
328 }
329
330 // _TlsSocket cannot extend _Socket and use _Socket's factory constructor.
331 Socket _socket;
332 String _host;
333 int _port;
334
335 var _status = NOT_CONNECTED;
336 bool _socketClosed = false;
337 bool _filterEmpty = false;
338 bool _connectPending = false;
339 bool _fireCloseEventPending = false;
340 Function _socketConnectHandler;
341 Function _socketWriteHandler;
342 Function _socketDataHandler;
343 Function _socketCloseHandler;
344 Timer scheduledDataEvent;
345
346 _TlsFilter _tlsFilter;
347 }
348
349
350 class _TlsExternalBuffer {
351 static final int SIZE = 8 * 1024;
352 _TlsExternalBuffer() : start = 0, length = 0;
353
354 void advanceStart(int numBytes) {
355 start += numBytes;
356 length -= numBytes;
357 if (length == 0) {
358 start = 0;
359 }
360 }
361
362 int get free => SIZE - (start + length);
363
364 List data; // This will be a ExternalByteArray, backed by C allocated data.
365 int start;
366 int length;
367 }
368
369
370 abstract class _TlsFilter {
371 external factory _TlsFilter();
372
373 void connect(String hostName, int port);
374 void destroy();
375 void handshake();
376 void init();
377 int processBuffer(int bufferIndex);
378 void registerHandshakeCallbacks(Function startHandshakeHandler,
379 Function finishHandshakeHandler);
380 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698