Skip to content

Commit

Permalink
Make iOS BoringSSL hook fallback to the real verification callback
Browse files Browse the repository at this point in the history
This is useful because it means that traffic which bypasses the proxy
(e.g. by using TLS passthrough or similar) will still succeed! This
makes it easier to handle any issues later on.
  • Loading branch information
pimterry committed Feb 2, 2024
1 parent 140af9b commit 713a628
Showing 1 changed file with 36 additions and 21 deletions.
57 changes: 36 additions & 21 deletions ios/ios-tls-override.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,32 +48,47 @@ const crypto_buffer_data = new NativeFunction(
const SSL_VERIFY_NONE = 0x0;
const SSL_VERIFY_PEER = 0x1;

const VerificationCallback = new NativeCallback(function (ssl, out_alert) {
// Extremely dumb certificate validation: we accept any chain where the *exact* CA cert
// we were given is present. No flexibility for non-trivial cert chains, and zero
// validation of expiry/hostname/etc.
// We cache the verification callbacks we create. In general (in testing, 100% of the time) the
// 'real' callback is always the exact same address, so this is much more efficient than creating
// a new callback every time.
const verificationCallbackCache = {};

const peerCerts = SSL_get0_peer_certificates(ssl);
const buildVerificationCallback = (realCallbackAddr) => {
if (!verificationCallbackCache[realCallbackAddr]) {
const realCallback = new NativeFunction(realCallbackAddr, 'int', ['pointer','pointer']);

// Loop through every cert in the chain:
for (let i = 0; i < sk_num(peerCerts); i++) {
// For each cert, check if it *exactly* matches our configured CA cert:
const cert = sk_value(peerCerts, i);
const certDataLength = crypto_buffer_len(cert).toNumber();
const hookedCallback = new NativeCallback(function (ssl, out_alert) {
// Extremely dumb certificate validation: we accept any chain where the *exact* CA cert
// we were given is present. No flexibility for non-trivial cert chains, and zero
// validation beyond presence of the expected CA certificate.

if (certDataLength !== CERT_DER.byteLength) continue;
const peerCerts = SSL_get0_peer_certificates(ssl);

const certPointer = crypto_buffer_data(cert);
const certData = new Uint8Array(certPointer.readByteArray(certDataLength));
// Loop through every cert in the chain:
for (let i = 0; i < sk_num(peerCerts); i++) {
// For each cert, check if it *exactly* matches our configured CA cert:
const cert = sk_value(peerCerts, i);
const certDataLength = crypto_buffer_len(cert).toNumber();

if (certData.every((byte, j) => CERT_DER[j] === byte)) {
return SSL_VERIFY_NONE;
}
if (certDataLength !== CERT_DER.byteLength) continue;

const certPointer = crypto_buffer_data(cert);
const certData = new Uint8Array(certPointer.readByteArray(certDataLength));

if (certData.every((byte, j) => CERT_DER[j] === byte)) {
return SSL_VERIFY_NONE;
}
}

// No matched peer - fallback to the provided callback instead:
return realCallback(ssl, out_alert);
}, 'int', ['pointer','pointer']);

verificationCallbackCache[realCallbackAddr] = hookedCallback;
}

// No matched peer - fallback to default OpenSSL cert verification
return SSL_VERIFY_PEER;
},'int',['pointer','pointer']);
return verificationCallbackCache[realCallbackAddr];
};

const customVerifyAddrs = [
Module.findExportByName("libboringssl.dylib", "SSL_set_custom_verify"),
Expand All @@ -88,8 +103,8 @@ customVerifyAddrs.forEach((set_custom_verify_addr) => {

// When this function is called, ignore the provided callback, and
// configure our callback instead:
Interceptor.replace(set_custom_verify_fn, new NativeCallback(function(ssl, mode, _ignoredProvidedCallback) {
set_custom_verify_fn(ssl, mode, VerificationCallback);
Interceptor.replace(set_custom_verify_fn, new NativeCallback(function(ssl, mode, providedCallbackAddr) {
set_custom_verify_fn(ssl, mode, buildVerificationCallback(providedCallbackAddr));
}, 'void', ['pointer', 'int', 'pointer']));
});

Expand Down

0 comments on commit 713a628

Please sign in to comment.