package crypto import ( "encoding/base64" "errors" "strings" "testing" ) func TestFingerprint_ParseRoundTrip(t *testing.T) { sc, err := LoadOrCreateServerCert(t.TempDir()) if err != nil { t.Fatal(err) } got, err := ParseFingerprint(string(sc.Fingerprint)) if err != nil || got != sc.Fingerprint { t.Errorf("ParseFingerprint = %q, %v", got, err) } for _, bad := range []string{"", "abc", strings.Repeat("A", 42), strings.Repeat("A", 44), strings.Repeat("+", 43), "sha256:" + string(sc.Fingerprint)} { if _, err := ParseFingerprint(bad); !errors.Is(err, ErrInvalidFingerprint) { t.Errorf("ParseFingerprint(%q): erwartet ErrInvalidFingerprint, bekommen %v", bad, err) } } } func TestEnrollmentToken_RoundTrip(t *testing.T) { sc, _ := LoadOrCreateServerCert(t.TempDir()) tok, err := NewEnrollmentToken(sc.Fingerprint, "") if err != nil { t.Fatalf("NewEnrollmentToken: %v", err) } s := tok.String() if !strings.HasPrefix(s, TokenPrefix) { t.Errorf("Token %q ohne Präfix %q", s, TokenPrefix) } if !strings.HasPrefix(tok.Secret, TokenPrefix) || strings.Contains(tok.Secret, ".") { t.Errorf("Secret %q: erwartet Präfix und keinen Fingerprint-Anteil", tok.Secret) } parsed, err := ParseEnrollmentToken(s) if err != nil { t.Fatalf("ParseEnrollmentToken: %v", err) } if parsed != tok { t.Errorf("Round-Trip weicht ab: %+v vs %+v", parsed, tok) } if parsed2, _ := ParseEnrollmentToken(" " + s + "\n"); parsed2 != tok { t.Error("Leerraum beim Kopieren sollte toleriert werden") } } func TestEnrollmentToken_Unique(t *testing.T) { sc, _ := LoadOrCreateServerCert(t.TempDir()) seen := map[string]bool{} for range 100 { tok, err := NewEnrollmentToken(sc.Fingerprint, "") if err != nil { t.Fatal(err) } if seen[tok.Secret] { t.Fatal("doppeltes Secret") } seen[tok.Secret] = true } } func TestEnrollmentToken_Errors(t *testing.T) { sc, _ := LoadOrCreateServerCert(t.TempDir()) good, _ := NewEnrollmentToken(sc.Fingerprint, "") secretPart := strings.TrimPrefix(good.Secret, TokenPrefix) fp := string(sc.Fingerprint) tests := map[string]string{ "leer": "", "falsches Präfix": "sltn_token_" + secretPart + "." + fp, "ohne Fingerprint": good.Secret, "leerer Fingerprint": good.Secret + ".", "kaputter Fingerprint": good.Secret + ".xyz", "Secret zu kurz": TokenPrefix + "abc." + fp, "Secret kein base64url": TokenPrefix + strings.Repeat("!", 43) + "." + fp, "zu viele Teile": good.String() + ".extra", } for name, s := range tests { t.Run(name, func(t *testing.T) { if _, err := ParseEnrollmentToken(s); !errors.Is(err, ErrInvalidToken) { t.Errorf("erwartet ErrInvalidToken, bekommen %v", err) } }) } if _, err := NewEnrollmentToken("kaputt", ""); !errors.Is(err, ErrInvalidFingerprint) { t.Errorf("NewEnrollmentToken mit kaputtem Fingerprint: %v", err) } } func TestEnrollmentToken_WithServerURL(t *testing.T) { sc, _ := LoadOrCreateServerCert(t.TempDir()) tok, err := NewEnrollmentToken(sc.Fingerprint, "https://Ingress.example.de:8443/") if err != nil { t.Fatal(err) } if tok.ServerURL != "https://ingress.example.de:8443" { t.Errorf("URL nicht normalisiert: %q", tok.ServerURL) } s := tok.String() if parts := strings.Split(s, "."); len(parts) != 3 { t.Fatalf("erwartet drei Teile: %q", s) } if strings.ContainsAny(s, ":/?=+ ") { t.Errorf("Token nicht shell-/kopiersicher: %q", s) } parsed, err := ParseEnrollmentToken(s) if err != nil { t.Fatalf("ParseEnrollmentToken: %v", err) } if parsed != tok { t.Errorf("Round-Trip: %+v vs %+v", parsed, tok) } // Ohne URL: zweiteilig wie bisher. plain, _ := NewEnrollmentToken(sc.Fingerprint, "") if parsed, err := ParseEnrollmentToken(plain.String()); err != nil || parsed.ServerURL != "" { t.Errorf("Token ohne URL: %+v %v", parsed, err) } if _, err := NewEnrollmentToken(sc.Fingerprint, "http://ingress:8443"); !errors.Is(err, ErrInvalidServerURL) { t.Errorf("http-URL: %v", err) } } func TestEnrollmentToken_InvalidServerURLPart(t *testing.T) { sc, _ := LoadOrCreateServerCert(t.TempDir()) base, _ := NewEnrollmentToken(sc.Fingerprint, "") enc := func(u string) string { return base.String() + "." + base64.RawURLEncoding.EncodeToString([]byte(u)) } for name, s := range map[string]string{ "kein base64url": base.String() + ".!!!", "leer": base.String() + ".", "http": enc("http://ingress:8443"), "mit Pfad": enc("https://ingress:8443/admin"), "mit Zugangsdaten": enc("https://user:pw@ingress:8443"), "zu lang": enc("https://" + strings.Repeat("a", 300) + ".de"), } { if _, err := ParseEnrollmentToken(s); !errors.Is(err, ErrInvalidToken) { t.Errorf("%s: erwartet ErrInvalidToken, bekommen %v", name, err) } } } func TestValidateServerURL(t *testing.T) { valid := map[string]string{ "https://ingress.x.de:8443": "https://ingress.x.de:8443", "https://ingress.x.de:8443/": "https://ingress.x.de:8443", "https://Ingress.X.de": "https://ingress.x.de", "https://[fd00::1]:8443": "https://[fd00::1]:8443", "https://100.64.0.1:8443": "https://100.64.0.1:8443", } for in, want := range valid { if got, err := ValidateServerURL(in); err != nil || got != want { t.Errorf("ValidateServerURL(%q) = %q, %v; erwartet %q", in, got, err, want) } } for _, bad := range []string{"", "ingress:8443", "http://ingress:8443", "https://", "https://u:p@x:8443", "https://x:8443/pfad", "https://x:8443?q=1", "https://x:8443#frag", "https://x:99999", "ftp://x"} { if _, err := ValidateServerURL(bad); !errors.Is(err, ErrInvalidServerURL) { t.Errorf("ValidateServerURL(%q): erwartet ErrInvalidServerURL, bekommen %v", bad, err) } } }