+/ccerts
/cert.pem
/certgen.cmd
/certs
redo-ifchange cert.pem tofuproxy.cmd fifos/ensure
-mkdir -p certs
+mkdir -p certs ccerts
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")
fifos.FIFOs = *fifosDir
fifos.Init()
tofuproxy.Certs = *certs
+ tofuproxy.CCerts = *ccerts
tofuproxy.DNSSrv = *dnsSrv
tofuproxy.CACert = caCert
tofuproxy.CAPrv = caPrv
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)
}
}
var hsh []byte
switch tlsa.MatchingType {
+ case 0:
+ hsh = toMatch
case 1:
our := sha256.Sum256(toMatch)
hsh = our[:]
@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
@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:
@image{dialog,,,Example dialog,.webp}
+@item If you want to use TLS client certificates, then place them to
+@file{-ccerts} directory.
+
@end itemize
var (
NoTAI bool
- FIFOs string
+ FIFOs string
SinkCert = make(chan string)
SinkDANE = make(chan string)
SinkErr = make(chan string)
SinkRedir = make(chan string)
SinkReq = make(chan string)
SinkTLS = make(chan string)
-
)
func sinker(c chan string, p string) {
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 (
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=
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,
) 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 {