Skip to content

Commit 95ce805

Browse files
committed
Add requests pinned using config, OkHTTP & Volley+SSLContext
1 parent a31a0e1 commit 95ce805

File tree

7 files changed

+215
-22
lines changed

7 files changed

+215
-22
lines changed

app/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ android {
99

1010
defaultConfig {
1111
applicationId "tech.httptoolkit.pinning_demo"
12-
minSdkVersion 21
12+
minSdkVersion 24
1313
targetSdkVersion 30
1414
versionCode 1
1515
versionName "1.0"
@@ -40,4 +40,7 @@ dependencies {
4040
implementation 'androidx.appcompat:appcompat:1.2.0'
4141
implementation 'com.google.android.material:material:1.3.0'
4242
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
43+
44+
implementation 'com.squareup.okhttp3:okhttp:4.5.0'
45+
implementation 'com.android.volley:volley:1.2.0'
4346
}

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
<uses-permission android:name="android.permission.INTERNET" />
66

77
<application
8+
android:networkSecurityConfig="@xml/network_security_config"
9+
810
android:allowBackup="true"
911
android:icon="@mipmap/ic_launcher"
1012
android:label="@string/app_name"

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

Lines changed: 125 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@ import android.widget.Toast
88
import androidx.annotation.IdRes
99
import androidx.appcompat.app.AppCompatActivity
1010
import androidx.core.content.ContextCompat
11-
import kotlinx.coroutines.Dispatchers
12-
import kotlinx.coroutines.GlobalScope
13-
import kotlinx.coroutines.launch
14-
import kotlinx.coroutines.withContext
11+
import com.android.volley.toolbox.HurlStack
12+
import com.android.volley.toolbox.StringRequest
13+
import com.android.volley.toolbox.Volley
14+
import kotlinx.coroutines.*
15+
import okhttp3.CertificatePinner
16+
import okhttp3.OkHttpClient
17+
import okhttp3.Request
18+
import java.io.BufferedInputStream
1519
import java.net.HttpURLConnection
1620
import java.net.URL
21+
import java.security.KeyStore
22+
import java.security.cert.CertificateFactory
23+
import javax.net.ssl.SSLContext
24+
import javax.net.ssl.TrustManagerFactory
1725

1826

1927
class MainActivity : AppCompatActivity() {
@@ -22,8 +30,8 @@ class MainActivity : AppCompatActivity() {
2230
setContentView(R.layout.activity_main)
2331
}
2432

25-
private suspend fun onStart(@IdRes id: Int) {
26-
withContext(Dispatchers.Main) {
33+
private fun onStart(@IdRes id: Int) {
34+
GlobalScope.launch(Dispatchers.Main) {
2735
val button = findViewById<Button>(id)
2836
button.setBackgroundColor(
2937
ContextCompat.getColor(this@MainActivity, R.color.purple_500)
@@ -32,32 +40,36 @@ class MainActivity : AppCompatActivity() {
3240
}
3341
}
3442

35-
private suspend fun onSuccess(@IdRes id: Int) {
36-
withContext(Dispatchers.Main) {
43+
private fun onSuccess(@IdRes id: Int) {
44+
println("onSuccess")
45+
GlobalScope.launch(Dispatchers.Main) {
46+
println("dispatched")
3747
val button = findViewById<Button>(id)
3848
button.setBackgroundColor(
39-
ContextCompat.getColor(this@MainActivity, R.color.success)
49+
ContextCompat.getColor(this@MainActivity, R.color.success)
4050
)
41-
val img: Drawable = ContextCompat.getDrawable(this@MainActivity,
42-
R.drawable.baseline_check_circle_24
51+
val img: Drawable = ContextCompat.getDrawable(
52+
this@MainActivity,
53+
R.drawable.baseline_check_circle_24
4354
)!!
4455
button.setCompoundDrawablesWithIntrinsicBounds(img, null, null, null)
4556
}
4657
}
4758

48-
private suspend fun onError(@IdRes id: Int, message: String) {
49-
withContext(Dispatchers.Main) {
59+
private fun onError(@IdRes id: Int, message: String) {
60+
GlobalScope.launch(Dispatchers.Main) {
5061
val button = findViewById<Button>(id)
5162
button.setBackgroundColor(
52-
ContextCompat.getColor(this@MainActivity, R.color.failure)
63+
ContextCompat.getColor(this@MainActivity, R.color.failure)
5364
)
54-
val img: Drawable = ContextCompat.getDrawable(this@MainActivity,
65+
val img: Drawable = ContextCompat.getDrawable(
66+
this@MainActivity,
5567
R.drawable.baseline_cancel_24
5668
)!!
5769
button.setCompoundDrawablesWithIntrinsicBounds(img, null, null, null)
5870

59-
val duration = Toast.LENGTH_SHORT
60-
val toast = Toast.makeText(applicationContext, message, duration)
71+
val duration = Toast.LENGTH_LONG
72+
val toast = Toast.makeText(this@MainActivity, message, duration)
6173
toast.show()
6274
}
6375
}
@@ -79,4 +91,100 @@ class MainActivity : AppCompatActivity() {
7991
}
8092
}
8193
}
94+
95+
fun sendConfigPinned(view: View) {
96+
GlobalScope.launch(Dispatchers.IO) {
97+
onStart(R.id.config_pinned)
98+
try {
99+
// Untrusted in system store, trusted & pinned in network config:
100+
val mURL = URL("https://untrusted-root.badssl.com")
101+
with(mURL.openConnection() as HttpURLConnection) {
102+
println("URL: ${this.url}")
103+
println("Response Code: ${this.responseCode}")
104+
}
105+
106+
onSuccess(R.id.config_pinned)
107+
} catch (e: Throwable) {
108+
println(e)
109+
onError(R.id.config_pinned, e.toString())
110+
}
111+
}
112+
}
113+
114+
fun sendOkHttpPinned(view: View) {
115+
GlobalScope.launch(Dispatchers.IO) {
116+
onStart(R.id.okhttp_pinned)
117+
118+
try {
119+
val hostname = "badssl.com"
120+
val certificatePinner = CertificatePinner.Builder()
121+
// DigiCert SHA2 Secure Server CA (valid until March 2023)
122+
.add(hostname, "sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=")
123+
.build()
124+
125+
val client = OkHttpClient.Builder()
126+
.certificatePinner(certificatePinner)
127+
.build()
128+
val request = Request.Builder()
129+
.url("https://badssl.com")
130+
.build();
131+
132+
client.newCall(request).execute().use { response ->
133+
println("URL: ${request.url}")
134+
println("Response Code: ${response.code}")
135+
}
136+
137+
onSuccess(R.id.okhttp_pinned)
138+
} catch (e: Throwable) {
139+
println(e)
140+
onError(R.id.okhttp_pinned, e.toString())
141+
}
142+
}
143+
}
144+
145+
fun sendVolleyPinned(view: View) {
146+
onStart(R.id.volley_pinned)
147+
148+
try {
149+
// Create an HTTP client that only trusts our specific certificate:
150+
val cf = CertificateFactory.getInstance("X.509")
151+
val caStream = BufferedInputStream(resources.openRawResource(R.raw.example_com_digicert_ca))
152+
val ca = cf.generateCertificate(caStream)
153+
caStream.close()
154+
155+
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
156+
keyStore.load(null, null)
157+
keyStore.setCertificateEntry("ca", ca)
158+
159+
val trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
160+
val trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm)
161+
trustManagerFactory.init(keyStore)
162+
163+
val context = SSLContext.getInstance("TLS")
164+
context.init(null, trustManagerFactory.trustManagers, null)
165+
166+
val requestQueue = Volley.newRequestQueue(this@MainActivity,
167+
HurlStack(null, context.socketFactory)
168+
)
169+
170+
// Make a request using that client:
171+
val stringRequest = StringRequest(
172+
com.android.volley.Request.Method.GET,
173+
"https://example.com",
174+
{ _ ->
175+
println("Volley success")
176+
this@MainActivity.onSuccess(R.id.volley_pinned)
177+
},
178+
{
179+
println(it.toString())
180+
this@MainActivity.onError(R.id.volley_pinned, it.toString())
181+
}
182+
)
183+
184+
requestQueue.add(stringRequest)
185+
} catch (e: Throwable) {
186+
println(e)
187+
onError(R.id.volley_pinned, e.toString())
188+
}
189+
}
82190
}

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,25 @@
2424
android:text="Unpinned request" />
2525

2626
<Button
27-
android:id="@+id/code_pinned"
27+
android:id="@+id/config_pinned"
2828
android:layout_width="match_parent"
2929
android:layout_height="wrap_content"
30-
android:text="Code-pinned request" />
30+
android:onClick="sendConfigPinned"
31+
android:text="Config-pinned request" />
3132

3233
<Button
33-
android:id="@+id/config_pinned"
34+
android:id="@+id/okhttp_pinned"
3435
android:layout_width="match_parent"
3536
android:layout_height="wrap_content"
36-
android:text="Config-pinned request" />
37+
android:onClick="sendOkHttpPinned"
38+
android:text="OkHTTP pinned request" />
39+
40+
<Button
41+
android:id="@+id/volley_pinned"
42+
android:layout_width="match_parent"
43+
android:layout_height="wrap_content"
44+
android:onClick="sendVolleyPinned"
45+
android:text="Volley pinned request" />
3746

3847
</LinearLayout>
3948

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIGfjCCBGagAwIBAgIJAJeg/PrX5Sj9MA0GCSqGSIb3DQEBCwUAMIGBMQswCQYD
3+
VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j
4+
aXNjbzEPMA0GA1UECgwGQmFkU1NMMTQwMgYDVQQDDCtCYWRTU0wgVW50cnVzdGVk
5+
IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTE2MDcwNzA2MzEzNVoXDTM2
6+
MDcwMjA2MzEzNVowgYExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
7+
MRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZCYWRTU0wxNDAyBgNV
8+
BAMMK0JhZFNTTCBVbnRydXN0ZWQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
9+
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKQtPMhEH073gis/HISWAi
10+
bOEpCtOsatA3JmeVbaWal8O/5ZO5GAn9dFVsGn0CXAHR6eUKYDAFJLa/3AhjBvWa
11+
tnQLoXaYlCvBjodjLEaFi8ckcJHrAYG9qZqioRQ16Yr8wUTkbgZf+er/Z55zi1yn
12+
CnhWth7kekvrwVDGP1rApeLqbhYCSLeZf5W/zsjLlvJni9OrU7U3a9msvz8mcCOX
13+
fJX9e3VbkD/uonIbK2SvmAGMaOj/1k0dASkZtMws0Bk7m1pTQL+qXDM/h3BQZJa5
14+
DwTcATaa/Qnk6YHbj/MaS5nzCSmR0Xmvs/3CulQYiZJ3kypns1KdqlGuwkfiCCgD
15+
yWJy7NE9qdj6xxLdqzne2DCyuPrjFPS0mmYimpykgbPnirEPBF1LW3GJc9yfhVXE
16+
Cc8OY8lWzxazDNNbeSRDpAGbBeGSQXGjAbliFJxwLyGzZ+cG+G8lc+zSvWjQu4Xp
17+
GJ+dOREhQhl+9U8oyPX34gfKo63muSgo539hGylqgQyzj+SX8OgK1FXXb2LS1gxt
18+
VIR5Qc4MmiEG2LKwPwfU8Yi+t5TYjGh8gaFv6NnksoX4hU42gP5KvjYggDpR+NSN
19+
CGQSWHfZASAYDpxjrOo+rk4xnO+sbuuMk7gORsrl+jgRT8F2VqoR9Z3CEdQxcCjR
20+
5FsfTymZCk3GfIbWKkaeLQIDAQABo4H2MIHzMB0GA1UdDgQWBBRvx4NzSbWnY/91
21+
3m1u/u37l6MsADCBtgYDVR0jBIGuMIGrgBRvx4NzSbWnY/913m1u/u37l6MsAKGB
22+
h6SBhDCBgTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
23+
BAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDE0MDIGA1UEAwwrQmFk
24+
U1NMIFVudHJ1c3RlZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eYIJAJeg/PrX
25+
5Sj9MAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IC
26+
AQBQU9U8+jTRT6H9AIFm6y50tXTg/ySxRNmeP1Ey9Zf4jUE6yr3Q8xBv9gTFLiY1
27+
qW2qfkDSmXVdBkl/OU3+xb5QOG5hW7wVolWQyKREV5EvUZXZxoH7LVEMdkCsRJDK
28+
wYEKnEErFls5WPXY3bOglBOQqAIiuLQ0f77a2HXULDdQTn5SueW/vrA4RJEKuWxU
29+
iD9XPnVZ9tPtky2Du7wcL9qhgTddpS/NgAuLO4PXh2TQ0EMCll5reZ5AEr0NSLDF
30+
c/koDv/EZqB7VYhcPzr1bhQgbv1dl9NZU0dWKIMkRE/T7vZ97I3aPZqIapC2ulrf
31+
KrlqjXidwrGFg8xbiGYQHPx3tHPZxoM5WG2voI6G3s1/iD+B4V6lUEvivd3f6tq7
32+
d1V/3q1sL5DNv7TvaKGsq8g5un0TAkqaewJQ5fXLigF/yYu5a24/GUD783MdAPFv
33+
gWz8F81evOyRfpf9CAqIswMF+T6Dwv3aw5L9hSniMrblkg+ai0K22JfoBcGOzMtB
34+
Ke/Ps2Za56dTRoY/a4r62hrcGxufXd0mTdPaJLw3sJeHYjLxVAYWQq4QKJQWDgTS
35+
dAEWyN2WXaBFPx5c8KIW95Eu8ShWE00VVC3oA4emoZ2nrzBXLrUScifY6VaYYkkR
36+
2O2tSqU8Ri3XRdgpNPDWp8ZL49KhYGYo3R/k98gnMHiY5g==
37+
-----END CERTIFICATE-----
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
3+
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4+
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
5+
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
6+
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
7+
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
8+
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
9+
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10+
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
11+
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12+
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13+
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14+
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15+
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16+
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17+
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
18+
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19+
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20+
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21+
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22+
-----END CERTIFICATE-----
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<network-security-config>
3+
<domain-config>
4+
<domain includeSubdomains="false">untrusted-root.badssl.com</domain>
5+
<pin-set>
6+
<pin digest="SHA-256">sr2tjak7H6QRi8o0fyIXGWdPiU32rDsczcIEAqA+s4g=</pin>
7+
</pin-set>
8+
<trust-anchors>
9+
<certificates src="@raw/badssl_untrusted_root" />
10+
</trust-anchors>
11+
</domain-config>
12+
</network-security-config>

0 commit comments

Comments
 (0)