go-gemini/tofu/tofu.go

337 lines
7.8 KiB
Go
Raw Normal View History

2021-01-10 21:44:32 +00:00
// Package tofu implements trust on first use using hosts and fingerprints.
package tofu
2020-09-25 22:53:20 +00:00
import (
"bufio"
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
"bytes"
"crypto/sha256"
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
"crypto/x509"
"encoding/base64"
2020-09-25 22:53:20 +00:00
"fmt"
"io"
2021-01-14 21:54:38 +00:00
"os"
"path/filepath"
2021-01-14 23:50:03 +00:00
"sort"
2020-09-25 22:53:20 +00:00
"strings"
2020-12-17 22:07:00 +00:00
"sync"
2020-09-25 22:53:20 +00:00
)
// KnownHosts represents a list of known hosts.
// The zero value for KnownHosts represents an empty list ready to use.
//
// KnownHosts is safe for concurrent use by multiple goroutines.
type KnownHosts struct {
hosts map[string]Host
mu sync.RWMutex
2020-11-09 17:26:08 +00:00
}
// Add adds a host to the list of known hosts.
2021-01-25 17:02:09 +00:00
func (k *KnownHosts) Add(h Host) {
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
k.mu.Lock()
defer k.mu.Unlock()
if k.hosts == nil {
2021-01-14 19:15:08 +00:00
k.hosts = map[string]Host{}
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
}
k.hosts[h.Hostname] = h
2020-11-09 17:04:53 +00:00
}
// Lookup returns the known host entry corresponding to the given hostname.
func (k *KnownHosts) Lookup(hostname string) (Host, bool) {
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
k.mu.RLock()
defer k.mu.RUnlock()
c, ok := k.hosts[hostname]
return c, ok
}
// Entries returns the known host entries sorted by hostname.
func (k *KnownHosts) Entries() []Host {
2021-01-14 23:50:03 +00:00
keys := make([]string, 0, len(k.hosts))
for key := range k.hosts {
keys = append(keys, key)
}
sort.Strings(keys)
hosts := make([]Host, 0, len(k.hosts))
for _, key := range keys {
hosts = append(hosts, k.hosts[key])
}
return hosts
}
// WriteTo writes the list of known hosts to the provided io.Writer.
func (k *KnownHosts) WriteTo(w io.Writer) (int64, error) {
k.mu.RLock()
defer k.mu.RUnlock()
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
var written int
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
bw := bufio.NewWriter(w)
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
for _, h := range k.hosts {
n, err := bw.WriteString(h.String())
written += n
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
if err != nil {
return int64(written), err
2020-11-09 17:04:53 +00:00
}
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
bw.WriteByte('\n')
written += 1
2020-09-26 03:06:54 +00:00
}
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
return int64(written), bw.Flush()
2020-09-26 03:06:54 +00:00
}
2021-01-14 22:09:31 +00:00
// Load loads the known hosts entries from the provided path.
func (k *KnownHosts) Load(path string) error {
2021-03-09 13:58:36 +00:00
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDONLY, 0644)
2021-01-14 22:09:31 +00:00
if err != nil {
return err
}
defer f.Close()
return k.Parse(f)
}
// Parse parses the provided io.Reader and adds the parsed hosts to the list.
2020-11-09 17:26:08 +00:00
// Invalid entries are ignored.
//
2021-01-15 00:56:04 +00:00
// For more control over errors encountered during parsing, use bufio.Scanner
// in combination with ParseHost. For example:
//
// var knownHosts tofu.KnownHosts
// scanner := bufio.NewScanner(r)
// for scanner.Scan() {
2021-01-15 00:57:52 +00:00
// host, err := tofu.ParseHost(scanner.Bytes())
2021-01-15 00:56:04 +00:00
// if err != nil {
// // handle error
// } else {
// knownHosts.Add(host)
// }
// }
// err := scanner.Err()
// if err != nil {
// // handle error
// }
//
func (k *KnownHosts) Parse(r io.Reader) error {
k.mu.Lock()
defer k.mu.Unlock()
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
if k.hosts == nil {
2021-01-14 19:15:08 +00:00
k.hosts = map[string]Host{}
}
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
2020-09-25 22:53:20 +00:00
scanner := bufio.NewScanner(r)
for scanner.Scan() {
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
text := scanner.Bytes()
if len(text) == 0 {
continue
}
2020-09-25 22:53:20 +00:00
h, err := ParseHost(text)
2020-11-06 03:30:13 +00:00
if err != nil {
continue
2020-11-06 03:30:13 +00:00
}
2021-03-06 20:48:51 +00:00
if h.Algorithm != "sha256" {
continue
}
2020-11-06 03:30:13 +00:00
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
k.hosts[h.Hostname] = h
}
return scanner.Err()
2020-09-26 00:55:37 +00:00
}
2020-09-25 22:53:20 +00:00
2021-01-15 00:56:04 +00:00
// TOFU implements basic trust on first use.
2021-01-15 00:40:19 +00:00
//
// If the host is not on file, it is added to the list.
// If the fingerprint does not match the one on file, an error is returned.
func (k *KnownHosts) TOFU(hostname string, cert *x509.Certificate) error {
host := NewHost(hostname, cert.Raw)
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
knownHost, ok := k.Lookup(hostname)
if !ok {
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
k.Add(host)
return nil
}
if host.Fingerprint != knownHost.Fingerprint {
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
return fmt.Errorf("fingerprint for %q does not match", hostname)
}
return nil
}
2021-01-14 21:54:38 +00:00
// HostWriter writes host entries to an io.WriteCloser.
//
// HostWriter is safe for concurrent use by multiple goroutines.
type HostWriter struct {
bw *bufio.Writer
2021-01-14 21:54:38 +00:00
cl io.Closer
2021-01-14 21:35:54 +00:00
mu sync.Mutex
}
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
2021-01-14 21:54:38 +00:00
// NewHostWriter returns a new host writer that writes to
// the provided io.WriteCloser.
func NewHostWriter(w io.WriteCloser) *HostWriter {
return &HostWriter{
bw: bufio.NewWriter(w),
2021-01-14 21:54:38 +00:00
cl: w,
}
}
2021-01-25 17:02:09 +00:00
// OpenHostsFile returns a new host writer that appends to the file at the given path.
2021-01-15 00:56:04 +00:00
// The file is created if it does not exist.
2021-01-25 17:02:09 +00:00
func OpenHostsFile(path string) (*HostWriter, error) {
2021-01-14 21:54:38 +00:00
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
2021-01-14 21:54:38 +00:00
return NewHostWriter(f), nil
}
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
// WriteHost writes the host to the underlying io.Writer.
2021-01-14 21:35:54 +00:00
func (h *HostWriter) WriteHost(host Host) error {
h.mu.Lock()
defer h.mu.Unlock()
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
2021-01-14 21:35:54 +00:00
h.bw.WriteString(host.String())
h.bw.WriteByte('\n')
if err := h.bw.Flush(); err != nil {
2021-01-14 21:54:38 +00:00
return fmt.Errorf("failed to write host: %w", err)
}
return nil
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
}
2021-01-15 00:56:04 +00:00
// Close closes the underlying io.Closer.
2021-01-14 21:54:38 +00:00
func (h *HostWriter) Close() error {
h.mu.Lock()
defer h.mu.Unlock()
return h.cl.Close()
}
2021-01-25 17:02:09 +00:00
// PersistentHosts represents a persistent set of known hosts.
type PersistentHosts struct {
hosts *KnownHosts
writer *HostWriter
}
2021-02-23 14:21:21 +00:00
// NewPersistentHosts returns a new persistent set of known hosts that stores
// known hosts in hosts and writes new hosts to writer.
2021-01-25 17:02:09 +00:00
func NewPersistentHosts(hosts *KnownHosts, writer *HostWriter) *PersistentHosts {
return &PersistentHosts{
hosts,
writer,
}
}
// LoadPersistentHosts loads persistent hosts from the file at the given path.
func LoadPersistentHosts(path string) (*PersistentHosts, error) {
hosts := &KnownHosts{}
if err := hosts.Load(path); err != nil {
return nil, err
}
writer, err := OpenHostsFile(path)
if err != nil {
return nil, err
}
return &PersistentHosts{
hosts,
writer,
}, nil
}
// Add adds a host to the list of known hosts.
// It returns an error if the host could not be persisted.
func (p *PersistentHosts) Add(h Host) error {
err := p.writer.WriteHost(h)
if err != nil {
return fmt.Errorf("failed to persist host: %w", err)
}
p.hosts.Add(h)
return nil
}
// Lookup returns the known host entry corresponding to the given hostname.
func (p *PersistentHosts) Lookup(hostname string) (Host, bool) {
return p.hosts.Lookup(hostname)
}
// Entries returns the known host entries sorted by hostname.
func (p *PersistentHosts) Entries() []Host {
return p.hosts.Entries()
}
// TOFU implements trust on first use with a persistent set of known hosts.
//
// If the host is not on file, it is added to the list.
// If the fingerprint does not match the one on file, an error is returned.
func (p *PersistentHosts) TOFU(hostname string, cert *x509.Certificate) error {
host := NewHost(hostname, cert.Raw)
2021-01-25 17:02:09 +00:00
knownHost, ok := p.Lookup(hostname)
if !ok {
2021-01-25 17:02:09 +00:00
return p.Add(host)
}
if host.Fingerprint != knownHost.Fingerprint {
2021-01-25 17:02:09 +00:00
return fmt.Errorf("fingerprint for %q does not match", hostname)
}
return nil
}
// Close closes the underlying HostWriter.
func (p *PersistentHosts) Close() error {
return p.writer.Close()
}
// Host represents a host entry with a fingerprint using a certain algorithm.
2021-01-14 19:15:08 +00:00
type Host struct {
Hostname string // hostname
Algorithm string // fingerprint algorithm e.g. sha256
Fingerprint string // fingerprint
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
}
// NewHost returns a new host with a SHA256 fingerprint of
// the provided raw data.
func NewHost(hostname string, raw []byte) Host {
sum := sha256.Sum256(raw)
2021-01-14 19:15:08 +00:00
return Host{
Hostname: hostname,
Algorithm: "sha256",
Fingerprint: base64.StdEncoding.EncodeToString(sum[:]),
2021-01-14 19:15:08 +00:00
}
}
// ParseHost parses a host from the provided text.
func ParseHost(text []byte) (Host, error) {
var h Host
err := h.UnmarshalText(text)
return h, err
}
// String returns a string representation of the host.
func (h Host) String() string {
var b strings.Builder
b.WriteString(h.Hostname)
b.WriteByte(' ')
b.WriteString(h.Algorithm)
b.WriteByte(' ')
b.WriteString(h.Fingerprint)
return b.String()
}
// UnmarshalText unmarshals the host from the provided text.
2021-01-14 19:15:08 +00:00
func (h *Host) UnmarshalText(text []byte) error {
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
parts := bytes.Split(text, []byte(" "))
if len(parts) != 3 {
2021-03-06 20:48:51 +00:00
return fmt.Errorf("expected the format 'hostname algorithm fingerprint'")
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
}
2021-01-14 19:15:08 +00:00
h.Hostname = string(parts[0])
2021-03-06 20:48:51 +00:00
h.Algorithm = string(parts[1])
h.Fingerprint = string(parts[2])
tofu: Refactor This commit changes underlying file handling and known hosts parsing. A known hosts file opened through Load() never closed the underlying file. During known hosts parsing most errors were unchecked, or just led to the line being skipped. I removed the KnownHosts type, which didn't really have a role after the refactor. The embedding of KnownHosts in KnownHosts file has been removed as it also leaked the map unprotected by the mutex. The Fingerprint type is now KnownHost and has taken over the responsibility of marshalling and unmarshalling. SetOutput now takes a WriteCloser so that we can close the underlying writer when it's replaced, or when it's explicitly closed through the new Close() function. KnownHostsFile.Add() now also writes the known host to the output if set. I think that makes sense expectation-wise for the type. Turned WriteAll() into WriteTo() to conform with the io.WriterTo interface. Load() is now Open() to better reflect the fact that a file is opened, and kept open. It can now also return errors from the parsing process. The parser does a lot more error checking, and this might be an area where I've changed a desired behaviour as invalid entries no longer are ignored, but aborts the parsing process. That could be changed to a warning, or some kind of parsing feedback. I added KnownHostsFile.TOFU() to fill the developer experience gap that was left after the client no longer knows about KnownHostsFile. It implements a basic non-interactive TOFU flow.
2021-01-13 21:33:48 +00:00
return nil
}