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 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 } | |
OLD | NEW |