我正在开发一个分析一些 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。
我知道在这种情况下帮助我并不容易,但是,嘿,感谢愿意尝试的人 :)