| /* SSLSocketImpl.java -- implementation of an SSL client socket. |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is a part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or (at |
| your option) any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 |
| USA |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.javax.net.ssl.provider; |
| |
| import gnu.classpath.debug.Component; |
| import gnu.classpath.debug.SystemLogger; |
| |
| import java.io.DataInputStream; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.InetAddress; |
| import java.net.Socket; |
| import java.net.SocketAddress; |
| import java.net.SocketException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.SocketChannel; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| import javax.net.ssl.HandshakeCompletedEvent; |
| import javax.net.ssl.HandshakeCompletedListener; |
| import javax.net.ssl.SSLEngineResult; |
| import javax.net.ssl.SSLException; |
| import javax.net.ssl.SSLSession; |
| import javax.net.ssl.SSLSocket; |
| import javax.net.ssl.SSLEngineResult.HandshakeStatus; |
| import javax.net.ssl.SSLEngineResult.Status; |
| |
| /** |
| * @author Casey Marshall (csm@gnu.org) |
| */ |
| public class SSLSocketImpl extends SSLSocket |
| { |
| private class SocketOutputStream extends OutputStream |
| { |
| private final ByteBuffer buffer; |
| private final OutputStream out; |
| |
| SocketOutputStream() throws IOException |
| { |
| buffer = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); |
| if (underlyingSocket != null) |
| out = underlyingSocket.getOutputStream(); |
| else |
| out = SSLSocketImpl.super.getOutputStream(); |
| } |
| |
| @Override public void write(byte[] buf, int off, int len) throws IOException |
| { |
| if (!initialHandshakeDone |
| || engine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) |
| { |
| doHandshake(); |
| if (handshakeException != null) |
| throw handshakeException; |
| } |
| |
| int k = 0; |
| while (k < len) |
| { |
| synchronized (engine) |
| { |
| int l = Math.min(len-k, getSession().getApplicationBufferSize()); |
| ByteBuffer in = ByteBuffer.wrap(buf, off+k, l); |
| SSLEngineResult result = engine.wrap(in, buffer); |
| if (result.getStatus() == Status.CLOSED) |
| return; |
| if (result.getStatus() != Status.OK) |
| throw new SSLException("unexpected SSL state " + result.getStatus()); |
| buffer.flip(); |
| out.write(buffer.array(), 0, buffer.limit()); |
| k += result.bytesConsumed(); |
| buffer.clear(); |
| } |
| } |
| } |
| |
| @Override public void write(int b) throws IOException |
| { |
| write(new byte[] { (byte) b }); |
| } |
| |
| @Override public void close() throws IOException |
| { |
| SSLSocketImpl.this.close(); |
| } |
| } |
| |
| private class SocketInputStream extends InputStream |
| { |
| private final ByteBuffer inBuffer; |
| private final ByteBuffer appBuffer; |
| private final DataInputStream in; |
| |
| SocketInputStream() throws IOException |
| { |
| inBuffer = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); |
| inBuffer.limit(0); |
| appBuffer = ByteBuffer.allocate(getSession().getApplicationBufferSize()); |
| appBuffer.flip(); |
| if (underlyingSocket != null) |
| in = new DataInputStream(underlyingSocket.getInputStream()); |
| else |
| in = new DataInputStream(SSLSocketImpl.super.getInputStream()); |
| } |
| |
| @Override public int read(byte[] buf, int off, int len) throws IOException |
| { |
| if (!initialHandshakeDone || |
| engine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) |
| { |
| doHandshake(); |
| if (handshakeException != null) |
| throw handshakeException; |
| } |
| |
| if (!appBuffer.hasRemaining()) |
| { |
| int x = in.read(); |
| if (x == -1) |
| return -1; |
| inBuffer.clear(); |
| inBuffer.put((byte) x); |
| inBuffer.putInt(in.readInt()); |
| int reclen = inBuffer.getShort(3) & 0xFFFF; |
| in.readFully(inBuffer.array(), 5, reclen); |
| inBuffer.position(0).limit(reclen + 5); |
| synchronized (engine) |
| { |
| appBuffer.clear(); |
| SSLEngineResult result = engine.unwrap(inBuffer, appBuffer); |
| Status status = result.getStatus(); |
| if (status == Status.CLOSED && result.bytesProduced() == 0) |
| return -1; |
| } |
| inBuffer.compact(); |
| appBuffer.flip(); |
| } |
| int l = Math.min(len, appBuffer.remaining()); |
| appBuffer.get(buf, off, l); |
| return l; |
| } |
| |
| @Override public int read() throws IOException |
| { |
| byte[] b = new byte[1]; |
| if (read(b) == -1) |
| return -1; |
| return b[0] & 0xFF; |
| } |
| } |
| |
| private static final SystemLogger logger = SystemLogger.getSystemLogger(); |
| |
| private SSLEngineImpl engine; |
| private Set<HandshakeCompletedListener> listeners; |
| private Socket underlyingSocket; |
| private boolean isHandshaking; |
| private IOException handshakeException; |
| private boolean initialHandshakeDone = false; |
| private final boolean autoClose; |
| |
| public SSLSocketImpl(SSLContextImpl contextImpl, String host, int port) |
| { |
| this(contextImpl, host, port, new Socket(), true); |
| } |
| |
| public SSLSocketImpl(SSLContextImpl contextImpl, String host, int port, |
| Socket underlyingSocket, boolean autoClose) |
| { |
| engine = new SSLEngineImpl(contextImpl, host, port); |
| engine.setUseClientMode(true); // default to client mode |
| listeners = new HashSet<HandshakeCompletedListener>(); |
| this.underlyingSocket = underlyingSocket; |
| this.autoClose = autoClose; |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#addHandshakeCompletedListener(javax.net.ssl.HandshakeCompletedListener) |
| */ |
| @Override |
| public void addHandshakeCompletedListener(HandshakeCompletedListener listener) |
| { |
| listeners.add(listener); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getEnableSessionCreation() |
| */ |
| @Override public boolean getEnableSessionCreation() |
| { |
| return engine.getEnableSessionCreation(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getEnabledCipherSuites() |
| */ |
| @Override public String[] getEnabledCipherSuites() |
| { |
| return engine.getEnabledCipherSuites(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getEnabledProtocols() |
| */ |
| @Override public String[] getEnabledProtocols() |
| { |
| return engine.getEnabledProtocols(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getNeedClientAuth() |
| */ |
| @Override public boolean getNeedClientAuth() |
| { |
| return engine.getNeedClientAuth(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getSession() |
| */ |
| @Override public SSLSession getSession() |
| { |
| return engine.getSession(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getSupportedCipherSuites() |
| */ |
| @Override public String[] getSupportedCipherSuites() |
| { |
| return engine.getSupportedCipherSuites(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getSupportedProtocols() |
| */ |
| @Override public String[] getSupportedProtocols() |
| { |
| return engine.getSupportedProtocols(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getUseClientMode() |
| */ |
| @Override public boolean getUseClientMode() |
| { |
| return engine.getUseClientMode(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#getWantClientAuth() |
| */ |
| @Override public boolean getWantClientAuth() |
| { |
| return engine.getWantClientAuth(); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#removeHandshakeCompletedListener(javax.net.ssl.HandshakeCompletedListener) |
| */ |
| @Override |
| public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) |
| { |
| listeners.remove(listener); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#setEnableSessionCreation(boolean) |
| */ |
| @Override public void setEnableSessionCreation(boolean enable) |
| { |
| engine.setEnableSessionCreation(enable); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#setEnabledCipherSuites(java.lang.String[]) |
| */ |
| @Override public void setEnabledCipherSuites(String[] suites) |
| { |
| engine.setEnabledCipherSuites(suites); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#setEnabledProtocols(java.lang.String[]) |
| */ |
| @Override public void setEnabledProtocols(String[] protocols) |
| { |
| engine.setEnabledProtocols(protocols); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#setNeedClientAuth(boolean) |
| */ |
| @Override public void setNeedClientAuth(boolean needAuth) |
| { |
| engine.setNeedClientAuth(needAuth); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#setUseClientMode(boolean) |
| */ |
| @Override public void setUseClientMode(boolean clientMode) |
| { |
| engine.setUseClientMode(clientMode); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#setWantClientAuth(boolean) |
| */ |
| @Override public void setWantClientAuth(boolean wantAuth) |
| { |
| engine.setWantClientAuth(wantAuth); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.SSLSocket#startHandshake() |
| */ |
| @Override public void startHandshake() throws IOException |
| { |
| if (isHandshaking) |
| return; |
| |
| if (handshakeException != null) |
| throw handshakeException; |
| |
| Thread t = new Thread(new Runnable() |
| { |
| public void run() |
| { |
| try |
| { |
| doHandshake(); |
| } |
| catch (IOException ioe) |
| { |
| handshakeException = ioe; |
| } |
| } |
| }, "HandshakeThread@" + System.identityHashCode(this)); |
| t.start(); |
| } |
| |
| void doHandshake() throws IOException |
| { |
| synchronized (engine) |
| { |
| if (isHandshaking) |
| { |
| try |
| { |
| engine.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| } |
| return; |
| } |
| isHandshaking = true; |
| } |
| |
| if (initialHandshakeDone) |
| throw new SSLException("rehandshaking not yet implemented"); |
| |
| long now = -System.currentTimeMillis(); |
| engine.beginHandshake(); |
| |
| HandshakeStatus status = engine.getHandshakeStatus(); |
| assert(status != HandshakeStatus.NOT_HANDSHAKING); |
| |
| ByteBuffer inBuffer = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); |
| inBuffer.position(inBuffer.limit()); |
| ByteBuffer outBuffer = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); |
| ByteBuffer emptyBuffer = ByteBuffer.allocate(0); |
| SSLEngineResult result = null; |
| |
| DataInputStream sockIn = new DataInputStream(underlyingSocket.getInputStream()); |
| OutputStream sockOut = underlyingSocket.getOutputStream(); |
| |
| try |
| { |
| while (status != HandshakeStatus.NOT_HANDSHAKING |
| && status != HandshakeStatus.FINISHED) |
| { |
| logger.logv(Component.SSL_HANDSHAKE, "socket processing state {0}", |
| status); |
| |
| if (inBuffer.capacity() != getSession().getPacketBufferSize()) |
| { |
| ByteBuffer b |
| = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); |
| if (inBuffer.hasRemaining()) |
| b.put(inBuffer).flip(); |
| inBuffer = b; |
| } |
| if (outBuffer.capacity() != getSession().getPacketBufferSize()) |
| outBuffer |
| = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); |
| |
| switch (status) |
| { |
| case NEED_UNWRAP: |
| // Read in a single SSL record. |
| inBuffer.clear(); |
| int i = sockIn.read(); |
| if (i == -1) |
| throw new EOFException(); |
| if ((i & 0x80) == 0x80) // SSLv2 client hello. |
| { |
| inBuffer.put((byte) i); |
| int v2len = (i & 0x7f) << 8; |
| i = sockIn.read(); |
| v2len = v2len | (i & 0xff); |
| inBuffer.put((byte) i); |
| sockIn.readFully(inBuffer.array(), 2, v2len); |
| inBuffer.position(0).limit(v2len + 2); |
| } |
| else |
| { |
| inBuffer.put((byte) i); |
| inBuffer.putInt(sockIn.readInt()); |
| int reclen = inBuffer.getShort(3) & 0xFFFF; |
| sockIn.readFully(inBuffer.array(), 5, reclen); |
| inBuffer.position(0).limit(reclen + 5); |
| } |
| result = engine.unwrap(inBuffer, emptyBuffer); |
| status = result.getHandshakeStatus(); |
| if (result.getStatus() != Status.OK) |
| throw new SSLException("unexpected SSL status " |
| + result.getStatus()); |
| break; |
| |
| case NEED_WRAP: |
| { |
| outBuffer.clear(); |
| result = engine.wrap(emptyBuffer, outBuffer); |
| status = result.getHandshakeStatus(); |
| if (result.getStatus() != Status.OK) |
| throw new SSLException("unexpected SSL status " |
| + result.getStatus()); |
| outBuffer.flip(); |
| sockOut.write(outBuffer.array(), outBuffer.position(), |
| outBuffer.limit()); |
| } |
| break; |
| |
| case NEED_TASK: |
| { |
| Runnable task; |
| while ((task = engine.getDelegatedTask()) != null) |
| task.run(); |
| status = engine.getHandshakeStatus(); |
| } |
| break; |
| |
| case FINISHED: |
| break; |
| } |
| } |
| |
| initialHandshakeDone = true; |
| |
| HandshakeCompletedEvent hce = new HandshakeCompletedEvent(this, getSession()); |
| for (HandshakeCompletedListener l : listeners) |
| { |
| try |
| { |
| l.handshakeCompleted(hce); |
| } |
| catch (ThreadDeath td) |
| { |
| throw td; |
| } |
| catch (Throwable x) |
| { |
| logger.log(Component.WARNING, |
| "HandshakeCompletedListener threw exception", x); |
| } |
| } |
| |
| now += System.currentTimeMillis(); |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_HANDSHAKE, |
| "handshake completed in {0}ms in thread {1}", now, |
| Thread.currentThread().getName()); |
| } |
| catch (SSLException ssle) |
| { |
| handshakeException = ssle; |
| throw ssle; |
| } |
| finally |
| { |
| synchronized (engine) |
| { |
| isHandshaking = false; |
| engine.notifyAll(); |
| } |
| } |
| } |
| |
| // Methods overriding Socket. |
| |
| @Override public void bind(SocketAddress bindpoint) throws IOException |
| { |
| underlyingSocket.bind(bindpoint); |
| } |
| |
| @Override public void connect(SocketAddress endpoint) throws IOException |
| { |
| underlyingSocket.connect(endpoint); |
| } |
| |
| @Override public void connect(SocketAddress endpoint, int timeout) |
| throws IOException |
| { |
| underlyingSocket.connect(endpoint, timeout); |
| } |
| |
| @Override public InetAddress getInetAddress() |
| { |
| return underlyingSocket.getInetAddress(); |
| } |
| |
| @Override public InetAddress getLocalAddress() |
| { |
| return underlyingSocket.getLocalAddress(); |
| } |
| |
| @Override public int getPort() |
| { |
| return underlyingSocket.getPort(); |
| } |
| |
| @Override public int getLocalPort() |
| { |
| return underlyingSocket.getLocalPort(); |
| } |
| |
| @Override public SocketAddress getRemoteSocketAddress() |
| { |
| return underlyingSocket.getRemoteSocketAddress(); |
| } |
| |
| public SocketAddress getLocalSocketAddress() |
| { |
| return underlyingSocket.getLocalSocketAddress(); |
| } |
| |
| @Override public SocketChannel getChannel() |
| { |
| throw new UnsupportedOperationException("use javax.net.ssl.SSLEngine for NIO"); |
| } |
| |
| @Override public InputStream getInputStream() throws IOException |
| { |
| return new SocketInputStream(); |
| } |
| |
| @Override public OutputStream getOutputStream() throws IOException |
| { |
| return new SocketOutputStream(); |
| } |
| |
| @Override public void setTcpNoDelay(boolean on) throws SocketException |
| { |
| underlyingSocket.setTcpNoDelay(on); |
| } |
| |
| @Override public boolean getTcpNoDelay() throws SocketException |
| { |
| return underlyingSocket.getTcpNoDelay(); |
| } |
| |
| @Override public void setSoLinger(boolean on, int linger) throws SocketException |
| { |
| underlyingSocket.setSoLinger(on, linger); |
| } |
| |
| public int getSoLinger() throws SocketException |
| { |
| return underlyingSocket.getSoLinger(); |
| } |
| |
| @Override public void sendUrgentData(int x) throws IOException |
| { |
| throw new UnsupportedOperationException("not supported"); |
| } |
| |
| @Override public void setOOBInline(boolean on) throws SocketException |
| { |
| underlyingSocket.setOOBInline(on); |
| } |
| |
| @Override public boolean getOOBInline() throws SocketException |
| { |
| return underlyingSocket.getOOBInline(); |
| } |
| |
| @Override public void setSoTimeout(int timeout) throws SocketException |
| { |
| underlyingSocket.setSoTimeout(timeout); |
| } |
| |
| @Override public int getSoTimeout() throws SocketException |
| { |
| return underlyingSocket.getSoTimeout(); |
| } |
| |
| @Override public void setSendBufferSize(int size) throws SocketException |
| { |
| underlyingSocket.setSendBufferSize(size); |
| } |
| |
| @Override public int getSendBufferSize() throws SocketException |
| { |
| return underlyingSocket.getSendBufferSize(); |
| } |
| |
| @Override public void setReceiveBufferSize(int size) throws SocketException |
| { |
| underlyingSocket.setReceiveBufferSize(size); |
| } |
| |
| @Override public int getReceiveBufferSize() throws SocketException |
| { |
| return underlyingSocket.getReceiveBufferSize(); |
| } |
| |
| @Override public void setKeepAlive(boolean on) throws SocketException |
| { |
| underlyingSocket.setKeepAlive(on); |
| } |
| |
| @Override public boolean getKeepAlive() throws SocketException |
| { |
| return underlyingSocket.getKeepAlive(); |
| } |
| |
| @Override public void setTrafficClass(int tc) throws SocketException |
| { |
| underlyingSocket.setTrafficClass(tc); |
| } |
| |
| @Override public int getTrafficClass() throws SocketException |
| { |
| return underlyingSocket.getTrafficClass(); |
| } |
| |
| @Override public void setReuseAddress(boolean reuseAddress) |
| throws SocketException |
| { |
| underlyingSocket.setReuseAddress(reuseAddress); |
| } |
| |
| @Override public boolean getReuseAddress() throws SocketException |
| { |
| return underlyingSocket.getReuseAddress(); |
| } |
| |
| @Override public void close() throws IOException |
| { |
| // XXX closure alerts. |
| if (autoClose) |
| underlyingSocket.close(); |
| } |
| |
| @Override public void shutdownInput() throws IOException |
| { |
| underlyingSocket.shutdownInput(); |
| } |
| |
| @Override public void shutdownOutput() throws IOException |
| { |
| underlyingSocket.shutdownOutput(); |
| } |
| |
| @Override public boolean isConnected() |
| { |
| return underlyingSocket.isConnected(); |
| } |
| |
| @Override public boolean isBound() |
| { |
| return underlyingSocket.isBound(); |
| } |
| |
| @Override public boolean isClosed() |
| { |
| return underlyingSocket.isClosed(); |
| } |
| |
| @Override public boolean isInputShutdown() |
| { |
| return underlyingSocket.isInputShutdown(); |
| } |
| |
| @Override public boolean isOutputShutdown() |
| { |
| return underlyingSocket.isOutputShutdown(); |
| } |
| } |