diff --git a/Dockerfile.chrome b/Dockerfile.chrome new file mode 100644 index 0000000..b9330d0 --- /dev/null +++ b/Dockerfile.chrome @@ -0,0 +1,78 @@ +# Use same base as the existing Dockerfile +FROM python:3.10.1-slim-buster + +WORKDIR /build + +# Dependencies for downloading and building BoringSSL +RUN apt-get update && \ + apt-get install -y git g++ cmake golang-go ninja-build curl unzip zlib1g-dev libbrotli-dev + +# BoringSSL doesn't have versions. Choose a commit that is used in a stable +# Chromium version. +ARG BORING_SSL_COMMIT=3a667d10e94186fd503966f5638e134fe9fb4080 +RUN curl -L https://github.com/google/boringssl/archive/${BORING_SSL_COMMIT}.zip -o boringssl.zip && \ + unzip boringssl && \ + mv boringssl-${BORING_SSL_COMMIT} boringssl + +# Compile BoringSSL. +# See https://boringssl.googlesource.com/boringssl/+/HEAD/BUILDING.md +RUN cd boringssl && \ + mkdir build && cd build && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=on -GNinja .. && \ + ninja + +# Fix the directory structure so that curl can compile against it. +# See https://everything.curl.dev/source/build/tls/boringssl +RUN mkdir boringssl/build/lib && \ + ln -s ../crypto/libcrypto.a boringssl/build/lib/libcrypto.a && \ + ln -s ../ssl/libssl.a boringssl/build/lib/libssl.a && \ + cp -R boringssl/include boringssl/build + +ARG NGHTTP2_VERSION=nghttp2-1.46.0 +ARG NGHTTP2_URL=https://github.com/nghttp2/nghttp2/releases/download/v1.46.0/nghttp2-1.46.0.tar.bz2 + +# The following are needed because we are going to change some autoconf scripts, +# both for libnghttp2 and curl. +RUN apt-get install -y autoconf automake autotools-dev pkg-config libtool + +# Download nghttp2 for HTTP/2.0 support. +RUN curl -o ${NGHTTP2_VERSION}.tar.bz2 -L ${NGHTTP2_URL} +RUN tar xf ${NGHTTP2_VERSION}.tar.bz2 + +# Patch nghttp2 pkg config file to support static builds. +COPY libnghttp2-*.patch ${NGHTTP2_VERSION}/ +RUN cd ${NGHTTP2_VERSION} && \ + for p in $(ls libnghttp2-*.patch); do patch -p1 < $p; done && \ + autoreconf -i && automake && autoconf + +# Compile nghttp2 +RUN cd ${NGHTTP2_VERSION} && \ + ./configure && \ + make && make install + +# Download curl. +ARG CURL_VERSION=curl-7.81.0 +RUN curl -o ${CURL_VERSION}.tar.xz https://curl.se/download/${CURL_VERSION}.tar.xz +RUN tar xf ${CURL_VERSION}.tar.xz + +# Patch curl and re-generate the configure script +COPY curl-*.patch ${CURL_VERSION}/ +RUN cd ${CURL_VERSION} && \ + for p in $(ls curl-*.patch); do patch -p1 < $p; done && \ + autoreconf -fi + +# Compile curl with BoringSSL +RUN cd ${CURL_VERSION} && \ + ./configure --with-openssl=/build/boringssl/build --enable-static --disable-shared --with-nghttp2=/usr/local LIBS="-pthread" CFLAGS="-I/build/boringssl/build" && \ + make + +# 'xxd' is needed for the wrapper script +RUN apt-get install -y xxd + +RUN mkdir out && \ + cp ${CURL_VERSION}/src/curl out/curl-impersonate-ch + +# Wrapper script +COPY curl_chrome* out/ + +RUN chmod +x out/* diff --git a/curl-openssl.patch b/curl-openssl.patch new file mode 100644 index 0000000..3145af0 --- /dev/null +++ b/curl-openssl.patch @@ -0,0 +1,60 @@ +--- curl-7.81.0-original/lib/vtls/openssl.c 2022-01-03 18:36:46.000000000 +0200 ++++ curl-7.81.0/lib/vtls/openssl.c 2022-02-18 17:05:57.253198793 +0200 +@@ -78,2 +78,4 @@ + ++#include ++ + #ifdef USE_AMISSL +@@ -2631,2 +2633,27 @@ + ++/* Taken from Chromium and adapted to C, ++ * see net/ssl/cert_compression.cc ++ */ ++int DecompressBrotliCert(SSL* ssl, ++ CRYPTO_BUFFER** out, ++ size_t uncompressed_len, ++ const uint8_t* in, ++ size_t in_len) { ++ uint8_t* data; ++ CRYPTO_BUFFER* decompressed = CRYPTO_BUFFER_alloc(&data, uncompressed_len); ++ if (!decompressed) { ++ return 0; ++ } ++ ++ size_t output_size = uncompressed_len; ++ if (BrotliDecoderDecompress(in_len, in, &output_size, data) != ++ BROTLI_DECODER_RESULT_SUCCESS || ++ output_size != uncompressed_len) { ++ return 0; ++ } ++ ++ *out = decompressed; ++ return 1; ++} ++ + static CURLcode ossl_connect_step1(struct Curl_easy *data, +@@ -2769,2 +2796,5 @@ + #ifdef SSL_OP_NO_TICKET ++ /* curl-impersonate patch. ++ * Don't turn on SSL_OP_NO_TICKET, we want TLS extension 35 (session_ticket) ++ * to be sent. */ + ctx_options |= SSL_OP_NO_TICKET; +@@ -2939,2 +2969,18 @@ + ++ /* curl-impersonate ++ * Configure BoringSSL to behave like Chrome. ++ * See Constructor of SSLContext at net/socket/ssl_client_socket_impl.cc ++ * and SSLClientSocketImpl::Init() ++ * in the Chromium's source code. */ ++ ++ /* Enable TLS GREASE. */ ++ SSL_CTX_set_grease_enabled(backend->ctx, 1); ++ ++ /* Add support for TLS extension 27 - compress_certificate. ++ * Add Brotli decompression. See Chromium net/ssl/cert_compression.cc */ ++ SSL_CTX_add_cert_compression_alg(backend->ctx, TLSEXT_cert_compression_brotli, NULL, DecompressBrotliCert); ++ ++ /* Enable TLS extensions 5 - status_request and 18 - signed_certificate_timestamp. */ ++ SSL_CTX_enable_ocsp_stapling(backend->ctx); ++ SSL_CTX_enable_signed_cert_timestamps(backend->ctx); + diff --git a/curl_chrome98 b/curl_chrome98 new file mode 100755 index 0000000..58e20f2 --- /dev/null +++ b/curl_chrome98 @@ -0,0 +1,40 @@ +#!/bin/bash + +# Find the directory of this script +dir=`echo "$0" | sed 's%/[^/]*$%%'` + +PIPE=/tmp/curl-pipe + +rm -f "$PIPE" && mkfifo "$PIPE" +exec 5<>"$PIPE" 3>"$PIPE" 4<"$PIPE" 5>&- + +# The list of ciphers can be obtained by looking at the Client Hello message in +# Wireshark, then converting it using this reference +# https://wiki.mozilla.org/Security/Cipher_Suites +"$dir/curl-impersonate-ch" \ + --ciphers TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305,ECDHE-RSA-CHACHA20-POLY1305,ECDHE-RSA-AES128-SHA,ECDHE-RSA-AES256-SHA,AES128-GCM-SHA256,AES256-GCM-SHA384,AES128-SHA,AES256-SHA \ + -H 'Upgrade-Insecure-Requests: 1' \ + -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36' \ + -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' \ + -H 'Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"' \ + -H 'Sec-Ch-Ua-Mobile: ?0' \ + -H 'Sec-Ch-Ua-Platform: Windows' \ + -H 'Sec-Fetch-Site: none' \ + -H 'Sec-Fetch-Mode: navigate' \ + -H 'Sec-Fetch-User: ?1' \ + -H 'Sec-Fetch-Dest: document' \ + -H 'Accept-Encoding: gzip, deflate, br' \ + -H 'Accept-Language: en-US,en;q=0.9' \ + --http2 --false-start --tlsv1.2 \ + $@ >&3 + +exec 3>&- + +IFS= read -d '' -r -n 2 -u 4 header + +# Due to the "Accept-Encoding: gzip" header, we may receive a gzipped file. +if [ "$(echo -n $header | xxd -l 2 -p)" == "1f8b" ]; then + (printf "%s" "$header"; cat <&4) | gzip -cd; +else + printf "%s" "$header"; cat <&4; +fi