0

I'm working on a tool which analyze some SSL Services, and right now I'm trying to test the client-initiated renegotiation.

I'm using BouncyCastle to do so, with a TlsClientProtocol with a custom function, because BC doesn't "handle" natively the renegotiation.

So, right now I'm using this class:

package org.bouncycastle.crypto.tls;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Hashtable;

import org.bouncycastle.crypto.tls.Certificate;
import org.bouncycastle.crypto.tls.CipherSuite;
import org.bouncycastle.crypto.tls.DefaultTlsClient;
import org.bouncycastle.crypto.tls.ExtensionType;
import org.bouncycastle.crypto.tls.ServerOnlyTlsAuthentication;
import org.bouncycastle.crypto.tls.TlsAuthentication;
import org.bouncycastle.crypto.tls.TlsClientProtocol;
import org.bouncycastle.util.encoders.Hex;

/**
 *
 * @version 1.0
 */
public class TestProtocol extends TlsClientProtocol {
    private byte[] verifyData;


    public TestProtocol(InputStream input, OutputStream output) {
        super(input, output);
    }

    // I need to replace this method to have the verifyData,
    // because we need to send it into the renegotiation_info ext
    @Override
    protected void sendFinishedMessage() throws IOException {
        verifyData = createVerifyData(getContext().isServer());

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        TlsUtils.writeUint8(HandshakeType.finished, bos);
        TlsUtils.writeUint24(verifyData.length, bos);
        bos.write(verifyData);
        byte[] message = bos.toByteArray();

        safeWriteRecord(ContentType.handshake, message, 0, message.length);
    }

    public void renegotiate() throws IOException {

        this.connection_state = CS_START;
        sendClientHelloMessage();
        this.connection_state = CS_CLIENT_HELLO;

        completeHandshake();
    }



    public static void main(String[] args) throws IOException, InterruptedException {
        Socket s = new Socket("10.0.0.101", 443);

        final TestProtocol proto = new TestProtocol(s.getInputStream(), s.getOutputStream());

        proto.connect(new DefaultTlsClient() {
            public TlsAuthentication getAuthentication() throws IOException {
                return new ServerOnlyTlsAuthentication() {                  
                    public void notifyServerCertificate(Certificate serverCertificate) throws IOException {}
                };
            }

            @Override
            public int[] getCipherSuites() {
                return new int[]{CipherSuite.TLS_RSA_WITH_NULL_SHA, CipherSuite.TLS_RSA_WITH_NULL_MD5};
            }


            private boolean first = true;

            @Override
            public Hashtable getClientExtensions() throws IOException {
                @SuppressWarnings("unchecked")
                Hashtable<Integer, byte[]> clientExtensions = super.getClientExtensions();

                if (clientExtensions == null) {
                    clientExtensions = new Hashtable<Integer, byte[]>();
                }

                // If this is the first ClientHello, we're not doing anything
                if (first) {
                    first = false;
                }
                else {
                    // If this is the second, we add the renegotiation_info extension
                    byte[] ext = new byte[proto.verifyData.length + 1];

                    ext[0] = (byte) proto.verifyData.length;
                    System.arraycopy(proto.verifyData, 0, ext, 1, proto.verifyData.length);

                    clientExtensions.put(ExtensionType.renegotiation_info, ext);
                }

                clientExtensions.put(ExtensionType.session_ticket, new byte[] {});

                return clientExtensions;
            }
        });

        proto.renegotiate();
    }
}

And it's working.. Almost..

When I call the renegotiate() method, it :
- sends the ClientHello
- receives the ServerHello
- receives the Certificate
- receives the ServerHelloDone
- sends the ClientKeyExchange
- sends the ChangeCipherSpec
- sends the Finish
- receives an alert: Fatal, Decrypt Error ; instead of NewSessionTicket,ChangeCipherSpec,Finish

And I just can't figure out why. I thought it could be the SeqNumber used to create the MAC which would need a refresh, but no. When I'm giving an obviously wrong value, I receive also a MAC Error Alert.

To do my testing, I'm using a server allowing CLEAR cipher suites and obviously allowing Client-initiated Renegotiation.

When I'm trying with OpenSSL, it works fine, and I can't see where is the difference, what I'm doing wrong.

The server is on a private VPN, so you can't use it to test things, but here are the .cap of the handshakes.

https://stuff.stooit.com/d/1/528b4a314e35d/openssl.cap
https://stuff.stooit.com/d/1/528b4a54a68cd/my.cap

The first one is the working one, using openSSL. And the second one is mine, using BouncyCastle.

I'm aware that it won't be very easy to help me on this case, but hey, thanks to ppl who'll try :)

Tiller
  • 436
  • 1
  • 4
  • 22

1 Answers1

1

Ok, as always I found the answer a few time after posting my question -- (Even if I was on it for hours / days).

The problem comes with the "Finished" message the client sends. The verify_data is a hash containing all previous handshake messages of the current negotiation.

But in my case, it also contained the handshake messages of the first negotiation, so the verify_data doesn't have the good value.

So to make it works, I need to reset the RecordStream.hash, using RecordStream.hash.reset()

Tiller
  • 436
  • 1
  • 4
  • 22
  • Currently I am trying to achieve the same. But the BC 1.54 TlsClientProtocol implementation in receiveServerHelloMessage treats "every" ServerHello to be the first and therefor crashes on a renegotiation_info extension. Can someone help here? – Martin Mar 10 '16 at 16:43