Refactors TOFU approach

This commit is contained in:
sloumdrone 2019-09-28 10:20:23 -07:00
parent 6c52299c7a
commit bd004d74c2
2 changed files with 70 additions and 46 deletions

View File

@ -551,7 +551,7 @@ func (c *client) doLinkCommand(action, target string) {
num -= 1
links := c.PageState.History[c.PageState.Position].Links
if num >= len(links) || num < 0 {
if num >= len(links) || num < 1 {
c.SetMessage(fmt.Sprintf("Invalid link id: %s", target), true)
c.DrawMessage()
return

View File

@ -56,15 +56,55 @@ func (t *TofuDigest) Find(host string) (string, error) {
return "", fmt.Errorf("Invalid hostname, no key saved")
}
func (t *TofuDigest) Match(host, hash string) bool {
func (t *TofuDigest) Match(host string, cState *tls.ConnectionState) error {
host = strings.ToLower(host)
if _, ok := t.certs[host]; !ok {
return false
now := time.Now()
for _, cert := range cState.PeerCertificates {
if t.certs[host] != hashCert(cert.Raw) {
continue
}
if now.Before(cert.NotBefore) {
return fmt.Errorf("Certificate is not valid yet")
}
if now.After(cert.NotAfter) {
return fmt.Errorf("EXP")
}
if err := cert.VerifyHostname(host); err != nil {
return fmt.Errorf("Certificate error: %s", err)
}
return nil
}
if t.certs[host] == hash {
return true
return fmt.Errorf("No matching certificate was found for host %q", host)
}
func (t *TofuDigest) newCert(host string, cState *tls.ConnectionState) error {
host = strings.ToLower(host)
now := time.Now()
for _, cert := range cState.PeerCertificates {
if now.Before(cert.NotBefore) {
continue
}
if now.After(cert.NotAfter) {
continue
}
if err := cert.VerifyHostname(host); err != nil {
continue
}
t.Add(host, hashCert(cert.Raw))
return nil
}
return false
return fmt.Errorf("No valid certificates were offered by host %q", host)
}
func (t *TofuDigest) IniDump() string {
@ -105,53 +145,37 @@ func Retrieve(host, port, resource string, td *TofuDigest) (string, error) {
return "", err
}
now := time.Now()
defer conn.Close()
// Verify that the handshake ahs completed and that
// the hostname on the certificate(s) from the server
// is the hostname we have requested
connState := conn.ConnectionState()
// Begin TOFU screening...
// If no certificates are offered, bail out
if len(connState.PeerCertificates) < 1 {
return "", fmt.Errorf("Insecure, no certificates offered by server")
}
hostCertExists := td.Exists(host)
matched := false
for _, cert := range connState.PeerCertificates {
if hostCertExists {
if td.Match(host, hashCert(cert.Raw)) {
matched = true
if now.Before(cert.NotBefore) {
return "", fmt.Errorf("Server certificate error: certificate not valid yet")
}
if now.After(cert.NotAfter) {
return "", fmt.Errorf("Server certificate error: certificate expired")
}
if err = cert.VerifyHostname(host); err != nil {
return "", fmt.Errorf("Server certificate error: %s", err)
}
break
}
} else {
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
continue
if td.Exists(host) {
err := td.Match(host, &connState)
if err != nil && err.Error() != "EXP" {
// On any error other than EXP (expired), return the error
return "", err
} else if err.Error() == "EXP" {
// If the certificate we had was expired, check if they have
// offered a new valid cert and update the certificate
err := td.newCert(host, &connState)
if err != nil {
// If there are no valid certs to offer, let the client know
return "", err
}
if err = cert.VerifyHostname(host); err != nil {
return "", fmt.Errorf("Server certificate error: %s", err)
}
td.Add(host, hashCert(cert.Raw))
matched = true
}
}
if !matched {
return "", fmt.Errorf("Server certificate error: No matching certificate provided")
} else {
err = td.newCert(host, &connState)
if err != nil {
// If there are no valid certs to offer, let the client know
return "", err
}
}
send := "gemini://" + addr + "/" + resource + "\r\n"
@ -345,7 +369,7 @@ func hashCert(cert []byte) string {
for i, data := range hash {
hex[i] = []byte(fmt.Sprintf("%02X", data))
}
return fmt.Sprintf("%s", string(bytes.Join(hex, []byte("-"))))
return fmt.Sprintf("%s", string(bytes.Join(hex, []byte(":"))))
}