rampeer
rampeer

Reputation: 61

Using client certificates with twitter finagle

My server is using TLSv1.2 and requires client certificate for connections. I can send requests to server using CURL (This request works fine):

curl --data "SAMPLETEXT" https://myserver.com/webservice --insecure --key privkey.pem --cert certificate.cert

(yes, server has self-signed certificate and requires --insecure flag; no, I cannot fix this). Now, I want to create client to send requests from Scala code. MyClient is object containing needed passwords and paths. To do so 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 later use it to build client:

  private val httpClient =
  richHttpBuilder(HttpEndpoint(baseUri))
    .hostConnectionLimit(1)
    .tlsWithoutValidation()
    .tls(sslContext, Some(MyClient.host))
    .build()

but I still get error:

The future returned an exception of type: com.twitter.finagle.ChannelWriteException, with message: com.twitter.finagle.SslHandshakeException: General SSLEngine problem at remote address:

What I'm doing wrong?

Upvotes: 3

Views: 2757

Answers (1)

rampeer
rampeer

Reputation: 61

It took me week to realize what I was doing wrong.

Options .tlsWithoutValidation() and .tls(sslContext, Some(MyClient.host)) cannot be used at the same time because they configure same property (Transport.TLSClientEngine) of the builder.

There are three solutions.

  1. Use proper server certificate. This one is unapplicable, unfortunately.

  2. Add server certificate to keystore. It will be marked as trusted, and client will happily work without tlsWithoutValidation.

  3. Use ignorant trust manager that doesn't validates anything:

      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 trust manager:

    context.init(kmf.getKeyManagers, new IgnorantTrustManager(), null)
    

    tlsWithoutValidation option must be removed:

      richHttpBuilder(HttpEndpoint(baseUri))
        .hostConnectionLimit(1)
        .tls(sslContext, Some(YandexClient.host))
        .build()
    

    This solution eliminates the whole purpose of certificates, so it should be used for tests only.

Upvotes: 3

Related Questions