|
19 | 19 |
|
20 | 20 | package com.cloud.utils.nio; |
21 | 21 |
|
22 | | -import com.cloud.utils.PropertiesUtil; |
23 | | -import com.cloud.utils.db.DbProperties; |
24 | | -import org.apache.cloudstack.utils.security.SSLUtils; |
25 | | -import org.apache.log4j.Logger; |
26 | | - |
27 | | -import javax.net.ssl.KeyManagerFactory; |
28 | | -import javax.net.ssl.SSLContext; |
29 | | -import javax.net.ssl.SSLEngine; |
30 | | -import javax.net.ssl.SSLEngineResult; |
31 | | -import javax.net.ssl.SSLEngineResult.HandshakeStatus; |
32 | | -import javax.net.ssl.SSLException; |
33 | | -import javax.net.ssl.SSLSession; |
34 | | -import javax.net.ssl.TrustManager; |
35 | | -import javax.net.ssl.TrustManagerFactory; |
36 | 22 | import java.io.File; |
37 | 23 | import java.io.FileInputStream; |
38 | 24 | import java.io.IOException; |
39 | 25 | import java.io.InputStream; |
40 | 26 | import java.net.InetSocketAddress; |
| 27 | +import java.net.SocketTimeoutException; |
41 | 28 | import java.nio.ByteBuffer; |
| 29 | +import java.nio.channels.Channels; |
42 | 30 | import java.nio.channels.ClosedChannelException; |
| 31 | +import java.nio.channels.ReadableByteChannel; |
43 | 32 | import java.nio.channels.SelectionKey; |
44 | 33 | import java.nio.channels.SocketChannel; |
45 | 34 | import java.security.GeneralSecurityException; |
46 | 35 | import java.security.KeyStore; |
47 | 36 | import java.util.concurrent.ConcurrentLinkedQueue; |
48 | 37 |
|
| 38 | +import javax.net.ssl.KeyManagerFactory; |
| 39 | +import javax.net.ssl.SSLContext; |
| 40 | +import javax.net.ssl.SSLEngine; |
| 41 | +import javax.net.ssl.SSLEngineResult; |
| 42 | +import javax.net.ssl.SSLEngineResult.HandshakeStatus; |
| 43 | +import javax.net.ssl.SSLSession; |
| 44 | +import javax.net.ssl.TrustManager; |
| 45 | +import javax.net.ssl.TrustManagerFactory; |
| 46 | + |
| 47 | +import org.apache.cloudstack.utils.security.SSLUtils; |
| 48 | +import org.apache.log4j.Logger; |
| 49 | + |
| 50 | +import com.cloud.utils.PropertiesUtil; |
| 51 | +import com.cloud.utils.db.DbProperties; |
| 52 | + |
49 | 53 | /** |
50 | 54 | */ |
51 | 55 | public class Link { |
@@ -449,185 +453,115 @@ public static SSLContext initSSLContext(boolean isClient) throws GeneralSecurity |
449 | 453 | return sslContext; |
450 | 454 | } |
451 | 455 |
|
452 | | - public static ByteBuffer enlargeBuffer(ByteBuffer buffer, final int sessionProposedCapacity) { |
453 | | - if (buffer == null || sessionProposedCapacity < 0) { |
454 | | - return buffer; |
455 | | - } |
456 | | - if (sessionProposedCapacity > buffer.capacity()) { |
457 | | - buffer = ByteBuffer.allocate(sessionProposedCapacity); |
458 | | - } else { |
459 | | - buffer = ByteBuffer.allocate(buffer.capacity() * 2); |
| 456 | + public static void doHandshake(SocketChannel ch, SSLEngine sslEngine, boolean isClient) throws IOException { |
| 457 | + if (s_logger.isTraceEnabled()) { |
| 458 | + s_logger.trace("SSL: begin Handshake, isClient: " + isClient); |
460 | 459 | } |
461 | | - return buffer; |
462 | | - } |
463 | 460 |
|
464 | | - public static ByteBuffer handleBufferUnderflow(final SSLEngine engine, ByteBuffer buffer) { |
465 | | - if (engine == null || buffer == null) { |
466 | | - return buffer; |
467 | | - } |
468 | | - if (buffer.position() < buffer.limit()) { |
469 | | - return buffer; |
| 461 | + SSLEngineResult engResult; |
| 462 | + SSLSession sslSession = sslEngine.getSession(); |
| 463 | + HandshakeStatus hsStatus; |
| 464 | + ByteBuffer in_pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); |
| 465 | + ByteBuffer in_appBuf = ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); |
| 466 | + ByteBuffer out_pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); |
| 467 | + ByteBuffer out_appBuf = ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); |
| 468 | + int count; |
| 469 | + ch.socket().setSoTimeout(60 * 1000); |
| 470 | + InputStream inStream = ch.socket().getInputStream(); |
| 471 | + // Use readCh to make sure the timeout on reading is working |
| 472 | + ReadableByteChannel readCh = Channels.newChannel(inStream); |
| 473 | + |
| 474 | + if (isClient) { |
| 475 | + hsStatus = SSLEngineResult.HandshakeStatus.NEED_WRAP; |
| 476 | + } else { |
| 477 | + hsStatus = SSLEngineResult.HandshakeStatus.NEED_UNWRAP; |
470 | 478 | } |
471 | | - ByteBuffer replaceBuffer = enlargeBuffer(buffer, engine.getSession().getPacketBufferSize()); |
472 | | - buffer.flip(); |
473 | | - replaceBuffer.put(buffer); |
474 | | - return replaceBuffer; |
475 | | - } |
476 | 479 |
|
477 | | - private static boolean doHandshakeUnwrap(final SocketChannel socketChannel, final SSLEngine sslEngine, |
478 | | - ByteBuffer peerAppData, ByteBuffer peerNetData, final int appBufferSize) throws IOException { |
479 | | - if (socketChannel == null || sslEngine == null || peerAppData == null || peerNetData == null || appBufferSize < 0) { |
480 | | - return false; |
481 | | - } |
482 | | - if (socketChannel.read(peerNetData) < 0) { |
483 | | - if (sslEngine.isInboundDone() && sslEngine.isOutboundDone()) { |
484 | | - return false; |
485 | | - } |
486 | | - try { |
487 | | - sslEngine.closeInbound(); |
488 | | - } catch (SSLException e) { |
489 | | - s_logger.warn("This SSL engine was forced to close inbound due to end of stream."); |
| 480 | + while (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED) { |
| 481 | + if (s_logger.isTraceEnabled()) { |
| 482 | + s_logger.trace("SSL: Handshake status " + hsStatus); |
490 | 483 | } |
491 | | - sslEngine.closeOutbound(); |
492 | | - // After closeOutbound the engine will be set to WRAP state, |
493 | | - // in order to try to send a close message to the client. |
494 | | - return true; |
495 | | - } |
496 | | - peerNetData.flip(); |
497 | | - SSLEngineResult result = null; |
498 | | - try { |
499 | | - result = sslEngine.unwrap(peerNetData, peerAppData); |
500 | | - peerNetData.compact(); |
501 | | - } catch (SSLException sslException) { |
502 | | - s_logger.error("SSL error occurred while processing unwrap data: " + sslException.getMessage()); |
503 | | - sslEngine.closeOutbound(); |
504 | | - return true; |
505 | | - } |
506 | | - switch (result.getStatus()) { |
507 | | - case OK: |
508 | | - break; |
509 | | - case BUFFER_OVERFLOW: |
510 | | - // Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap. |
511 | | - peerAppData = enlargeBuffer(peerAppData, appBufferSize); |
512 | | - break; |
513 | | - case BUFFER_UNDERFLOW: |
514 | | - // Will occur either when no data was read from the peer or when the peerNetData buffer |
515 | | - // was too small to hold all peer's data. |
516 | | - peerNetData = handleBufferUnderflow(sslEngine, peerNetData); |
517 | | - break; |
518 | | - case CLOSED: |
519 | | - if (sslEngine.isOutboundDone()) { |
520 | | - return false; |
521 | | - } else { |
522 | | - sslEngine.closeOutbound(); |
523 | | - break; |
524 | | - } |
525 | | - default: |
526 | | - throw new IllegalStateException("Invalid SSL status: " + result.getStatus()); |
527 | | - } |
528 | | - return true; |
529 | | - } |
530 | | - |
531 | | - private static boolean doHandshakeWrap(final SocketChannel socketChannel, final SSLEngine sslEngine, |
532 | | - ByteBuffer myAppData, ByteBuffer myNetData, ByteBuffer peerNetData, |
533 | | - final int netBufferSize) throws IOException { |
534 | | - if (socketChannel == null || sslEngine == null || myNetData == null || peerNetData == null |
535 | | - || myAppData == null || netBufferSize < 0) { |
536 | | - return false; |
537 | | - } |
538 | | - myNetData.clear(); |
539 | | - SSLEngineResult result = null; |
540 | | - try { |
541 | | - result = sslEngine.wrap(myAppData, myNetData); |
542 | | - } catch (SSLException sslException) { |
543 | | - s_logger.error("SSL error occurred while processing wrap data: " + sslException.getMessage()); |
544 | | - sslEngine.closeOutbound(); |
545 | | - return true; |
546 | | - } |
547 | | - switch (result.getStatus()) { |
548 | | - case OK : |
549 | | - myNetData.flip(); |
550 | | - while (myNetData.hasRemaining()) { |
551 | | - socketChannel.write(myNetData); |
| 484 | + engResult = null; |
| 485 | + if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) { |
| 486 | + out_pkgBuf.clear(); |
| 487 | + out_appBuf.clear(); |
| 488 | + out_appBuf.put("Hello".getBytes()); |
| 489 | + engResult = sslEngine.wrap(out_appBuf, out_pkgBuf); |
| 490 | + out_pkgBuf.flip(); |
| 491 | + int remain = out_pkgBuf.limit(); |
| 492 | + while (remain != 0) { |
| 493 | + remain -= ch.write(out_pkgBuf); |
| 494 | + if (remain < 0) { |
| 495 | + throw new IOException("Too much bytes sent?"); |
| 496 | + } |
552 | 497 | } |
553 | | - break; |
554 | | - case BUFFER_OVERFLOW: |
555 | | - // Will occur if there is not enough space in myNetData buffer to write all the data |
556 | | - // that would be generated by the method wrap. Since myNetData is set to session's packet |
557 | | - // size we should not get to this point because SSLEngine is supposed to produce messages |
558 | | - // smaller or equal to that, but a general handling would be the following: |
559 | | - myNetData = enlargeBuffer(myNetData, netBufferSize); |
560 | | - break; |
561 | | - case BUFFER_UNDERFLOW: |
562 | | - throw new SSLException("Buffer underflow occurred after a wrap. We should not reach here."); |
563 | | - case CLOSED: |
564 | | - try { |
565 | | - myNetData.flip(); |
566 | | - while (myNetData.hasRemaining()) { |
567 | | - socketChannel.write(myNetData); |
| 498 | + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { |
| 499 | + in_appBuf.clear(); |
| 500 | + // One packet may contained multiply operation |
| 501 | + if (in_pkgBuf.position() == 0 || !in_pkgBuf.hasRemaining()) { |
| 502 | + in_pkgBuf.clear(); |
| 503 | + count = 0; |
| 504 | + try { |
| 505 | + count = readCh.read(in_pkgBuf); |
| 506 | + } catch (SocketTimeoutException ex) { |
| 507 | + if (s_logger.isTraceEnabled()) { |
| 508 | + s_logger.trace("Handshake reading time out! Cut the connection"); |
| 509 | + } |
| 510 | + count = -1; |
| 511 | + } |
| 512 | + if (count == -1) { |
| 513 | + throw new IOException("Connection closed with -1 on reading size."); |
568 | 514 | } |
569 | | - // At this point the handshake status will probably be NEED_UNWRAP |
570 | | - // so we make sure that peerNetData is clear to read. |
571 | | - peerNetData.clear(); |
572 | | - } catch (Exception e) { |
573 | | - s_logger.error("Failed to send server's CLOSE message due to socket channel's failure."); |
| 515 | + in_pkgBuf.flip(); |
574 | 516 | } |
575 | | - break; |
576 | | - default: |
577 | | - throw new IllegalStateException("Invalid SSL status: " + result.getStatus()); |
578 | | - } |
579 | | - return true; |
580 | | - } |
581 | | - |
582 | | - public static boolean doHandshake(final SocketChannel socketChannel, final SSLEngine sslEngine, final boolean isClient) throws IOException { |
583 | | - if (socketChannel == null || sslEngine == null) { |
584 | | - return false; |
585 | | - } |
586 | | - final int appBufferSize = sslEngine.getSession().getApplicationBufferSize(); |
587 | | - final int netBufferSize = sslEngine.getSession().getPacketBufferSize(); |
588 | | - ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize); |
589 | | - ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize); |
590 | | - ByteBuffer myNetData = ByteBuffer.allocate(netBufferSize); |
591 | | - ByteBuffer peerNetData = ByteBuffer.allocate(netBufferSize); |
592 | | - |
593 | | - final long startTimeMills = System.currentTimeMillis(); |
594 | | - |
595 | | - HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus(); |
596 | | - while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED |
597 | | - && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { |
598 | | - final long timeTaken = System.currentTimeMillis() - startTimeMills; |
599 | | - if (timeTaken > 60000L) { |
600 | | - s_logger.warn("SSL Handshake has taken more than 60s to connect to: " + socketChannel.getRemoteAddress() + |
601 | | - ". Please investigate this connection."); |
602 | | - return false; |
603 | | - } |
604 | | - switch (handshakeStatus) { |
605 | | - case NEED_UNWRAP: |
606 | | - if (!doHandshakeUnwrap(socketChannel, sslEngine, peerAppData, peerNetData, appBufferSize)) { |
607 | | - return false; |
| 517 | + engResult = sslEngine.unwrap(in_pkgBuf, in_appBuf); |
| 518 | + ByteBuffer tmp_pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); |
| 519 | + int loop_count = 0; |
| 520 | + while (engResult.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { |
| 521 | + // The client is too slow? Cut it and let it reconnect |
| 522 | + if (loop_count > 10) { |
| 523 | + throw new IOException("Too many times in SSL BUFFER_UNDERFLOW, disconnect guest."); |
608 | 524 | } |
609 | | - break; |
610 | | - case NEED_WRAP: |
611 | | - if (!doHandshakeWrap(socketChannel, sslEngine, myAppData, myNetData, peerNetData, netBufferSize)) { |
612 | | - return false; |
| 525 | + // We need more packets to complete this operation |
| 526 | + if (s_logger.isTraceEnabled()) { |
| 527 | + s_logger.trace("SSL: Buffer underflowed, getting more packets"); |
613 | 528 | } |
614 | | - break; |
615 | | - case NEED_TASK: |
616 | | - Runnable task; |
617 | | - while ((task = sslEngine.getDelegatedTask()) != null) { |
618 | | - new Thread(task).run(); |
| 529 | + tmp_pkgBuf.clear(); |
| 530 | + count = ch.read(tmp_pkgBuf); |
| 531 | + if (count == -1) { |
| 532 | + throw new IOException("Connection closed with -1 on reading size."); |
619 | 533 | } |
620 | | - break; |
621 | | - case FINISHED: |
622 | | - break; |
623 | | - case NOT_HANDSHAKING: |
624 | | - break; |
625 | | - default: |
626 | | - throw new IllegalStateException("Invalid SSL status: " + handshakeStatus); |
| 534 | + tmp_pkgBuf.flip(); |
| 535 | + |
| 536 | + in_pkgBuf.mark(); |
| 537 | + in_pkgBuf.position(in_pkgBuf.limit()); |
| 538 | + in_pkgBuf.limit(in_pkgBuf.limit() + tmp_pkgBuf.limit()); |
| 539 | + in_pkgBuf.put(tmp_pkgBuf); |
| 540 | + in_pkgBuf.reset(); |
| 541 | + |
| 542 | + in_appBuf.clear(); |
| 543 | + engResult = sslEngine.unwrap(in_pkgBuf, in_appBuf); |
| 544 | + loop_count++; |
| 545 | + } |
| 546 | + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) { |
| 547 | + Runnable run; |
| 548 | + while ((run = sslEngine.getDelegatedTask()) != null) { |
| 549 | + if (s_logger.isTraceEnabled()) { |
| 550 | + s_logger.trace("SSL: Running delegated task!"); |
| 551 | + } |
| 552 | + run.run(); |
| 553 | + } |
| 554 | + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { |
| 555 | + throw new IOException("NOT a handshaking!"); |
| 556 | + } |
| 557 | + if (engResult != null && engResult.getStatus() != SSLEngineResult.Status.OK) { |
| 558 | + throw new IOException("Fail to handshake! " + engResult.getStatus()); |
627 | 559 | } |
628 | | - handshakeStatus = sslEngine.getHandshakeStatus(); |
| 560 | + if (engResult != null) |
| 561 | + hsStatus = engResult.getHandshakeStatus(); |
| 562 | + else |
| 563 | + hsStatus = sslEngine.getHandshakeStatus(); |
629 | 564 | } |
630 | | - return true; |
631 | 565 | } |
632 | 566 |
|
633 | 567 | } |
0 commit comments