由于 java JSSE 尚不支持通道绑定令牌 (CBT),因此我无法使用 RFC 5056 描述的简单“tls-unique”令牌通过委托的 TLS 连接编写 MitM 证明身份验证握手。
如果您想了解为什么 CBT 在应用程序级身份验证中至关重要,请参阅 https://csb.stevekerrison.com/post/2022-01-channel-binding/
“tls-server-end-point”令牌也不可用,但这让我觉得我可以从服务器的公钥证书中编写一些替代品,双方都应该同意(如果没有 MitM)。
所以我想,好吧,让我们对每一侧预期/看到的服务器公钥进行 sha256 并将其用作令牌。
我的问题是,一侧在 java 中,另一侧在 c# 中,并且它们无法生成可比较的通用 byte[] 。我的意思是,API 确实报告了证书、公钥或签名的各种编码格式,但它们在这些语言中的格式并不相同。
我设法让一个 C# 客户端连接到一个 Java 服务器,控制两个源,并在每一端深入研究 RSA 公钥以查找模数和指数(即使如此,BigInteger 的 byte[] 表示仍然稍微有点不准确)关闭一个符号字节),但我不想知道所有这样的证书类型。在未来所有 TLS 变化的喧嚣中,这样做不会长久存在。这意味着维护补丁,尤其是两种语言的补丁。
我的问题是,这些证书、公钥和签名是什么格式,如何解析它们,以及为什么在不同实现中表示它们的 byte[] 不完全相同,因为它们是通过 TCP/TLS 协议发送的?
---- csharp 客户端选项:[主机名 [端口]]--------
using System;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Net.Sockets;
class DumpCerts
{
static void Main(string[] args)
{
string server = args.Length>0 ? args[0] : "google.com";
int port = args.Length>1 ? int.Parse(args[1]) : 443;
using (var client = new TcpClient(server, port)) {
using (var sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(CertificateValidationCallback), null))
{
sslStream.AuthenticateAsClient(server);
var remoteCertificate = sslStream.RemoteCertificate as X509Certificate2;
if (remoteCertificate != null)
{
//Console.WriteLine("Certificate: " + Convert.ToBase64String(remoteCertificate.RawData));
//Console.WriteLine("Public Key: " + Convert.ToBase64String(remoteCertificate.GetPublicKey()));
X509Chain chain = new X509Chain();
chain.Build(remoteCertificate);
int i=0;
foreach (var element in chain.ChainElements)
{
var cert = element.Certificate as X509Certificate2;
var pubk = cert.PublicKey;
//NOTE: cert.GetPublicKey() is a byte[] equals to pubk.EncodedKeyValue.RawData
byte[] sig = cert.GetRawCertData();
Console.WriteLine("\n\n-----------\npub key["+i+"]: "+cert.Subject
+"\npubl key : " + pubk
+"\npubl key oid: " + pubk.Oid
+"\npubl key oid value: " + pubk.Oid.Value
+"\npubk.enc key val len: " + (pubk.EncodedKeyValue.RawData).Length
+"\npubk.enc key val hex: " + byteArrayToHex(pubk.EncodedKeyValue.RawData)
+"\nsig len: " + sig.Length
+"\nsig hex: " + byteArrayToHex(sig)
);
//using (var rsa = RSA.Create()) {
// rsa.ImportSubjectPublicKeyInfo(pubk.EncodedKeyValue.RawData, out _);
// byte[] publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
using (RSA rsa = remoteCertificate.GetRSAPublicKey())
{
var rsaParameters = rsa.ExportParameters(false);
Console.WriteLine("\n"
+"\nmodulus len: "+ (rsaParameters.Modulus.Length)
+"\nmodulus hex: "+ byteArrayToHex(rsaParameters.Modulus)
+"\nexponent len: "+ (rsaParameters.Exponent.Length)
+"\nexponent hex: "+ byteArrayToHex(rsaParameters.Exponent)
);
}
i++;
}
}
}
}
}
static bool CertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true; // Always accept the certificate
}
static string byteArrayToHex(byte[] input) {
int bytesPerLine = 64;
char[] HEX = "0123456789ABCDEF".ToCharArray();
StringBuilder result = new StringBuilder();
byte[] buffer = new byte[bytesPerLine];
int offset = 0;
while (offset < input.Length) {
int length = Math.Min(buffer.Length, input.Length - offset);
Array.Copy(input, offset, buffer, 0, length);
StringBuilder hex = new StringBuilder();
StringBuilder ascii = new StringBuilder();
for (int i = 0; i < length; i++) {
int v = buffer[i] & 0xFF;
hex.Append(HEX[v >> 4]);
hex.Append(HEX[v & 0x0F]);
ascii.Append((v >= 32 && v <= 127) ? (char)buffer[i] : '.');
}
result.AppendFormat("\n\t{0,-" + (2 * bytesPerLine) + "} | {1}", hex.ToString(), ascii.ToString());
offset += length;
}
return result.ToString();
}
}
------ java 服务器选项:
-i
package clientserverkit;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
public class DumpCerts {
private boolean verbose = false;
private String bindIp;
private int port = 33300;
private String cafilename;
private char[] capwd;
private String ksfilename;
private char[] kspwd;
private char[] keypwd;
//---
private SSLContext ctx;
private InetAddress bindAddr;
public static void main(String[] args) throws Exception {
DumpCerts dc = new DumpCerts();
dc.parseOptions(args);
dc.initTls();
dc.doServer();
}
void parseOptions(String[] args) throws UnknownHostException {
for(int i=0; i<args.length; ) {
String opt = args[i++];
switch(opt) {
case "-v": verbose = true; break;
case "-i": bindIp = args[i++]; break;
case "-p": port = Integer.parseInt(args[i++]); break;
case "-ca": cafilename = args[i++]; capwd = args[i++].toCharArray(); break;
case "-ks": ksfilename = args[i++]; kspwd = args[i++].toCharArray(); keypwd = args[i++].toCharArray(); break;
default:
throw new IllegalArgumentException("Unknown option "+opt);
}
}
if(bindIp!=null) {
bindAddr = bindIp!=null && !bindIp.equalsIgnoreCase("-") ? InetAddress.getByName(bindIp) : null;
}
}
private void initTls() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException {
KeyManager[] kma =null;
TrustManager[] tma = null;
//--- setup key
Provider bcProvider = SSLContext.getDefault().getProvider();
if(ksfilename!=null) {
File ksfile = new File(ksfilename);
if(!ksfile.isFile() && ksfile.canRead())
throw new IllegalArgumentException("cannot read "+ksfile);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm(), bcProvider);
KeyStore ks = KeyStore.getInstance(ksfile, kspwd);
kmf.init(ks, kspwd);
Arrays.fill(kspwd, 's');
Arrays.fill(keypwd, 'k');
kma = kmf.getKeyManagers();
}
//--- setup trusted CA
if(cafilename!=null) {
File cafile = new File(cafilename);
if(!cafile.isFile() && cafile.canRead()){
throw new IllegalArgumentException("cannot read "+cafile);
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm(), bcProvider);
KeyStore caks = KeyStore.getInstance(cafile, capwd);
Arrays.fill(capwd, 'c');
tmf.init(caks);
tma = tmf.getTrustManagers();
}
//--- make tls ctx and pull socket factories
SecureRandom sr = new SecureRandom();
ctx = SSLContext.getInstance("TLS", bcProvider);
//ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(kma, tma, sr);
}
void doServer() throws IOException {
ServerSocket serverSocket = null;
try {
SSLServerSocketFactory sssf = ctx.getServerSocketFactory();
SSLServerSocket sss = (SSLServerSocket)sssf.createServerSocket(port, 50, bindAddr);
//sss.setNeedClientAuth(true);//mandatory
sss.setWantClientAuth(true);//optional
serverSocket = sss;
while(!Thread.interrupted()) {
try(Socket s = serverSocket.accept()) {
dumpInfo(s, verbose);
}
}
} finally {
if(serverSocket!=null)
serverSocket.close();
}
}
static void dumpInfo(Socket s, boolean verbose) {
System.out.println("\n\n===== socket dump ===================================================================================");
System.out.println("local skt : "+s.getLocalSocketAddress());
System.out.println("remote skt : "+s.getRemoteSocketAddress());
if(s instanceof SSLSocket ss) {
SSLSession sess = ss.getSession();
System.out.println("sess proto : "+sess.getProtocol());
System.out.println("sess cipher suite: "+sess.getCipherSuite());
System.out.println("sess id: (hex) "+bytesToHex(sess.getId()));
System.out.println("sess peer host: "+sess.getPeerHost());
System.out.println("sess peer port: "+sess.getPeerPort());
System.out.println("sess local principal : "+sess.getLocalPrincipal());
dumpCerts("local", sess.getLocalCertificates(), verbose);
try {
System.out.println("sess peer principal: "+sess.getPeerPrincipal());
dumpCerts("peer", sess.getPeerCertificates(), verbose);
} catch (SSLPeerUnverifiedException e) {
System.out.println("(peer unverified)");
}
for(String n: sess.getValueNames()) {
System.out.println("sess value["=="]: "+sess.getValue(n));
}
System.out.println("sess creation time: "+new Date(sess.getCreationTime()));
System.out.println("sess access time : "+new Date(sess.getLastAccessedTime()));
System.out.println("sess pkt buff : "+sess.getPacketBufferSize());
System.out.println("sess app buff : "+sess.getApplicationBufferSize());
SSLSessionContext ctx = sess.getSessionContext();
if(ctx!=null) {
System.out.println("sess ctx sess cache sz: "+ctx.getSessionCacheSize());
System.out.println("sess ctx sess timeout : "+ctx.getSessionTimeout());
}
if(sess instanceof ExtendedSSLSession xsession) {
List<SNIServerName> list = xsession.getRequestedServerNames();
if(list!=null)
list.forEach(sni -> System.out.println("xsess sni: "+sni.toString()));
System.out.println("xsess local supported algos: "+Arrays.asList(xsession.getLocalSupportedSignatureAlgorithms()));
System.out.println("xsess peer supported algos: "+Arrays.asList(xsession.getPeerSupportedSignatureAlgorithms()));
}
}
}
static void dumpCerts(String prefix, Certificate[] certs, boolean verbose) {
if(certs==null) {
System.out.println("(no "+prefix+" certs)");
return;
}
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
for(int i=0; i<certs.length; i++) {
md.reset();
PublicKey publicKey = certs[i].getPublicKey();
byte[] pubk = publicKey.getEncoded();
byte[] sha256 = md.digest(pubk);
System.out.println("\n-----certs["+i+"]------------");
System.out.println(prefix+" certs["+i+"] publ key algo="+publicKey.getAlgorithm()+", format="+publicKey.getFormat());
System.out.println(prefix+" certs["+i+"] publ key enc len : "+pubk.length);
System.out.println(prefix+" certs["+i+"] publ key enc hex : "+bytesToHex(pubk));
System.out.println(prefix+" certs["+i+"] sha256(publ key enc) : "+bytesToHex(sha256));
if(certs[i] instanceof X509Certificate xcert) {
try {
byte[] tbsder = xcert.getTBSCertificate();
System.out.println(prefix+" certs["+i+"] tbsder len : "+tbsder.length);
System.out.println(prefix+" certs["+i+"] tbsder hex : "+bytesToHex(tbsder));
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
byte[] sig = xcert.getSignature();
System.out.println(prefix+" certs["+i+"] sig len : "+sig.length);
System.out.println(prefix+" certs["+i+"] sig hex : "+bytesToHex(sig));
}
if(publicKey instanceof RSAPublicKey rsapk) {
System.out.println(prefix+" certs["+i+"] len(modulus) : "+rsapk.getModulus().toByteArray().length);
System.out.println(prefix+" certs["+i+"] hex(modulus) : "+bytesToHex(rsapk.getModulus().toByteArray()));
System.out.println(prefix+" certs["+i+"] len(exponent): "+rsapk.getPublicExponent().toByteArray().length);
System.out.println(prefix+" certs["+i+"] hex(exponent): "+bytesToHex(rsapk.getPublicExponent().toByteArray()));
}
}
if(verbose) {
for(int i=0; i<certs.length; i++) {
System.out.println("\n-----"+prefix+" certs["+i+"].toString()---------\n"+certs[i]);
}
}
} catch (NoSuchAlgorithmException e) {
System.out.println("(hash algo not found): "+e);
}
}
public static String bytesToHex(byte[] input) {
int bytesPerLine = 64;
char[] HEX = "0123456789ABCDEF".toCharArray();
StringBuilder result = new StringBuilder();
byte[] buffer = new byte[bytesPerLine];
int offset = 0;
while (offset < input.length) {
int length = Math.min(buffer.length, input.length - offset);
System.arraycopy(input, offset, buffer, 0, length);
StringBuilder hex = new StringBuilder();
StringBuilder ascii = new StringBuilder();
for (int i = 0; i < length; i++) {
int v = buffer[i] & 0xFF;
hex.append(HEX[v >>> 4]);
hex.append(HEX[v & 0x0F]);
ascii.append((v >= 32 && v <= 127) ? (char)buffer[i] : '.');
}
//result.append("\n\t").append(hex).append(" | ").append(ascii);
result.append(String.format("\n\t%-"+(2*bytesPerLine)+"s | %s", hex.toString(), ascii.toString()));
offset += length;
}
return result.toString();
}
}
找到了。 Csharp 需要拉
byte[] certder = cert.GetRawCertData();
而java需要拉
byte[] certder = certs[i].getEncoded();