如何将Java nio SSLBytechannel更改为ByteChannel

时间:2013-06-24 19:29:18

标签: java ssl nio

您可以使用以下代码将bytechannel升级为SSL。

有谁知道如何恢复这个过程?我希望能够升级或降级java nio bytechannel,或者在不关闭套接字的情况下更改通道。

使用以下代码立即写入以升级字节通道。我请你专业化创建一个反向功能。请。

//呼叫

ByteChannel sslbytechannel = upgradeChannel2ServerSSLChannel(sourcebytechannel);


//function

    private ByteChannel upgradeChannel2ServerSSLChannel(ByteChannel channel) {
        try {
            if (log.isLoggable(Level.FINE)) {
                log.fine("Switching socket to SSL");
            }

            KeyStore ks = KeyStore.getInstance("JKS");
            File kf = new File(getExproxy().getKeystoreFilename()); 
            ks.load(new FileInputStream(kf), getExproxy().getKeystorePassword());

            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, getExproxy().getKeystoreKeysPassword());

            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(ks);

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

            SSLEngine engine = sslContext.createSSLEngine();
            engine.setUseClientMode(false);
            engine.beginHandshake();

            return new SSLByteChannel(channel, engine);
        } catch(Exception e) {
            log.log(Level.SEVERE, "Exception during server SSL channel upgrade", e);
        }
        return null;
    }

//Class

    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.ByteChannel;
    import java.nio.channels.ClosedChannelException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLEngineResult;
    import javax.net.ssl.SSLException;
    import java

x.net.ssl.SSLSession;

/**
 * Upgrade a ByteChannel for SSL.
 *
 * <p>
 * Change Log:
 * </p>
 * <ul>
 *  <li>v1.0.1 - Dead lock bug fix, take into account EOF during read and unwrap.</li>
 *  <li>v1.0.0 - First public release.</li>
 * </ul>
 *
 * <p>
 * This source code is given to the Public Domain. Do what you want with it.
 * This software comes with no guarantees or warranties.
 * Please visit <a href="http://perso.wanadoo.fr/reuse/sslbytechannel/">http://perso.wanadoo.fr/reuse/sslbytechannel/</a>
 * periodically to check for updates or to contribute improvements.
 * </p>
 *
 * @author David Crosson
 * @author david.crosson@wanadoo.fr
 * @version 1.0.0
 */
public class SSLByteChannel implements ByteChannel {
    private ByteChannel wrappedChannel;
    private boolean closed = false;
    private SSLEngine engine;

    private final ByteBuffer inAppData;
    private final ByteBuffer outAppData;

    private final ByteBuffer inNetData;
    private final ByteBuffer outNetData;

    private final Logger log = Logger.getLogger(getClass().getName());


    /**
     * Creates a new instance of SSLByteChannel
     * @param wrappedChannel The byte channel on which this ssl channel is built. 
     * This channel contains encrypted data.
     * @param engine A SSLEngine instance that will remember SSL current
     * context. Warning, such an instance CAN NOT be shared
     * between multiple SSLByteChannel.
     */
    public SSLByteChannel(ByteChannel wrappedChannel, SSLEngine engine) {
        this.wrappedChannel = wrappedChannel;
        this.engine = engine;

        SSLSession session = engine.getSession();
        inAppData  = ByteBuffer.allocate(session.getApplicationBufferSize());
        outAppData = ByteBuffer.allocate(session.getApplicationBufferSize());

        inNetData  = ByteBuffer.allocate(session.getPacketBufferSize());
        outNetData = ByteBuffer.allocate(session.getPacketBufferSize());
    }


    /**
     * Ends SSL operation and close the wrapped byte channel
     * @throws java.io.IOException May be raised by close operation on wrapped byte channel
     */
    public void close() throws java.io.IOException {
        if (!closed) {
            try {
                engine.closeOutbound();
                sslLoop(wrap());
                wrappedChannel.close();
            } finally {
                closed=true;
            }
        }
    }


    public SSLByteChannel(ByteChannel wrappedChannel) {
        this.wrappedChannel = null;
        this.engine = null;


        inAppData  = ByteBuffer.allocate(4096);
        outAppData = ByteBuffer.allocate(4096);

        inNetData  = ByteBuffer.allocate(4096);
        outNetData = ByteBuffer.allocate(4096);
    }



    /**
     * Is the channel open ?
     * @return true if the channel is still open
     */
    public boolean isOpen() {
        return !closed;
    }

    /**
     * Fill the given buffer with some bytes and return the number of bytes
     * added in the buffer.<br>
     * This method may return immediately with nothing added in the buffer.
     * This method must be use exactly in the same way of ByteChannel read
     * operation, so be careful with buffer position, limit, ... Check
     * corresponding javadoc.
     * @param byteBuffer The buffer that will received read bytes
     * @throws java.io.IOException May be raised by ByteChannel read operation
     * @return The number of bytes read
     */
    public int read(java.nio.ByteBuffer byteBuffer) throws java.io.IOException {
        boolean eofDuringUnwrap = false;
        if (isOpen()) {
            try {
                SSLEngineResult r = sslLoop(unwrap());
                if (r==null) eofDuringUnwrap = true;
            } catch(SSLException e) {
                log.log(Level.SEVERE, "SSLException while reading", e);// TODO : Better SSL Exception management must be done
            } catch(ClosedChannelException e) {
                close();
            }
        }

        inAppData.flip();
        int posBefore = inAppData.position();
        byteBuffer.put(inAppData);
        int posAfter = inAppData.position();
        inAppData.compact();

        if (posAfter - posBefore > 0) return posAfter - posBefore ;
        if (isOpen())
            return (eofDuringUnwrap)?-1:0;
        else 
            return -1;
    }

