Using client certificates with twitter finagle
My server is using TLSv1.2 and requires a client certificate for connections. I can send requests to the server using CURL (this request works fine):
curl --data "SAMPLETEXT" https://myserver.com/webservice --insecure --key privkey.pem --cert certificate.cert
(yes, the server has a self-signed certificate and a flag is required --insecure
; no, I can't fix that). Now I want to create a client to send requests from Scala code. MyClient
is an object containing the required passwords and paths. For this I create SSLContext
:
private val keyStore = {
//Setting up BouncyCastle provider for message signing
Security.addProvider(new BouncyCastleProvider())
//Loading keystore from specified file
val clientStore = KeyStore.getInstance("JKS")
val inputStream = new FileInputStream(MyClient.keystore)
clientStore.load(inputStream, MyClient.keystorePassword.toCharArray)
inputStream.close()
clientStore
}
//Retrieving certificate and key
private val cert = keyStore.getCertificate(MyClient.keyAlias).asInstanceOf[X509Certificate]
private val key = keyStore.getKey(MyClient.keyAlias, MyClient.keystorePassword.toCharArray).asInstanceOf[PrivateKey]
//Creating SSL context
private val sslContext = {
val context = SSLContext.getInstance("TLS")
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
val kmf: KeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
kmf.init(keyStore, MyClient.keystorePassword.toCharArray)
tmf.init(keyStore)
context.init(kmf.getKeyManagers, tmf.getTrustManagers, null)
context
}
and then use it to create a client:
private val httpClient =
richHttpBuilder(HttpEndpoint(baseUri))
.hostConnectionLimit(1)
.tlsWithoutValidation()
.tls(sslContext, Some(MyClient.host))
.build()
but i still get the error:
In the future, an exception of type: com.twitter.finagle.ChannelWriteException is returned, with the message: com.twitter.finagle.SslHandshakeException: common SSLEngine problem at remote address:
What am I doing wrong?
source to share
It took me a week to figure out what I was doing wrong.
The parameters .tlsWithoutValidation()
and .tls(sslContext, Some(MyClient.host))
cannot be used at the same time because they set the same property ( Transport.TLSClientEngine
) of the constructor.
There are three solutions.
-
Use the correct server certificate. Unfortunately, this is not applicable.
-
Add the server certificate to the keystore. It will be marked as trusted and the client will happily work without
tlsWithoutValidation
. -
Use an ignorant trust manager that checks nothing:
private[this] class IgnorantTrustManager extends X509TrustManager { def getAcceptedIssuers(): Array[X509Certificate] = new Array[X509Certificate](0) def checkClientTrusted(certs: Array[X509Certificate], authType: String) { } def checkServerTrusted(certs: Array[X509Certificate], authType: String) { } }
Then use it as a trusted manager:
context.init(kmf.getKeyManagers, new IgnorantTrustManager(), null)
tlsWithoutValidation
you need to remove:richHttpBuilder(HttpEndpoint(baseUri)) .hostConnectionLimit(1) .tls(sslContext, Some(YandexClient.host)) .build()
This solution removes the whole purpose of certificates, so it should only be used for tests.
source to share