UNPKG

brave-real-playwright-core

Version:

Brave-optimized Playwright Core (v1.55.0) with comprehensive stealth patches and error stack sanitization

384 lines (383 loc) 15.4 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var socksClientCertificatesInterceptor_exports = {}; __export(socksClientCertificatesInterceptor_exports, { ClientCertificatesProxy: () => ClientCertificatesProxy, getMatchingTLSOptionsForOrigin: () => getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded: () => rewriteOpenSSLErrorIfNeeded }); module.exports = __toCommonJS(socksClientCertificatesInterceptor_exports); var import_events = require("events"); var import_http2 = __toESM(require("http2")); var import_net = __toESM(require("net")); var import_stream = __toESM(require("stream")); var import_tls = __toESM(require("tls")); var import_socksProxy = require("./utils/socksProxy"); var import_utils = require("../utils"); var import_browserContext = require("./browserContext"); var import_network = require("./utils/network"); var import_debugLogger = require("./utils/debugLogger"); var import_happyEyeballs = require("./utils/happyEyeballs"); var import_utilsBundle = require("../utilsBundle"); let dummyServerTlsOptions = void 0; function loadDummyServerCertsIfNeeded() { if (dummyServerTlsOptions) return; const { cert, key } = (0, import_utils.generateSelfSignedCertificate)(); dummyServerTlsOptions = { key, cert }; } class SocksProxyConnection { constructor(socksProxy, uid, host, port) { this._firstPackageReceived = false; this._closed = false; this.socksProxy = socksProxy; this.uid = uid; this.host = host; this.port = port; this._serverCloseEventListener = () => { this._browserEncrypted.destroy(); }; this._browserEncrypted = new import_stream.default.Duplex({ read: () => { }, write: (data, encoding, callback) => { this.socksProxy._socksProxy.sendSocketData({ uid: this.uid, data }); callback(); }, destroy: (error, callback) => { if (error) socksProxy._socksProxy.sendSocketError({ uid: this.uid, error: error.message }); else socksProxy._socksProxy.sendSocketEnd({ uid: this.uid }); callback(); } }); } async connect() { const proxyAgent = this.socksProxy.getProxyAgent(this.host, this.port); if (proxyAgent) this._serverEncrypted = await proxyAgent.callback(new import_events.EventEmitter(), { host: rewriteToLocalhostIfNeeded(this.host), port: this.port, secureEndpoint: false }); else this._serverEncrypted = await (0, import_happyEyeballs.createSocket)(rewriteToLocalhostIfNeeded(this.host), this.port); this._serverEncrypted.once("close", this._serverCloseEventListener); this._serverEncrypted.once("error", (error) => this._browserEncrypted.destroy(error)); if (this._closed) { this._serverEncrypted.destroy(); return; } this.socksProxy._socksProxy.socketConnected({ uid: this.uid, host: this._serverEncrypted.localAddress, port: this._serverEncrypted.localPort }); } onClose() { this._serverEncrypted.destroy(); this._browserEncrypted.destroy(); this._closed = true; } onData(data) { if (!this._firstPackageReceived) { this._firstPackageReceived = true; if (data[0] === 22) this._establishTlsTunnel(this._browserEncrypted, data); else this._establishPlaintextTunnel(this._browserEncrypted); } this._browserEncrypted.push(data); } _establishPlaintextTunnel(browserEncrypted) { browserEncrypted.pipe(this._serverEncrypted); this._serverEncrypted.pipe(browserEncrypted); } _establishTlsTunnel(browserEncrypted, clientHello) { const browserALPNProtocols = parseALPNFromClientHello(clientHello) || ["http/1.1"]; import_debugLogger.debugLogger.log("client-certificates", `Browser->Proxy ${this.host}:${this.port} offers ALPN ${browserALPNProtocols}`); const serverDecrypted = import_tls.default.connect({ socket: this._serverEncrypted, host: this.host, port: this.port, rejectUnauthorized: !this.socksProxy.ignoreHTTPSErrors, ALPNProtocols: browserALPNProtocols, servername: !import_net.default.isIP(this.host) ? this.host : void 0, secureContext: this.socksProxy.secureContextMap.get(new URL(`https://${this.host}:${this.port}`).origin) }, async () => { const browserDecrypted = await this._upgradeToTLSIfNeeded(browserEncrypted, serverDecrypted.alpnProtocol); import_debugLogger.debugLogger.log("client-certificates", `Proxy->Server ${this.host}:${this.port} chooses ALPN ${browserDecrypted.alpnProtocol}`); browserDecrypted.pipe(serverDecrypted); serverDecrypted.pipe(browserDecrypted); const cleanup = (error) => this._serverEncrypted.destroy(error); browserDecrypted.once("error", cleanup); serverDecrypted.once("error", cleanup); browserDecrypted.once("close", cleanup); serverDecrypted.once("close", cleanup); if (this._closed) serverDecrypted.destroy(); }); serverDecrypted.once("error", async (error) => { import_debugLogger.debugLogger.log("client-certificates", `error when connecting to server: ${error.message.replaceAll("\n", " ")}`); this._serverEncrypted.removeListener("close", this._serverCloseEventListener); this._serverEncrypted.destroy(); const browserDecrypted = await this._upgradeToTLSIfNeeded(this._browserEncrypted, serverDecrypted.alpnProtocol); const responseBody = (0, import_utils.escapeHTML)("Playwright client-certificate error: " + error.message).replaceAll("\n", " <br>"); if (browserDecrypted.alpnProtocol === "h2") { if ("performServerHandshake" in import_http2.default) { const session = import_http2.default.performServerHandshake(browserDecrypted); session.on("error", (error2) => { this._browserEncrypted.destroy(error2); }); session.once("stream", (stream2) => { const cleanup = (error2) => { session.close(); this._browserEncrypted.destroy(error2); }; stream2.once("end", cleanup); stream2.once("error", cleanup); stream2.respond({ [import_http2.default.constants.HTTP2_HEADER_CONTENT_TYPE]: "text/html", [import_http2.default.constants.HTTP2_HEADER_STATUS]: 503 }); stream2.end(responseBody); }); } else { this._browserEncrypted.destroy(error); } } else { browserDecrypted.end([ "HTTP/1.1 503 Internal Server Error", "Content-Type: text/html; charset=utf-8", "Content-Length: " + Buffer.byteLength(responseBody), "", responseBody ].join("\r\n")); } }); } async _upgradeToTLSIfNeeded(socket, alpnProtocol) { this._brorwserDecrypted ??= new Promise((resolve, reject) => { const dummyServer = import_tls.default.createServer({ ...dummyServerTlsOptions, ALPNProtocols: [alpnProtocol || "http/1.1"] }); dummyServer.emit("connection", socket); dummyServer.once("secureConnection", (tlsSocket) => { dummyServer.close(); resolve(tlsSocket); }); dummyServer.once("error", (error) => { dummyServer.close(); reject(error); }); }); return this._brorwserDecrypted; } } class ClientCertificatesProxy { constructor(contextOptions) { this._connections = /* @__PURE__ */ new Map(); this.secureContextMap = /* @__PURE__ */ new Map(); (0, import_browserContext.verifyClientCertificates)(contextOptions.clientCertificates); this.ignoreHTTPSErrors = contextOptions.ignoreHTTPSErrors; this._proxy = contextOptions.proxy; this._initSecureContexts(contextOptions.clientCertificates); this._socksProxy = new import_socksProxy.SocksProxy(); this._socksProxy.setPattern("*"); this._socksProxy.addListener(import_socksProxy.SocksProxy.Events.SocksRequested, async (payload) => { try { const connection = new SocksProxyConnection(this, payload.uid, payload.host, payload.port); await connection.connect(); this._connections.set(payload.uid, connection); } catch (error) { import_debugLogger.debugLogger.log("client-certificates", `Failed to connect to ${payload.host}:${payload.port}: ${error.message}`); this._socksProxy.socketFailed({ uid: payload.uid, errorCode: error.code }); } }); this._socksProxy.addListener(import_socksProxy.SocksProxy.Events.SocksData, (payload) => { this._connections.get(payload.uid)?.onData(payload.data); }); this._socksProxy.addListener(import_socksProxy.SocksProxy.Events.SocksClosed, (payload) => { this._connections.get(payload.uid)?.onClose(); this._connections.delete(payload.uid); }); loadDummyServerCertsIfNeeded(); } getProxyAgent(host, port) { const proxyFromOptions = (0, import_network.createProxyAgent)(this._proxy); if (proxyFromOptions) return proxyFromOptions; const proxyFromEnv = (0, import_utilsBundle.getProxyForUrl)(`https://${host}:${port}`); if (proxyFromEnv) return (0, import_network.createProxyAgent)({ server: proxyFromEnv }); } _initSecureContexts(clientCertificates) { const origin2certs = /* @__PURE__ */ new Map(); for (const cert of clientCertificates || []) { const origin = normalizeOrigin(cert.origin); const certs = origin2certs.get(origin) || []; certs.push(cert); origin2certs.set(origin, certs); } for (const [origin, certs] of origin2certs) { try { this.secureContextMap.set(origin, import_tls.default.createSecureContext(convertClientCertificatesToTLSOptions(certs))); } catch (error) { error = rewriteOpenSSLErrorIfNeeded(error); throw (0, import_utils.rewriteErrorMessage)(error, `Failed to load client certificate: ${error.message}`); } } } static async create(progress, contextOptions) { const proxy = new ClientCertificatesProxy(contextOptions); try { await progress.race(proxy._socksProxy.listen(0, "127.0.0.1")); return proxy; } catch (error) { await proxy.close(); throw error; } } proxySettings() { return { server: `socks5://127.0.0.1:${this._socksProxy.port()}` }; } async close() { await this._socksProxy.close(); } } function normalizeOrigin(origin) { try { return new URL(origin).origin; } catch (error) { return origin; } } function convertClientCertificatesToTLSOptions(clientCertificates) { if (!clientCertificates || !clientCertificates.length) return; const tlsOptions = { pfx: [], key: [], cert: [] }; for (const cert of clientCertificates) { if (cert.cert) tlsOptions.cert.push(cert.cert); if (cert.key) tlsOptions.key.push({ pem: cert.key, passphrase: cert.passphrase }); if (cert.pfx) tlsOptions.pfx.push({ buf: cert.pfx, passphrase: cert.passphrase }); } return tlsOptions; } function getMatchingTLSOptionsForOrigin(clientCertificates, origin) { const matchingCerts = clientCertificates?.filter( (c) => normalizeOrigin(c.origin) === origin ); return convertClientCertificatesToTLSOptions(matchingCerts); } function rewriteToLocalhostIfNeeded(host) { return host === "local.playwright" ? "localhost" : host; } function rewriteOpenSSLErrorIfNeeded(error) { if (error.message !== "unsupported" && error.code !== "ERR_CRYPTO_UNSUPPORTED_OPERATION") return error; return (0, import_utils.rewriteErrorMessage)(error, [ "Unsupported TLS certificate.", "Most likely, the security algorithm of the given certificate was deprecated by OpenSSL.", "For more details, see https://github.com/openssl/openssl/blob/master/README-PROVIDERS.md#the-legacy-provider", "You could probably modernize the certificate by following the steps at https://github.com/nodejs/node/issues/40672#issuecomment-1243648223" ].join("\n")); } function parseALPNFromClientHello(buffer) { if (buffer.length < 6) return null; if (buffer[0] !== 22) return null; let offset = 5; if (buffer[offset] !== 1) return null; offset += 4; offset += 2; offset += 32; if (offset >= buffer.length) return null; const sessionIdLength = buffer[offset]; offset += 1 + sessionIdLength; if (offset + 2 > buffer.length) return null; const cipherSuitesLength = buffer.readUInt16BE(offset); offset += 2 + cipherSuitesLength; if (offset >= buffer.length) return null; const compressionMethodsLength = buffer[offset]; offset += 1 + compressionMethodsLength; if (offset + 2 > buffer.length) return null; const extensionsLength = buffer.readUInt16BE(offset); offset += 2; const extensionsEnd = offset + extensionsLength; if (extensionsEnd > buffer.length) return null; while (offset + 4 <= extensionsEnd) { const extensionType = buffer.readUInt16BE(offset); offset += 2; const extensionLength = buffer.readUInt16BE(offset); offset += 2; if (offset + extensionLength > extensionsEnd) return null; if (extensionType === 16) return parseALPNExtension(buffer.subarray(offset, offset + extensionLength)); offset += extensionLength; } return null; } function parseALPNExtension(buffer) { if (buffer.length < 2) return null; const listLength = buffer.readUInt16BE(0); if (listLength !== buffer.length - 2) return null; const protocols = []; let offset = 2; while (offset < buffer.length) { const protocolLength = buffer[offset]; offset += 1; if (offset + protocolLength > buffer.length) break; const protocol = buffer.subarray(offset, offset + protocolLength).toString("utf8"); protocols.push(protocol); offset += protocolLength; } return protocols.length > 0 ? protocols : null; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ClientCertificatesProxy, getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded });