From: Sergey Matveev Date: Wed, 8 Sep 2021 14:22:03 +0000 (+0300) Subject: TLS client certificates X-Git-Tag: v0.1.0~75 X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=678feeff35d0ff6dff2908ba19f99c8324046190;hp=245f8770d1815ef2adc14b0ce7c7bd22c13b3873;p=tofuproxy.git TLS client certificates --- diff --git a/.gitignore b/.gitignore index 1132f68..1da43c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/ccerts /cert.pem /certgen.cmd /certs diff --git a/all.do b/all.do index fbac54c..db8bcbf 100644 --- a/all.do +++ b/all.do @@ -1,2 +1,2 @@ redo-ifchange cert.pem tofuproxy.cmd fifos/ensure -mkdir -p certs +mkdir -p certs ccerts diff --git a/cmd/tofuproxy/main.go b/cmd/tofuproxy/main.go index 5335ffb..8a0be00 100644 --- a/cmd/tofuproxy/main.go +++ b/cmd/tofuproxy/main.go @@ -33,6 +33,7 @@ func main() { prvPath := flag.String("key", "prv.pem", "Path to server PKCS#8 private key") bind := flag.String("bind", "[::1]:8080", "Bind address") certs := flag.String("certs", "./certs", "Directory with pinned certificates") + ccerts := flag.String("ccerts", "./ccerts", "Directory with client certificates") dnsSrv := flag.String("dns", "[::1]:53", "DNS server") fifosDir := flag.String("fifos", "fifos", "Directory with FIFOs") notai := flag.Bool("notai", false, "Do not prepend TAI64N to logs") @@ -53,6 +54,7 @@ func main() { fifos.FIFOs = *fifosDir fifos.Init() tofuproxy.Certs = *certs + tofuproxy.CCerts = *ccerts tofuproxy.DNSSrv = *dnsSrv tofuproxy.CACert = caCert tofuproxy.CAPrv = caPrv @@ -65,7 +67,7 @@ func main() { Handler: &tofuproxy.Handler{}, TLSNextProto: tofuproxy.TLSNextProtoS, } - log.Println("listening:", *bind, "certs:", *certs) + log.Println("listening:", *bind, "dns:", *dnsSrv, "certs:", *certs, "ccerts:", *ccerts) if err := srv.Serve(ln); err != nil { log.Fatalln(err) } diff --git a/dane.go b/dane.go index cd97220..d3218dd 100644 --- a/dane.go +++ b/dane.go @@ -72,6 +72,8 @@ func dane(addr string, cert *x509.Certificate) (bool, bool) { } var hsh []byte switch tlsa.MatchingType { + case 0: + hsh = toMatch case 1: our := sha256.Sum256(toMatch) hsh = our[:] diff --git a/doc/index.texi b/doc/index.texi index be8e76f..10c8582 100644 --- a/doc/index.texi +++ b/doc/index.texi @@ -58,51 +58,74 @@ creating some kind of complex configuration framework. @itemize -@item Effective responses proxying, without storing them in the memory first. +@item +@strong{Effective} responses proxying, without storing them in the memory first. -@item TLS connection between client and @command{tofuproxy} has the - proper hostname set in ephemeral on-the-fly generated certificate. +@item +TLS connection between client and @command{tofuproxy} has the +@strong{proper hostname} set in ephemeral on-the-fly generated +certificate. -@item @code{HEAD} method is forbidden, as Xombrero loves it too much and - it does not send User-Agent to differentiate it from others. +@item +@code{HEAD} method is forbidden. -@item @code{www.reddit.com} is redirected to @code{old.reddit.com}. +@item +@code{www.reddit.com} is redirected to @code{old.reddit.com}. -@item @url{https://habr.com/ru/all/, Хабр}'s resolution reduced images - are redirected to their full size variants. +@item +@url{https://habr.com/ru/all/, Хабр}'s resolution reduced images are +redirected to their full size variants. -@item Various spying domains (advertisement, tracking counters) are denied. +@item +Various @strong{spying} domains (advertisement, tracking counters) are denied. -@item Web fonts downloads are forbidden. +@item +Web @strong{fonts} downloads are forbidden. -@item Permanent HTTP redirects are replaced with HTML page with the link. +@item +@strong{Permanent} HTTP @strong{redirects} are replaced with HTML page +with the link. -@item Temporary HTTP redirects are replaced with HTML too, if it is - neither @url{https://newsboat.org/, Newsboat} nor image paths. +@item +@strong{Temporary} HTTP @strong{redirects} are replaced with HTML too, +if it is neither @url{https://newsboat.org/, Newsboat} nor image paths. -@item WebP images, if it is not Xombrero, is transcoded to PNG. +@item +@strong{WebP} images, if it is not Xombrero, is transcoded to PNG. -@item JPEG XL and AVIF images are transparently transcoded to PNG too. +@item +@strong{JPEG XL} and @strong{AVIF} images are transparently transcoded +to PNG too. -@item Default Go's checks are applied to all certificates. If they pass, - then certificate chain is saved on the disk. Future connections are - compared against it, warning you about SPKI change and waiting for - your decision either to accept new chain (possibly once per - session), or reject it. +@item +Default Go's checks are applied to all certificates. If they pass, then +certificate chain is saved on the disk (@strong{TOFU}). Future +connections are compared against it, warning you about SPKI change +(@strong{SPKI pinning}) and waiting for your decision either to accept +new chain (possibly once per session), or reject it. -@item Even when native Go's checks are failed, you can still make a - decision to forcefully trust the domain. +@item +Even when native Go's checks are failed, you can still make a decision +to forcefully trust the domain. -@item HTTP-based unauthorized responses are intercepted and - user/password input dialog is shown. It automatically loads initial - form values from @file{.netrc}. +@item +@strong{HTTP-based authorization} requests are intercepted and +user/password input dialogue is shown. It automatically loads +@strong{initial form} values from @file{.netrc}. -@item Optionally DANE-EE check is also made for each domain you visit. +@item +TLS @strong{client certificates} supported: separate dialogue window for +certificate choice. -@item TLS session resumption and keep-alives are also supported. +@item +Optional @strong{DANE-EE} check is also made for each domain you visit. -@item And Go itself tries also to act as a -@url{https://http2.github.io/, HTTP/2} client too. +@item +TLS @strong{session resumption} and @strong{keep-alives} are also supported. + +@item +And Go itself tries also to act as a @url{https://http2.github.io/, +HTTP/2} client too. @end itemize diff --git a/doc/usage.texi b/doc/usage.texi index 8cfbb06..064d57e 100644 --- a/doc/usage.texi +++ b/doc/usage.texi @@ -37,7 +37,7 @@ Run @command{tofuproxy} itself. By default it will bind to @example $ ./tofuproxy.cmd -main.go:316: listening: [::1]:8080 certs: ./certs +main.go:70: listening: [::1]:8080 dns: [::1]:53 certs: ./certs ccerts: ./ccerts @end example @item Trust your newly generated CA: @@ -63,4 +63,7 @@ will be shown Tk-dialog through the @command{wish} invocation. GnuTLS'es @image{dialog,,,Example dialog,.webp} +@item If you want to use TLS client certificates, then place them to +@file{-ccerts} directory. + @end itemize diff --git a/fifos/fifos.go b/fifos/fifos.go index 61f0f63..f07085f 100644 --- a/fifos/fifos.go +++ b/fifos/fifos.go @@ -28,7 +28,7 @@ import ( var ( NoTAI bool - FIFOs string + FIFOs string SinkCert = make(chan string) SinkDANE = make(chan string) SinkErr = make(chan string) @@ -37,7 +37,6 @@ var ( SinkRedir = make(chan string) SinkReq = make(chan string) SinkTLS = make(chan string) - ) func sinker(c chan string, p string) { diff --git a/go.mod b/go.mod index b836561..779bef0 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/miekg/dns v1.1.29 go.cypherpunks.ru/tai64n/v2 v2.0.0 - go.cypherpunks.ru/ucspi v0.0.0-20210904200151-57c1e9924fef + go.cypherpunks.ru/ucspi v0.0.0-20210908140534-cfdc20a8225f ) require ( diff --git a/go.sum b/go.sum index 3c12a21..a3c1ed0 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= go.cypherpunks.ru/tai64n/v2 v2.0.0 h1:AlohA1/zRqInhIGK7CVnn7tC5/vt1TaOAEyBgeu5Ruo= go.cypherpunks.ru/tai64n/v2 v2.0.0/go.mod h1:9eeXyzoNO/8grf+bw+T1HXRCRjHVXjUsvnIyaUkjS5I= -go.cypherpunks.ru/ucspi v0.0.0-20210904200151-57c1e9924fef h1:tGNNuzuVYOYkvWQuu4bYMapbFfCVPv0kPeBHrDxtQ2I= -go.cypherpunks.ru/ucspi v0.0.0-20210904200151-57c1e9924fef/go.mod h1:x/y6IZN50ZDjQHhsF10B9p3s+YdJi9/kUUGmuuDAXYw= +go.cypherpunks.ru/ucspi v0.0.0-20210908140534-cfdc20a8225f h1:vxGYZtzCyEXh2+7c0egZODfQQtguwdiCA8WBgfqPnX4= +go.cypherpunks.ru/ucspi v0.0.0-20210908140534-cfdc20a8225f/go.mod h1:x/y6IZN50ZDjQHhsF10B9p3s+YdJi9/kUUGmuuDAXYw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/tls.go b/tls.go index 4c29a6f..2db3ceb 100644 --- a/tls.go +++ b/tls.go @@ -104,6 +104,7 @@ func (h *HTTPSHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func dialTLS(ctx context.Context, network, addr string) (net.Conn, error) { host := strings.TrimSuffix(addr, ":443") + ccg := ClientCertificateGetter{host} cfg := tls.Config{ VerifyPeerCertificate: func( rawCerts [][]byte, @@ -111,8 +112,9 @@ func dialTLS(ctx context.Context, network, addr string) (net.Conn, error) { ) error { return verifyCert(host, nil, rawCerts, verifiedChains) }, - ClientSessionCache: sessionCache, - NextProtos: []string{"h2", "http/1.1"}, + ClientSessionCache: sessionCache, + NextProtos: []string{"h2", "http/1.1"}, + GetClientCertificate: ccg.get, } conn, dialErr := tls.Dial(network, addr, &cfg) if dialErr != nil {