Skip to content

Commit c0ff8b8

Browse files
committed
Add a *custom* certificate pinning check
1 parent 2c9d61b commit c0ff8b8

File tree

2 files changed

+70
-6
lines changed

2 files changed

+70
-6
lines changed

app/src/main/java/tech/httptoolkit/pinning_demo/MainActivity.kt

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,20 @@ import kotlinx.coroutines.*
1515
import okhttp3.CertificatePinner
1616
import okhttp3.OkHttpClient
1717
import okhttp3.Request
18+
import okio.ByteString.Companion.decodeBase64
19+
import okio.ByteString.Companion.toByteString
1820
import java.io.BufferedInputStream
21+
import java.io.BufferedReader
22+
import java.io.InputStreamReader
23+
import java.io.PrintWriter
1924
import java.net.URL
2025
import java.security.KeyStore
26+
import java.security.cert.Certificate
2127
import java.security.cert.CertificateFactory
22-
import javax.net.ssl.HttpsURLConnection
23-
import javax.net.ssl.SSLContext
24-
import javax.net.ssl.TrustManagerFactory
28+
import java.security.cert.X509Certificate
29+
import javax.net.ssl.*
2530

31+
const val BADSSL_UNTRUSTED_ROOT_SHA256 = "sr2tjak7H6QRi8o0fyIXGWdPiU32rDsczcIEAqA+s4g="
2632

2733
class MainActivity : AppCompatActivity() {
2834
override fun onCreate(savedInstanceState: Bundle?) {
@@ -120,15 +126,14 @@ class MainActivity : AppCompatActivity() {
120126
try {
121127
val hostname = "badssl.com"
122128
val certificatePinner = CertificatePinner.Builder()
123-
// DigiCert SHA2 Secure Server CA (valid until March 2023)
124-
.add(hostname, "sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=")
129+
.add(hostname, "sha256/${BADSSL_UNTRUSTED_ROOT_SHA256}")
125130
.build()
126131

127132
val client = OkHttpClient.Builder()
128133
.certificatePinner(certificatePinner)
129134
.build()
130135
val request = Request.Builder()
131-
.url("https://badssl.com")
136+
.url("https://untrusted-root.badssl.com")
132137
.build();
133138

134139
client.newCall(request).execute().use { response ->
@@ -212,4 +217,56 @@ class MainActivity : AppCompatActivity() {
212217
}
213218
}
214219
}
220+
221+
fun sendManuallyCustomPinned(view: View) {
222+
GlobalScope.launch(Dispatchers.IO) {
223+
onStart(R.id.manually_pinned)
224+
try {
225+
// Disable trust manager checks - we'll check the certificate manually ourselves later
226+
val trustManager = arrayOf<TrustManager>(object : X509TrustManager {
227+
override fun getAcceptedIssuers(): Array<X509Certificate?>? {
228+
return null
229+
}
230+
231+
override fun checkClientTrusted(certs: Array<X509Certificate?>?, authType: String?) {}
232+
override fun checkServerTrusted(certs: Array<X509Certificate?>?, authType: String?) {}
233+
})
234+
235+
val context = SSLContext.getInstance("TLS")
236+
context.init(null, trustManager, null)
237+
238+
val socket = context.socketFactory.createSocket("untrusted-root.badssl.com", 443) as SSLSocket
239+
240+
val certs = socket.session.peerCertificates
241+
242+
if (!certs.any { cert -> doesCertMatchPin(BADSSL_UNTRUSTED_ROOT_SHA256, cert) }) {
243+
socket.close() // Close the socket immediately without sending a request
244+
throw Error("Unrecognized cert hash.")
245+
}
246+
247+
// Send a real request, just to make it clear that we trust the connection:
248+
val pw = PrintWriter(socket.outputStream)
249+
pw.println("GET / HTTP/1.1")
250+
pw.println("Host: untrusted-root.badssl.com")
251+
pw.println("")
252+
pw.flush()
253+
254+
val br = BufferedReader(InputStreamReader(socket.inputStream))
255+
val responseLine = br.readLine()
256+
257+
println("Response was: $responseLine")
258+
socket.close()
259+
260+
onSuccess(R.id.manually_pinned)
261+
} catch (e: Throwable) {
262+
println(e)
263+
onError(R.id.manually_pinned, e.toString())
264+
}
265+
}
266+
}
267+
268+
private fun doesCertMatchPin(pin: String, cert: Certificate): Boolean {
269+
val certHash = cert.publicKey.encoded.toByteString().sha256()
270+
return certHash == pin.decodeBase64()
271+
}
215272
}

app/src/main/res/layout/activity_main.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@
5151
android:onClick="sendTrustKitPinned"
5252
android:text="TrustKit pinned request" />
5353

54+
<Button
55+
android:id="@+id/manually_pinned"
56+
android:layout_width="match_parent"
57+
android:layout_height="wrap_content"
58+
android:onClick="sendManuallyCustomPinned"
59+
android:text="Manually pinned request" />
60+
5461
</LinearLayout>
5562

5663
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)