在学校Java网络编程的时候,都会接触到Socket,Socket是基于TCP的数据传输方式,但Socket是没有证书认证的,它无法建立带证书认证的连接。还好,Java中提供了带证书认证的SSLsocket。
最近需要做一个客户端认证的需求,我需要在客户端配置自己的证书,然后向服务器发送数据,本文主要用于记录。
SSL证书的格式有很多,有.p12证书,.crt+.key证书,还有.jks证书,网上大多数案例都是基于.jks证书去实现认证,如果你的证书类型是.p12证书或者是crt证书,你可以手动把p12证书转换成jks证书,如果你是crt证书,下面会提供一个crt证书转换成jks证书的方法。
public class KeyStoreUtils {
public void generateKeyStore(String privatePath, String certPath, String ksPath, String password) throws Exception {
KeyStore var17 = KeyStore.getInstance("PKCS12");
var17.load((InputStream) null, (char[]) null);
byte[] bytes = null;
PrivateKey key = getPrivateKeyFromPem(privatePath);
Certificate certificate = readCert(certPath);
var17.setKeyEntry("Eric's Key", key, (char[]) null, new Certificate[]{certificate});
//加载证书链所有证书
var17.setKeyEntry("Eric's Key", key, (char[]) null, certificates);
FileOutputStream var18 = new FileOutputStream(ksPath);
var17.store(var18, password.toCharArray());
var18.flush();
}
// 获取私匙
public static PrivateKey getPrivateKeyFromPem(String privateFile) throws Exception {
BufferedReader br = new BufferedReader(new FileReader(new File(privateFile)));
String s = br.readLine();
String str = "";
s = br.readLine();
while (s.charAt(0) != '-') {
str += s + "\r";
s = br.readLine();
}
BASE64Decoder base64decoder = new BASE64Decoder();
byte[] b = base64decoder.decodeBuffer(str);
// 生成私匙
KeyFactory kf = KeyFactory.getInstance(PropertiesReader.getKey("cert_encryption"));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(b);
PrivateKey privateKey = kf.generatePrivate(keySpec);
return privateKey;
}
public static Certificate readCert(String certPath) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream in1 = new FileInputStream(certPath);
Collection<? extends Certificate> certificates = cf.generateCertificates(in1);
Iterator<? extends Certificate> iterator = certificates.iterator();
Certificate next = iterator.next();
return next;
}
public static void main(String[] args) throws Exception{
KeyStoreUtils keyStoreUtils = new KeyStoreUtils();
keyStoreUtils.generateKeyStore("./certificate/client_dev.key","./certificate/client_dev.crt","./certificate/client_dev.jks","123456");
}
}
SSLContext:SSLContext类可以创建SSLSocket实例,在这个类中配置自己的证书和自己所信任的证书。
KeyManagerFactory:加载自己的证书文件,并且生成KeyManager[]数据,配置到SSLContext。
TrustManagerFactory:加载自己信任的客户端证书,双向认证的时候配置自己信任哪些服务端证书。
KeyStore:加载证书的类。
TrustManager[]:信任的证书信任的Manager数组。
Manager[]:自己的证书的Manager数组。
代码如下,通过KeyStore去加载证书,这里我客户端证书和信任的服务器证书读的同一个,真实情况肯定是两个不一样的证书。
代码:
SSLContext ctx = SSLContext.getInstance("SSL");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
tks.load(new FileInputStream("C://"), "password".toCharArray());
kmf.init(ks, new char[0]);
kmf.init(ks, "passworld".toCharArray());
tmf.init(tks);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
log.info("创建sslsocket连接中。。。。。。");
sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket("121.0.0.1", 8000);
如果只需要配置客户端证书,客户端信任所有服务器方的证书,可以这样配置。
把TrustManager通过如下方式生成,定义一个类继续TrustManager,X509TrustManager类。
TrustManager[] trustAllCerts = new TrustManager[1];
TrustManager tm = new miTM();
trustAllCerts[0] = tm;
static class miTM implements TrustManager,X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(X509Certificate[] certs) {
return true;
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
return;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
return;
}
}
ctx.init(kmf.getKeyManagers(), trustAllCerts, null);