2

When I run the client Handshake process on my Java application in order to establish SSL connection, I get SSLException on the second call to the wrap method. I understand that in this point the client sends the CLientKeyExchange and ChangeCipherSpec to the server. The error message that I get from the exception is "General SSLEngine problem". Maybe there is a problem with my certificate that I need to send?

Here is the exception stack details:

Details: General SSLEngine problem
Trace detail #0: com.sun.net.ssl.internal.ssl.Handshaker.checkThrown(Handshaker.java:994)
Trace detail #1: com.sun.net.ssl.internal.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:459)
Trace detail #2: com.sun.net.ssl.internal.ssl.SSLEngineImpl.writeAppRecord(SSLEngineImpl.java:1058)
Trace detail #3: com.sun.net.ssl.internal.ssl.SSLEngineImpl.wrap(SSLEngineImpl.java:1030)
Trace detail #4: javax.net.ssl.SSLEngine.wrap(SSLEngine.java:411)
Trace detail #5: epic.clarity.extract.CRSslSocketHandler.doHandshake(CRSslSocketHandler.java:333)
Trace detail #6: epic.clarity.extract.CRSslSocketHandler.readAndUnwrap(CRSslSocketHandler.java:282)
Trace detail #7: epic.clarity.extract.CRSslSocketHandler.doHandshake(CRSslSocketHandler.java:322)
Trace detail #8: epic.clarity.extract.CRSslSocketHandler.readAndUnwrap(CRSslSocketHandler.java:282)
Trace detail #9: epic.clarity.extract.CRSslSocketHandler.doHandshake(CRSslSocketHandler.java:322)
Trace detail #10: epic.clarity.extract.CRSslSocketHandler.ReadExtFile(CRSslSocketHandler.java:159)

Here is my code, the handshake starts on the Read method:

public class CRSslSocketHandler extends CRSocketHandler{

    private SSLEngine _engine;
    private SSLEngineResult.HandshakeStatus hsStatus;

    /**
    * Stores the result from the last operation performed by the SSLEngine 
    */
    private SSLEngineResult.Status status = null;

    /** Application data decrypted from the data received from the peer.
    * This buffer must have enough space for a full unwrap operation,
    * so we can't use the buffer provided by the application, since we
    * have no control over its size.
    */
    private final ByteBuffer peerAppData;
    /** Network data received from the peer. Encrypted. */  
    private final ByteBuffer peerNetData;
    /** Network data to be sent to the peer. Encrypted. */
    private final ByteBuffer netData;  

    /** Used during handshake, for the operations that don't consume any data */
    private ByteBuffer dummy;   


    private boolean initialHandshake = false;  

    private final String[] AvailProtocol = {"TLSv1"};

    private boolean FirstTime = true;

    //Debug
    private BufferedWriter  debugFile;
    private BufferedWriter  test;


    /** Creates a new instance of CRSslSocketHandler */
   public CRSslSocketHandler(SocketChannel channel, DualKeyHashtable queryCollection, DualKeyHashtable routineCollection, long sn, String hostName, int portNum) throws Exception {
       super(channel, queryCollection, routineCollection, sn);


       // Debug purposes
       debugFile = new BufferedWriter(new FileWriter("SslDebug.txt",true));
       test = new BufferedWriter(new FileWriter("ssltest.txt",true));
       try{
           System.out.println("Create Ssl Context");
            test.write("Create Ssl Context");  

            SSLContext sslContext = SSLContext.getInstance("TLSv1");
            System.out.println("Init Ssl Context");
            test.write("Init Ssl Context");

            /String Pass = "password";
            char[] passphrase = Pass.toCharArray();

            System.out.println("Get keys");
            test.write("Get keys");
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(new FileInputStream("keystore.ks"), passphrase);
             System.out.println("Get trust");
            test.write("Get trust");          
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            System.out.println("Init trust");
            test.write("Init trust");           
            tmf.init(ks); 

            System.out.println("Init context");
            test.write("Init context");         
            sslContext.init(null, tmf.getTrustManagers(), null);
             System.out.println("Create engine host name: " + hostName + " port number: " + portNum);
            test.write("Create engine host name: " + hostName + " port number: " + portNum);
            _engine = sslContext.createSSLEngine(hostName, portNum);
            // _engine = sslContext.createSSLEngine();
            System.out.println("Set client mode");
            test.write("Set client mode");            
            _engine.setUseClientMode(true);
            _engine.setEnabledProtocols(AvailProtocol);



            test.write("Begin Hanshaking");
            System.out.println("Begin Hanshaking");
            _engine.beginHandshake();
            hsStatus = _engine.getHandshakeStatus();
       }
       catch (IOException e){

       }
       catch (Exception e) {
           System.out.println("Exception: " + e.getMessage());
           test.write("Exception: " + e.getMessage());
           test.close();
       }
     System.out.println("Alocate buffers");
      test.write("Alocate buffers");
      SSLSession  session = _engine.getSession();
      peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
      peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
      netData = ByteBuffer.allocate(session.getPacketBufferSize());
      // Change the position of the buffers so that a 
      // call to hasRemaining() returns false. A buffer is considered
      // empty when the position is set to its limit, that is when
      // hasRemaining() returns false.      
      peerAppData.position(peerAppData.limit());
      netData.position(netData.limit());
      char c = (char)(28);
      String msg = "OK" + c;
      dummy = ByteBuffer.wrap(msg.getBytes());
      // dummy = ByteBuffer.allocate(0);
      initialHandshake = true;
      test.close();
    }

