java - 使用有效的客户端证书时出现 HttpClient 403 错误

标签 java ssl certificate apache-commons-httpclient

我正在尝试使用 Java 自动执行网站上的某些任务。我有一个该网站的有效客户端(当我使用 firefox 登录时有效),但是当我尝试使用 http 客户端登录时,我不断收到 403 错误。请注意,我希望我的信任库信任任何东西(我知道这不安全,但此时我并不担心)。

这是我的代码:

    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 根本无法在我的 keystore 中找到合适的证书。看起来 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;
    }
}

然后我实现了一个 key 管理器,该管理器始终使用我想要的来自 PKCS12 (.pfx) 证书的 key :

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 还包含其颁发者证书,这将 起作用。但它没有(耶!)。因此,当我如上所述使用 key 管理器时,出现 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;
}
...

在那之后,我只需要好好利用我的自定义 key 和信任管理器:

            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");

关于java - 使用有效的客户端证书时出现 HttpClient 403 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14403444/

相关文章:

java - 使用给定的 p12 证书连接到 https 站点

powershell - 确定是否使用 CN(通过 powershell 2.0)安装了证书?

java - 如何判断字符串中的某个字符是否为大写

java - 如何使用仅带有 SRC 的 Selenium 单击图像

.htaccess - SSL:HTTP www 重定向到非 www HTTPS

ssl - 无法验证 ip 的证书,因为它不包含任何 IP SAN

java - hibernate中的传递持久性顺序

java - Android dagger2 使用 Factory 将 Intent 注入(inject) viewModel

maven-2 - 记录 Jetty SSL 客户端证书问题

ios - xcodebuild - 通过命令行设置证书