    /**
     * Write remaining bytes of the given byte buffer.
     * This method may return immediately with nothing written.
     * This method must be use exactly in the same way of ByteChannel write
     * operation, so be careful with buffer position, limit, ... Check
     * corresponding javadoc.
     * @param byteBuffer buffer with remaining bytes to write
     * @throws java.io.IOException May be raised by ByteChannel write operation
     * @return The number of bytes written
     */
    public int write(java.nio.ByteBuffer byteBuffer) throws java.io.IOException {
        if (!isOpen()) return 0;
        int posBefore, posAfter;

        posBefore = byteBuffer.position();
        if (byteBuffer.remaining() < outAppData.remaining()) {
            outAppData.put(byteBuffer);  // throw a BufferOverflowException if byteBuffer.remaining() > outAppData.remaining()
        } else {
            while (byteBuffer.hasRemaining() && outAppData.hasRemaining()) {
             outAppData.put(byteBuffer.get());
            }
        }
        posAfter = byteBuffer.position();

        if (isOpen()) {
            try {
                while(true) {
                    SSLEngineResult r = sslLoop(wrap());
                    if (r.bytesConsumed() == 0 && r.bytesProduced()==0) break;
                };
            } catch(SSLException e) {
                log.log(Level.SEVERE, "SSLException while reading", e); // TODO : Better SSL Exception management must be done
            } catch(ClosedChannelException e) {
                close();
            }
        }

        return posAfter - posBefore;
    }


    public  void writeclean(java.nio.ByteBuffer byteBuffer) throws java.io.IOException {


        if (isOpen()) {
            try {
                while(true) {
                    wrappedChannel.write(outAppData); 
                }
            } catch(SSLException e) {
                log.log(Level.SEVERE, "SSLException while reading", e); // TODO : Better SSL Exception management must be done
            } catch(ClosedChannelException e) {
                close();
            }
        }


    }



    private SSLEngineResult unwrap() throws IOException, SSLException {
        int l;
        while((l = wrappedChannel.read(inNetData)) > 0) {
            try {
                Thread.sleep(10); // Small tempo as non blocking channel is used
            } catch(InterruptedException e) {
            }
        }

        inNetData.flip();

        if (l==-1 && !inNetData.hasRemaining()) return null;

        SSLEngineResult ser = engine.unwrap(inNetData, inAppData); 
        inNetData.compact();

        return ser;
    }

    private SSLEngineResult wrap() throws IOException, SSLException {
        SSLEngineResult ser=null;

        outAppData.flip();
        ser = engine.wrap(outAppData,  outNetData);
        outAppData.compact();

        outNetData.flip();
        while(outNetData.hasRemaining()) {
            int l = wrappedChannel.write(outNetData); // TODO : To be enhanced (potential deadlock ?)
            try {
                Thread.sleep(10);  // Small tempo as non blocking channel is used
            } catch(InterruptedException e) {
            }
        }
        outNetData.compact();

        return ser;
    }

    private SSLEngineResult sslLoop(SSLEngineResult ser) throws SSLException, IOException {
        if (ser==null) return ser;
        //log.finest(String.format("%s - %s\n", ser.getStatus().toString(), ser.getHandshakeStatus().toString()));
     //   System.out.println(String.format("%s - %s\n", ser.getStatus().toString(), ser.getHandshakeStatus().toString()));
        while(   ser.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.FINISHED
              && ser.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
            switch(ser.getHandshakeStatus()) {
                case NEED_TASK:
                    //Executor exec = Executors.newSingleThreadExecutor();
                    Runnable task;
                    while ((task=engine.getDelegatedTask()) != null) {
                        //exec.execute(task);
                        task.run();
                    }
                    // Must continue with wrap as data must be sent
                case NEED_WRAP:
                    ser = wrap();
                    break;
                case NEED_UNWRAP:
                    ser = unwrap();
                    break;
            }
            if (ser == null) return ser;
        }
        switch(ser.getStatus()) {
            case CLOSED:
                log.finest("SSLEngine operations finishes, closing the socket");
                try {
                    wrappedChannel.close();
                } finally {
                    closed=true;
                }
                break;
        }
        return ser;
    }

}

1 个答案:

答案 0 :(得分:2)

使用常规HTTPS请求,您无法开始说纯文本,然后切换到SSL,然后再切换回纯文本。您必须提交纯文本或SSL通信模式。

我能想到的唯一允许将纯文本升级到SSL的实际实现是使用ESMTP的STARTTLS。但即便如此,一旦建立SSL连接,您就无法降级回纯文本。

因此,除非您正在推出自己的服务器协议,否则不需要降级SSL。

修改
用于回退到未加密通信的伪代码

ByteChannel sslByteChannel = upgradeChannel2ServerSSLChannel(sourceByteChannel);

try
{
    doSslPortion( sslByteChannel );

    doPlainPortion( sourceByteChannel );
}
finally
{
    sourceByteChannel.close( );
}