Skip to content

Commit 2aad338

Browse files
authored
Add --tls-random (#37)
* Added --tls-random, closes #35 * Added tls-random to changelog
1 parent ee7c6c9 commit 2aad338

File tree

5 files changed

+93
-43
lines changed

5 files changed

+93
-43
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ adheres to [Semantic Versioning][semver].
1111

1212
## [Unreleased]
1313

14+
### Added
15+
16+
* Added support for `--tls-random` command-line argument. This option is used to
17+
enable TLS Random for TLS ClientHello. ([#35][#35])
18+
19+
[#35]: https://github.com/ameshkov/gocurl/issues/35
20+
1421
[unreleased]: https://github.com/ameshkov/gocurl/compare/v1.4.7...HEAD
1522

1623
## [1.4.7] - 2025-04-02

README.md

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ Also, you can use some new stuff that is not supported by curl.
8989
* `gocurl --tls-split-hello 5:50 https://httpbin.agrd.workers.dev/get` split
9090
TLS ClientHello in two parts and make a 50ms delay after sending the first
9191
part.
92+
* `gocurl --tls-random "gyufwmGYeIiq0B4nUjEYu3NcqVdlHbIXhx74fq4terc=" https://httpbin.agrd.workers.dev/get`
93+
use a custom TLS ClientHello random value.
9294
* `gocurl -v --ech https://crypto.cloudflare.com/cdn-cgi/trace` enables support
9395
for ECH (Encrypted Client Hello) for the request. More on this [below](#ech).
9496
* `gocurl --dns-servers "tls://dns.google" https://httpbin.agrd.workers.dev/get`
@@ -262,58 +264,41 @@ Usage:
262264
gocurl [OPTIONS]
263265
264266
Application Options:
265-
--url=<URL> URL the request will be made to. Can be specified
266-
without any flags.
267+
--url=<URL> URL the request will be made to. Can be specified without any flags.
267268
-X, --request=<method> HTTP method. GET by default.
268-
-d, --data=<data> Sends the specified data to the HTTP server using
269-
content type application/x-www-form-urlencoded.
270-
-H, --header= Extra header to include in the request. Can be
271-
specified multiple times.
272-
-x, --proxy=[protocol://username:password@]host[:port] Use the specified proxy. The proxy string can be
273-
specified with a protocol:// prefix.
274-
--connect-to=<HOST1:PORT1:HOST2:PORT2> For a request to the given HOST1:PORT1 pair, connect
275-
to HOST2:PORT2 instead. Can be specified multiple
276-
times.
269+
-d, --data=<data> Sends the specified data to the HTTP server using content type application/x-www-form-urlencoded.
270+
-H, --header= Extra header to include in the request. Can be specified multiple times.
271+
-x, --proxy=[protocol://username:password@]host[:port] Use the specified proxy. The proxy string can be specified with a protocol:// prefix.
272+
--connect-to=<HOST1:PORT1:HOST2:PORT2> For a request to the given HOST1:PORT1 pair, connect to HOST2:PORT2 instead. Can be specified
273+
multiple times.
277274
-I, --head Fetch the headers only.
278275
-k, --insecure Disables TLS verification of the connection.
279276
--tlsv1.3 Forces gocurl to use TLS v1.3 or newer.
280277
--tlsv1.2 Forces gocurl to use TLS v1.2 or newer.
281-
--tls-max=<VERSION> (TLS) VERSION defines maximum supported TLS version.
282-
Can be 1.2 or 1.3. The minimum acceptable version is
283-
set by tlsv1.2 or tlsv1.3.
278+
--tls-max=<VERSION> (TLS) VERSION defines maximum supported TLS version. Can be 1.2 or 1.3. The minimum acceptable
279+
version is set by tlsv1.2 or tlsv1.3.
284280
--ciphers=<space-separated list of ciphers> Specifies which ciphers to use in the connection, see
285-
https://go.dev/src/crypto/tls/cipher_suites.go for the
286-
full list of available ciphers.
287-
--tls-servername=<HOSTNAME> Specifies the server name that will be sent in TLS
288-
ClientHello
281+
https://go.dev/src/crypto/tls/cipher_suites.go for the full list of available ciphers.
282+
--tls-servername=<HOSTNAME> Specifies the server name that will be sent in TLS ClientHello
289283
--http1.1 Forces gocurl to use HTTP v1.1.
290284
--http2 Forces gocurl to use HTTP v2.
291285
--http3 Forces gocurl to use HTTP v3.
292286
--ech Enables ECH support for the request.
293-
--echgrease Forces sending ECH grease in the ClientHello, but does
294-
not try to resolve the ECH configuration.
295-
--echconfig=<base64-encoded data> ECH configuration to use for this request. Implicitly
296-
enables --ech when specified.
297-
-4, --ipv4 This option tells gocurl to use IPv4 addresses only
298-
when resolving host names.
299-
-6, --ipv6 This option tells gocurl to use IPv6 addresses only
300-
when resolving host names.
301-
--dns-servers=<DNSADDR1,DNSADDR2> DNS servers to use when making the request. Supports
302-
encrypted DNS: tls://, https://, quic://, sdns://
303-
--resolve=<[+]host:port:addr[,addr]...> Provide a custom address for a specific host. port is
304-
ignored by gocurl. '*' can be used instead of the host
305-
name. Can be specified multiple times.
306-
--tls-split-hello=<CHUNKSIZE:DELAY> An option that allows splitting TLS ClientHello in two
307-
parts in order to avoid common DPI systems detecting
308-
TLS. CHUNKSIZE is the size of the first bytes before
309-
ClientHello is split, DELAY is delay in milliseconds
310-
before sending the second part.
311-
--json-output Makes gocurl write machine-readable output in JSON
312-
format.
313-
-o, --output=<file> Defines where to write the received data. If not set,
314-
gocurl will write everything to stdout.
315-
--experiment=<name[:value]> Allows enabling experimental options. See the
316-
documentation for available options. Can be specified
287+
--echgrease Forces sending ECH grease in the ClientHello, but does not try to resolve the ECH configuration.
288+
--echconfig=<base64-encoded data> ECH configuration to use for this request. Implicitly enables --ech when specified.
289+
-4, --ipv4 This option tells gocurl to use IPv4 addresses only when resolving host names.
290+
-6, --ipv6 This option tells gocurl to use IPv6 addresses only when resolving host names.
291+
--dns-servers=<DNSADDR1,DNSADDR2> DNS servers to use when making the request. Supports encrypted DNS: tls://, https://, quic://,
292+
sdns://
293+
--resolve=<[+]host:port:addr[,addr]...> Provide a custom address for a specific host. port is ignored by gocurl. '*' can be used instead of
294+
the host name. Can be specified multiple times.
295+
--tls-split-hello=<CHUNKSIZE:DELAY> An option that allows splitting TLS ClientHello in two parts in order to avoid common DPI systems
296+
detecting TLS. CHUNKSIZE is the size of the first bytes before ClientHello is split, DELAY is delay
297+
in milliseconds before sending the second part.
298+
--tls-random=<base64> Base64-encoded 32-byte TLS ClientHello random value.
299+
--json-output Makes gocurl write machine-readable output in JSON format.
300+
-o, --output=<file> Defines where to write the received data. If not set, gocurl will write everything to stdout.
301+
--experiment=<name[:value]> Allows enabling experimental options. See the documentation for available options. Can be specified
317302
multiple times.
318303
-v, --verbose Verbose output (optional).
319304

internal/client/clientdialer.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package client
22

33
import (
44
"context"
5+
"crypto/rand"
56
"crypto/tls"
67
"fmt"
8+
"io"
79
"net"
810

911
"github.com/ameshkov/gocurl/internal/client/cfcrypto"
@@ -162,6 +164,37 @@ func createDialFunc(
162164
return dial, nil
163165
}
164166

167+
// tlsRandomReader is an io.Reader that returns the provided TLS random bytes,
168+
// and then fallbacks to crypto/rand.Reader.
169+
type tlsRandomReader struct {
170+
data []byte
171+
pos int
172+
}
173+
174+
// type check
175+
var _ io.Reader = (*tlsRandomReader)(nil)
176+
177+
// Read implements io.Reader for *tlsRandomReader. It returns the provided TLS
178+
// random bytes, and then fallbacks to crypto/rand.Reader.
179+
func (r *tlsRandomReader) Read(p []byte) (n int, err error) {
180+
if r.pos < len(r.data) {
181+
toCopy := len(r.data) - r.pos
182+
if toCopy > len(p) {
183+
toCopy = len(p)
184+
}
185+
copy(p, r.data[r.pos:r.pos+toCopy])
186+
r.pos += toCopy
187+
if toCopy < len(p) {
188+
// Fill the rest from crypto/rand.Reader
189+
nn, err := io.ReadFull(rand.Reader, p[toCopy:])
190+
return toCopy + nn, err
191+
}
192+
return toCopy, nil
193+
}
194+
// All data consumed, fallback to crypto/rand.Reader
195+
return rand.Read(p)
196+
}
197+
165198
// createTLSConfig creates TLS config based on the configuration.
166199
func createTLSConfig(cfg *config.Config, out *output.Output) (tlsConfig *tls.Config) {
167200
tlsConfig = &tls.Config{
@@ -184,6 +217,11 @@ func createTLSConfig(cfg *config.Config, out *output.Output) (tlsConfig *tls.Con
184217
tlsConfig.InsecureSkipVerify = true
185218
}
186219

220+
if cfg.TLSRandom != nil && len(cfg.TLSRandom) == 32 {
221+
out.Debug("Overriding TLS ClientHello random value")
222+
tlsConfig.Rand = &tlsRandomReader{data: cfg.TLSRandom}
223+
}
224+
187225
if websocket.IsWebSocket(cfg.RequestURL) {
188226
out.Debug("Forcing ALPN http/1.1 as this is a WebSocket request")
189227

internal/config/config.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ type Config struct {
6464
// ForceHTTP2 forces using HTTP/2.
6565
ForceHTTP2 bool
6666

67-
// ForceHTTP2 forces using HTTP/3.
67+
// ForceHTTP3 forces using HTTP/3.
6868
ForceHTTP3 bool
6969

7070
// ECH forces usage of Encrypted Client Hello for the request. If other
@@ -106,6 +106,10 @@ type Config struct {
106106
// chunk of ClientHello.
107107
TLSSplitDelay int
108108

109+
// TLSRandom is a 32-byte value to override the TLS ClientHello random.
110+
// If nil, use default.
111+
TLSRandom []byte
112+
109113
// OutputJSON enables writing output in JSON format.
110114
OutputJSON bool
111115

@@ -261,6 +265,18 @@ func ParseConfig() (cfg *Config, err error) {
261265
}
262266
}
263267

268+
if opts.TLSRandom != "" {
269+
var b []byte
270+
b, err = base64.StdEncoding.DecodeString(opts.TLSRandom)
271+
if err != nil {
272+
return nil, fmt.Errorf("--tls-random must be a valid base64 string: %w", err)
273+
}
274+
if len(b) != 32 {
275+
return nil, fmt.Errorf("--tls-random must decode to exactly 32 bytes (got %d)", len(b))
276+
}
277+
cfg.TLSRandom = b
278+
}
279+
264280
if opts.ECHConfig != "" {
265281
cfg.ECHConfigs, err = unmarshalECHConfigs(opts.ECHConfig)
266282
if err != nil {

internal/config/options.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ type Options struct {
103103
// in milliseconds before sending the second part.
104104
TLSSplitHello string `long:"tls-split-hello" description:"An option that allows splitting TLS ClientHello in two parts in order to avoid common DPI systems detecting TLS. CHUNKSIZE is the size of the first bytes before ClientHello is split, DELAY is delay in milliseconds before sending the second part." value-name:"<CHUNKSIZE:DELAY>"`
105105

106+
// TLSRandom allows overriding the TLS ClientHello random value. Must be
107+
// a base64-encoded 32-byte string.
108+
TLSRandom string `long:"tls-random" description:"Base64-encoded 32-byte TLS ClientHello random value." value-name:"<base64>"`
109+
106110
// OutputJSON enables writing output in JSON format.
107111
OutputJSON bool `long:"json-output" description:"Makes gocurl write machine-readable output in JSON format." optional:"yes" optional-value:"true"`
108112

0 commit comments

Comments
 (0)