Differentiate between unknown and untrusted certificates
This commit is contained in:
parent
fc96076472
commit
bf3e6b3c5c
30
README.md
30
README.md
@ -51,9 +51,9 @@ clients. Here is a simple client using TOFU to authenticate certificates:
|
|||||||
```go
|
```go
|
||||||
client := &gemini.Client{
|
client := &gemini.Client{
|
||||||
KnownHosts: gemini.LoadKnownHosts(".local/share/gemini/known_hosts"),
|
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 the certificate is in the known hosts list, allow the connection
|
||||||
if knownHosts.Has(cert) {
|
if err := knownHosts.Lookup(cert); {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Prompt the user
|
// 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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -19,6 +19,7 @@ var (
|
|||||||
ErrInvalidURL = errors.New("gemini: requested URL is invalid")
|
ErrInvalidURL = errors.New("gemini: requested URL is invalid")
|
||||||
ErrCertificateNotValid = errors.New("gemini: certificate is invalid")
|
ErrCertificateNotValid = errors.New("gemini: certificate is invalid")
|
||||||
ErrCertificateNotTrusted = errors.New("gemini: certificate is not trusted")
|
ErrCertificateNotTrusted = errors.New("gemini: certificate is not trusted")
|
||||||
|
ErrCertificateUnknown = errors.New("gemini: certificate is unknown")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request represents a Gemini request.
|
// Request represents a Gemini request.
|
||||||
@ -171,7 +172,8 @@ type Client struct {
|
|||||||
|
|
||||||
// TrustCertificate, if not nil, will be called to determine whether the
|
// TrustCertificate, if not nil, will be called to determine whether the
|
||||||
// client should trust the given certificate.
|
// 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.
|
// 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) {
|
if c.KnownHosts == nil || !c.KnownHosts.Has(cert) {
|
||||||
return ErrCertificateNotTrusted
|
return ErrCertificateNotTrusted
|
||||||
}
|
}
|
||||||
} else if !c.TrustCertificate(cert, c.KnownHosts) {
|
} else if err := c.TrustCertificate(cert, c.KnownHosts); err != nil {
|
||||||
return ErrCertificateNotTrusted
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -15,9 +15,9 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
client = &gemini.Client{
|
client = &gemini.Client{
|
||||||
TrustCertificate: func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) bool {
|
TrustCertificate: func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
|
||||||
// Trust all certificates
|
// Trust all certificates
|
||||||
return true
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cert tls.Certificate
|
cert tls.Certificate
|
||||||
|
27
tofu.go
27
tofu.go
@ -71,6 +71,33 @@ func (k *KnownHosts) Has(cert *x509.Certificate) bool {
|
|||||||
return false
|
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.
|
// Parse parses the provided reader and adds the parsed known hosts to the list.
|
||||||
// Invalid lines are ignored.
|
// Invalid lines are ignored.
|
||||||
func (k *KnownHosts) Parse(r io.Reader) {
|
func (k *KnownHosts) Parse(r io.Reader) {
|
||||||
|
Loading…
Reference in New Issue
Block a user