Asymmetric JWT (RS256) + Key Rotation
Source: Custom
Topics: RSA, RS256, JWT, JWKS, key rotation, asymmetric crypto
Problem
The HS256 challenge used a shared secret — anyone who can verify can also forge. Here you
sign with an RSA private key (RS256) and verify with the public key, so verifiers can
authenticate tokens without holding signing power. Then support key rotation via a kid-keyed
key set (a minimal JWKS), the way a gateway rotates signing keys without downtime.
Requirements:
Sign(claims Claims, kid string, priv *rsa.PrivateKey) (string, error) — RS256 JWT with kid in the header.
KeySet — a kid → *rsa.PublicKey map (a minimal JWKS) supporting Add / Remove.
(*KeySet) Verify(token string) (Claims, error) — read the token's kid, select the matching
public key, verify the RSA signature, and check exp.
- Errors:
ErrMalformed, ErrUnknownKey, ErrInvalidSignature, ErrExpired.
type Claims struct {
Subject string `json:"sub"`
ExpiresAt int64 `json:"exp"`
}
func Sign(claims Claims, kid string, priv *rsa.PrivateKey) (string, error)
type KeySet struct { ... }
func NewKeySet() *KeySet
func (ks *KeySet) Add(kid string, pub *rsa.PublicKey)
func (ks *KeySet) Remove(kid string)
func (ks *KeySet) Verify(token string) (Claims, error)
Key concepts
- Asymmetric signing: the holder of the private key signs; anyone with the public key
verifies but cannot forge. This is why real identity providers publish a JWKS of public keys.
kid header: the key ID tells the verifier which public key to use, so multiple keys can be
valid at once — the foundation of zero-downtime rotation (add new key → start signing with it →
remove the old key once all its tokens have expired).
- RS256 internals: sign =
RSASSA-PKCS1-v1_5(SHA256(header.payload)); the signature is then
base64url-encoded as the third JWT segment.
crypto.SHA256 + rsa.SignPKCS1v15/VerifyPKCS1v15: the stdlib primitives — no third-party
JWT library needed.
Run
go test -v ./challenges/crypto/jwt-asymmetric/
Sign in to submit your solution.