From 7436b359d20ead5b3e2703a3dbfc3c53ac8a3381 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sun, 4 Sep 2022 22:44:15 +0300 Subject: [PATCH 1/7] fmt --- auth.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth.go b/auth.go index 3c8eb15..fde1570 100644 --- a/auth.go +++ b/auth.go @@ -5,12 +5,13 @@ import ( "crypto/subtle" "encoding/base64" "errors" - "golang.org/x/crypto/bcrypt" "net/http" "net/url" "os" "strconv" "strings" + + "golang.org/x/crypto/bcrypt" ) const AUTH_REQUIRED_MSG = "Proxy authentication required.\n" From 8b5dc9dccbad7d935537464b8ffee607a247bc71 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sun, 4 Sep 2022 23:07:14 +0300 Subject: [PATCH 2/7] autocert --- go.sum | 2 ++ main.go | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 6760904..2068491 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index 93716dc..71d3425 100644 --- a/main.go +++ b/main.go @@ -7,10 +7,15 @@ import ( "log" "net/http" "os" + "path/filepath" + "strings" "time" + + "golang.org/x/crypto/acme/autocert" ) var ( + home, _ = os.UserHomeDir() version = "undefined" ) @@ -26,6 +31,23 @@ func arg_fail(msg string) { os.Exit(2) } +type CSVArg []string + +func (a *CSVArg) Set(s string) error { + *a = strings.Split(s, ",") + return nil +} + +func (a *CSVArg) String() string { + if a == nil { + return "" + } + if *a == nil { + return "" + } + return strings.Join(*a, ",") +} + type CLIArgs struct { bind_address string auth string @@ -35,7 +57,10 @@ type CLIArgs struct { list_ciphers bool ciphers string disableHTTP2 bool - showVersion bool + showVersion bool + autocert bool + autocertWhitelist CSVArg + autocertDir string } func list_ciphers() { @@ -58,6 +83,9 @@ func parse_args() CLIArgs { flag.StringVar(&args.ciphers, "ciphers", "", "colon-separated list of enabled ciphers") flag.BoolVar(&args.disableHTTP2, "disable-http2", false, "disable HTTP2") flag.BoolVar(&args.showVersion, "version", false, "show program version and exit") + flag.BoolVar(&args.autocert, "autocert", false, "issue TLS certificates automatically") + flag.Var(&args.autocertWhitelist, "autocert-whitelist", "restrict autocert domains to this comma-separated list") + flag.StringVar(&args.autocertDir, "autocert-dir", filepath.Join(home, ".dumbproxy", "autocert"), "path to autocert cache") flag.Parse() return args } @@ -70,7 +98,6 @@ func run() int { return 0 } - if args.list_ciphers { list_ciphers() return 0 @@ -116,6 +143,18 @@ func run() int { cfg.CipherSuites = makeCipherList(args.ciphers) server.TLSConfig = cfg err = server.ListenAndServeTLS("", "") + } else if args.autocert { + m := &autocert.Manager{ + Cache: autocert.DirCache(args.autocertDir), + Prompt: autocert.AcceptTOS, + } + if args.autocertWhitelist != nil { + m.HostPolicy = autocert.HostWhitelist([]string(args.autocertWhitelist)...) + } + cfg := m.TLSConfig() + cfg.CipherSuites = makeCipherList(args.ciphers) + server.TLSConfig = cfg + err = server.ListenAndServeTLS("", "") } else { err = server.ListenAndServe() } From 20a09ba94876356c4955f0c9580207828c985d30 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sun, 4 Sep 2022 23:07:58 +0300 Subject: [PATCH 3/7] upgrade --- go.mod | 5 ++++- go.sum | 24 +++++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index ea86cc3..422eafc 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/Snawoot/dumbproxy go 1.13 -require golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 +require ( + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 + golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect +) diff --git a/go.sum b/go.sum index 2068491..f57a52f 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,15 @@ -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From d7b3454fa6d2c665e8a98bcf5d968bb2ce20c957 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sun, 4 Sep 2022 23:59:21 +0300 Subject: [PATCH 4/7] autocert: customizable ACME directory --- main.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.go b/main.go index 71d3425..698a2a8 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "golang.org/x/crypto/acme" "golang.org/x/crypto/acme/autocert" ) @@ -61,6 +62,7 @@ type CLIArgs struct { autocert bool autocertWhitelist CSVArg autocertDir string + autocertACME string } func list_ciphers() { @@ -86,6 +88,7 @@ func parse_args() CLIArgs { flag.BoolVar(&args.autocert, "autocert", false, "issue TLS certificates automatically") flag.Var(&args.autocertWhitelist, "autocert-whitelist", "restrict autocert domains to this comma-separated list") flag.StringVar(&args.autocertDir, "autocert-dir", filepath.Join(home, ".dumbproxy", "autocert"), "path to autocert cache") + flag.StringVar(&args.autocertACME, "autocert-acme", autocert.DefaultACMEDirectory, "custom ACME endpoint") flag.Parse() return args } @@ -147,6 +150,7 @@ func run() int { m := &autocert.Manager{ Cache: autocert.DirCache(args.autocertDir), Prompt: autocert.AcceptTOS, + Client: &acme.Client{DirectoryURL: args.autocertACME}, } if args.autocertWhitelist != nil { m.HostPolicy = autocert.HostWhitelist([]string(args.autocertWhitelist)...) From fa23c7b75c9d92a7d2f8d026ff3744639e5730d6 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Mon, 5 Sep 2022 00:24:48 +0300 Subject: [PATCH 5/7] autocert: add email option --- main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.go b/main.go index 698a2a8..b656a1d 100644 --- a/main.go +++ b/main.go @@ -63,6 +63,7 @@ type CLIArgs struct { autocertWhitelist CSVArg autocertDir string autocertACME string + autocertEmail string } func list_ciphers() { @@ -89,6 +90,7 @@ func parse_args() CLIArgs { flag.Var(&args.autocertWhitelist, "autocert-whitelist", "restrict autocert domains to this comma-separated list") flag.StringVar(&args.autocertDir, "autocert-dir", filepath.Join(home, ".dumbproxy", "autocert"), "path to autocert cache") flag.StringVar(&args.autocertACME, "autocert-acme", autocert.DefaultACMEDirectory, "custom ACME endpoint") + flag.StringVar(&args.autocertEmail, "autocert-email", "", "email used for ACME registration") flag.Parse() return args } @@ -151,6 +153,7 @@ func run() int { Cache: autocert.DirCache(args.autocertDir), Prompt: autocert.AcceptTOS, Client: &acme.Client{DirectoryURL: args.autocertACME}, + Email: args.autocertEmail, } if args.autocertWhitelist != nil { m.HostPolicy = autocert.HostWhitelist([]string(args.autocertWhitelist)...) From 0d51373079455cf64f2cd4521de654f9283d73d4 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Mon, 5 Sep 2022 00:36:49 +0300 Subject: [PATCH 6/7] autocert: HTTP-01 handler --- main.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/main.go b/main.go index b656a1d..e44ce64 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,7 @@ type CLIArgs struct { autocertDir string autocertACME string autocertEmail string + autocertHTTP string } func list_ciphers() { @@ -91,6 +92,7 @@ func parse_args() CLIArgs { flag.StringVar(&args.autocertDir, "autocert-dir", filepath.Join(home, ".dumbproxy", "autocert"), "path to autocert cache") flag.StringVar(&args.autocertACME, "autocert-acme", autocert.DefaultACMEDirectory, "custom ACME endpoint") flag.StringVar(&args.autocertEmail, "autocert-email", "", "email used for ACME registration") + flag.StringVar(&args.autocertHTTP, "autocert-http", "", "listen address for HTTP-01 challenges handler of ACME") flag.Parse() return args } @@ -158,6 +160,12 @@ func run() int { if args.autocertWhitelist != nil { m.HostPolicy = autocert.HostWhitelist([]string(args.autocertWhitelist)...) } + if args.autocertHTTP != "" { + go func() { + log.Fatalf("HTTP-01 ACME challenge server stopped: %v", + http.ListenAndServe(args.autocertHTTP, m.HTTPHandler(nil))) + }() + } cfg := m.TLSConfig() cfg.CipherSuites = makeCipherList(args.ciphers) server.TLSConfig = cfg From dcae447e553b113638c1f41429b0f151ae9c2c58 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Mon, 5 Sep 2022 01:01:00 +0300 Subject: [PATCH 7/7] upd doc --- README.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a0e7767..d28614b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ You can say thanks to the author by donations to these wallets: * Supports CONNECT method and forwarding of HTTPS connections * Supports `Basic` proxy authentication * Supports TLS operation mode (HTTP(S) proxy over TLS) +* Native ACME support (can issue TLS certificates automatically using Let's Encrypt or BuyPass) * Supports client authentication with client TLS certificates * Supports HTTP/2 * Resilient to DPI (including active probing, see `hidden_domain` option for authentication providers) @@ -69,15 +70,33 @@ sudo snap install dumbproxy Just run program and it'll start accepting connections on port 8080 (default). -Example: run proxy on port 1234 with `Basic` authentication with username `admin` and password `123456`: +### Example: plain proxy + +Run proxy on port 1234 with `Basic` authentication with username `admin` and password `123456`: ```sh dumbproxy -bind-address :1234 -auth 'static://?username=admin&password=123456' ``` +### Example: HTTP proxy over TLS (LetsEncrypt automatic certs) + +Run HTTPS proxy (HTTP proxy over TLS) with automatic certs from LetsEncrypt on port 443 with `Basic` authentication with username `admin` and password `123456`: + +```sh +dumbproxy -bind-address :443 -auth 'static://?username=admin&password=123456' -autocert +``` + +### Example: HTTP proxy over TLS (BuyPass automatic certs) + +Run HTTPS proxy (HTTP proxy over TLS) with automatic certs from BuyPass on port 443 with `Basic` authentication with username `admin` and password `123456`: + +```sh +dumbproxy -bind-address :443 -auth 'static://?username=admin&password=123456' -autocert -autocert-acme 'https://api.buypass.com/acme/directory' -autocert-email YOUR-EMAIL@EXAMPLE.ORG -autocert-http :80 +``` + ## Using HTTP-over-TLS proxy -It's quite trivial to set up program which supports proxies to use dumbproxy in plain HTTP mode. However, using HTTP proxy over TLS connection with browsers is little bit tricky. Note that TLS must be enabled (`-cert` and `-key` options) for this to work. +It's quite trivial to set up program which supports proxies to use dumbproxy in plain HTTP mode. However, using HTTP proxy over TLS connection with browsers is little bit tricky. Note that TLS must be enabled (`-cert` and `-key` options or `-autocert` option) for this to work. ### Routing all browsers on Windows via HTTPS proxy @@ -149,6 +168,18 @@ Authentication parameters are passed as URI via `-auth` parameter. Scheme of URI $ ~/go/bin/dumbproxy -h -auth string auth parameters (default "none://") + -autocert + issue TLS certificates automatically + -autocert-acme string + custom ACME endpoint (default "https://acme-v02.api.letsencrypt.org/directory") + -autocert-dir string + path to autocert cache (default "/home/user/.dumbproxy/autocert") + -autocert-email string + email used for ACME registration + -autocert-http string + listen address for HTTP-01 challenges handler of ACME + -autocert-whitelist value + restrict autocert domains to this comma-separated list -bind-address string HTTP proxy listen address (default ":8080") -cafile string