// ********************************************************************** // // Copyright (c) 2003-2010 ZeroC, Inc. All rights reserved. // // This copy of Ice is licensed to you under the terms described in the // ICE_LICENSE file included in this distribution. // // ********************************************************************** package IceSSL; import java.nio.*; import javax.net.ssl.*; import javax.net.ssl.SSLEngineResult.*; final class TransceiverI implements IceInternal.Transceiver { public java.nio.channels.SelectableChannel fd() { assert(_fd != null); return _fd; } public int initialize() { try { if(_state == StateNeedConnect) { _state = StateConnectPending; return IceInternal.SocketOperation.Connect; } else if(_state <= StateConnectPending) { IceInternal.Network.doFinishConnect(_fd); _state = StateConnected; _desc = IceInternal.Network.fdToString(_fd); } assert(_state == StateConnected); int status = handshakeNonBlocking(); if(status != IceInternal.SocketOperation.None) { return status; } } catch(Ice.LocalException ex) { if(_instance.networkTraceLevel() >= 2) { String s = "failed to establish ssl connection\n" + _desc + "\n" + ex; _logger.trace(_instance.networkTraceCategory(), s); } throw ex; } return IceInternal.SocketOperation.None; } public void close() { if(_state == StateHandshakeComplete && _instance.networkTraceLevel() >= 1) { String s = "closing ssl connection\n" + toString(); _logger.trace(_instance.networkTraceCategory(), s); } assert(_fd != null); if(_state >= StateConnected) { try { // // Send the close_notify message. // _engine.closeOutbound(); _netOutput.clear(); while(!_engine.isOutboundDone()) { _engine.wrap(_emptyBuffer, _netOutput); try { // // Note: we can't block to send the close_notify message. In some cases, the // close_notify message might therefore not be receieved by the peer. This is // not a big issue since the Ice protocol isn't subject to truncation attacks. // flushNonBlocking(); } catch(Ice.LocalException ex) { // Ignore. } } } catch(SSLException ex) { // // We can't throw in close. // // Ice.SecurityException se = new Ice.SecurityException(); // se.reason = "IceSSL: SSL failure while shutting down socket"; // se.initCause(ex); // throw se; } try { _engine.closeInbound(); } catch(SSLException ex) { // // SSLEngine always raises an exception with this message: // // Inbound closed before receiving peer's close_notify: possible truncation attack? // // We would probably need to wait for a response in shutdown() to avoid this. // For now, we'll ignore this exception. // //_logger.error("IceSSL: error during close\n" + ex.getMessage()); } } try { IceInternal.Network.closeSocket(_fd); } finally { _fd = null; } } public boolean write(IceInternal.Buffer buf) { // // If the handshake isn't completed yet, we shouldn't be writing. // if(_state < StateHandshakeComplete) { throw new Ice.ConnectionLostException(); } int status = writeNonBlocking(buf.b); if(status != IceInternal.SocketOperation.None) { assert(status == IceInternal.SocketOperation.Write); return false; } return true; } public boolean read(IceInternal.Buffer buf, Ice.BooleanHolder moreData) { // // If the handshake isn't completed yet, we shouldn't be reading (read can be // called by the thread pool when the connection is registered/unregistered // with the pool to be closed). // if(_state < StateHandshakeComplete) { throw new Ice.ConnectionLostException(); } int rem = 0; if(_instance.networkTraceLevel() >= 3) { rem = buf.b.remaining(); } // // Try to satisfy the request from data we've already decrypted. // int pos = buf.b.position(); fill(buf.b); if(_instance.networkTraceLevel() >= 3 && buf.b.position() > pos) { String s = "received " + (buf.b.position() - pos) + " of " + rem + " bytes via ssl\n" + toString(); _logger.trace(_instance.networkTraceCategory(), s); } if(_stats != null && buf.b.position() > pos) { _stats.bytesReceived(type(), buf.b.position() - pos); } // // Read and decrypt more data if necessary. Note that we might read // more data from the socket than is actually necessary to fill the // caller's stream. // try { while(buf.b.hasRemaining()) { _netInput.flip(); SSLEngineResult result = _engine.unwrap(_netInput, _appInput); _netInput.compact(); switch(result.getStatus()) { case BUFFER_OVERFLOW: { assert(false); break; } case BUFFER_UNDERFLOW: { int status = readNonBlocking(); if(status != IceInternal.SocketOperation.None) { assert(status == IceInternal.SocketOperation.Read); moreData.value = false; return false; } continue; } case CLOSED: { throw new Ice.ConnectionLostException(); } case OK: { break; } } pos = buf.b.position(); fill(buf.b); if(_instance.networkTraceLevel() >= 3 && buf.b.position() > pos) { String s = "received " + (buf.b.position() - pos) + " of " + rem + " bytes via ssl\n" + toString(); _logger.trace(_instance.networkTraceCategory(), s); } if(_stats != null && buf.b.position() > pos) { _stats.bytesReceived(type(), buf.b.position() - pos); } } } catch(SSLException ex) { Ice.SecurityException e = new Ice.SecurityException(); e.reason = "IceSSL: error during read"; e.initCause(ex); throw e; } // // Return a boolean to indicate whether more data is available. // moreData.value = _netInput.position() > 0; return true; } public String type() { return "ssl"; } public String toString() { return _desc; } public Ice.ConnectionInfo getInfo() { return getNativeConnectionInfo(); } public void checkSendSize(IceInternal.Buffer buf, int messageSizeMax) { if(buf.size() > messageSizeMax) { IceInternal.Ex.throwMemoryLimitException(buf.size(), messageSizeMax); } } // // Only for use by ConnectorI, AcceptorI. // TransceiverI(Instance instance, javax.net.ssl.SSLEngine engine, java.nio.channels.SocketChannel fd, String host, boolean connected, boolean incoming, String adapterName) { _instance = instance; _engine = engine; _fd = fd; _host = host; _incoming = incoming; _adapterName = adapterName; _state = connected ? StateConnected : StateNeedConnect; _logger = instance.communicator().getLogger(); try { _stats = instance.communicator().getStats(); } catch(Ice.CommunicatorDestroyedException ex) { // Ignore. } _desc = IceInternal.Network.fdToString(_fd); _maxPacketSize = 0; if(System.getProperty("os.name").startsWith("Windows")) { // // On Windows, limiting the buffer size is important to prevent // poor throughput performances when transfering large amount of // data. See Microsoft KB article KB823764. // _maxPacketSize = IceInternal.Network.getSendBufferSize(_fd) / 2; if(_maxPacketSize < 512) { _maxPacketSize = 0; } } _appInput = ByteBuffer.allocateDirect(engine.getSession().getApplicationBufferSize() * 2); _netInput = ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize() * 2); _netOutput = ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize() * 2); } protected void finalize() throws Throwable { IceUtilInternal.Assert.FinalizerAssert(_fd == null); super.finalize(); } private NativeConnectionInfo getNativeConnectionInfo() { // // This can only be called on an open transceiver. // assert(_fd != null); NativeConnectionInfo info = new NativeConnectionInfo(); java.net.Socket socket = _fd.socket(); info.localAddress = socket.getLocalAddress().getHostAddress(); info.localPort = socket.getLocalPort(); if(socket.getInetAddress() != null) { info.remoteAddress = socket.getInetAddress().getHostAddress(); info.remotePort = socket.getPort(); } else { info.remoteAddress = ""; info.remotePort = -1; } SSLSession session = _engine.getSession(); info.cipher = session.getCipherSuite(); try { java.util.ArrayList<String> certs = new java.util.ArrayList<String>(); info.nativeCerts = session.getPeerCertificates(); for(java.security.cert.Certificate c : info.nativeCerts) { StringBuffer s = new StringBuffer("-----BEGIN CERTIFICATE-----\n"); s.append(IceUtilInternal.Base64.encode(c.getEncoded())); s.append("\n-----END CERTIFICATE-----"); certs.add(s.toString()); } info.certs = certs.toArray(new String[0]); } catch(java.security.cert.CertificateEncodingException ex) { } catch(javax.net.ssl.SSLPeerUnverifiedException ex) { // No peer certificates. } info.adapterName = _adapterName; info.incoming = _incoming; return info; } private int handshakeNonBlocking() { try { HandshakeStatus status = _engine.getHandshakeStatus(); while(_state != StateHandshakeComplete) { SSLEngineResult result = null; switch(status) { case FINISHED: handshakeCompleted(); break; case NEED_TASK: { Runnable task; while((task = _engine.getDelegatedTask()) != null) { task.run(); } status = _engine.getHandshakeStatus(); break; } case NEED_UNWRAP: { // // The engine needs more data. We might already have enough data in // the _netInput buffer to satisfy the engine. If not, the engine // responds with BUFFER_UNDERFLOW and we'll read from the socket. // _netInput.flip(); result = _engine.unwrap(_netInput, _appInput); _netInput.compact(); // // FINISHED is only returned from wrap or unwrap, not from engine.getHandshakeResult(). // status = result.getHandshakeStatus(); switch(result.getStatus()) { case BUFFER_OVERFLOW: assert(false); break; case BUFFER_UNDERFLOW: { assert(status == javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP); int ss = readNonBlocking(); if(ss != IceInternal.SocketOperation.None) { return ss; } break; } case CLOSED: throw new Ice.ConnectionLostException(); case OK: break; } break; } case NEED_WRAP: { // // The engine needs to send a message. // result = _engine.wrap(_emptyBuffer, _netOutput); if(result.bytesProduced() > 0) { int ss = flushNonBlocking(); if(ss != IceInternal.SocketOperation.None) { return ss; } } // // FINISHED is only returned from wrap or unwrap, not from engine.getHandshakeResult(). // status = result.getHandshakeStatus(); break; } case NOT_HANDSHAKING: assert(false); break; } if(result != null) { switch(result.getStatus()) { case BUFFER_OVERFLOW: assert(false); break; case BUFFER_UNDERFLOW: // Need to read again. assert(status == HandshakeStatus.NEED_UNWRAP); break; case CLOSED: throw new Ice.ConnectionLostException(); case OK: break; } } } } catch(SSLException ex) { Ice.SecurityException e = new Ice.SecurityException(); e.reason = "IceSSL: handshake error"; e.initCause(ex); throw e; } return IceInternal.SocketOperation.None; } private void handshakeCompleted() { _state = StateHandshakeComplete; // // IceSSL.VerifyPeer is translated into the proper SSLEngine configuration // for a server, but we have to do it ourselves for a client. // if(!_incoming) { int verifyPeer = _instance.communicator().getProperties().getPropertyAsIntWithDefault("IceSSL.VerifyPeer", 2); if(verifyPeer > 0) { try { _engine.getSession().getPeerCertificates(); } catch(javax.net.ssl.SSLPeerUnverifiedException ex) { Ice.SecurityException e = new Ice.SecurityException(); e.reason = "IceSSL: server did not supply a certificate"; e.initCause(ex); throw e; } } } // // Additional verification. // _instance.verifyPeer(getNativeConnectionInfo(), _fd, _host); if(_instance.networkTraceLevel() >= 1) { String s; if(_incoming) { s = "accepted ssl connection\n" + _desc; } else { s = "ssl connection established\n" + _desc; } _logger.trace(_instance.networkTraceCategory(), s); } if(_instance.securityTraceLevel() >= 1) { _instance.traceConnection(_fd, _engine, _incoming); } } private int writeNonBlocking(ByteBuffer buf) { // // This method has two purposes: encrypt the application's message buffer into our // _netOutput buffer, and write the contents of _netOutput to the socket without // blocking. // try { while(buf.hasRemaining() || _netOutput.position() > 0) { final int rem = buf.remaining(); if(rem > 0) { // // Encrypt the buffer. // SSLEngineResult result = _engine.wrap(buf, _netOutput); switch(result.getStatus()) { case BUFFER_OVERFLOW: // // Need to make room in _netOutput. // break; case BUFFER_UNDERFLOW: assert(false); break; case CLOSED: throw new Ice.ConnectionLostException(); case OK: break; } // // If the SSL engine consumed any of the application's message buffer, // then log it. // if(result.bytesConsumed() > 0) { if(_instance.networkTraceLevel() >= 3) { String s = "sent " + result.bytesConsumed() + " of " + rem + " bytes via ssl\n" + toString(); _logger.trace(_instance.networkTraceCategory(), s); } if(_stats != null) { _stats.bytesSent(type(), result.bytesConsumed()); } } } // // Write the encrypted data to the socket. We continue writing until we've written // all of _netOutput, or until flushNonBlocking indicates that it cannot write // (i.e., by returning NeedWrite). // if(_netOutput.position() > 0) { int ss = flushNonBlocking(); if(ss != IceInternal.SocketOperation.None) { assert(ss == IceInternal.SocketOperation.Write); return ss; } } } } catch(SSLException ex) { Ice.SecurityException e = new Ice.SecurityException(); e.reason = "IceSSL: error while encoding message"; e.initCause(ex); throw e; } assert(_netOutput.position() == 0); return IceInternal.SocketOperation.None; } private int flushNonBlocking() { _netOutput.flip(); final int size = _netOutput.limit(); int packetSize = size - _netOutput.position(); if(_maxPacketSize > 0 && packetSize > _maxPacketSize) { packetSize = _maxPacketSize; _netOutput.limit(_netOutput.position() + packetSize); } int status = IceInternal.SocketOperation.None; while(_netOutput.hasRemaining()) { try { assert(_fd != null); int ret = _fd.write(_netOutput); if(ret == -1) { throw new Ice.ConnectionLostException(); } else if(ret == 0) { status = IceInternal.SocketOperation.Write; break; } if(packetSize == _maxPacketSize) { assert(_netOutput.position() == _netOutput.limit()); packetSize = size - _netOutput.position(); if(packetSize > _maxPacketSize) { packetSize = _maxPacketSize; } _netOutput.limit(_netOutput.position() + packetSize); } } catch(java.io.InterruptedIOException ex) { continue; } catch(java.io.IOException ex) { Ice.ConnectionLostException se = new Ice.ConnectionLostException(); se.initCause(ex); throw se; } } if(status == IceInternal.SocketOperation.None) { _netOutput.clear(); } else { _netOutput.limit(size); _netOutput.compact(); } return status; } private int readNonBlocking() { while(true) { try { assert(_fd != null); int ret = _fd.read(_netInput); if(ret == -1) { throw new Ice.ConnectionLostException(); } else if(ret == 0) { return IceInternal.SocketOperation.Read; } break; } catch(java.io.InterruptedIOException ex) { continue; } catch(java.io.IOException ex) { Ice.ConnectionLostException se = new Ice.ConnectionLostException(); se.initCause(ex); throw se; } } return IceInternal.SocketOperation.None; } private void fill(ByteBuffer buf) { _appInput.flip(); if(_appInput.hasRemaining()) { int bytesAvailable = _appInput.remaining(); int bytesNeeded = buf.remaining(); if(bytesAvailable > bytesNeeded) { bytesAvailable = bytesNeeded; } if(buf.hasArray()) { // // Copy directly into the destination buffer's backing array. // byte[] arr = buf.array(); int offset = buf.arrayOffset() + buf.position(); _appInput.get(arr, offset, bytesAvailable); buf.position(offset + bytesAvailable); } else if(_appInput.hasArray()) { // // Copy directly from the source buffer's backing array. // byte[] arr = _appInput.array(); int offset = _appInput.arrayOffset() + _appInput.position(); buf.put(arr, offset, bytesAvailable); _appInput.position(offset + bytesAvailable); } else { // // Copy using a temporary array. // byte[] arr = new byte[bytesAvailable]; _appInput.get(arr); buf.put(arr); } } _appInput.compact(); } private Instance _instance; private java.nio.channels.SocketChannel _fd; private javax.net.ssl.SSLEngine _engine; private String _host; private boolean _incoming; private String _adapterName; private int _state; private Ice.Logger _logger; private Ice.Stats _stats; private String _desc; private int _maxPacketSize; private ByteBuffer _appInput; // Holds clear-text data to be read by the application. private ByteBuffer _netInput; // Holds encrypted data read from the socket. private ByteBuffer _netOutput; // Holds encrypted data to be written to the socket. private static ByteBuffer _emptyBuffer = ByteBuffer.allocate(0); // Used during handshaking. private static final int StateNeedConnect = 0; private static final int StateConnectPending = 1; private static final int StateConnected = 2; private static final int StateHandshakeComplete = 3; }