想通了自己的。出現403錯誤是因爲Java SSL未選擇我的客戶端證書。
我調試了SSL握手,發現服務器要求客戶端證書由一系列機構頒發,並且我的客戶端證書的頒發者不在該列表中。所以Java SSL根本無法在我的密鑰庫中找到合適的證書。它看起來像Web瀏覽器和Java實現SSL有點不同,因爲我的瀏覽器實際上詢問我使用哪個證書,無論服務器證書在客戶端證書頒發者方面要求什麼。
在這種情況下,服務器證書是責任。它是自簽名的,它通知可接受的發行人名單不完整。這與Java SSL實現不兼容。但服務器不是我的,除了抱怨巴西政府(他們的服務器),我無能爲力。如果沒有進一步的因,這裏是我的解決方法:
首先,我用的是信任任何一個的TrustManager(就像我在我的問題一樣):
public class MyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
然後我實現它總是使用密鑰我一個密鑰管理器從PKCS12(.PFX)證書要:
public class MyKeyManager extends X509ExtendedKeyManager {
KeyStore keystore = null;
String password = null;
public MyKeyManager(KeyStore keystore, String password) {
this.keystore = keystore;
this.password = password;
}
@Override
public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
return "";//can't be null
}
@Override
public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) {
return null;
}
@Override
public X509Certificate[] getCertificateChain(String arg0) {
try {
X509Certificate[] result = new X509Certificate[keystore.getCertificateChain(keystore.aliases().nextElement()).length];
for (int i=0; i < result.length; i++){
result[i] = (X509Certificate) keystore.getCertificateChain(keystore.aliases().nextElement())[i];
}
return result ;
} catch (Exception e) {
}
return null;
}
@Override
public String[] getClientAliases(String arg0, Principal[] arg1) {
try {
return new String[] { keystore.aliases().nextElement() };
} catch (Exception e) {
return null;
}
}
@Override
public PrivateKey getPrivateKey(String arg0) {
try {
return ((KeyStore.PrivateKeyEntry) keystore.getEntry(keystore.aliases().nextElement(),
new KeyStore.PasswordProtection(password.toCharArray()))).getPrivateKey();
} catch (Exception e) {
}
return null;
}
@Override
public String[] getServerAliases(String arg0, Principal[] arg1) {
return null;
}
}
這想,如果工作我PFX也包含在其頒發者證書。但它不(耶!)。所以當我像上面那樣使用密鑰管理器的時候,我得到了一個SSL握手錯誤(對端沒有通過身份驗證)。如果客戶端發送服務器信任的證書鏈,則服務器僅對客戶端進行身份驗證。由於我的證書(由巴西代理機構簽發)不包含其頒發者,因此其證書鏈僅包含其自身。服務器不喜歡這種情況,並拒絕認證客戶端。解決方法是手動創建的證書鏈:
...
@Override
//The order matters, your certificate should be the first one in the chain, its issuer the second, its issuer's issuer the third and so on.
public X509Certificate[] getCertificateChain(String arg0) {
X509Certificate[] result = new X509Certificate[2];
//The certificate chain contains only one entry in my case
result[0] = (X509Certificate) keystore.getCertificateChain(keystore.aliases().nextElement())[0];
//Implement getMyCertificateIssuer() according to your needs. In my case, I read it from a JKS keystore from my database
result[1] = getMyCertificateIssuer();
return result;
}
...
之後,它只是一個把我的自定義鍵和信任管理器很好地利用的事:
InputStream keystoreContents = null;//Read it from a file, a byte array or whatever floats your boat
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(keystoreContetns, "changeme".toCharArray());
SSLContext context = SSLContext.getInstance("TLSv1");
context.init(new KeyManager[] { new MyKeyManager(keystore, "changeme") },
new TrustManager[] { new MyTrustManager() }, new SecureRandom());
SSLSocketFactory sf = new SSLSocketFactory(context);
Scheme httpsScheme = new Scheme("https", 443, sf);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(httpsScheme);
ClientConnectionManager cm = new SingleClientConnManager(schemeRegistry);
HttpClient client = new DefaultHttpClient(cm);
HttpPost post = new HttpPost("https://www.someserver.com");
我想知道你是如何調試ssl手抖動過程來弄清楚這一點? –
@AlbertCheng我啓用了SSL調試,以查看來回發送的內容。除此之外,還涉及到很多試驗和錯誤。 – Andre
救了我一命大男人。但是,嘿,我期待着問題,這是巴西。乾杯,夥伴 –