java - 具有相互身份验证的 Https 请求通过 curl 但通过 java 失败

标签 java ssl curl mutual-authentication

github上有人问我一个关于我的图书馆的问题。这个库提供了一些工厂类来轻松创建 sslcontext。我确保不共享库的详细信息,只共享纯 java 代码和我使用的附加库。我尝试调试它并尝试重新格式化证书,但它不起作用。我得到的异常(exception)是:

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
java trustmanager 基本上是说我收到了服务器的证书,我刚刚检查了您的受信任证书列表,但找不到匹配的证书。所以我不信任这个服务器,因此我停止了这个请求。当我调试它时,我可以理解为什么信任管理器会抛出异常,因为根本没有与服务器相同的匹配证书。但是,当我将相同的文件与 curl 一起使用时,它可以工作,所以我不太确定证书和 key Material 是否存在问题,或者 java ssl 实现有一些限制,或者我没有正确配置它......我希望有人可以向我解释为什么它在 java 中失败并在 curl 中通过的行为差异。
客户端私钥内容:
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCw9Of6paN76eY7MApVZrPLt3Ss9rYOSkUHqw9ls8ewxLqlfk/S
7tdQ6g1+fa4HnKeSXRRrBv7RNlnwRPzBIha5QSwVSraT079xnEP/MGlZ+lyS4Kad
xv/m96jJXk9w8GaHY8OiwfJqcTolVKVEzDKVDIxp3noYfCRVW4RDm0FMJwIDAQAB
AoGARQMOYbMtogrjblva+9l071MZ3sbM05/lcgslkx1dGLRwslAjo3jgYj8ViipL
r85JkAxbBS6SPFd9FfZhuJSp1WnXAP63GHB8NUb+hqrFqDxLuNsFQ1Q04+0FDlJw
+YUD7QZWwXy28clt9arEIoPeikOmePDQF7FGuU6f0+aNxAECQQDcWFfTDLvAMPNX
T6WsuYYIs+1mYYxImElsaTfm36Ro9C+2atVnxnqY6JFhWh6Sn+7dNiWS4vsbH1uZ
7EiQKpoBAkEAzZc5jxmfG/5f8uKQh49ORTaIOSyivd/L60rVNcdRyukFrFPTb/Ey
o2LsvDzDclfdGdhehkEAdX+Biksz0gfWJwJAIvuvreVWpbPf3pvZnOuzmQwgA+I2
6IutFJY79t7I9pTWQmsByMEdU8uQ0VkCg5r6zIo9Ou3omizHWU/HUYRCAQJAJXmN
SmJXOFkT0EgwJCWhFMit6A4U1Bt5JjiLyLO+WwhCunjFL8B9hH7BvEYvMiaF7PId
uMcceE53pGe02HIJPQJAXuCB+O0zqnSci3dP5cH724qNbRMvH0IvI6PFK8RIEd7I
d+HKKVGXhPC1mMhIbAqydxmTCDewsSH9rlghhTvqIg==
-----END RSA PRIVATE KEY-----
客户端证书链内容:
-----BEGIN CERTIFICATE-----
MIICXDCCAcUCAQEwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCSUUxEDAOBgNV
BAgMB0RvbmVnYWwxFDASBgNVBAcMC0xldHRlcmtlbm55MRQwEgYDVQQDDAt0cGVh
cnNvbi5pZTEjMCEGCSqGSIb3DQEJARYUcm9vdGNlcnRAdHBlYXJzb24uaWUwHhcN
MjEwMTA4MDE1MDUzWhcNMjIwMTA4MDE1MDUzWjB9MQswCQYDVQQGEwJJRTEQMA4G
A1UECAwHRG9uZWdhbDEUMBIGA1UEBwwLTGV0dGVya2VubnkxHzAdBgNVBAMMFmNs
aWVudGNlcnQudHBlYXJzb24uaWUxJTAjBgkqhkiG9w0BCQEWFmNsaWVudGNlcnRA
dHBlYXJzb24uaWUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALD05/qlo3vp
5jswClVms8u3dKz2tg5KRQerD2Wzx7DEuqV+T9Lu11DqDX59rgecp5JdFGsG/tE2
WfBE/MEiFrlBLBVKtpPTv3GcQ/8waVn6XJLgpp3G/+b3qMleT3DwZodjw6LB8mpx
OiVUpUTMMpUMjGneehh8JFVbhEObQUwnAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEA
bwGc1UM0qhKrJdjhXiXcVcf+ebyQKJHe3wXX3ffdw7Y7Gg9CjbD21kdlTVlSJrLT
hC5k01NChPDScK0C5CUPjEtHCt/G4sEPy+R1tXAmOBMnJXHqHQ/gsk1xe2TXZTep
uX5Al1vhtY7iVOnxsoRt2rdvYLRhpvf+7qbrchlQtXE=
-----END CERTIFICATE-----
可信证书,ca
-----BEGIN CERTIFICATE-----
MIICVzCCAcACCQCr1Uz2zH5PwzANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJJ
RTEQMA4GA1UECAwHRG9uZWdhbDEUMBIGA1UEBwwLTGV0dGVya2VubnkxFDASBgNV
BAMMC3RwZWFyc29uLmllMSMwIQYJKoZIhvcNAQkBFhRyb290Y2VydEB0cGVhcnNv
bi5pZTAeFw0yMTAxMDgwMTQ0NDRaFw0yMjAxMDgwMTQ0NDRaMHAxCzAJBgNVBAYT
AklFMRAwDgYDVQQIDAdEb25lZ2FsMRQwEgYDVQQHDAtMZXR0ZXJrZW5ueTEUMBIG
A1UEAwwLdHBlYXJzb24uaWUxIzAhBgkqhkiG9w0BCQEWFHJvb3RjZXJ0QHRwZWFy
c29uLmllMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJ0E9LXa/6XNzm1CZ3
lHLez768tSAmrQ0qYFCiyrGhnfe+xJJAf7BSWRAUQmM7ULRj89NVMor7GyBYH1mq
1r9dI23i3KM8ZAeBN+32eifOH/TqE2QKLm2ORqBzNXsl1QTriXLR4aIs8bYUH6JP
9qGE24ncqeO1/Ry6o2DxQWEkLQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADq/CDEC
TwmegkvOMTH7mlWWgaoVWgBU6mtUjm2fJtQOFDewicQlxa2jF3LwDwb94J5vu6Kc
NAZwLWE70+pWhhcsXWc0rbA1Ayv1xkyi8LPWqqmCbz14R2tEDuPZJxKvS0DkGKqy
De4sdJsOo2GWhYsmY6HiOPElt5ndzI5NRZ6s
-----END CERTIFICATE-----
因此,以下 curl 命令将成功执行并为我提供正确的响应正文:
curl --cacert ca.pem --key client-key.pem --cert client-cert.pem https://prod.idrix.eu/secure/
为了在 Java 中加载这些文件,我使用 Bouncy CaSTLe,下面是解析它的完整片段,将其加载到 TrustManager、KeyManager、创建 SSLContext 并执行请求:
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Objects;
import java.util.stream.Collectors;

