package crypto import ( "crypto/rand" "encoding/base64" "errors" "fmt" "strings" ) // TokenPrefix kennzeichnet Enrollment-Tokens. const TokenPrefix = "sltn_enroll_" // ErrInvalidToken: Der Token-String hat nicht das Format // sltn_enroll_.. var ErrInvalidToken = errors.New("crypto: ungültiger Enrollment-Token") // EnrollmentToken ist die Einladung für einen neuen Agenten. // // Secret (inkl. Präfix) ist der geheime Teil: Der Server speichert davon nur // den Hash (store.AddEnrollmentToken), der Agent schickt es beim Enrollment. // Fingerprint ist nicht geheim; er sagt dem Agenten, welches // Server-Zertifikat er beim Erstkontakt erwarten muss. type EnrollmentToken struct { Secret string Fingerprint Fingerprint } // String liefert die Form, die der Admin an den Agenten weitergibt. func (t EnrollmentToken) String() string { return t.Secret + "." + string(t.Fingerprint) } // NewEnrollmentToken erzeugt einen Token mit 256 Bit Zufall für den Server // mit Fingerprint fp. func NewEnrollmentToken(fp Fingerprint) (EnrollmentToken, error) { if _, err := ParseFingerprint(string(fp)); err != nil { return EnrollmentToken{}, err } var b [32]byte if _, err := rand.Read(b[:]); err != nil { return EnrollmentToken{}, err } return EnrollmentToken{Secret: TokenPrefix + base64.RawURLEncoding.EncodeToString(b[:]), Fingerprint: fp}, nil } // ParseEnrollmentToken zerlegt einen Token-String. Leerraum am Rand (beim // Kopieren) wird ignoriert. func ParseEnrollmentToken(s string) (EnrollmentToken, error) { parts := strings.Split(strings.TrimSpace(s), ".") if len(parts) != 2 { return EnrollmentToken{}, fmt.Errorf("%w: erwartet %s.", ErrInvalidToken, TokenPrefix) } secret, rawFP := parts[0], parts[1] body, ok := strings.CutPrefix(secret, TokenPrefix) if !ok { return EnrollmentToken{}, fmt.Errorf("%w: Präfix %s fehlt", ErrInvalidToken, TokenPrefix) } if b, err := base64.RawURLEncoding.DecodeString(body); err != nil || len(b) != 32 { return EnrollmentToken{}, fmt.Errorf("%w: Secret beschädigt", ErrInvalidToken) } fp, err := ParseFingerprint(rawFP) if err != nil { return EnrollmentToken{}, fmt.Errorf("%w: %w", ErrInvalidToken, err) } return EnrollmentToken{Secret: secret, Fingerprint: fp}, nil }