android - 客户端证书的Android okhttp3错误
问题描述
我对使用 okhttp3 库发出 https 请求的 android 本机应用程序有问题。我必须使用客户端证书进行身份验证。问题是当我调用它的build
函数来获取某个字符串的长度时(我不知道它是什么字符串)。我发现,只有在构建之前调用时才会发生这种情况。HandshakeCertificates.Builder
NullPointerException
heldCertificate
我能够调试使用别名、密钥、密码和链参数调用的KeyStore.setKeyEntry
位置。BcKeyStoreSpi.engineSetKeyEntry
它们都不是空的,我认为它们具有正确的值(但我不是证书专家,所以可能缺少一些东西)。我无权访问 BcKeyStoreSpi.engineSetKeyEntry 的代码。
我在三星 Galaxy Tab A SM-T515 上使用带有工作配置文件的 Android 9 Enterprise。
依赖项(仅 okhttp 特定):
- 实现(“com.squareup.okhttp3:okhttp:4.1.0”)
- 实施(“com.squareup.okhttp3:okhttp-tls:4.1.0”)
有人可以帮我吗?谢谢
代码
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)
解决方案
您无法提取密钥来填充另一个密钥库。相反,您应该使用与本文类似的自定义密钥库。
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()
推荐阅读
- laravel - 如何在 Laravel 6 中返回图像?
- angular - 带有两个分页器的 Angular 一张桌子
- lambda - 如何在 aws lambda 中使用 gdal(geodjango)?
- c - 容器映像中缺少的内容阻止其将其进程直接连接到 NIC
- scala - 使用递归函数在 DataFrame 中链式记录
- g++ - 如何获取库的路径
- python - Python:字符串变量如何防止转义?
- vba - 从 Matlab 调用 VBA Ms Project 函数
- swift - 快速将空间转换为 %20 然后将 %20 转换为 %2520 的 URL 编码
- python - 网页上的数据取决于表单输入-如何访问表单源数据?