    protected int ReadExtFile(ByteBuffer buffer) throws IOException{
        if (initialHandshake) {
            doHandshake();
        }        
        // Check if the stream is closed.
        if (_engine.isInboundDone()) {
            // We reached EOF.
            return EOF;
        }
        // First check if there is decrypted data waiting in the buffers
        if (!peerAppData.hasRemaining()) {
            int appBytesProduced = readAndUnwrap();
            if (appBytesProduced == EOF){
                debugFile.write("Failed to read");
                debugFile.close();
                return appBytesProduced;
            }
            if (appBytesProduced == 0) {
                return appBytesProduced;
            } 
        }

        // It's not certain that we will have some data decrypted ready to 
        // be sent to the application. Anyway, copy as much data as possible
        int limit = Math.min(peerAppData.remaining(), buffer.remaining());
        for (int i = 0; i < limit; i++) {
            buffer.put(peerAppData.get());
        }
        return limit;
    }
    private int readAndUnwrap() throws IOException {
        // No decrypted data left on the buffers.
        // Try to read from the socket. There may be some data
        // on the peerNetData buffer, but it might not be sufficient.
        int bytesRead = _client.read(peerNetData);
        // decoder.decode(peerNetData, charBuffer, false);
        // charBuffer.flip();               
        System.out.println("Read bytes " + bytesRead);


        if (bytesRead == EOF) {
            // We will not receive any more data. Closing the engine
            // is a signal that the end of stream was reached.
            _engine.closeInbound();         
            // EOF. But do we still have some useful data available? 
            if (peerNetData.position() == 0 ||
            status == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
                // Yup. Either the buffer is empty or it's in underflow,
                // meaning that there is not enough data to reassemble a
                // TLS packet. So we can return EOF.
                return EOF;
            }
            // Although we reach EOF, we still have some data left to
            // be decrypted. We must process it 
        }

        // Prepare the application buffer to receive decrypted data
        peerAppData.clear();

        // Prepare the net data for reading. 
        peerNetData.flip();
        SSLEngineResult res;
        System.out.println("Do unwrap " + bytesRead);
        try{
            do {
                res = _engine.unwrap(peerNetData, peerAppData);
                System.out.println("Read status" + res.getHandshakeStatus().toString());
                // During an handshake renegotiation we might need to perform
                // several unwraps to consume e handshake data.
            } while (res.getStatus() == SSLEngineResult.Status.OK &&
            res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
            res.bytesProduced() == 0);

            System.out.println("Read status" + res.getHandshakeStatus().toString());
            // If the initial handshake finish after an unwrap, we must activate
            // the application interestes, if any were set during the handshake
            if (res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
                initialHandshake = false;
            }
           // If no data was produced, and the status is still ok, try to read once more
           if (peerAppData.position() == 0 && 
            res.getStatus() == SSLEngineResult.Status.OK &&
            peerNetData.hasRemaining()) {
               res = _engine.unwrap(peerNetData, peerAppData);                             
            }
           /*
            * The status may be:
            * OK - Normal operation
            * OVERFLOW - Should never happen since the application buffer is 
            *   sized to hold the maximum packet size.
            * UNDERFLOW - Need to read more data from the socket. It's normal.
            * CLOSED - The other peer closed the socket. Also normal.
            */
            status = res.getStatus();
            hsStatus = res.getHandshakeStatus();
            // Should never happen, the peerAppData must always have enough space
            // for an unwrap operation
            assert status != SSLEngineResult.Status.BUFFER_OVERFLOW : 
            "Buffer should not overflow: " + res.toString();  
        }
        catch (Exception e){
            System.out.println("Error: " + e.getMessage());
        }




        // The handshake status here can be different than NOT_HANDSHAKING
        // if the other peer closed the connection. So only check for it
        // after testing for closure.
        if (status == SSLEngineResult.Status.CLOSED) {
            debugFile.write("Connection is being closed by peer.");
            return EOF;
        }   

        // Prepare the buffer to be written again.
        peerNetData.compact();
        // And the app buffer to be read.
        peerAppData.flip();

        if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
                hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP ||
                hsStatus == SSLEngineResult.HandshakeStatus.FINISHED) 
        {
            debugFile.write("Rehandshaking...");
            doHandshake();
        }

