diff --git a/CHANGELOG.md b/CHANGELOG.md index dac8552..22102ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ adheres to [Semantic Versioning][semver]. ## [Unreleased] * Added support for the `--tls-max` argument. +* Added support for the `--ciphers` argument. [unreleased]: https://github.com/ameshkov/gocurl/compare/v1.4.1...HEAD diff --git a/README.md b/README.md index 0e98f2a..c2ab42b 100644 --- a/README.md +++ b/README.md @@ -290,6 +290,12 @@ Application Options: or 1.3. The minimum acceptable version is set by tlsv1.2 or tlsv1.3. + --ciphers= Specifies which ciphers to use in + the connection, see + https://go.dev/src/crypto/tls/ciph- + + er_suites.go for the full list of + available ciphers. --http1.1 Forces gocurl to use HTTP v1.1. --http2 Forces gocurl to use HTTP v2. --http3 Forces gocurl to use HTTP v3. diff --git a/internal/client/clientdialer.go b/internal/client/clientdialer.go index c9b1186..c3c5c9d 100644 --- a/internal/client/clientdialer.go +++ b/internal/client/clientdialer.go @@ -170,7 +170,9 @@ func createTLSConfig(cfg *config.Config, out *output.Output) (tlsConfig *tls.Con MaxVersion: cfg.TLSMaxVersion, } - tls.CipherSuites() + if len(cfg.TLSCiphers) > 0 { + tlsConfig.CipherSuites = cfg.TLSCiphers + } if cfg.Insecure { tlsConfig.InsecureSkipVerify = true diff --git a/internal/config/config.go b/internal/config/config.go index 3fe3938..ddbdc62 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -50,6 +50,10 @@ type Config struct { // TLSMaxVersion is a maximum supported TLS version. TLSMaxVersion uint16 + // TLSCiphers is a list of ciphers that the client will send in the TLS + // ClientHello. + TLSCiphers []uint16 + // ForceHTTP11 forces using HTTP/1.1. ForceHTTP11 bool @@ -217,6 +221,21 @@ func ParseConfig() (cfg *Config, err error) { cfg.TLSMaxVersion = tls.VersionTLS13 } + if opts.TLSCiphers != "" { + cipherNames := strings.Split(opts.TLSCiphers, " ") + cfg.TLSCiphers = []uint16{} + + for _, cipherName := range cipherNames { + cipher := getCipherSuiteByName(cipherName) + + if cipher == 0 { + return nil, fmt.Errorf("cipher %s not found", cipherName) + } + + cfg.TLSCiphers = append(cfg.TLSCiphers, cipher) + } + } + if opts.TLSSplitHello != "" { cfg.TLSSplitChunkSize, cfg.TLSSplitDelay, err = parseTLSSplitHello(opts.TLSSplitHello) if err != nil { @@ -244,6 +263,24 @@ func ParseConfig() (cfg *Config, err error) { return cfg, nil } +// getCipherSuiteByName tries to get the cipher suite by its name. Returns 0 +// if no matching cipher found. +func getCipherSuiteByName(cipherName string) (cipher uint16) { + for _, c := range tls.CipherSuites() { + if c.Name == cipherName { + return c.ID + } + } + + for _, c := range tls.InsecureCipherSuites() { + if c.Name == cipherName { + return c.ID + } + } + + return 0 +} + // parseConnectTo creates a "connect-to" map from the string representation. func parseConnectTo(connectTo []string) (m map[string]string, err error) { m = map[string]string{} diff --git a/internal/config/options.go b/internal/config/options.go index bd81c7d..1ffe710 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -47,6 +47,11 @@ type Options struct { // TLSMax specifies the maximum supported TLS version. TLSMax string `long:"tls-max" description:"(TLS) VERSION defines maximum supported TLS version. Can be 1.2 or 1.3. The minimum acceptable version is set by tlsv1.2 or tlsv1.3." value-name:""` + // TLSCiphers specifies which ciphers to use in the connection, see + // https://go.dev/src/crypto/tls/cipher_suites.go for the full list of + // available ciphers. + TLSCiphers string `long:"ciphers" description:"Specifies which ciphers to use in the connection, see https://go.dev/src/crypto/tls/cipher_suites.go for the full list of available ciphers." value-name:""` + // HTTPv11 forces to use HTTP v1.1. HTTPv11 bool `long:"http1.1" description:"Forces gocurl to use HTTP v1.1." optional:"yes" optional-value:"true"`