0

我正在开发一个分析一些 SSL 服务的工具,现在我正在尝试测试客户端发起的重新协商。

我正在使用 BouncyCastle 来执行此操作,并使用带有自定义函数的 TlsClientProtocol,因为 BC 本身并不“处理”重新协商。

所以,现在我正在使用这个类:

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();
    }
}

它正在工作..几乎..

当我调用 renegotiate() 方法时,它:
- 发送 ClientHello
- 接收 ServerHello
- 接收证书
- 接收
ServerHelloDone - 发送 ClientKeyExchange
- 发送 ChangeCipherSpec
- 发送 Finish
- 接收警报:致命,解密错误;而不是 NewSessionTicket,ChangeCipherSpec,Finish

我就是不知道为什么。我认为它可能是用于创建需要刷新的 MAC 的 SeqNumber,但不是。当我给出一个明显错误的值时,我还会收到一个 MAC 错误警报。

为了进行测试,我使用了一个允许 CLEAR 密码套件的服务器,并且显然允许客户端发起的重新协商。

当我尝试使用 OpenSSL 时,它运行良好,但我看不出区别在哪里,我做错了什么。

服务器在一个私有 VPN 上,所以你不能用它来测试东西,但这里是握手的 .cap。

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

第一个是工作的,使用openSSL。第二个是我的,使用 BouncyCastle。

我知道在这种情况下帮助我并不容易,但是,嘿,感谢愿意尝试的人 :)

4

1 回答 1

1

好的,和往常一样,我在发布我的问题后几次找到了答案——(即使我在上面呆了几个小时/几天)。

问题来自客户端发送的“完成”消息。verify_data 是包含当前协商的所有先前握手消息的散列。

但就我而言,它还包含第一次协商的握手消息,因此 verify_data 没有很好的价值。

所以为了让它工作,我需要重置RecordStream.hash, 使用RecordStream.hash.reset()

于 2013-11-19T11:58:48.180 回答