首页 > 解决方案 > 客户端证书的Android okhttp3错误

问题描述

我对使用 okhttp3 库发出 https 请求的 android 本机应用程序有问题。我必须使用客户端证书进行身份验证。问题是当我调用它的build函数来获取某个字符串的长度时(我不知道它是什么字符串)。我发现,只有在构建之前调用时才会发生这种情况。HandshakeCertificates.BuilderNullPointerExceptionheldCertificate

我能够调试使用别名、密钥、密码和链参数调用的KeyStore.setKeyEntry位置。BcKeyStoreSpi.engineSetKeyEntry它们都不是空的,我认为它们具有正确的值(但我不是证书专家,所以可能缺少一些东西)。我无权访问 BcKeyStoreSpi.engineSetKeyEntry 的代码。

我在三星 Galaxy Tab A SM-T515 上使用带有工作配置文件的 Android 9 Enterprise。

依赖项(仅 okhttp 特定):

有人可以帮我吗?谢谢

代码

PrivateKey privateKey = KeyChain.getPrivateKey(context, certAlias);
            X509Certificate[] certChain = KeyChain.getCertificateChain(context, certAlias);
            X509Certificate mdmCert = certChain[0];

            HandshakeCertificates.Builder builder = new HandshakeCertificates.Builder();
            builder.addPlatformTrustedCertificates();

            for (X509Certificate cert : certChain) {
                builder.addTrustedCertificate(cert);
            }

            builder.heldCertificate(        //if this is commented then everything is ok
                    new HeldCertificate(
                            new KeyPair(
                                    mdmCert.getPublicKey(),
                                    privateKey
                            ),
                            mdmCert
                    ),
                    certChain
            );

            HandshakeCertificates clientCertificates = builder.build(); //this throws exception

堆栈跟踪

java.lang.RuntimeException: An error occurred while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:354)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
    at java.util.concurrent.FutureTask.run(FutureTask.java:271)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:764)
 Caused by: java.security.KeyStoreException: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
    at com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi.engineSetKeyEntry(BcKeyStoreSpi.java:685)
    at java.security.KeyStore.setKeyEntry(KeyStore.java:1179)
    at okhttp3.tls.internal.TlsUtil.newKeyManager(TlsUtil.kt:85)
    at okhttp3.tls.HandshakeCertificates$Builder.build(HandshakeCertificates.kt:144)
    at cz.kctdata.skoenergo.ui.activity.MainActivity$GetCertAsyncTask.doInBackground(MainActivity.java:121)
    at cz.kctdata.skoenergo.ui.activity.MainActivity$GetCertAsyncTask.doInBackground(MainActivity.java:85)
    at android.os.AsyncTask$2.call(AsyncTask.java:333)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
    at java.lang.Thread.run(Thread.java:764) 

标签: androidhttpsokhttpclient-certificates

解决方案


您无法提取密钥来填充另一个密钥库。相反,您应该使用与本文类似的自定义密钥库。

https://medium.com/@_kbremner/android-and-client-authentication-fd416af19a04

        KeyChain.choosePrivateKeyAlias(this,
                { alias -> storeAlias(alias) },
                arrayOf("RSA", "DSA"),  // List of acceptable key types. null for any
                null,  // issuer, null for any
                null,  // host name of server requesting the cert, null if unavailable
                443,  // port of server requesting the cert, -1 if unavailable
                null) // alias to preselect, null if unavailable

关键代码将是

val pk = KeyChain.getPrivateKey(applicationContext, alias)!!
val chain = KeyChain.getCertificateChain(applicationContext, alias)!!


                val km = object : X509KeyManager {
                    override fun getClientAliases(keyType: String?, issuers: Array<Principal>): Array<String> {
                        return arrayOf(alias)
                    }

                    override fun chooseClientAlias(keyType: Array<out String>?, issuers: Array<out Principal>?, socket: Socket?): String {
                        return alias
                    }

                    override fun getServerAliases(keyType: String?, issuers: Array<Principal>): Array<String> {
                        return arrayOf()
                    }

                    override fun chooseServerAlias(keyType: String?, issuers: Array<Principal>, socket: Socket): String {
                        return ""
                    }

                    override fun getCertificateChain(alias: String?): Array<X509Certificate> {
                        return chain
                    }

                    override fun getPrivateKey(alias: String?): PrivateKey {
                        return pk
                    }
                }

                val sslContext = SSLContext.getInstance("TLS")
                sslContext.init(arrayOf(km), trustManagers, null)

                val client: OkHttpClient = OkHttpClient.Builder()
                        .sslSocketFactory(sslContext.socketFactory, trustManagers[0] as X509TrustManager)
                        .build()

推荐阅读