Repository navigation
SDK-7999 Reject TLS chains carrying the compromised eMudhra intermediate keys - #188
harshit-browserstack wants to merge 4 commits into
Conversation
Node still trusts the 8 eMudhra intermediates whose private keys are recoverable, so the endpoint lookup and the binary download could be served by anyone holding a certificate issued under them. Install a tls.checkServerIdentity wrapper in Local.js and in the two scripts startSync runs as child processes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
…CA names (SDK-7999) Two gaps from review: a parse failure on one certificate ended the walk for the whole chain, and Node links issuerCertificate by name, so a decoy certificate sent ahead of the real intermediate diverted the walk past the distrusted key. Skip only the certificate that fails to parse, and also match the 8 intermediates' subject names against each certificate's subject and issuer (they are pathlen:0, so the leaf's issuer is always one of them). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review follow-up — two gaps in the chain check, fixed in the latest commit (same change in browserstack-binary#1960, browserstack-node-agent#2742, browserstack-cypress-cli#1203, browserstack-local-nodejs#188):
Verified locally: the decoy chain is now refused in both orders on Node 16/18/22 (20/20), the earlier cases still hold (50/50, including a forced parse failure on the leaf), the 8 listed names match the parsed subjects of all 16 published certificates, and 20 hosts the SDK, binary and Local use still connect. |
OpenSSL chains names case-insensitively with whitespace collapsed, so a leaf naming its issuer 'em dv tls ca - g3b' still validates through the real intermediate; with a differently-cased decoy diverting the issuer walk, exact matching missed both names. Normalize names the same way before comparing, and never report an empty name when a key matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security-review follow-up (latest commit): names are now compared the way OpenSSL chains them (case-insensitive, whitespace collapsed). Reproduced the gap first: a leaf naming its issuer |
Jest runs each test file in its own context with its own global but shares core modules, so the SDK's per-file setup re-ran the install and wrapped tls.checkServerIdentity again for every test file. Keep the install state on the tls module instead; worker threads still get their own tls module and install their own check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re-review follow-up (latest commit): the install state now lives on the |
Jira: SDK-7999. Same fix as browserstack-binary#1960, browserstack-node-agent#2742 and browserstack-cypress-cli#1203.
Problem
The private keys of 8 eMudhra intermediate CAs are recoverable (incident), and Node.js has not shipped the distrust yet (nodejs/node#66871 is unreleased). The binary-endpoint lookup (
/binary/api/v1/endpoint, which carries the access key) and the binary download both accept a certificate issued under them, so anyone on the network path can read the key or serve their ownBrowserStackLocal, which this library then runs.Change
lib/distrustedCaCheck.jswrapstls.checkServerIdentity: Node's hostname check runs first, then the connection is refused (ERR_TLS_DISTRUSTED_INTERMEDIATE) if any certificate in the chain has one of the 8 published SPKI SHA-256 hashes. Matching the key catches both the emSign-issued and the Sectigo cross-signed copies.lib/Local.jsand inlib/fetchDownloadSourceUrl.js/lib/download.js, becausestartSyncruns those two as separatenodeprocesses that never loadLocal.js.crypto.createPublicKeyfrom the certificate (cert.pubkeyis the raw point for EC keys, andX509Certificateneeds Node 15.6). On Node older than 11.6, wherecreatePublicKeydoes not exist, the check is skipped.Tests
bs-hostpointed atdv-g3b-emudhra.we-have-a-problem.com: 1.5.16 completes TLS and receives the host's reply; with this change the endpoint lookup fails withERR_TLS_DISTRUSTED_INTERMEDIATEin bothstartSync(child process) andstart, on Node 14 and 22.BrowserStackLocalthroughstartSyncwith this change, and Automate sessions through the tunnel passed.npm test: 47 passing, 2 pending, 4 failing; the same 4 fail onmaster(the download tests buildLocalBinarywithout an access key).