Skip to content

Commit a2d4d4a

Browse files
author
Matthieu Gicquel
authored
Support wildcard subdomains pinning (#10)
`*.domain.com` will now pin `domain.com` and all its subdomains to the provided certificates. This is sort of a breaking change if there were people relying on the undocumented (and divergent between platforms) behavior where on iOS `domain.com` would pin the subdomains too (on Android it would not) closes #9
1 parent 055b6d9 commit a2d4d4a

File tree

4 files changed

+64
-6
lines changed

4 files changed

+64
-6
lines changed

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,30 @@ This package implements [public key pinning](https://cheatsheetseries.owasp.org/
7676
]
7777
```
7878

79-
Please note that you'll need to provide *2* public key hashes. This is to encourage having proper procedures in place to avoid locking users out, [as described here in the TrustKit docs](https://github.com/datatheorem/TrustKit/blob/master/docs/getting-started.md#always-provide-at-least-one-backup-pin).
79+
Please note that you'll need to provide _2_ public key hashes. This is to encourage having proper procedures in place to avoid locking users out, [as described here in the TrustKit docs](https://github.com/datatheorem/TrustKit/blob/master/docs/getting-started.md#always-provide-at-least-one-backup-pin).
80+
81+
#### Pinning subdomains
82+
83+
To pin a specific subdomain, simply include it in the string you provide, eg:
84+
85+
```jsonc
86+
"sslPinning": {
87+
"subdomain.domain.com": [/* ... */]
88+
}
89+
```
90+
91+
To pin a domain and all its subdomains, use a wildcard:
92+
93+
```jsonc
94+
"sslPinning": {
95+
// domain.com and all its subdomains will be pinned
96+
"*.domain.com": [/* ... */]
97+
}
98+
```
99+
100+
> The wildcard can only be used for the full lefmost part of the hostname.
101+
>
102+
> These are invalid: `*domain.com`, `domain.*.com`, `sub.*.domain.com`
80103
81104
### Generating the public key hashes
82105

@@ -153,7 +176,7 @@ SafeKeyboardDetector.showInputMethodPicker(); // can only be called on Android
153176

154177
Contributions are welcome. See the [Expo modules docs](https://docs.expo.dev/modules/get-started/) for information on how to build/run/develop on the project.
155178

156-
When making a change to the `plugin` folder, you'll need to run `yarn build` before prebuilding and building the example app.
179+
When making a change to the `plugin` folder, you'll need to run `yarn prepare` before prebuilding and building the example app.
157180

158181
# 👉 About BAM
159182

example/App.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export default function App() {
1818
<Button title="open modal" onPress={() => setIsModalVisible(true)} />
1919
<Button title="fetch - valid certificates" onPress={fetchValid} />
2020
<Button title="fetch - invalid certificates" onPress={fetchInvalid} />
21+
<Button
22+
title="fetch - invalid certificates - subdodmain"
23+
onPress={fetchInvalidSubdomain}
24+
/>
2125
<Button title="Is current keyboard safe?" onPress={checkIsKeyboardSafe} />
2226
{Platform.OS === "android" ? (
2327
<Button
@@ -67,6 +71,17 @@ const fetchInvalid = async () => {
6771
}
6872
};
6973

74+
const fetchInvalidSubdomain = async () => {
75+
try {
76+
const response = await fetch("https://login.yahoo.com");
77+
console.warn("❌ invalid certificated but fetch succeeded", {
78+
status: response.status,
79+
});
80+
} catch (error) {
81+
console.warn("✅ invalid certificate and fetch failed", error);
82+
}
83+
};
84+
7085
const checkIsKeyboardSafe = () => {
7186
const isKeyboardSafe =
7287
SafeKeyboardDetector.getCurrentInputMethodInfo().isInDefaultSafeList;

example/app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"../app.plugin.js",
3131
{
3232
"sslPinning": {
33-
"yahoo.com": [
33+
"*.yahoo.com": [
3434
"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=",
3535
"rFjc3wG7lTZe43zeYTvPq8k4xdDEutCmIhI5dn4oCeE="
3636
],

plugin/src/withSSLPinning.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ const withSSLPinning: ConfigPlugin<Props> = (config, props) => {
1818

1919
const domainsConfig = Object.fromEntries(
2020
Object.entries(props).map(([hostName, certificates]) => {
21+
// TrustKit does not recognize wildcards and won't pin anything if passed a domain with one
22+
const hasSubdomainsWildcard = hostName.startsWith("*.");
23+
const hostnameWithoutWildcard = hasSubdomainsWildcard
24+
? hostName.slice(2)
25+
: hostName;
26+
2127
return [
22-
hostName,
28+
hostnameWithoutWildcard,
2329
{
2430
TSKEnforcePinning: true,
25-
TSKIncludeSubdomains: true,
31+
TSKIncludeSubdomains: hasSubdomainsWildcard,
2632
TSKPublicKeyHashes: certificates,
2733
},
2834
];
@@ -53,10 +59,24 @@ const withSSLPinning: ConfigPlugin<Props> = (config, props) => {
5359
return config;
5460
}
5561

62+
const domainsConfig: Record<string, string[]> = {};
63+
64+
for (const [hostName, certificates] of Object.entries(props)) {
65+
const hasSubdomainsWildcard = hostName.startsWith("*.");
66+
67+
domainsConfig[hostName] = certificates;
68+
69+
// When passed "*.domain.com", OkHttp will pin all subdomains, but not the root domain
70+
// To align behaviour between platforms, add pinning of the root domain as well
71+
if (hasSubdomainsWildcard) {
72+
domainsConfig[hostName.slice(2)] = certificates;
73+
}
74+
}
75+
5676
gradleProperties.push({
5777
type: "property",
5878
key: "RNAS_PINNING_CONFIG",
59-
value: JSON.stringify(props).replaceAll('"', '\\\\"'),
79+
value: JSON.stringify(domainsConfig).replaceAll('"', '\\\\"'),
6080
});
6181

6282
return config;

0 commit comments

Comments
 (0)