1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.http.nio.reactor.ssl;
29
30 import java.io.IOException;
31 import java.net.InetSocketAddress;
32 import java.net.Socket;
33 import java.net.SocketAddress;
34 import java.nio.ByteBuffer;
35 import java.nio.channels.ByteChannel;
36 import java.nio.channels.SelectionKey;
37
38 import javax.net.ssl.SSLContext;
39 import javax.net.ssl.SSLEngine;
40 import javax.net.ssl.SSLEngineResult;
41 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
42 import javax.net.ssl.SSLEngineResult.Status;
43 import javax.net.ssl.SSLException;
44 import javax.net.ssl.SSLSession;
45
46 import org.apache.http.annotation.ThreadSafe;
47 import org.apache.http.nio.reactor.EventMask;
48 import org.apache.http.nio.reactor.IOSession;
49 import org.apache.http.nio.reactor.SessionBufferStatus;
50 import org.apache.http.nio.reactor.SocketAccessor;
51 import org.apache.http.util.Args;
52 import org.apache.http.util.Asserts;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 @ThreadSafe
72 public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAccessor {
73
74
75
76
77
78 public static final String SESSION_KEY = "http.session.ssl";
79
80 private final IOSession session;
81 private final SSLMode defaultMode;
82 private final SSLEngine sslEngine;
83 private final ByteBuffer inEncrypted;
84 private final ByteBuffer outEncrypted;
85 private final ByteBuffer inPlain;
86 private final ByteBuffer outPlain;
87 private final InternalByteChannel channel;
88 private final SSLSetupHandler handler;
89
90 private int appEventMask;
91 private SessionBufferStatus appBufferStatus;
92
93 private boolean endOfStream;
94 private volatile int status;
95 private volatile boolean initialized;
96
97
98
99
100
101
102
103
104
105 public SSLIOSession(
106 final IOSession session,
107 final SSLMode defaultMode,
108 final SSLContext sslContext,
109 final SSLSetupHandler handler) {
110 super();
111 Args.notNull(session, "IO session");
112 Args.notNull(sslContext, "SSL context");
113 this.session = session;
114 this.defaultMode = defaultMode;
115 this.appEventMask = session.getEventMask();
116 this.channel = new InternalByteChannel();
117 this.handler = handler;
118
119
120 this.session.setBufferStatus(this);
121
122 final SocketAddress address = session.getRemoteAddress();
123 if (address instanceof InetSocketAddress) {
124 final String hostname = ((InetSocketAddress) address).getHostName();
125 final int port = ((InetSocketAddress) address).getPort();
126 this.sslEngine = sslContext.createSSLEngine(hostname, port);
127 } else {
128 this.sslEngine = sslContext.createSSLEngine();
129 }
130
131
132 final int netBuffersize = this.sslEngine.getSession().getPacketBufferSize();
133 this.inEncrypted = ByteBuffer.allocate(netBuffersize);
134 this.outEncrypted = ByteBuffer.allocate(netBuffersize);
135
136
137 final int appBuffersize = this.sslEngine.getSession().getApplicationBufferSize();
138 this.inPlain = ByteBuffer.allocate(appBuffersize);
139 this.outPlain = ByteBuffer.allocate(appBuffersize);
140 }
141
142 protected SSLSetupHandler getSSLSetupHandler() {
143 return this.handler;
144 }
145
146
147
148
149
150 public boolean isInitialized() {
151 return this.initialized;
152 }
153
154
155
156
157
158
159
160
161
162
163
164 public synchronized void initialize(final SSLMode mode) throws SSLException {
165 Asserts.check(!this.initialized, "SSL I/O session already initialized");
166 if (this.status >= IOSession.CLOSING) {
167 return;
168 }
169 switch (mode) {
170 case CLIENT:
171 this.sslEngine.setUseClientMode(true);
172 break;
173 case SERVER:
174 this.sslEngine.setUseClientMode(false);
175 break;
176 }
177 if (this.handler != null) {
178 this.handler.initalize(this.sslEngine);
179 }
180 this.initialized = true;
181 this.sslEngine.beginHandshake();
182 doHandshake();
183 }
184
185
186
187
188
189
190
191
192
193
194 public void initialize() throws SSLException {
195 initialize(this.defaultMode);
196 }
197
198 public synchronized SSLSession getSSLSession() {
199 return this.sslEngine.getSession();
200 }
201
202
203
204
205
206
207 private SSLException convert(final RuntimeException ex) throws SSLException {
208 Throwable cause = ex.getCause();
209 if (cause == null) {
210 cause = ex;
211 }
212 return new SSLException(cause);
213 }
214
215 private SSLEngineResult doWrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
216 try {
217 return this.sslEngine.wrap(src, dst);
218 } catch (final RuntimeException ex) {
219 throw convert(ex);
220 }
221 }
222
223 private SSLEngineResult doUnwrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
224 try {
225 return this.sslEngine.unwrap(src, dst);
226 } catch (final RuntimeException ex) {
227 throw convert(ex);
228 }
229 }
230
231 private void doRunTask() throws SSLException {
232 try {
233 final Runnable r = this.sslEngine.getDelegatedTask();
234 if (r != null) {
235 r.run();
236 }
237 } catch (final RuntimeException ex) {
238 throw convert(ex);
239 }
240 }
241
242 private void doHandshake() throws SSLException {
243 boolean handshaking = true;
244
245 SSLEngineResult result = null;
246 while (handshaking) {
247 switch (this.sslEngine.getHandshakeStatus()) {
248 case NEED_WRAP:
249
250 this.outPlain.flip();
251 result = doWrap(this.outPlain, this.outEncrypted);
252 this.outPlain.compact();
253 if (result.getStatus() != Status.OK) {
254 handshaking = false;
255 }
256 break;
257 case NEED_UNWRAP:
258
259 this.inEncrypted.flip();
260 result = doUnwrap(this.inEncrypted, this.inPlain);
261 this.inEncrypted.compact();
262 if (result.getStatus() != Status.OK) {
263 handshaking = false;
264 }
265 break;
266 case NEED_TASK:
267 doRunTask();
268 break;
269 case NOT_HANDSHAKING:
270 handshaking = false;
271 break;
272 case FINISHED:
273 break;
274 }
275 }
276
277
278
279
280 if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
281 if (this.handler != null) {
282 this.handler.verify(this.session, this.sslEngine.getSession());
283 }
284 }
285 }
286
287 private void updateEventMask() {
288
289 if (this.status == CLOSING && this.sslEngine.isOutboundDone()
290 && (this.endOfStream || this.sslEngine.isInboundDone())) {
291 this.status = CLOSED;
292 }
293
294 if (this.status == ACTIVE && this.endOfStream
295 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
296 this.status = CLOSED;
297 }
298 if (this.status == CLOSED) {
299 this.session.close();
300 return;
301 }
302
303 final int oldMask = this.session.getEventMask();
304 int newMask = oldMask;
305 switch (this.sslEngine.getHandshakeStatus()) {
306 case NEED_WRAP:
307 newMask = EventMask.READ_WRITE;
308 break;
309 case NEED_UNWRAP:
310 newMask = EventMask.READ;
311 break;
312 case NOT_HANDSHAKING:
313 newMask = this.appEventMask;
314 break;
315 case NEED_TASK:
316 break;
317 case FINISHED:
318 break;
319 }
320
321
322 if (this.outEncrypted.position() > 0) {
323 newMask = newMask | EventMask.WRITE;
324 }
325
326
327 if (oldMask != newMask) {
328 this.session.setEventMask(newMask);
329 }
330 }
331
332 private int sendEncryptedData() throws IOException {
333 this.outEncrypted.flip();
334 final int bytesWritten = this.session.channel().write(this.outEncrypted);
335 this.outEncrypted.compact();
336 return bytesWritten;
337 }
338
339 private int receiveEncryptedData() throws IOException {
340 if (this.endOfStream) {
341 return -1;
342 }
343 return this.session.channel().read(this.inEncrypted);
344 }
345
346 private boolean decryptData() throws SSLException {
347 boolean decrypted = false;
348 while (this.inEncrypted.position() > 0) {
349 this.inEncrypted.flip();
350 final SSLEngineResult result = doUnwrap(this.inEncrypted, this.inPlain);
351 this.inEncrypted.compact();
352 if (result.getStatus() == Status.OK) {
353 decrypted = true;
354 } else {
355 break;
356 }
357 if (result.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) {
358 break;
359 }
360 if (this.endOfStream) {
361 break;
362 }
363 }
364 return decrypted;
365 }
366
367
368
369
370
371
372
373 public synchronized boolean isAppInputReady() throws IOException {
374 final int bytesRead = receiveEncryptedData();
375 if (bytesRead == -1) {
376 this.endOfStream = true;
377 }
378 doHandshake();
379 final HandshakeStatus status = this.sslEngine.getHandshakeStatus();
380 if (status == HandshakeStatus.NOT_HANDSHAKING || status == HandshakeStatus.FINISHED) {
381 decryptData();
382 }
383
384 return (this.appEventMask & SelectionKey.OP_READ) > 0
385 && (this.inPlain.position() > 0
386 || (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
387 || (this.endOfStream && this.status == ACTIVE));
388 }
389
390
391
392
393
394
395
396 public synchronized boolean isAppOutputReady() throws IOException {
397 return (this.appEventMask & SelectionKey.OP_WRITE) > 0
398 && this.status == ACTIVE
399 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
400 }
401
402
403
404
405
406
407 public synchronized void inboundTransport() throws IOException {
408 updateEventMask();
409 }
410
411
412
413
414
415
416 public synchronized void outboundTransport() throws IOException {
417 sendEncryptedData();
418 doHandshake();
419 updateEventMask();
420 }
421
422
423
424
425 public synchronized boolean isInboundDone() {
426 return this.sslEngine.isInboundDone();
427 }
428
429
430
431
432 public synchronized boolean isOutboundDone() {
433 return this.sslEngine.isOutboundDone();
434 }
435
436 private synchronized int writePlain(final ByteBuffer src) throws SSLException {
437 Args.notNull(src, "Byte buffer");
438 if (this.status != ACTIVE) {
439 return -1;
440 }
441 if (this.outPlain.position() > 0) {
442 this.outPlain.flip();
443 doWrap(this.outPlain, this.outEncrypted);
444 this.outPlain.compact();
445 }
446 if (this.outPlain.position() == 0) {
447 final SSLEngineResult result = doWrap(src, this.outEncrypted);
448 if (result.getStatus() == Status.CLOSED) {
449 this.status = CLOSED;
450 }
451 return result.bytesConsumed();
452 } else {
453 return 0;
454 }
455 }
456
457 private synchronized int readPlain(final ByteBuffer dst) {
458 Args.notNull(dst, "Byte buffer");
459 if (this.inPlain.position() > 0) {
460 this.inPlain.flip();
461 final int n = Math.min(this.inPlain.remaining(), dst.remaining());
462 for (int i = 0; i < n; i++) {
463 dst.put(this.inPlain.get());
464 }
465 this.inPlain.compact();
466 return n;
467 } else {
468 if (this.endOfStream) {
469 return -1;
470 } else {
471 return 0;
472 }
473 }
474 }
475
476 public synchronized void close() {
477 if (this.status >= CLOSING) {
478 return;
479 }
480 this.status = CLOSING;
481 this.sslEngine.closeOutbound();
482 updateEventMask();
483 }
484
485 public synchronized void shutdown() {
486 if (this.status == CLOSED) {
487 return;
488 }
489 this.status = CLOSED;
490 this.session.shutdown();
491 }
492
493 public int getStatus() {
494 return this.status;
495 }
496
497 public boolean isClosed() {
498 return this.status >= CLOSING || this.session.isClosed();
499 }
500
501 public ByteChannel channel() {
502 return this.channel;
503 }
504
505 public SocketAddress getLocalAddress() {
506 return this.session.getLocalAddress();
507 }
508
509 public SocketAddress getRemoteAddress() {
510 return this.session.getRemoteAddress();
511 }
512
513 public synchronized int getEventMask() {
514 return this.appEventMask;
515 }
516
517 public synchronized void setEventMask(final int ops) {
518 this.appEventMask = ops;
519 updateEventMask();
520 }
521
522 public synchronized void setEvent(final int op) {
523 this.appEventMask = this.appEventMask | op;
524 updateEventMask();
525 }
526
527 public synchronized void clearEvent(final int op) {
528 this.appEventMask = this.appEventMask & ~op;
529 updateEventMask();
530 }
531
532 public int getSocketTimeout() {
533 return this.session.getSocketTimeout();
534 }
535
536 public void setSocketTimeout(final int timeout) {
537 this.session.setSocketTimeout(timeout);
538 }
539
540 public synchronized boolean hasBufferedInput() {
541 return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
542 || this.inEncrypted.position() > 0
543 || this.inPlain.position() > 0;
544 }
545
546 public synchronized boolean hasBufferedOutput() {
547 return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedOutput())
548 || this.outEncrypted.position() > 0
549 || this.outPlain.position() > 0;
550 }
551
552 public synchronized void setBufferStatus(final SessionBufferStatus status) {
553 this.appBufferStatus = status;
554 }
555
556 public Object getAttribute(final String name) {
557 return this.session.getAttribute(name);
558 }
559
560 public Object removeAttribute(final String name) {
561 return this.session.removeAttribute(name);
562 }
563
564 public void setAttribute(final String name, final Object obj) {
565 this.session.setAttribute(name, obj);
566 }
567
568 private static void formatOps(final StringBuilder buffer, int ops) {
569 if ((ops & SelectionKey.OP_READ) > 0) {
570 buffer.append('r');
571 }
572 if ((ops & SelectionKey.OP_WRITE) > 0) {
573 buffer.append('w');
574 }
575 }
576
577 @Override
578 public String toString() {
579 final StringBuilder buffer = new StringBuilder();
580 buffer.append(this.session);
581 buffer.append("[");
582 switch (this.status) {
583 case ACTIVE:
584 buffer.append("ACTIVE");
585 break;
586 case CLOSING:
587 buffer.append("CLOSING");
588 break;
589 case CLOSED:
590 buffer.append("CLOSED");
591 break;
592 }
593 buffer.append("][");
594 formatOps(buffer, this.appEventMask);
595 buffer.append("][");
596 buffer.append(this.sslEngine.getHandshakeStatus());
597 if (this.sslEngine.isInboundDone()) {
598 buffer.append("][inbound done][");
599 }
600 if (this.sslEngine.isOutboundDone()) {
601 buffer.append("][outbound done][");
602 }
603 if (this.endOfStream) {
604 buffer.append("][EOF][");
605 }
606 buffer.append("][");
607 buffer.append(this.inEncrypted.position());
608 buffer.append("][");
609 buffer.append(this.inPlain.position());
610 buffer.append("][");
611 buffer.append(this.outEncrypted.position());
612 buffer.append("][");
613 buffer.append(this.outPlain.position());
614 buffer.append("]");
615 return buffer.toString();
616 }
617
618 public Socket getSocket(){
619 if (this.session instanceof SocketAccessor){
620 return ((SocketAccessor) this.session).getSocket();
621 } else {
622 return null;
623 }
624 }
625
626 private class InternalByteChannel implements ByteChannel {
627
628 public int write(final ByteBuffer src) throws IOException {
629 return SSLIOSession.this.writePlain(src);
630 }
631
632 public int read(final ByteBuffer dst) throws IOException {
633 return SSLIOSession.this.readPlain(dst);
634 }
635
636 public void close() throws IOException {
637 SSLIOSession.this.close();
638 }
639
640 public boolean isOpen() {
641 return !SSLIOSession.this.isClosed();
642 }
643
644 }
645
646 }