        return peerAppData.remaining();
    }

        public void closeSocket(boolean timeout){

            super.closeSocket(timeout);
            try{
                debugFile.write("Close");
                debugFile.close();
            }catch(IOException e){}
        }

    /**
     * Execute delegated tasks in the main thread. These are compute
     * intensive tasks, so there's no point in scheduling them in a different
     * thread.
     */
    private void doTasks() {
        Runnable task;
        while ((task = _engine.getDelegatedTask()) != null) {
            task.run();
        }
        hsStatus = _engine.getHandshakeStatus();
    }
        private void doHandshake() throws IOException {
            while (true) {
                SSLEngineResult res;
                System.out.println("Handshake status: " + hsStatus.toString());
                switch (hsStatus) {
                    case FINISHED:
                        initialHandshake = false;
                        return; 
                    case NEED_TASK:
                        doTasks();
                        // The hs status was updated, so go back to the switch
                        break;
                    case NEED_UNWRAP:
                        readAndUnwrap();
                        return;
                    case NEED_WRAP:

                        if (netData.hasRemaining()) {
                            return;
                        }
                        // Prepare to write
                            netData.clear();

                            try{
                            res = _engine.wrap(dummy, netData);

                            if (res.bytesProduced() == 0){
                                System.out.println("No net data produced during handshake wrap.");
                            }
                            else{
                                System.out.println("Result status: " + res.getStatus() + "Bytes porduced: " + res.bytesProduced());
                            }
                            if (res.bytesConsumed() != 0){
                                System.out.println("App data consumed during handshake wrap.");
                            }
                            hsStatus = res.getHandshakeStatus();
                        }catch(SSLException se){

                            System.out.println("Error: " + se.getMessage());
                            System.out.println("Details: " + se.getLocalizedMessage());
                            throw se;
                        }

                        System.out.println(hsStatus.toString());
                        netData.flip();
                        try{
                            int writebytes = _client.write(netData);
                            System.out.println("Number of bytes sent: " + writebytes);
                            if (netData.hasRemaining()) {
                                System.out.println("netdata has remaining");
                            }
                         }
                         catch(Exception e){
                             System.out.println(e.getMessage());
                         } 
                        break;
                    case NOT_HANDSHAKING:
                        assert false : "doHandshake() should never reach the NOT_HANDSHAKING state";
                        return;
            }
        }
    }


}
4

1 回答 1

5

使用SSLEngine起来非常困难。您的代码有几个问题。

例如NEED_WRAP意味着引擎想要写一些东西。如果传出的网络缓冲区中有数据,它将不会写入任何内容,因此,如果是这种情况,您需要将其写出以清空缓冲区,然后进行换行。仅仅忽略条件是不够的。

关于FINISHED状态还有一个微妙之处:它设置在每个SSLEngine方法返回的握手状态中,除了 getHandshakeStatus():即它是一个非常短暂的条件。

可能还有更多,但我没有时间看,抱歉。我可以推荐我书中SSLEngine的章节。它包含一个有效的 SSLEngine 管理器。

于 2010-10-14T07:07:54.197 回答