我正在运行一个dropwizard服务器,以及一个利用Apache HttpClient 4.5.1的客户端 .

给定一个包含公钥和私钥的.pfx文件,如何在服务器和客户端上构建我的密钥/信任存储以接受/信任并传递证书以进行身份验证?

我遇到的是客户端信任提供的服务器证书,但在服务器问候后,包含每个tls规范的证书请求,我的客户端无法找到合适的证书发回 .

我的第一个想法是将密钥库和信任库作为相同的pfx文件运行服务器,但是当将pfx文件作为服务器中的信任存储加载时,java会抛出空的证书链错误 . 所以我不得不手动创建一个信任库 .

以下是我认为允许整个过程成功的一般步骤:

  • 使用带有PKCS12密钥库类型的.pfx文件运行服务器 .

  • 从pfx文件中提取证书,并使用cert创建Java信任库 .

  • 使用上述clientCerts.jks文件作为信任库运行服务器

  • 使用设置为clientCerts.jks文件的密钥库运行客户端

  • 使用设置为.pfx PKCS12密钥库的信任库运行客户端 .

这些步骤不起作用,我尝试了其他不那么明显的排列,但没有一个起作用 . 我接近这个的方式有什么明显的错误吗?有没有人对实际让它起作用有任何建议?

下面有很多细节(包括ssl调试日志)

PFX证书信息:(它是一个有效的公司签名证书,但我没有任何地方可信任的根CA,这就是为什么我只创建一个信任存储,以便我可以信任客户端证书) .

$ openssl pkcs12 -info -in cert.pfx
Enter Import Password:
MAC Iteration 1
MAC verified OK
PKCS7 Data
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2000
Bag Attributes
    Microsoft Local Key set: <No Values>
    localKeyID: 01 00 00 00
    friendlyName: xxx
    Microsoft CSP Name: Microsoft RSA SChannel Cryptographic Provider
Key Attributes
    X509v3 Key Usage: 10
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----BEGIN ENCRYPTED PRIVATE KEY-----
xxx
-----END ENCRYPTED PRIVATE KEY-----
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2000
Certificate bag
Bag Attributes
    localKeyID: 01 00 00 00
    friendlyName: my.domain.com
subject=/C=US/O=My Company/OU=Web Servers/CN=my.domain.com
issuer=/C=US/O=My Company
-----BEGIN CERTIFICATE-----
xxx
-----END CERTIFICATE-----

Java Trust store创建:

//create pem file
openssl pkcs12 -in cert.pfx -out tempCert.crt -nokeys -clcerts

//convert to x509
openssl x509 -inform pem -in tempCert.crt -outform der -out tempx509Cert.cer

//create a java trust store
keytool -import -file tempx509Cert.cer -alias firstCA -keystore newJavaTrustStore.jks

Dropwizard配置:

applicationConnectors:
    - type: https
      port: 443
      bindHost: localhost 
      keyStorePath: ./cert.pfx
      keyStorePassword: pw
      keyStoreType: PKCS12
      trustStorePath: ./clientCerts.jks
      trustStorePassword: pw
      trustStoreType: JKS
      supportedProtocols: [TLSv1, TLSv1.1, TLSv1.2]
      excludedProtocols: [SSLv2Hello, SSLv3]
      validateCerts: false
      needClientAuth: true
      wantClientAuth: true

HttpClient配置值:

keyStorePath: ./clientCerts.jks
keyStorePassword: pw
keyStoreType: JKS   
trustStorePath: ./cert.pfx
trustStorePassword: pw
trustStoreType: PKCS12

HttpClient配置:

public static CloseableHttpClient getSecurePooledHttpClient(
        final String host,
        final int port,
        final boolean ssl,
        final String keystorePath,
        final String keystorePassword,
        final String keystoreType,
        final String trustStorePath,
        final String trustStorePassword,
        final String trustStoreType



) throws Exception {

    //Setup the keystore that will hold the client certificate
    KeyStore ks = KeyStore.getInstance(keystoreType);
    ks.load(new FileInputStream(new File(keystorePath)),
            keystorePassword.toCharArray());

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(ks, keystorePassword.toCharArray());

    //Setup the Trust Store so we know what certificates 
    //we can trust that are hosting the service
    KeyStore ts = KeyStore.getInstance((trustStoreType));
    ts.load(new FileInputStream(new File(trustStorePath)), 
            trustStorePassword.toCharArray());
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    tmf.init(ts);


    //setup our SSL context to be TLSv1.2, then setup the key and trust manager.
    SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);



    //Register the socket factory so that it uses the ssl Context and key
    // manager we created above
    Registry<ConnectionSocketFactory> socketFactoryRegistry = 
            RegistryBuilder.<ConnectionSocketFactory>create()
            .register("https", new SSLConnectionSocketFactory(sslContext, 
                    NoopHostnameVerifier.INSTANCE))
            .build();

    //Define an overridden routeplanner that setups up our default host 
    // so all our later calls can simply be
    //sub-routes.
    HttpRoutePlanner routePlanner = 
            new DefaultRoutePlanner(DefaultSchemePortResolver.INSTANCE)
    {
        @Override
        public HttpRoute determineRoute(
                final HttpHost target,
                final HttpRequest request,
                final HttpContext context) throws HttpException {
            return super.determineRoute(
                    target != null ? target : new HttpHost(host, port, ssl ? "https" : "http"),
                    request, context);
        }
    };
    return BuildClientWithRoutePlanner(socketFactoryRegistry, routePlanner);

客户端SSL调试:

...

*** ServerHello, TLSv1.2
RandomCookie:  Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: <empty>
***
%% Initialized:  [Session-7, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256]
** TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
*** Certificate chain
chain [0] = [
[
  Version: V3
  Subject: CN=my.domain.com, OU=Web Servers, O=My Company, C=US
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

.........

***
Found trusted certificate:
[
[
  Version: V3
  Subject: CN=my.domain.com, OU=Web Servers, O=My Company, C=US
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits

......

*** CertificateRequest
Cert Types: RSA, DSS, ECDSA
Supported Signature Algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Cert Authorities:
<CN=my.domain.com, OU=Web Servers, O=My Company, C=US>
*** ServerHelloDone
Warning: no suitable certificate found - continuing without client authentication
*** Certificate chain
<Empty>
***