How to Pin a Certificate with Square Okhttp

How can I pin a certificate with Square OKHTTP?

UPDATE FOR OKHTTP 3.0

OKHTTP 3.0 has built-in support for pinning certificates. Start off by pasting the following code:

 String hostname = "yourdomain.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();

Request request = new Request.Builder()
.url("https://" + hostname)
.build();
client.newCall(request).execute();

This will fail because AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA is not a valid hash of your certificate. The exception thrown will have the correct hashes of your certificate:

 javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Peer certificate chain:
sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject.com, OU=PositiveSSL
sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA
sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority
sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root
Pinned certificates for publicobject.com:
sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
at okhttp3.CertificatePinner.check(CertificatePinner.java)
at okhttp3.Connection.upgradeToTls(Connection.java)
at okhttp3.Connection.connect(Connection.java)
at okhttp3.Connection.connectAndSetOwner(Connection.java)

Make sure you add these to your CertificatePinner object, and you have successfully pinned your certificate:

 CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
.add("publicobject.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
.add("publicobject.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
.add("publicobject.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")
.build();

EVERYTHING PAST HERE IS FOR OLDER (2.x) VERSIONS OF OKHTTP

After reading this blog post I was able to modify the concept for use with OkHttp. You should use at least version 2.0 if you want to avoid using a global SSL context.

This modification applies only to the current instance of OkHttp, and changes that instance so that it only accepts certificates from the certificate specified. If you want other certificates (such as one from Twitter) to be accepted, you simply need to create a new OkHttp instance without the modifications described below.

1. Creating a TrustStore

In order to pin a certificate, you first need to create a truststore containing this certificate. To create the truststore we will use this handy script from nelenkov slightly modified for our purposes:

#!/bin/bash

if [ "$#" -ne 3 ]; then
echo "Usage: importcert.sh <CA cert PEM file> <bouncy castle jar> <keystore pass>"
exit 1
fi

CACERT=$1
BCJAR=$2
SECRET=$3

TRUSTSTORE=mytruststore.bks
ALIAS=`openssl x509 -inform PEM -subject_hash -noout -in $CACERT`

if [ -f $TRUSTSTORE ]; then
rm $TRUSTSTORE || exit 1
fi

echo "Adding certificate to $TRUSTSTORE..."
keytool -import -v -trustcacerts -alias $ALIAS \
-file $CACERT \
-keystore $TRUSTSTORE -storetype BKS \
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath $BCJAR \
-storepass $SECRET

echo ""
echo "Added '$CACERT' with alias '$ALIAS' to $TRUSTSTORE..."

To run this script you need 3 things:

  1. Make sure keytool (included in Android SDK) is on your $PATH.
  2. Make sure you have the latest BouncyCastle jar file download in the same dir as the script. (Download here)
  3. The certificate you want to pin.

Now run the script

./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass

Type 'yes' to trust the certificate, and when complete mytruststore.bks will be generated in your current dir.

2. Apply your TrustStore to your Android project

Create a directory raw under your res folder. Copy mytruststore.bks here.

Now here's a very simple class that pins your cert to OkHttp

import android.content.Context;
import android.util.Log;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.InputStream;
import java.io.Reader;
import java.security.KeyStore;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

/**
* Created by martin on 02/06/14.
*/
public class Pinning {

Context context;
public static String TRUST_STORE_PASSWORD = "your_secret";
private static final String ENDPOINT = "https://api.yourdomain.com/";

public Pinning(Context c) {
this.context = c;
}

private SSLSocketFactory getPinnedCertSslSocketFactory(Context context) {
try {
KeyStore trusted = KeyStore.getInstance("BKS");
InputStream in = context.getResources().openRawResource(R.raw.mytruststore);
trusted.load(in, TRUST_STORE_PASSWORD.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trusted);
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return sslContext.getSocketFactory();
} catch (Exception e) {
Log.e("MyApp", e.getMessage(), e);
}
return null;
}

public void makeRequest() {
try {
OkHttpClient client = new OkHttpClient();
client.setSslSocketFactory(getPinnedCertSslSocketFactory(context));

Request request = new Request.Builder()
.url(ENDPOINT)
.build();

Response response = client.newCall(request).execute();

Log.d("MyApp", response.body().string());

} catch (Exception e) {
Log.e("MyApp", e.getMessage(), e);

}
}
}

As you can see we instantiate a new instance of OkHttpClient and call setSslSocketFactory, passing in a SSLSocketFactory with our custom truststore. Make sure you set TRUST_STORE_PASSWORD to the password you passed into the shell script. Your OkHttp instance should now only accept the certificate you specified.

Square okHTTP certificate pinning - sslSocketFactory error

You should use the following syntax instead

OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getPinnedCertSslSocketFactory(this.context))
.build();

OkHttp3, Retrofit and certificate pinning: how to give an expiration to the pinning

Is there a method to tell OkHpttp3/Retrofit to have the desired behaviour?

You can do that yourself:

OkHttpClient.Builder = new OkHttpClient.Builder();

if (applyPins()) {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("dummy.com", "sha256/xxxxxxxxxx=")
.build();

builder..certificatePinner(certificatePinner);
}

OkHttpClient httpClient = builder
.callTimeout(240, TimeUnit.SECONDS)
.readTimeout(240, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();

Retrofit retrofitKripton = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(KriptonBinderConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.client(httpClient).build();

Implement applyPins() as a method that returns true if you want to apply the pins, false otherwise. For example, you might use your proposed date comparison.

OkHttp for Android: Option to NOT enforce Certificate/ public key pinning

Derived somewhat from you own answer on the issue tracker

package okhttp3

import javax.net.ssl.SSLPeerUnverifiedException

fun main() {
class LoggingCertificatePinnerInterceptor(val certificatePinner: CertificatePinner) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val host = chain.request().url.host
val certs = chain.connection()
?.handshake()?.peerCertificates.orEmpty()

try {
certificatePinner.check(host, certs)
} catch (e: SSLPeerUnverifiedException) {
e.printStackTrace()
}
return chain.proceed(chain.request())
}
}

val certificatePinner = CertificatePinner.Builder()
.add("graph.facebook.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
val client = OkHttpClient.Builder()
.addNetworkInterceptor(LoggingCertificatePinnerInterceptor(certificatePinner))
.build()

val request = Request.Builder()
.url("https://graph.facebook.com/robots.txt")
.build()
val response = client.newCall(request)
.execute()

println(response.code)
}
javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Peer certificate chain:
sha256/KVFbweB8Ag9f08MZWU7m7cG83tpv8Ml39JpOHU3ESMg=: CN=*.facebook.com, O="Facebook, Inc.", L=Menlo Park, ST=California, C=US
sha256/k2v657xBsOVe1PQRwOsHsw3bsGT2VzIqz5K+59sNQws=: CN=DigiCert SHA2 High Assurance Server CA, OU=www.digicert.com, O=DigiCert Inc, C=US
sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=: CN=DigiCert High Assurance EV Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US
Pinned certificates for graph.facebook.com:
sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
at okhttp3.CertificatePinner.check$okhttp(CertificatePinner.kt:199)
at okhttp3.CertificatePinner.check(CertificatePinner.kt:149)
at okhttp3.XxXKt$main$LoggingCertificatePinnerInterceptor.intercept(XxX.kt:13)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:197)
at okhttp3.internal.connection.RealCall.execute(RealCall.kt:148)
at okhttp3.XxXKt.main(XxX.kt:32)
at okhttp3.XxXKt.main(XxX.kt)
200

Handle site certificate expiry with OkHttp Certificate pinning on Android

This is the documented behaviour of CertificatePinner. So just add pins for your current and old certificate.

http://square.github.io/okhttp/3.x/okhttp/okhttp3/CertificatePinner.html#check-java.lang.String-java.util.List-

Confirms that at least one of the certificates pinned for hostname is
in peerCertificates. Does nothing if there are no certificates pinned
for hostname. OkHttp calls this after a successful TLS handshake, but
before the connection is used.

n.b. Because of the expiry of your certificates may happen before old clients update, it is usually advised to also pin against the CA you use also which is quite likely to be consistent across old and new certificates. This will ensure even if your current and next certificate expire or are revoked, you would be able to authenticate with a new certificate generated and older clients.

https://community.letsencrypt.org/t/hpkp-best-practices-if-you-choose-to-implement/4625

OkHttp: SSLPeerUnverifiedException Failed to find a trusted cert that signed X.509 Certificate

Certificate pinning is an additional security, therefore the certificate must be trusted by the used TrustManager and it have to match the pinned certificate.

As you have disable the DigiCert CA certificates the certificate is not trusted by the sued TrustManager and therefore you are getting the SSLPeerUnverifiedException.

The behavior as I described is documented in the JavaDoc of CertificatePinner:

Note about self-signed certificates

CertificatePinner can not be used to pin self-signed certificate if such certificate is not accepted by TrustManager.

okhttp doesn't validate pins correctly

You’re configuring it incorrectly. Replace pinningUrl with the hostname of the pinning URL. For example, you want example.com instead of http://example.com/. If you’d like to send a PR to make hostname validation more strict, it would be quite welcome.



Related Topics



Leave a reply



Submit