// Package tokentest signs tokens the way the hub does, for tests. package tokentest import ( "crypto/ed25519" "crypto/rand" "encoding/base64" "encoding/json" ) // Key is a test signing key. type Key struct { ID string Private ed25519.PrivateKey Public ed25519.PublicKey } // NewKey makes a key with ID kid. func NewKey(kid string) *Key { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { panic(err) } return &Key{ID: kid, Private: priv, Public: pub} } // Sign returns a compact EdDSA JWT with these claims, with the header the hub sends. func (k *Key) Sign(claims map[string]any) string { return k.SignWithHeader(map[string]any{"alg": "EdDSA", "kid": k.ID, "typ": "JWT"}, claims) } // SignWithHeader signs claims with any header. func (k *Key) SignWithHeader(header, claims map[string]any) string { input := part(header) + "." + part(claims) return input + "." + base64.RawURLEncoding.EncodeToString(ed25519.Sign(k.Private, []byte(input))) } func part(v any) string { data, err := json.Marshal(v) if err != nil { panic(err) } return base64.RawURLEncoding.EncodeToString(data) } // JWKS returns a key set holding these keys, as the hub serves it. func JWKS(keys ...*Key) []byte { var set struct { Keys []map[string]string `json:"keys"` } set.Keys = []map[string]string{} for _, k := range keys { set.Keys = append(set.Keys, map[string]string{ "kty": "OKP", "crv": "Ed25519", "x": base64.RawURLEncoding.EncodeToString(k.Public), "kid": k.ID, "alg": "EdDSA", "use": "sig", }) } data, _ := json.Marshal(set) return data }