Our app requires to authenticate some endpoints with client certificate. We create CSR, call endpoint and receive back client certificate. We have trouble calling secure endpoint that requires this client certificate.
App have BouncyCastle dependency
org.bouncycastle:bcpkix-jdk15to18:1.77.
This is how we proceeed. First, we create keystore
private val keystore: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(
null,
null
)
}
Then we generate keypair
val keyPairGenerator = KeyPairGenerator.getInstance(
"EC",
"AndroidKeyStore"
)
val keyGenParameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
KEY_TAG,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).apply {
setDigests(KeyProperties.DIGEST_SHA256)
setKeySize(KEY_256)
setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) //tried with and without
}.build()
keyPairGenerator.initialize(keyGenParameterSpec)
val keyPair = keyPairGenerator.generateKeyPair()
Then we generate certificate signing request
val jcaPKCS10CertificationRequestBuilder = JcaPKCS10CertificationRequestBuilder(
X500Principal("CN=$COMMON_NAME, O=$ORGANIZATION, C=$COUNTRY"),
keyPair.public
)
val contentSigner = JcaContentSignerBuilder(KEY_ALGORITHM).build(keyPair.private)
val csr = jcaPKCS10CertificationRequestBuilder.build(contentSigner)
val stringWriter = StringWriter()
val pemWriter = PEMWriter(stringWriter)
pemWriter.writeObject(csr)
pemWriter.close()
stringWriter.close()
val csr = stringWriter.toString()
We send this csr to backend and in response we receive signed PEM client certificate.
We save this certifciate:
val inputStream = input.byteInputStream()
val certificateFactory = CertificateFactory.getInstance("X.509")
val certificate = certificateFactory.generateCertificate(inputStream)
inputStream.close()
val certChain = arrayOf(generatedCertificate as? X509Certificate)
keystore.setKeyEntry(
KEY_TAG,
privateKey,
null,
certChain
)
We then load socketFactory
val keyManagerFactory = KeyManagerFactory.getInstance("X509")
keyManagerFactory.init(keystore, null)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.keyManagers, null, null)
Result.success(sslContext.socketFactory)
and trust manager
val trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
trustManagerFactory.init(null as KeyStore?)
val trustManager = trustManagerFactory.trustManagers[0] as X509TrustManager
create Retrofit and OkHttp client
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(
OkHttpClient.Builder().apply {
protocols(listOf(Protocol.HTTP_1_1))
}.sslSocketFactory(
socketFactory,
trustManager
).build()
)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
and call endpoint. But we get an error:
--> GET *
--> END GET
Preferred provider doesn't support key:
java.security.InvalidKeyException: Keystore operation failed
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1373)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1413)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
at android.security.keystore.AndroidKeyStoreSignatureSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreSignatureSpiBase.java:219)
at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineInitSign(AndroidKeyStoreSignatureSpiBase.java:99)
at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineInitSign(AndroidKeyStoreSignatureSpiBase.java:77)
at java.security.Signature$Delegate.init(Signature.java:1357)
at java.security.Signature$Delegate.chooseProvider(Signature.java:1310)
at java.security.Signature$Delegate.engineInitSign(Signature.java:1385)
at java.security.Signature.initSign(Signature.java:679)
at com.android.org.conscrypt.CryptoUpcalls.signDigestWithPrivateKey(CryptoUpcalls.java:80)
at com.android.org.conscrypt.CryptoUpcalls.ecSignDigestWithPrivateKey(CryptoUpcalls.java:68)
at com.android.org.conscrypt.NativeCrypto.SSL_read(Native Method)
at com.android.org.conscrypt.NativeSsl.read(NativeSsl.java:411)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(ConscryptFileDescriptorSocket.java:549)
at okio.InputStreamSource.read(JvmOkio.kt:93)
at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:128)
at okio.RealBufferedSource.indexOf(RealBufferedSource.kt:430)
at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:323)
at okhttp3.internal.http1.HeadersReader.readLine(HeadersReader.kt:29)
at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:180)
at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.kt:110)
at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.kt:221)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:517)
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:919)
Caused by: android.security.KeyStoreException: Incompatible digest
at android.security.KeyStore.getKeyStoreException(KeyStore.java:1303)
... 38 more
Could not find provider for algorithm: NONEwithECDSA
Could not sign message in EcdsaMethodDoSign!
<-- HTTP FAILED: javax.net.ssl.SSLException: Read error: ssl=0x79fcd80608: I/O error during system call, Success
---> javax.net.ssl.SSLException: Read error: ssl=0x79fcd80608: I/O error during system call, Success