简体   繁体   English

如何创建包含客户端证书链的BKS(BouncyCastle)格式Java密钥库

[英]How to create a BKS (BouncyCastle) format Java Keystore that contains a client certificate chain

I'm writing an Android app that requires SSL client authentication. 我正在编写一个需要SSL客户端身份验证的Android应用。 I know how to create a JKS keystore for a desktop Java application, but Android only supports the BKS format. 我知道如何为桌面Java应用程序创建JKS密钥库,但Android仅支持BKS格式。 Every way I've tried to create the keystore results in the following error: 我试图创建密钥库的每一种方式都会导致以下错误:
handling exception: javax.net.ssl.SSLHandshakeException: null cert chain

So it looks like the client is never sending a proper certificate chain, probably because I'm not creating the keystore properly. 所以看起来客户端永远不会发送正确的证书链,可能是因为我没有正确创建密钥库。 I'm unable to enable SSL debugging like I can on the desktop, so that's making this much more difficult than it should be. 我无法在桌面上启用SSL调试,因此这使得它比应该更加困难。

For reference the following is the command that IS working to create a BKS truststore : 作为参考,以下是IS用于创建BKS 信任库的命令:
keytool -importcert -v -trustcacerts -file "cacert.pem" -alias ca -keystore "mySrvTruststore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest


Here is the command I've tried that is NOT working to create a BKS client keystore : 这是我尝试过的命令,它无法创建BKS客户端密钥库

cat clientkey.pem clientcert.pem cacert.pem > client.pem

keytool -import -v -file <(openssl x509 -in client.pem) -alias client -keystore "clientkeystore" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest

Detailed Step by Step instructions I followed to achieve this 为实现这一目标,我遵循详细的逐步说明

  • Download bouncycastle JAR from http://repo2.maven.org/maven2/org/bouncycastle/bcprov-ext-jdk15on/1.46/bcprov-ext-jdk15on-1.46.jar or take it from the "doc" folder. http://repo2.maven.org/maven2/org/bouncycastle/bcprov-ext-jdk15on/1.46/bcprov-ext-jdk15on-1.46.jar下载bouncycastle JAR,或从“doc”文件夹中下载。
  • Configure BouncyCastle for PC using one of the below methods. 使用以下方法之一配置BouncyCastle for PC。
    • Adding the BC Provider Statically (Recommended) 静态添加BC提供程序(推荐)
      • Copy the bcprov-ext-jdk15on-1.46.jar to each 将bcprov-ext-jdk15on-1.46.jar复制到每个
        • D:\\tools\\jdk1.5.0_09\\jre\\lib\\ext (JDK (bundled JRE) D:\\ tools \\ jdk1.5.0_09 \\ jre \\ lib \\ ext(JDK(捆绑的JRE)
        • D:\\tools\\jre1.5.0_09\\lib\\ext (JRE) D:\\ tools \\ jre1.5.0_09 \\ lib \\ ext(JRE)
        • C:\\ (location to be used in env variable) C:\\(在env变量中使用的位置)
      • Modify the java.security file under 修改下的java.security文件
        • D:\\tools\\jdk1.5.0_09\\jre\\lib\\security d:\\工具\\ jdk1.5.0_09 \\ JRE \\ lib \\ security中
        • D:\\tools\\jre1.5.0_09\\lib\\security d:\\工具\\ jre1.5.0_09 \\ lib \\ security中
        • and add the following entry 并添加以下条目
          • security.provider.7=org.bouncycastle.jce.provider.BouncyCastleProvider security.provider.7 = org.bouncycastle.jce.provider.BouncyCastleProvider
      • Add the following environment variable in "User Variables" section 在“用户变量”部分中添加以下环境变量
        • CLASSPATH=%CLASSPATH%;c:\\bcprov-ext-jdk15on-1.46.jar CLASSPATH =%CLASSPATH%; C:\\ bcprov-EXT-jdk15on-1.46.jar
    • Add bcprov-ext-jdk15on-1.46.jar to CLASSPATH of your project and Add the following line in your code 将bcprov-ext-jdk15on-1.46.jar添加到项目的CLASSPATH中,并在代码中添加以下行
      • Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
  • Generate the Keystore using Bouncy Castle 使用Bouncy Castle生成Keystore
    • Run the following command 运行以下命令
      • keytool -genkey -alias myproject -keystore C:/myproject.keystore -storepass myproject -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider keytool -genkey -alias myproject -keystore C:/myproject.keystore -storepass myproject -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
    • This generates the file C:\\myproject.keystore 这将生成文件C:\\ myproject.keystore
    • Run the following command to check if it is properly generated or not 运行以下命令以检查是否正确生成
      • keytool -list -keystore C:\\myproject.keystore -storetype BKS keytool -list -keystore C:\\ myproject.keystore -storetype BKS
  • Configure BouncyCastle for TOMCAT 为TOMCAT配置BouncyCastle

    • Open D:\\tools\\apache-tomcat-6.0.35\\conf\\server.xml and add the following entry 打开D:\\ tools \\ apache-tomcat-6.0.35 \\ conf \\ server.xml并添加以下条目

      • <Connector port="8443" keystorePass="myproject" alias="myproject" keystore="c:/myproject.keystore" keystoreType="BKS" SSLEnabled="true" clientAuth="false" protocol="HTTP/1.1" scheme="https" secure="true" sslProtocol="TLS" sslImplementationName="org.bouncycastle.jce.provider.BouncyCastleProvider"/> <Connector port =“8443”keystorePass =“myproject”alias =“myproject”keystore =“c:/myproject.keystore”keystoreType =“BKS”SSLEnabled =“true”clientAuth =“false”protocol =“HTTP / 1.1”scheme =“https”secure =“true”sslProtocol =“TLS”sslImplementationName =“org.bouncycastle.jce.provider.BouncyCastleProvider”/>
    • Restart the server after these changes. 在这些更改后重新启动服务器。

  • Configure BouncyCastle for Android Client 为Android客户端配置BouncyCastle
    • No need to configure since Android supports Bouncy Castle Version 1.46 internally in the provided "android.jar". 无需配置,因为Android在提供的“android.jar”内部支持Bouncy Castle版本1.46。
    • Just implement your version of HTTP Client (MyHttpClient.java can be found below) and set the following in code 只需实现您的HTTP客户端版本(MyHttpClient.java可以在下面找到)并在代码中设置以下内容
      • SSLSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); SSLSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
    • If you don't do this, it gives an exception as below 如果你不这样做,它会给出一个例外,如下所示
      • javax.net.ssl.SSLException: hostname in certificate didn't match: <192.168.104.66> != javax.net.ssl.SSLException:证书中的主机名不匹配:<192.168.104.66>!=
    • In production mode, change the above code to 在生产模式下,将上面的代码更改为
      • SSLSocketFactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); SSLSocketFactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