public class App {

    private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
    private static final JcaPEMKeyConverter KEY_CONVERTER = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER);
    private static final JcaX509CertificateConverter CERTIFICATE_CONVERTER = new JcaX509CertificateConverter().setProvider(BOUNCY_CASTLE_PROVIDER);

    public static void main(String[] args) throws Exception {
        String clientKeyContent = App.getContent(App.class.getClassLoader().getResourceAsStream("client-key.pem"));
        String clientCertificateChainContent = App.getContent(App.class.getClassLoader().getResourceAsStream("client-cert.pem"));
        String caCertificateContent = App.getContent(App.class.getClassLoader().getResourceAsStream("ca.pem"));

        Reader reader = new StringReader(clientKeyContent);
        PEMParser pemParser = new PEMParser(reader);
        Object object = pemParser.readObject();

        PrivateKeyInfo privateKeyInfo = ((PEMKeyPair) object).getPrivateKeyInfo();
        PrivateKey clientKey = KEY_CONVERTER.getPrivateKey(privateKeyInfo);
        reader.close();
        pemParser.close();

        reader = new StringReader(clientCertificateChainContent);
        pemParser = new PEMParser(reader);

        object = pemParser.readObject();
        X509Certificate clientCertificateChain = CERTIFICATE_CONVERTER.getCertificate((X509CertificateHolder) object);
        reader.close();
        pemParser.close();

        reader = new StringReader(caCertificateContent);
        pemParser = new PEMParser(reader);

        object = pemParser.readObject();
        X509Certificate caCertificate = CERTIFICATE_CONVERTER.getCertificate((X509CertificateHolder) object);
        reader.close();
        pemParser.close();

        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, "password".toCharArray());
        keyStore.setKeyEntry("client", clientKey, "".toCharArray(), new Certificate[]{clientCertificateChain});
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, "".toCharArray());

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, "password".toCharArray());
        trustStore.setCertificateEntry("ca", caCertificate);
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);

        HttpClient httpClient = HttpClient.newBuilder()
                .sslContext(sslContext)
                .build();

        HttpRequest request = HttpRequest.newBuilder()
                .GET()
                .uri(URI.create("https://prod.idrix.eu/secure/"))
                .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }

    public static String getContent(InputStream inputStream) throws IOException {
        try (InputStreamReader inputStreamReader = new InputStreamReader(Objects.requireNonNull(inputStream), StandardCharsets.UTF_8);
             BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {

            return bufferedReader.lines()
                    .collect(Collectors.joining(System.lineSeparator()));
        }
    }

}
我正在使用 Java 11 和 Bouncy CaSTLe,具体的 artifact-id 见下文:
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.68</version>
</dependency>
所以对我来说,不清楚为什么 curl 命令有效并且 java 中的设置失败。我是否配置错误,任何帮助都会很棒!我试图提供尽可能多的细节。私钥是一个测试 key ,所以在这里分享它不会有害。

最佳答案

我发现了问题所在,因此我想与其他有共同点的人分享。
看起来在 mac os x curl 上默认使用系统受信任的证书,而在 java 端则并非如此。我不确定 curl 在其他操作系统中的行为是什么。 https://prod.idrix.eu/secure/有一个由 DST Root CA X3 签名的证书我禁用了它并重新运行与我最初问题中描述的完全相同的设置,并且 curl https 请求失败,就像它在 java 端也失败一样。
我从钥匙串(keychain)中禁用了它,如下所示:
enter image description here
enter image description here

关于java - 具有相互身份验证的 Https 请求通过 curl 但通过 java 失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65648160/

相关文章:

docker - 使用 Mosquitto 客户端测试 VerneMQ 代理 TLS

Facebook Chatbot Persistent Menu 不起作用(需要参数 setting_type)

java - 通过指定键和值类型扩展映射

java - JPA 合并第一次不起作用,但第二次起作用

c - 使用 C 和 Lua 中的套接字保存来自 http 响应的图像

rest - 仅为我的应用程序验证 REST API 客户端

php - 如何设置PHP将文件下载到特定目录?

javascript - PHP & cURL——奇怪的输出

java - eclipse 将母语更改为英语

Java DOM 弄乱了我的 XML header 并自行添加属性