-
Notifications
You must be signed in to change notification settings - Fork 117
Open
Description
Hello,
Thanks for this package - it's great. We have some specific requirements for a project I'm working on, and I'd like to feed back a patch-package patch and some scripts I've made.
It implements this PR: #147 without the allowInvalidCertificates part (thanks!). I think with the allowInvalidCertificates part made optional this should be merged, but there has been no activity on this project for some time...
The use-case here is updating the pins via an OTA js bundle rather than relying on the app stores.
Here is the patch-package diff:
diff --git a/node_modules/react-native-ssl-pinning/index.d.ts b/node_modules/react-native-ssl-pinning/index.d.ts
index 7d0aebf..ce70fdd 100644
--- a/node_modules/react-native-ssl-pinning/index.d.ts
+++ b/node_modules/react-native-ssl-pinning/index.d.ts
@@ -17,6 +17,7 @@ export namespace ReactNativeSSLPinning {
sslPinning: {
certs: string[]
},
+ certBase64?: string,
timeoutInterval?: number,
disableAllSecurity?: boolean,
caseSensitiveHeaders?: boolean = false
diff --git a/node_modules/react-native-ssl-pinning/ios/RNSslPinning/RNSslPinning.m b/node_modules/react-native-ssl-pinning/ios/RNSslPinning/RNSslPinning.m
index 3179a72..806a399 100644
--- a/node_modules/react-native-ssl-pinning/ios/RNSslPinning/RNSslPinning.m
+++ b/node_modules/react-native-ssl-pinning/ios/RNSslPinning/RNSslPinning.m
@@ -215,6 +215,7 @@ RCT_EXPORT_METHOD(fetch:(NSString *)url obj:(NSDictionary *)obj callback:(RCTRes
AFSecurityPolicy *policy;
BOOL pkPinning = [[obj objectForKey:@"pkPinning"] boolValue];
BOOL disableAllSecurity = [[obj objectForKey:@"disableAllSecurity"] boolValue];
+ NSString *certBase64=[obj objectForKey:@"certBase64"];
NSSet *certificates = [AFSecurityPolicy certificatesInBundle:[NSBundle mainBundle]];
@@ -224,6 +225,12 @@ RCT_EXPORT_METHOD(fetch:(NSString *)url obj:(NSDictionary *)obj callback:(RCTRes
policy.validatesDomainName = false;
policy.allowInvalidCertificates = true;
}
+ else if (certBase64) {
+ NSURL *certData = [NSURL URLWithString: [NSString stringWithFormat:@"data:application/octet-stream;base64,%@", certBase64]];
+ NSData *certRaw = [NSData dataWithContentsOfURL:certData];
+ policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate
+ withPinnedCertificates:[NSSet setWithObject:certRaw]];
+ }
else if (pkPinning){
policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey withPinnedCertificates:certificates];
}This issue body was partially generated by patch-package.
The following bash script can be used to generate a json file containing the necessary values:
# setup
CERTS_DIR="./.certs"
CERTS_OUTPUT="$CERTS_DIR/certs.json"
DOMAINS=(
"google.com"
"bing.com"
)
(mkdir $CERTS_DIR 2> /dev/null || true)
(rm $CERTS_DIR/* 2> /dev/null || true)
(rm $CERTS_OUTPUT 2> /dev/null || true)
echo "------------------------------------------------------"
echo "Generating certificate pins for the following domains:"
for domain in ${DOMAINS[@]}; do
echo $domain
done
# get certificates from urls
for domain in ${DOMAINS[@]}; do
openssl s_client -showcerts -servername $domain -connect $domain:443 < /dev/null 2> /dev/null | openssl x509 -outform der > $CERTS_DIR/$domain.cer
openssl s_client -servername $domain -connect $domain:443 < /dev/null 2> /dev/null | openssl x509 -outform PEM > $CERTS_DIR/$domain.crt
openssl x509 -in $CERTS_DIR/$domain.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 > $CERTS_DIR/$domain.fingerprint
done
printf "{ certs: [" > $CERTS_OUTPUT
for domain in ${DOMAINS[@]}; do
filepath=$CERTS_DIR/$domain
certbase64=$(cat $filepath.cer | base64 | tr -d '\n')
certsha256=$(cat $filepath.fingerprint | tr -d '\n')
printf "{ domain: '$domain', sha256: '$certsha256', certBase64: '$certbase64' }," >> $CERTS_OUTPUT
done
printf "] }" >> $CERTS_OUTPUT
rm $CERTS_DIR/*.cer
rm $CERTS_DIR/*.crt
rm $CERTS_DIR/*.fingerprint
echo "Certificate pinning complete"
echo "------------------------------------------------------"
Then it can be consumed by js like so:
import { fetch as pinnedFetch, ReactNativeSSLPinning } from 'react-native-ssl-pinning'
import Constants from 'expo-constants'
import pins from '../../.certs/certs.json'
type PinningFunctionOptions = Omit<ReactNativeSSLPinning.Options, 'pkPinning' | 'sslPinning'>
export default async function fetchWithCertPinning(url: string, options: PinningFunctionOptions | RequestInit) {
// Use standard fetch for mock
if (Constants.manifest?.extra?.API_USE_MOCK === 'true') {
// eslint-disable-next-line no-console
console.warn('NO SSL PINNING USED')
return fetch(url, options as RequestInit)
}
// Extract domain from url
const domain = url.match(/^https?:\/\/([^/?#]+)(?:[/?#]|$)/i)?.[1]
const pin = pins.certs.find((x) => x.domain === domain)
if (!pin) {
throw new Error(`No pinned certificate found for domain ${domain}`)
}
const fetchOptions: ReactNativeSSLPinning.Options = {
...(options as PinningFunctionOptions),
pkPinning: true, // On android, uses the certs array below, which contains sha256 hashes of the certificate private keys
sslPinning: {
certs: [`sha256/${pin.sha256}`]
},
certBase64: pin.certBase64 // On ios, feeds in a base64 copy of the ssl certificate
}
try {
const result = await pinnedFetch(url, fetchOptions)
return result
} catch (e) {
// eslint-disable-next-line no-console
console.error(e)
if (typeof e === 'string') {
return {
status: 'CertificatePinningError',
json: () => {}
}
}
return e as ReactNativeSSLPinning.Response
}
}
Metadata
Metadata
Assignees
Labels
No labels