MyHttpClient.java MyHttpClient.java

package com.arisglobal.aglite.network;

import java.io.InputStream;
import java.security.KeyStore;

import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;

import com.arisglobal.aglite.activity.R;

import android.content.Context;

public class MyHttpClient extends DefaultHttpClient {

    final Context context;

    public MyHttpClient(Context context) {
        this.context = context;
    }

    @Override
    protected ClientConnectionManager createClientConnectionManager() {
        SchemeRegistry registry = new SchemeRegistry();

        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));

        // Register for port 443 our SSLSocketFactory with our keystore to the ConnectionManager
        registry.register(new Scheme("https", newSslSocketFactory(), 443));
        return new SingleClientConnManager(getParams(), registry);
    }

    private SSLSocketFactory newSslSocketFactory() {
        try {
            // Get an instance of the Bouncy Castle KeyStore format
            KeyStore trusted = KeyStore.getInstance("BKS");

            // Get the raw resource, which contains the keystore with your trusted certificates (root and any intermediate certs)
            InputStream in = context.getResources().openRawResource(R.raw.aglite);
            try {
                // Initialize the keystore with the provided trusted certificates.
                // Also provide the password of the keystore
                trusted.load(in, "aglite".toCharArray());
            } finally {
                in.close();
            }

            // Pass the keystore to the SSLSocketFactory. The factory is responsible for the verification of the server certificate.
            SSLSocketFactory sf = new SSLSocketFactory(trusted);

            // Hostname verification from certificate
            // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}

How to invoke the above code in your Activity class: 如何在Activity类中调用上面的代码:

DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpResponse response = client.execute(...);

我使用Portecle ,它就像一个魅力。

I don't think your problem is with the BouncyCastle keystore; 我认为您的问题不在于BouncyCastle密钥库; I think the problem is with a broken javax.net.ssl package in Android. 我认为问题在于Android中破坏的javax.net.ssl包。 The BouncyCastle keystore is a supreme annoyance because Android changed a default Java behavior without documenting it anywhere -- and removed the default provider -- but it does work. BouncyCastle密钥库是一个至高无上的烦恼,因为Android更改了默认的Java行为,而没有在任何地方记录它 - 并删除了默认提供程序 - 但它确实有效。

Note that for SSL authentication you may require 2 keystores. 请注意,对于SSL身份验证,您可能需要2个密钥库。 The "TrustManager" keystore, which contains the CA certs, and the "KeyManager" keystore, which contains your client-site public/private keys. “TrustManager”密钥库(包含CA证书)和“KeyManager”密钥库(包含客户端站点公钥/私钥)。 (The documentation is somewhat vague on what needs to be in the KeyManager keystore.) In theory, you shouldn't need the TrustManager keystore if all of your certficates are signed by "well-known" Certifcate Authorities, eg, Verisign, Thawte, and so on. (文档对于KeyManager密钥库中需要的内容有些模糊。)理论上,如果所有证书都由“众所周知的”证书颁发机构签署,例如Verisign,Thawte,则不需要TrustManager密钥库。等等。 Let me know how that works for you. 让我知道这对你有用。 Your server will also require the CA for whatever was used to sign your client. 您的服务器还将要求CA用于签署客户端的任何内容。

I could not create an SSL connection using javax.net.ssl at all. 我根本无法使用javax.net.ssl创建SSL连接。 I disabled the client SSL authentication on the server side, and I still could not create the connection. 我在服务器端禁用了客户端SSL身份验证,但仍然无法创建连接。 Since my end goal was an HTTPS GET, I punted and tried using the Apache HTTP Client that's bundled with Android. 由于我的最终目标是HTTPS GET,因此我尝试使用与Android捆绑在一起的Apache HTTP客户端。 That sort-of worked. 那种工作。 I could make the HTTPS conection, but I still could not use SSL auth. 我可以进行HTTPS连接,但我仍然无法使用SSL身份验证。 If I enabled the client SSL authentication on my server, the connection would fail. 如果我在服务器上启用了客户端SSL身份验证,则连接将失败。 I haven't checked the Apache HTTP Client code, but I suspect they are using their own SSL implementation, and don't use javax.net.ssl. 我没有检查Apache HTTP客户端代码,但我怀疑他们正在使用自己的SSL实现,并且不使用javax.net.ssl。

Not sure you resolved this issue or not, but this is how I do it and it works on Android: 不确定您是否解决了这个问题,但这是我如何做到的,它适用于Android:

  1. Use openssl to merge client's cert(cert must be signed by a CA that accepted by server) and private key into a PCKS12 format key pair: openssl pkcs12 -export -in clientcert.pem -inkey clientkey.pem -out client.p12 使用openssl将客户端的证书(证书必须由服务器接受的CA签名)和私钥合并到PCKS12格式密钥对中: openssl pkcs12 -export -in clientcert.pem -inkey clientkey.pem -out client.p12
  2. You may need patch your JRE to umlimited strength encryption depends on your key strength: copy the jar files from JCE 5.0 unlimited strength Jurisdiction Policy FIles and override those in your JRE (eg.C:\\Program Files\\Java\\jre6\\lib\\security) 你可能需要补丁你的JRE到umlimited力量加密取决于你的关键优势:从JCE 5.0复制jar文件无限强度管辖权政策FIles并覆盖你的JRE中的那些(例如:C:\\ Program Files \\ Java \\ jre6 \\ lib \\ security )
  3. Use Portecle tool mentioned above and create a new keystore with BKS format 使用上面提到的Portecle工具并创建一个具有BKS格式的新密钥库
  4. Import PCKS12 key pair generated in step 1 and save it as BKS keystore. 导入步骤1中生成的PCKS12密钥对,并将其保存为BKS密钥库。 This keystore works with Android client authentication. 此密钥库适用于Android客户端身份验证。
  5. If you need to do certificate chain, you can use this IBM tool: KeyMan to merge client's PCKS12 key pair with CA cert. 如果需要进行证书链,可以使用此IBM工具: KeyMan将客户端的PCKS12密钥对与CA证书合并。 But it only generate JKS keystore, so you again need Protecle to convert it to BKS format. 但它只生成JKS密钥库,因此您再次需要Protecle将其转换为BKS格式。

命令行:

keytool -genseckey -alias aliasName -keystore truststore.bks -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/to/jar/bcprov-jdk16-1.46.jar -storetype BKS

Use this manual http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/ This guide really helped me. 使用本手册http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/本指南对我很有帮助 It is important to observe a sequence of certificates in the store. 在商店中观察一系列证书非常重要。 For example: import the lowermost Intermediate CA certificate first and then all the way up to the Root CA certificate . 例如:首先导入最低级的中级CA证书,然后一直导入根CA证书

Your command for creating the BKS keystore looks correct for me. 您创建BKS密钥库的命令对我来说是正确的。

How do you initialize the keystore. 你如何初始化密钥库。

You need to craeate and pass your own SSLSocketFactory. 你需要创建并传递自己的SSLSocketFactory。 Here is an example which uses Apache's org.apache.http.conn.ssl.SSLSocketFactory 这是一个使用Apache的org.apache.http.conn.ssl.SSLSocketFactory的示例

But I think you can do pretty the same on the javax.net.ssl.SSLSocketFactory 但我认为你可以在javax.net.ssl.SSLSocketFactory上做同样的事情

    private SSLSocketFactory newSslSocketFactory() {
    try {
        // Get an instance of the Bouncy Castle KeyStore format
        KeyStore trusted = KeyStore.getInstance("BKS");
        // Get the raw resource, which contains the keystore with
        // your trusted certificates (root and any intermediate certs)
        InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
        try {
            // Initialize the keystore with the provided trusted certificates
            // Also provide the password of the keystore
            trusted.load(in, "testtest".toCharArray());
        } finally {
            in.close();
        }
        // Pass the keystore to the SSLSocketFactory. The factory is responsible
        // for the verification of the server certificate.
        SSLSocketFactory sf = new SSLSocketFactory(trusted);
        // Hostname verification from certificate
        // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
        sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
        return sf;
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

Please let me know if it worked. 如果有效,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM