aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md30
-rw-r--r--client.go8
-rw-r--r--examples/client/client.go4
-rw-r--r--tofu.go27
4 files changed, 62 insertions, 7 deletions
diff --git a/README.md b/README.md
index 596a5f7..bec4258 100644
--- a/README.md
+++ b/README.md
@@ -51,9 +51,9 @@ clients. Here is a simple client using TOFU to authenticate certificates:
```go
client := &gemini.Client{
KnownHosts: gemini.LoadKnownHosts(".local/share/gemini/known_hosts"),
- TrustCertificate: func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) bool {
+ TrustCertificate: func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
// If the certificate is in the known hosts list, allow the connection
- if knownHosts.Has(cert) {
+ if err := knownHosts.Lookup(cert); {
return true
}
// Prompt the user
@@ -70,3 +70,29 @@ client := &gemini.Client{
},
}
```
+
+```go
+client := &gemini.Client{
+ TrustCertificate: func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
+ err := knownHosts.Lookup(cert)
+ if err != nil {
+ switch err {
+ case gemini.ErrCertificateNotTrusted:
+ // Alert the user that the certificate is not trusted
+ alertUser()
+ case gemini.ErrCertificateUnknown:
+ // Prompt the user to trust the certificate
+ if userTrustsCertificateTemporarily() {
+ // Temporarily trust the certificate
+ return nil
+ } else if user.TrustsCertificatePermanently() {
+ // Add the certificate to the known hosts file
+ knownHosts.Add(cert)
+ return nil
+ }
+ }
+ }
+ return err
+ },
+}
+```
diff --git a/client.go b/client.go
index 6d2fd4b..bb1fa90 100644
--- a/client.go
+++ b/client.go
@@ -19,6 +19,7 @@ var (
ErrInvalidURL = errors.New("gemini: requested URL is invalid")
ErrCertificateNotValid = errors.New("gemini: certificate is invalid")
ErrCertificateNotTrusted = errors.New("gemini: certificate is not trusted")
+ ErrCertificateUnknown = errors.New("gemini: certificate is unknown")
)
// Request represents a Gemini request.
@@ -171,7 +172,8 @@ type Client struct {
// TrustCertificate, if not nil, will be called to determine whether the
// client should trust the given certificate.
- TrustCertificate func(cert *x509.Certificate, knownHosts *KnownHosts) bool
+ // If error is not nil, the connection will be aborted.
+ TrustCertificate func(cert *x509.Certificate, knownHosts *KnownHosts) error
}
// Send sends a Gemini request and returns a Gemini response.
@@ -196,8 +198,8 @@ func (c *Client) Send(req *Request) (*Response, error) {
if c.KnownHosts == nil || !c.KnownHosts.Has(cert) {
return ErrCertificateNotTrusted
}
- } else if !c.TrustCertificate(cert, c.KnownHosts) {
- return ErrCertificateNotTrusted
+ } else if err := c.TrustCertificate(cert, c.KnownHosts); err != nil {
+ return err
}
return nil
},
diff --git a/examples/client/client.go b/examples/client/client.go
index 4f852bf..31e24b2 100644
--- a/examples/client/client.go
+++ b/examples/client/client.go
@@ -15,9 +15,9 @@ import (
var (
client = &gemini.Client{
- TrustCertificate: func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) bool {
+ TrustCertificate: func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
// Trust all certificates
- return true
+ return nil
},
}
cert tls.Certificate
diff --git a/tofu.go b/tofu.go
index 4461fdd..d3ab251 100644
--- a/tofu.go
+++ b/tofu.go
@@ -71,6 +71,33 @@ func (k *KnownHosts) Has(cert *x509.Certificate) bool {
return false
}
+// Lookup looks for the provided certificate in the list of known hosts.
+// If the hostname is in the list, but the fingerprint differs,
+// Lookup returns ErrCertificateNotTrusted.
+// If the hostname is not in the list, Lookup returns ErrCertificateUnknown.
+// If the certificate is found and the fingerprint matches, error will be nil.
+func (k *KnownHosts) Lookup(cert *x509.Certificate) error {
+ now := time.Now().Unix()
+ hostname := cert.Subject.CommonName
+ fingerprint := Fingerprint(cert)
+ for i := range k.hosts {
+ if k.hosts[i].Hostname != hostname {
+ continue
+ }
+ if k.hosts[i].Expires <= now {
+ // Certificate is expired
+ continue
+ }
+ if k.hosts[i].Fingerprint == fingerprint {
+ // Fingerprint matches
+ return nil
+ }
+ // Fingerprint does not match
+ return ErrCertificateNotTrusted
+ }
+ return ErrCertificateUnknown
+}
+
// Parse parses the provided reader and adds the parsed known hosts to the list.
// Invalid lines are ignored.
func (k *KnownHosts) Parse(r io.Reader) {