Validate the certificate chain (for our CA) when hooking iOS BoringSSL

This is really cool. Rather than just blindly disabling all TLS
validation, we now verify the cert directly against the CA you provide.
We only do extremely basic checks (some more testing required to
validate this provides even basic guarantees) so this shouldn't be
relied for rock-solid TLS validation (probably even after it's been
tested tbh) and it won't handle many real-world cases of CA validation,
but in terms of "do a local MitM while retaining the basics of TLS
protection" it should do a reasonable job, hopefully.
This commit is contained in:
Tim Perry
2024-02-02 17:59:16 +01:00
parent 88016333b5
commit 140af9b8be
+59 -3
View File
@@ -13,10 +13,66 @@ try {
}
}
const SSL_VERIFY_NONE = 0x0;
// Get the peer certificates from an SSL pointer. Returns a pointer to a STACK_OF(CRYPTO_BUFFER)
// which requires use of the next few methods below to actually access.
// https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_get0_peer_certificates
const SSL_get0_peer_certificates = new NativeFunction(
Module.findExportByName('libboringssl.dylib', 'SSL_get0_peer_certificates'),
'pointer', ['pointer']
);
const VerificationCallback = new NativeCallback(function (ssl, out_alert){
return SSL_VERIFY_NONE;
// Stack methods:
// https://commondatastorage.googleapis.com/chromium-boringssl-docs/stack.h.html
const sk_num = new NativeFunction(
Module.findExportByName('libboringssl.dylib', 'sk_num'),
'size_t', ['pointer']
);
const sk_value = new NativeFunction(
Module.findExportByName('libboringssl.dylib', 'sk_value'),
'pointer', ['pointer', 'int']
);
// Crypto buffer methods:
// https://commondatastorage.googleapis.com/chromium-boringssl-docs/pool.h.html
const crypto_buffer_len = new NativeFunction(
Module.findExportByName('libboringssl.dylib', 'CRYPTO_BUFFER_len'),
'size_t', ['pointer']
);
const crypto_buffer_data = new NativeFunction(
Module.findExportByName('libboringssl.dylib', 'CRYPTO_BUFFER_data'),
'pointer', ['pointer']
);
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.
const peerCerts = SSL_get0_peer_certificates(ssl);
// 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 (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 default OpenSSL cert verification
return SSL_VERIFY_PEER;
},'int',['pointer','pointer']);
const customVerifyAddrs = [