Skip to content

Commit df48bdf

Browse files
committed
Add a custom SSLContext pinning demo as a manual middleground
This is far more plausible as a manual pinning solution than the existing custom raw TLS socket approach, and useful to test out TrustManager/SSLContext hackery.
1 parent 4214f6b commit df48bdf

File tree

2 files changed

+62
-9
lines changed

2 files changed

+62
-9
lines changed

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

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ import androidx.annotation.IdRes
99
import androidx.appcompat.app.AppCompatActivity
1010
import androidx.core.content.ContextCompat
1111
import com.android.volley.RequestQueue
12-
import com.android.volley.toolbox.*
12+
import com.android.volley.toolbox.BasicNetwork
13+
import com.android.volley.toolbox.HurlStack
14+
import com.android.volley.toolbox.NoCache
15+
import com.android.volley.toolbox.StringRequest
1316
import com.datatheorem.android.trustkit.TrustKit
14-
import kotlinx.coroutines.*
17+
import kotlinx.coroutines.Dispatchers
18+
import kotlinx.coroutines.GlobalScope
19+
import kotlinx.coroutines.launch
1520
import okhttp3.CertificatePinner
1621
import okhttp3.OkHttpClient
1722
import okhttp3.Request
@@ -218,9 +223,50 @@ class MainActivity : AppCompatActivity() {
218223
}
219224
}
220225

221-
fun sendManuallyCustomPinned(view: View) {
226+
// Manually pinned by building an SSLContext that trusts only the correct certificate, and then
227+
// connecting with the native HttpsUrlConnection API:
228+
fun sendCustomContextPinned(view: View) {
222229
GlobalScope.launch(Dispatchers.IO) {
223-
onStart(R.id.manually_pinned)
230+
onStart(R.id.custom_context_pinned)
231+
232+
val cf = CertificateFactory.getInstance("X.509")
233+
val caStream = BufferedInputStream(resources.openRawResource(R.raw.lets_encrypt_isrg_root))
234+
val caCertificate = cf.generateCertificate(caStream)
235+
236+
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
237+
keyStore.load(null)
238+
keyStore.setCertificateEntry("ca", caCertificate)
239+
240+
val trustManagerFactory = TrustManagerFactory
241+
.getInstance(TrustManagerFactory.getDefaultAlgorithm())
242+
trustManagerFactory.init(keyStore)
243+
244+
try {
245+
val context = SSLContext.getInstance("TLS")
246+
context.init(null, trustManagerFactory.trustManagers, null)
247+
248+
val mURL = URL("https://sha256.badssl.com")
249+
with(mURL.openConnection() as HttpsURLConnection) {
250+
this.sslSocketFactory = context.socketFactory
251+
252+
println("URL: ${this.url}")
253+
println("Response Code: ${this.responseCode}")
254+
}
255+
256+
onSuccess(R.id.custom_context_pinned)
257+
} catch (e: Throwable) {
258+
println(e)
259+
onError(R.id.custom_context_pinned, e.toString())
260+
}
261+
}
262+
}
263+
264+
// Manually pinned at the lowest level: creating a raw TLS connection, disabling all checks,
265+
// and then directly analysing the certificate that's received after connection, before doing
266+
// HTTP by just writing & reading raw strings. Not a good idea, but the hardest to unpin!
267+
fun sendCustomRawSocketPinned(view: View) {
268+
GlobalScope.launch(Dispatchers.IO) {
269+
onStart(R.id.custom_raw_socket_pinned)
224270
try {
225271
// Disable trust manager checks - we'll check the certificate manually ourselves later
226272
val trustManager = arrayOf<TrustManager>(object : X509TrustManager {
@@ -257,10 +303,10 @@ class MainActivity : AppCompatActivity() {
257303
println("Response was: $responseLine")
258304
socket.close()
259305

260-
onSuccess(R.id.manually_pinned)
306+
onSuccess(R.id.custom_raw_socket_pinned)
261307
} catch (e: Throwable) {
262308
println(e)
263-
onError(R.id.manually_pinned, e.toString())
309+
onError(R.id.custom_raw_socket_pinned, e.toString())
264310
}
265311
}
266312
}

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,18 @@
5252
android:text="TrustKit pinned request" />
5353

5454
<Button
55-
android:id="@+id/manually_pinned"
55+
android:id="@+id/custom_context_pinned"
5656
android:layout_width="match_parent"
5757
android:layout_height="wrap_content"
58-
android:onClick="sendManuallyCustomPinned"
59-
android:text="Manually pinned request" />
58+
android:onClick="sendCustomContextPinned"
59+
android:text="Custom context-pinned request" />
60+
61+
<Button
62+
android:id="@+id/custom_raw_socket_pinned"
63+
android:layout_width="match_parent"
64+
android:layout_height="wrap_content"
65+
android:onClick="sendCustomRawSocketPinned"
66+
android:text="Custom manually pinned request" />
6067

6168
</LinearLayout>
6269

0 commit comments

Comments
 (0)