Refactor client.TrustCertificate workflow

This commit is contained in:
Adnan Maolood 2020-10-31 22:34:51 -04:00
parent b9cb7fe71d
commit 65a5065250
6 changed files with 81 additions and 108 deletions

View File

@ -16,7 +16,7 @@ import (
"time" "time"
) )
// CertificateStore maps hostnames to certificates. // CertificateStore maps certificate scopes to certificates.
// The zero value of CertificateStore is an empty store ready to use. // The zero value of CertificateStore is an empty store ready to use.
type CertificateStore struct { type CertificateStore struct {
store map[string]tls.Certificate store map[string]tls.Certificate

View File

@ -43,12 +43,10 @@ type Client struct {
// the request will not be sent again and the response will be returned. // the request will not be sent again and the response will be returned.
CreateCertificate func(hostname, path string) (tls.Certificate, error) CreateCertificate func(hostname, path string) (tls.Certificate, error)
// TrustCertificate determines whether the client should trust // TrustCertificate is called to determine whether the client
// the provided certificate. // should trust a certificate it has not seen before.
// If the returned error is not nil, the connection will be aborted. // If TrustCertificate is nil, the certificate will not be trusted.
// If TrustCertificate is nil, the client will check KnownHosts TrustCertificate func(hostname string, cert *x509.Certificate) Trust
// for the certificate.
TrustCertificate func(hostname string, cert *x509.Certificate, knownHosts *KnownHosts) error
} }
// Get performs a Gemini request for the given url. // Get performs a Gemini request for the given url.
@ -198,10 +196,25 @@ func (c *Client) verifyConnection(req *Request, cs tls.ConnectionState) error {
if err := verifyHostname(cert, hostname); err != nil { if err := verifyHostname(cert, hostname); err != nil {
return err return err
} }
// Check that the client trusts the certificate // Check the known hosts
var err error err := c.KnownHosts.Lookup(hostname, cert)
switch err {
case ErrCertificateExpired, ErrCertificateNotFound:
default:
return err
}
// See if the client trusts the certificate
if c.TrustCertificate != nil { if c.TrustCertificate != nil {
return c.TrustCertificate(hostname, cert, &c.KnownHosts) switch c.TrustCertificate(hostname, cert) {
case TrustOnce:
c.KnownHosts.AddTemporary(hostname, cert)
return nil
case TrustAlways:
c.KnownHosts.Add(hostname, cert)
return nil
default:
return ErrCertificateNotTrusted
}
} else { } else {
err = c.KnownHosts.Lookup(hostname, cert) err = c.KnownHosts.Lookup(hostname, cert)
} }

View File

@ -8,12 +8,22 @@ import (
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"time" "time"
"git.sr.ht/~adnano/go-gemini" "git.sr.ht/~adnano/go-gemini"
) )
const trustPrompt = `The certificate offered by %s is of unknown trust. Its fingerprint is:
%s
If you knew the fingerprint to expect in advance, verify that this matches.
Otherwise, this should be safe to trust.
[t]rust always; trust [o]nce; [a]bort
=> `
var ( var (
scanner = bufio.NewScanner(os.Stdin) scanner = bufio.NewScanner(os.Stdin)
client = &gemini.Client{} client = &gemini.Client{}