【问题标题】:How do I pass the client certificate with HTTP client?如何通过 HTTP 客户端传递客户端证书?
【发布时间】:2013-09-03 17:00:54
【问题描述】:

我想在服务 A 和 B 之间使用相互 SSL 身份验证。我目前正在用 Java 实现从服务 A 传递客户端证书。我正在使用 Apache DefaultHttpClient 来执行我的请求。我能够从内部凭据管理器中检索服务 A 的客户端证书,并将其保存为字节数组。

DefaultHttpClient client = new DefaultHttpClient();
byte [] certificate = localCertManager.retrieveCert();

我在这方面的经验很少,非常感谢您的帮助!

我想也许它应该以某种方式通过 HTTP 客户端或标头中的参数传递。

如何通过 HTTP 客户端传递客户端证书?

【问题讨论】:

标签: apache ssl https ssl-certificate apache-commons-httpclient


【解决方案1】:

客户端证书在建立连接时为sent during the TLS handshake,无法在该连接中通过 HTTP 发送。

通信是这样分层的:

  • 内的HTTP(应用层协议)
  • 内的 TLS(表示层协议)
  • 内的 TCP(传输层协议)
  • IP(网络层协议)

您需要在 TLS 握手期间发送客户端证书,然后才能影响任何 HTTP(方法、标头、URL、请求正文)。服务器将不接受稍后发送的客户端证书。

我建议从 DefaultHttpClient(已弃用)切换到 CloseableHttpClient,这样可以更干净地使用 try-with-resources。

Apache HttpClient 4.5 使 Mutual TLS 相当方便。此答案已通过 Apache HttpClient 4.5.3 测试。

基本的起点是使用 loadKeyMaterial 将您的客户端证书及其密钥(客户端密钥对)加载到 SSLContext

SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(
                MutualHttpsMain.class.getResource(TEST_CLIENT_KEYSTORE_RESOURCE),
                storePassword, keyPassword,
                (aliases, socket) -> aliases.keySet().iterator().next()
        ).build();

最后用那个套接字工厂构建一个 HTTP 客户端:

CloseableHttpClient httpclient = HttpClients
        .custom().setSSLContext(sslContext).build();

使用该客户端,您的所有请求都可以在隐含的相互 TLS 身份验证的情况下执行:

CloseableHttpResponse closeableHttpResponse = httpclient.execute(
        new HttpGet(URI.create("https://mutual-tls.example.com/")));

这是一个使用 Apache HttpClient 的双向 TLS 的完整可运行示例:

import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import java.io.Console;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;

public class MutualHttpsMain {
    private static final String TEST_URL = "https://mutual-tls.example.com/";
    private static final String TEST_CLIENT_KEYSTORE_RESOURCE = "/mutual-tls-keystore.p12";

    public static void main(String[] args) throws GeneralSecurityException, IOException {
        Console console = System.console();
        char[] storePassword = console.readPassword("Key+Keystore password: ");
        char[] keyPassword = storePassword;
        SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(
                MutualHttpsMain.class.getResource(TEST_CLIENT_KEYSTORE_RESOURCE),
                storePassword, keyPassword,
                (aliases, socket) -> aliases.keySet().iterator().next()
        ).build();
        try (CloseableHttpClient httpclient = HttpClients
                .custom().setSSLContext(sslContext).build();
             CloseableHttpResponse closeableHttpResponse = httpclient.execute(
                    new HttpGet(URI.create(TEST_URL)))) {
            console.writer().println(closeableHttpResponse.getStatusLine());
            HttpEntity entity = closeableHttpResponse.getEntity();
            try (InputStream content = entity.getContent();
                 ReadableByteChannel src = Channels.newChannel(content);
                 WritableByteChannel dest = Channels.newChannel(System.out)) {
                ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
                while (src.read(buffer) != -1) {
                    buffer.flip();
                    dest.write(buffer);
                    buffer.compact();
                }
                buffer.flip();
                while (buffer.hasRemaining())
                    dest.write(buffer);
            }
        }
    }
}

通常最好使用 Gradle 或 Maven 来运行类似的东西,但为了尽可能减少 Yak shave,我提供了用于构建和运行它的基准 JDK 指令。

从以下页面下载 JAR:

将上面的完整示例保存为 MutualHttpsMain.java

将您的 PKCS#12 复制到同一目录中的 mutual-tls-keystore.p12

编译如下(在 macOS/Linux/*nix-likes 上):

javac MutualHttpsMain.java -cp httpclient-4.5.3.jar:httpcore-4.4.8.jar

或者在 Windows 上:

javac MutualHttpsMain.java -cp httpclient-4.5.3.jar;httpcore-4.4.8.jar

运行如下(在 macOS/Linux/*nix-likes 上):

java -cp httpclient-4.5.3.jar:commons-codec-1.10.jar:commons-logging-1.2.jar:httpcore-4.4.8.jar:. MutualHttpsMain

运行如下(在 Windows 上):

java -cp httpclient-4.5.3.jar;commons-codec-1.10.jar;commons-logging-1.2.jar;httpcore-4.4.8.jar;. MutualHttpsMain

【讨论】:

  • @magnus 有一个简洁的例子双向 TLS java HTTP 客户端例子在这里stackoverflow.com/a/32513368/154527
  • password, password, 令人困惑。最好分别重命名为keyPasswordkeystorePassword
  • @degr 公平点。您如何看待我为解决这一困惑而进行的编辑?
  • 据我所知,PKCS#12 不支持 storePassword 和 keyPassword 之间的区别,实际上根据我的经验,它们必须相同。
  • 证书不应使用密码保护 - 它是公共信息。密钥库密码 - 来自文件的密码,其中存储了密钥、证书和密钥对以及其他内容。私钥像往常一样受密码保护。但它是不同的密码。保持此密码相同 - 就像住在多套公寓的房子里,房子入口和里面的每个公寓都有相同的钥匙。如果一切都属于你,那就安全了。但是,我不是安全工程师,这只是我的理解,可能是错误的。
【解决方案2】:

您需要告诉 SSLSocketFactory(org.apache.http,而不是 javax)您的密钥库,并配置您的 DefaultHTTPClient 以将其用于 https 连接。

这里有一个例子:http://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientCustomSSL.java

【讨论】:

  • 酷,这真的很有帮助!
  • 这开始有点正确,但是链接的示例由于不使用客户端证书而使事情变得混乱。它实际上设置了替代 CA 证书来信任所连接的服务器,但对于不太熟悉 SSLContexts 的人来说,这可能是一个正确的例子。我遵循了这一点并花费了大量时间来解开 loadTrustMaterial 不起作用。
  • 链接失效
  • 这个链接可能是同一个例子:javatips.net/api/uw-android-master/UWPreloader/…
猜你喜欢
  • 2017-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-30
  • 2018-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多