我试图自动化使用Java在网站上的一些任务。 我有那个网站有效的客户端(当我登录使用Firefox的作品),但我不断收到一个403错误,当我尝试使用HTTP客户端进行登录。 请注意,我想我的信任存储相信任何东西(我知道这是不是安全的,但在这一点上我并不担心这一点)。
这里是我的代码:
KeyStore keystore = getKeyStore();//Implemented somewhere else and working ok
String password = "changeme";
SSLContext context = SSLContext.getInstance("SSL");
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmfactory.init(keystore, password.toCharArray());
context.init(kmfactory.getKeyManagers(), new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
} }, 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);
HttpGet get = new HttpGet("https://theurl.com");
HttpResponse response = client.execute(get);
System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8"));
最后一条语句打印出一个403错误。 我缺少的是在这里吗?
想通了拥有自己的。 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");
首先,无论你选择解决您的问题,不要使用任何信任信任管理。 从客户端查看,这无关什么使用的是去客户端证书,但它确实打开潜在的MITM攻击的连接(虽然MITM攻击者仍然有问题冒充合法的客户端)。
对于客户端证书方面,而不是使用其自己的密钥管理器上的定制方式,每次重新构建链,你能解决您的密钥库设置您的客户端证书项使用完整的链条,描述在这个答案 。 既然你已经开始了与一个PFX文件(PKCS#12),它可能是更好的密钥库首先转换为JKS,与keytool -importkeystore
,描述在这个答案 。