software steht so weit.
jetzt build + docker build und testen
This commit is contained in:
1 parent
42f0c0accb
commit
ca6cdcca63
90 files changed
+10298
-480
No files matched your search
+21
-118
@@ -1,142 +1,45 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnknownServer: Kein Pin und kein erwarteter Fingerprint – ein
|
||||
// ungeprüfter Erstkontakt wird verweigert.
|
||||
ErrUnknownServer = errors.New("crypto: unbekannter Server – Fingerprint aus dem Enrollment-Token erforderlich")
|
||||
// ErrFingerprintMismatch: Das Server-Zertifikat passt nicht zum Pin bzw.
|
||||
// zum erwarteten Fingerprint (möglicher Man-in-the-Middle).
|
||||
// ErrUnknownServer: Kein Fingerprint bekannt – ein ungeprüfter Kontakt
|
||||
// wird verweigert (kein blindes TOFU).
|
||||
ErrUnknownServer = errors.New("crypto: unbekannter Server – Fingerprint aus der Einladung erforderlich")
|
||||
// ErrFingerprintMismatch: Das Server-Zertifikat passt nicht zum Pin
|
||||
// (möglicher Man-in-the-Middle).
|
||||
ErrFingerprintMismatch = errors.New("crypto: Server-Fingerprint stimmt nicht – möglicher Man-in-the-Middle")
|
||||
// ErrCorruptKnownServers: Die known_servers-Datei ist nicht lesbar.
|
||||
ErrCorruptKnownServers = errors.New("crypto: known_servers beschädigt")
|
||||
)
|
||||
|
||||
// KnownServers ist die Pin-Datei des Agenten (analog zu SSH known_hosts).
|
||||
// Format: eine Zeile "<server> <fingerprint>", # für Kommentare. Einträge
|
||||
// werden nur angehängt, nie stillschweigend geändert.
|
||||
type KnownServers struct {
|
||||
Path string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Lookup liefert den Pin für server. Eine fehlende Datei bedeutet: kein Pin.
|
||||
func (k *KnownServers) Lookup(server string) (Fingerprint, bool, error) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
return k.lookup(server)
|
||||
}
|
||||
|
||||
func (k *KnownServers) lookup(server string) (Fingerprint, bool, error) {
|
||||
data, err := os.ReadFile(k.Path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return "", false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
sc := bufio.NewScanner(bytes.NewReader(data))
|
||||
for line := 1; sc.Scan(); line++ {
|
||||
text := strings.TrimSpace(sc.Text())
|
||||
if text == "" || strings.HasPrefix(text, "#") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(text)
|
||||
if len(fields) != 2 {
|
||||
return "", false, fmt.Errorf("%w: %s Zeile %d", ErrCorruptKnownServers, k.Path, line)
|
||||
}
|
||||
fp, err := ParseFingerprint(fields[1])
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("%w: %s Zeile %d: %w", ErrCorruptKnownServers, k.Path, line, err)
|
||||
}
|
||||
if fields[0] == server {
|
||||
return fp, true, nil
|
||||
}
|
||||
}
|
||||
return "", false, sc.Err()
|
||||
}
|
||||
|
||||
// Add pinnt fp für server. Ist bereits ein anderer Fingerprint gepinnt,
|
||||
// liefert Add ErrFingerprintMismatch und ändert nichts.
|
||||
func (k *KnownServers) Add(server string, fp Fingerprint) error {
|
||||
if len(strings.Fields(server)) != 1 || strings.TrimSpace(server) != server {
|
||||
return fmt.Errorf("crypto: ungültiger Servername %q", server)
|
||||
}
|
||||
if _, err := ParseFingerprint(string(fp)); err != nil {
|
||||
return err
|
||||
}
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
pinned, ok, err := k.lookup(server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
if pinned != fp {
|
||||
return fmt.Errorf("%w: %s ist auf %s gepinnt, nicht %s", ErrFingerprintMismatch, server, pinned, fp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
f, err := os.OpenFile(k.Path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(f, "%s %s\n", server, fp)
|
||||
if cerr := f.Close(); err == nil {
|
||||
err = cerr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ClientTLSConfig liefert die TLS-Konfiguration des Agenten für server
|
||||
// (host:port aus der --server-URL). Statt einer CA prüft sie den
|
||||
// Fingerprint:
|
||||
// PinnedTLSConfig liefert die TLS-Konfiguration des Agenten. Statt einer CA
|
||||
// wird der Fingerprint des Server-Zertifikats geprüft – unabhängig von der
|
||||
// Adresse, über die der Server erreicht wird (DNS, interne IP, NetBird):
|
||||
// Derselbe Server hat überall denselben Fingerprint.
|
||||
//
|
||||
// - Pin vorhanden: Das Zertifikat muss dazu passen (expected wird ignoriert).
|
||||
// - Kein Pin, expected gesetzt (Fingerprint aus dem Enrollment-Token):
|
||||
// Das Zertifikat muss dazu passen und wird dann gepinnt.
|
||||
// - Kein Pin, kein expected: Verbindung wird verweigert (ErrUnknownServer).
|
||||
//
|
||||
// Die Prüfung läuft im TLS-Handshake, also bevor irgendein Request-Byte
|
||||
// (Token, Signatur, Body) gesendet wird. Nur TLS 1.3.
|
||||
func ClientTLSConfig(server string, ks *KnownServers, expected Fingerprint) *tls.Config {
|
||||
// Beim Erstkontakt kommt want aus der Einladung, danach aus node.json. Die
|
||||
// Prüfung läuft im TLS-Handshake, also bevor irgendein Request-Byte (Token,
|
||||
// Signatur, Body) gesendet wird. Nur TLS 1.3.
|
||||
func PinnedTLSConfig(want Fingerprint) *tls.Config {
|
||||
return &tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
// Die CA-Prüfung entfällt bewusst; VerifyConnection ersetzt sie
|
||||
// durch die strengere Fingerprint-Prüfung.
|
||||
// Die CA- und Hostnamen-Prüfung entfällt bewusst; VerifyConnection
|
||||
// ersetzt sie durch die strengere Fingerprint-Prüfung.
|
||||
InsecureSkipVerify: true,
|
||||
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||
if want == "" {
|
||||
return ErrUnknownServer
|
||||
}
|
||||
if len(cs.PeerCertificates) == 0 {
|
||||
return fmt.Errorf("%w: kein Zertifikat", ErrFingerprintMismatch)
|
||||
}
|
||||
actual := FingerprintOf(cs.PeerCertificates[0])
|
||||
pinned, ok, err := ks.Lookup(server)
|
||||
if err != nil {
|
||||
return err
|
||||
if got := FingerprintOf(cs.PeerCertificates[0]); got != want {
|
||||
return fmt.Errorf("%w: Server präsentiert %s, erwartet %s", ErrFingerprintMismatch, got, want)
|
||||
}
|
||||
if ok {
|
||||
if actual != pinned {
|
||||
return fmt.Errorf("%w: %s präsentiert %s, gepinnt ist %s", ErrFingerprintMismatch, server, actual, pinned)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if expected == "" {
|
||||
return fmt.Errorf("%w (%s präsentiert %s)", ErrUnknownServer, server, actual)
|
||||
}
|
||||
if actual != expected {
|
||||
return fmt.Errorf("%w: %s präsentiert %s, Token erwartet %s", ErrFingerprintMismatch, server, actual, expected)
|
||||
}
|
||||
return ks.Add(server, actual)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user