From 713a6283194fa31e890e7e00526532b349deecee Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Fri, 2 Feb 2024 17:02:36 +0100 Subject: [PATCH] Make iOS BoringSSL hook fallback to the real verification callback 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. --- ios/ios-tls-override.js | 57 ++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/ios/ios-tls-override.js b/ios/ios-tls-override.js index 3294e9c..b69baa2 100644 --- a/ios/ios-tls-override.js +++ b/ios/ios-tls-override.js @@ -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"), @@ -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'])); });