diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf6a95..0c7a586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Edit current URL with e (#87) - If `emoji_favicons` is enabled, new bookmarks will have the domain's favicon prepended (#69, #90) - The `BROWSER` env var is now also checked when opening web links on Unix (#93) +- Allow specifying a client certificate ### Changed - Disabling the `color` config setting also disables ANSI colors in pages (#79, #86) diff --git a/client/client.go b/client/client.go index 4bc1557..f5d4b99 100644 --- a/client/client.go +++ b/client/client.go @@ -2,23 +2,74 @@ package client import ( + "io/ioutil" "net" "net/url" "github.com/makeworld-the-better-one/go-gemini" + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" ) +var certCache = make(map[string][][]byte) + +func clientCert(host string) ([]byte, []byte) { + if cert := certCache[host]; cert != nil { + return cert[0], cert[1] + } + + // Expand paths staring with ~/ + certPath, err := homedir.Expand(viper.GetString("auth.certs." + host)) + if err != nil { + certPath = viper.GetString("auth.certs." + host) + } + keyPath, err := homedir.Expand(viper.GetString("auth.keys." + host)) + if err != nil { + keyPath = viper.GetString("auth.keys." + host) + } + if certPath == "" && keyPath == "" { + certCache[host] = [][]byte{nil, nil} + return nil, nil + } + + cert, err := ioutil.ReadFile(certPath) + if err != nil { + certCache[host] = [][]byte{nil, nil} + return nil, nil + } + key, err := ioutil.ReadFile(keyPath) + if err != nil { + certCache[host] = [][]byte{nil, nil} + return nil, nil + } + + certCache[host] = [][]byte{cert, key} + return cert, key +} + +// HasClientCert returns whether or not a client certificate exists for a host. +func HasClientCert(host string) bool { + cert, _ := clientCert(host) + return cert != nil +} + // Fetch returns response data and an error. // The error text is human friendly and should be displayed. func Fetch(u string) (*gemini.Response, error) { + parsed, _ := url.Parse(u) + cert, key := clientCert(parsed.Host) - res, err := gemini.Fetch(u) + var res *gemini.Response + var err error + if cert != nil { + res, err = gemini.FetchWithCert(u, cert, key) + } else { + res, err = gemini.Fetch(u) + } if err != nil { return nil, err } - parsed, _ := url.Parse(u) - ok := handleTofu(parsed.Hostname(), parsed.Port(), res.Cert) if !ok { return res, ErrTofu @@ -29,7 +80,16 @@ func Fetch(u string) (*gemini.Response, error) { // FetchWithProxy is the same as Fetch, but uses a proxy. func FetchWithProxy(proxyHostname, proxyPort, u string) (*gemini.Response, error) { - res, err := gemini.FetchWithHost(net.JoinHostPort(proxyHostname, proxyPort), u) + parsed, _ := url.Parse(u) + cert, key := clientCert(parsed.Host) + + var res *gemini.Response + var err error + if cert != nil { + res, err = gemini.FetchWithHostAndCert(net.JoinHostPort(proxyHostname, proxyPort), u, cert, key) + } else { + res, err = gemini.FetchWithHost(net.JoinHostPort(proxyHostname, proxyPort), u) + } if err != nil { return nil, err } diff --git a/config/default.go b/config/default.go index 11cc1ef..cac1ad8 100644 --- a/config/default.go +++ b/config/default.go @@ -59,6 +59,18 @@ page_max_time = 10 emoji_favicons = false +[auth] +# Authentication settings + +[auth.certs] +# Client certificates +# "example.com" = "mycert.crt" + +[auth.keys] +# Client certificate keys +# "example.com" = "mycert.key" + + [keybindings] # In the future there will be more settings here. diff --git a/default-config.toml b/default-config.toml index 4242bb1..4256091 100644 --- a/default-config.toml +++ b/default-config.toml @@ -56,6 +56,18 @@ page_max_time = 10 emoji_favicons = false +[auth] +# Authentication settings + +[auth.certs] +# Client certificates +# "example.com" = "mycert.crt" + +[auth.keys] +# Client certificate keys +# "example.com" = "mycert.key" + + [keybindings] # In the future there will be more settings here. diff --git a/go.mod b/go.mod index a94d5e1..61549ab 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/gdamore/tcell v1.3.1-0.20200608133353-cb1e5d6fa606 github.com/google/go-cmp v0.5.0 // indirect - github.com/makeworld-the-better-one/go-gemini v0.8.4 + github.com/makeworld-the-better-one/go-gemini v0.9.0 github.com/makeworld-the-better-one/go-isemoji v1.1.0 github.com/makeworld-the-better-one/progressbar/v3 v3.3.5-0.20200710151429-125743e22b4f github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index 8639569..2ce1789 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,8 @@ github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tW github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/makeworld-the-better-one/go-gemini v0.8.4 h1:ntsQ9HnlJCmC9PDqXp/f1SCALjBMwh69BbT4BhFRFaw= -github.com/makeworld-the-better-one/go-gemini v0.8.4/go.mod h1:P7/FbZ+IEIbA/d+A0Y3w2GNgD8SA2AcNv7aDGJbaWG4= +github.com/makeworld-the-better-one/go-gemini v0.9.0 h1:Iz4ywRDrfsyoR8xZOkSKGXXftMR2spIV6ibVuhrKvSw= +github.com/makeworld-the-better-one/go-gemini v0.9.0/go.mod h1:P7/FbZ+IEIbA/d+A0Y3w2GNgD8SA2AcNv7aDGJbaWG4= github.com/makeworld-the-better-one/go-isemoji v1.1.0 h1:wZBHOKB5zAIgaU2vaWnXFDDhatebB8TySrNVxjVV84g= github.com/makeworld-the-better-one/go-isemoji v1.1.0/go.mod h1:FBjkPl9rr0G4vlZCc+Mr+QcnOfGCTbGWYW8/1sp06I0= github.com/makeworld-the-better-one/progressbar/v3 v3.3.5-0.20200710151429-125743e22b4f h1:YEUlTs5gb35UlBLTgqrub9axWTYB3d7/8TxrkJDZpRI=