準備:
在網上找了若干資料,大多是Java服務器C++客戶端的,無奈只有用英文搜索些國外站點的資料。
1.下載openssl,做測試(Linux版本的安裝)。
安裝openssl
下載:openssl-0.9.7m.tar.gz
解壓:tar xzvf openssl-0.9.7m.tar.gz
cd openssl-0.9.7m/
默認配置:./config
重新建立依賴關係:make depend
測試:make test
安裝:make install
安裝完成後就可以用openssl自帶的s_server和s_client做測試了,很方便。
2.下載bcprov-jdk15-143.jar,早知道有這個東西就省事了。(BouncyCastle.org出品的Java開源SSL工具包)
步驟:
一、跑通Java客戶端與C++ openssl雙向通訊。
1.建立證書(注:需要輸入信息的時候需要與CA生成時統一,但不能完全一致,姓名可以不一)
新建目錄:mkdir openssl_demo2
拷貝 openssl-0.9.7m/apps/ 下的CA.sh、openssl.cnf 到 openssl_demo2 下
cd openssl_demo2
2.新建CA公私鑰
./CA.sh -newca
該命令的工作:
創建目錄:demoCA/ 、 demoCA/private/ 、 demoCA/certs/ 、demoCA/private/ 、demoCA/newcerts/
文件:demoCA/serial(寫入“01”) 、demoCA/index.txt
創建CA公私鑰:demoCA/cacert.pem、demoCA/private/cakey.pem
3.新建服務器端私鑰+公鑰簽名請求
openssl req -newkey rsa:1024 -out serverreq.pem -keyout sslserverkey.pem
4.用CA私鑰爲服務端請求籤名生成服務端證書
openssl ca -in serverreq.pem -out sslservercert.pem -config ./openssl.cnf
下面的步驟注意版本,java版本過低可能keytool做證書有問題。
OpenSSL 0.9.6m 17 Mar 2004
java -version -- Java(TM) SE Runtime Environment (build 1.6.0_03-b05)
5.生成客戶端私鑰並存入sslclient.keystore
keytool -genkey -alias sslclient -validity 365 -keyalg RSA -keysize 1024 -keystore sslclient.keystore -keypass 123456 -storepass 123456
6.從sslclient.keystore中提取客戶端簽名請求
keytool -certreq -alias sslclient -sigalg SHA1withRSA -file sslclient.csr -keypass 123456 -storepass 123456 -keystore sslclient.keystore
7.用CA私鑰爲服務端請求籤名生成客戶端證書
openssl ca -in sslclient.csr -out sslclient.crt -cert demoCA/cacert.pem -keyfile demoCA/private/cakey.pem -notext -config openssl.cnf
8.轉換客戶端證書格式
openssl x509 -in sslclient.crt -out sslclient.der -outform DER
9.sslclient.keystore導入根證書
keytool -imp
10.sslclient.keystore導入客戶端證書
keytool -imp
11.運行程序
運行openssl自帶測試服務端:openssl s_server -cert sslservercert.pem -key sslserverkey.pem -CAfile demoCA/cacert.pem -state -Verify 1 -ssl3
運行Java客戶端(略)。
(上面的步驟參考了這篇文章:http://www.blogjava.net/alwayscy/archive/2009/02/03/85161.html
由於下面的問題,不能完全採用上面的方案。)
二、問題:由於證書的生產和頒發是由另外一個VC程序統一管理的,所以不可能爲了我的Java程序而做一個額外的功能。證書頒發程序是將CA根證書、客戶端證書和客戶端私鑰下發到系統某個目錄下然後我去取用的。麻煩在程序生成的證書都是PEM格式的,不能直接導入Java的JKS庫,需要做個轉換。
1.在網上的幾篇文章分別搜到些 PEM證書+私鑰合併爲pkcs12格式密鑰庫、pkcs12格式密鑰庫導入JKS密鑰庫、CA證書導入JKS密鑰庫的文章,於是整理了一下做了個小例子。
package change_format;
imp
imp
imp
imp
imp
imp
imp
public class ConvertPEMToJKS {
// certificate store format
public static final String PKCS12 = "PKCS12";
public static final String JKS = "JKS";
// PKCS12 keystore properties
public static final String INPUT_KEYSTORE_FILE = "src/change_format/keystore.pkcs12";
public static final String KEYSTORE_PASSWORD = "123456";
// JKS output file
public static final String OUTPUT_KEYSTORE_FILE = "src/change_format/keystore.jks";
public static final String CACERT_FILE = "src/change_format/ca2.crt";
public static final String PEMPRIKEY_FILE = "src/change_format/clientssl.pri";
public static final String PEMPUBKEY_FILE = "src/change_format/clientssl.crt";
public static void pktojks() {
try {
KeyStore inputKeyStore = KeyStore.getInstance("PKCS12");
FileInputStream fis = new FileInputStream(INPUT_KEYSTORE_FILE);
// If the keystore password is empty(""), then we have to set
// to null, otherwise it won't work!!!
char[] nPassword = null;
if ((KEYSTORE_PASSWORD == null)
|| KEYSTORE_PASSWORD.trim().equals("")) {
nPassword = null;
} else {
nPassword = KEYSTORE_PASSWORD.toCharArray();
}
inputKeyStore.load(fis, nPassword);
fis.close();
// System.out.println("keystore type=" + inputKeyStore.getType());
// ----------------------------------------------------------------------
// get a JKS keystore and initialize it.
KeyStore outputKeyStore = KeyStore.getInstance("JKS");
outputKeyStore.load(null, "123456".toCharArray());
// Now we loop all the aliases, we need the alias to get keys.
// It seems that this value is the "Friendly name" field in the
// detals tab <-- Certificate window <-- view <-- Certificate
// Button <-- Content tab <-- Internet Options <-- Tools menu
// In MS IE 6.
Enumeration enume = inputKeyStore.aliases();
while (enume.hasMoreElements()) // we are readin just on
// certificate.
{
String keyAlias = (String) enume.nextElement();
// System.out.println("alias=[" + keyAlias + "]");
if (inputKeyStore.isKeyEntry(keyAlias)) {
Key key = inputKeyStore.getKey(keyAlias, nPassword);
Certificate[] certChain = inputKeyStore
.getCertificateChain(keyAlias);
outputKeyStore.setKeyEntry(keyAlias, key, "123456"
.toCharArray(), certChain);
}
}
FileOutputStream out = new FileOutputStream(OUTPUT_KEYSTORE_FILE);
outputKeyStore.store(out, nPassword);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void jksaddcacert() {
try {
FileInputStream cacertfile = new FileInputStream(CACERT_FILE);
FileInputStream oldjks = new FileInputStream(OUTPUT_KEYSTORE_FILE);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf
.generateCertificate(cacertfile);
cacertfile.close();
KeyStore.TrustedCertificateEntry trustedEntry = new KeyStore.TrustedCertificateEntry(
cert);
KeyStore outputKeyStore = KeyStore.getInstance("JKS");
outputKeyStore.load(oldjks, "123456".toCharArray());
oldjks.close();
outputKeyStore.setEntry("ca_root", trustedEntry, null);
FileOutputStream out = new FileOutputStream(OUTPUT_KEYSTORE_FILE);
outputKeyStore.store(out, KEYSTORE_PASSWORD.toCharArray());
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static final String localDir = "src\\change_format\\";
public static void pemaddpk() {
try {
Runtime rt = Runtime.getRuntime();
Process ps = null;
String exeStr = "openssl pkcs12 -export -in " + localDir
+ "clientssl.crt -inkey " + localDir
+ "clientssl.pri -out " + localDir
+ "keystore.pkcs12 -passout pass:123456";
System.out.println(exeStr);
ps = rt.exec(exeStr);
ps.waitFor();
int i = ps.exitValue();
if (i == 0) {
System.out.println("Convert PKCS12 Sucess!");
} else {
System.out.println("Convert PKCS12 Error!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ConvertPEMToJKS.pemaddpk();
ConvertPEMToJKS.pktojks();
ConvertPEMToJKS.jksaddcacert();
}
}
2.如上例子中遇到一個問題,就是我程序所在的部署機將來需要安裝openssl才能執行pemaddpk()方法中的openssl腳本;或者需要部署 人員額外對證書頒發程序頒發的證書做個轉化。這兩種方法都是比較繁瑣的,終於找到BouncyCastle的開源SSL工具包可以直接讀入PEM格式並導入JKS,真是Java做OpenSSL的福音啊。
將bcprov-jdk15-143.jar導入工作路徑,我抽出了一小段代碼,以後要用到SSL的話可以方便的調用。
SSLContextBuild.java :
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
public class SSLContextBuild {
public static SSLContext getSSLContext() {
// 獲取證書路徑
Properties prop = new Properties();
try {
//這裏的路徑是放在javaweb應用程序的web-inf/class下的
prop.load(SSLContextBuild.class.getClassLoader()
.getResourceAsStream("SSLCertPath.properties"));
} catch (IOException e) {
e.printStackTrace();
}
SSLContext sslContext = null;
try {
// 設定Security的Provider提供程序
Security
.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// 讀入客戶端證書
PEMReader pr = new PEMReader(new FileReader(prop.get("client_cert")
.toString()));
X509Certificate cert = (X509Certificate) pr.readObject();
pr.close();
// 讀入客戶端私鑰
PEMReader kr = new PEMReader(new FileReader(prop.get(
"client_privatekey").toString()), new PasswordFinder() {
public char[] getPassword() {
return "".toCharArray();
}
});
KeyPair key = (KeyPair) kr.readObject();
kr.close();
// 建立空JKS
KeyStore ksKeys = KeyStore.getInstance("JKS");
ksKeys.load(null, "123456".toCharArray());
// 導入客戶端私鑰和證書
ksKeys.setKeyEntry("clientkey", key.getPrivate(), "123456"
.toCharArray(), new Certificate[] { cert });
ksKeys.setCertificateEntry("clientcert", cert);
// 導入根證書作爲trustedEntry,待改進代碼,此處沒用bouncycastle的API,用了應該能簡化點
FileInputStream cacertfile = new FileInputStream(prop.get("cacert")
.toString());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cacert = (X509Certificate) cf
.generateCertificate(cacertfile);
cacertfile.close();
KeyStore.TrustedCertificateEntry trustedEntry = new KeyStore.TrustedCertificateEntry(
cacert);
ksKeys.setEntry("ca_root", trustedEntry, null);
// 構建KeyManager、TrustManager
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ksKeys, "123456".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ksKeys);
// 構建SSLContext
sslContext = SSLContext.getInstance("SSL");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}
}
SSLCertPath.properties :
cacert=E:\\Ben\\ca2.crt
client_cert=E:\\Ben\\clientcert.crt
client_privatekey=E:\\Ben\\clientkey.pri
調用的時候:
try {
sslSocket = (SSLSocket) SSLContextBuild.getSSLContext()
.getSocketFactory()
.createSocket(HOST, PORT); //HOST like "192.168.1.X";PORT like 8888
sslSocket.setEnabledProtocols(new String[] { "SSLv3" }); //注意此處要與你服務器端的程序設定一致。現在比較常用的協議是SSLv3/TLSv1。
} catch (Exception e) {
e.printStackTrace();
}
文章比較老了,特別是pem 的讀取,沒有什麼借鑑意義。不過思路可以考慮,所以轉載下來。