143 lines
4.9 KiB
Go
143 lines
4.9 KiB
Go
// Package testuril helps setting up required data for testing,
|
|
// such as tokens, claims and verifiers.
|
|
package testutil
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"encoding/json"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/zitadel/oidc/v2/pkg/oidc"
|
|
"gopkg.in/square/go-jose.v2"
|
|
)
|
|
|
|
const SignatureAlgorithm = jose.PS512
|
|
|
|
// KeySet implements oidc.Keys and
|
|
// additionally can create tokens and claims that can
|
|
// be validated by this KeySet.
|
|
type KeySet struct {
|
|
Private *rsa.PrivateKey
|
|
Public *rsa.PublicKey
|
|
|
|
Signer jose.Signer
|
|
}
|
|
|
|
func NewKeySet() *KeySet {
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: SignatureAlgorithm, Key: privateKey}, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return &KeySet{
|
|
Private: privateKey,
|
|
Public: &privateKey.PublicKey,
|
|
Signer: signer,
|
|
}
|
|
}
|
|
|
|
func (k *KeySet) signEncodeTokenClaims(claims any) string {
|
|
payload, err := json.Marshal(claims)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
object, err := k.Signer.Sign(payload)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
token, err := object.CompactSerialize()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return token
|
|
}
|
|
|
|
func claimsMap(claims any) map[string]any {
|
|
data, err := json.Marshal(claims)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dst := make(map[string]any)
|
|
if err = json.Unmarshal(data, &dst); err != nil {
|
|
panic(err)
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// NewIDToken creates a new IDTokenClaims with passed data and returns a signed token and claims.
|
|
func (k *KeySet) NewIDToken(issuer, subject string, audience []string, expiration, authTime time.Time, nonce string, acr string, amr []string, clientID string, skew time.Duration, atHash string) (string, *oidc.IDTokenClaims) {
|
|
claims := oidc.NewIDTokenClaims(issuer, subject, audience, expiration, authTime, nonce, acr, amr, clientID, skew)
|
|
claims.AccessTokenHash = atHash
|
|
token := k.signEncodeTokenClaims(claims)
|
|
|
|
// set this so that assertion in tests will work
|
|
claims.SignatureAlg = SignatureAlgorithm
|
|
claims.Claims = claimsMap(claims)
|
|
return token, claims
|
|
}
|
|
|
|
// NewAcccessToken creates a new AccessTokenClaims with passed data and returns a signed token and claims.
|
|
func (k *KeySet) NewAccessToken(issuer, subject string, audience []string, expiration time.Time, jwtid, clientID string, skew time.Duration) (string, *oidc.AccessTokenClaims) {
|
|
claims := oidc.NewAccessTokenClaims(issuer, subject, audience, expiration, jwtid, clientID, skew)
|
|
token := k.signEncodeTokenClaims(claims)
|
|
|
|
// set this so that assertion in tests will work
|
|
claims.SignatureAlg = SignatureAlgorithm
|
|
claims.Claims = claimsMap(claims)
|
|
return token, claims
|
|
}
|
|
|
|
const InvalidSignatureToken = `eyJhbGciOiJQUzUxMiJ9.eyJpc3MiOiJsb2NhbC5jb20iLCJzdWIiOiJ0aW1AbG9jYWwuY29tIiwiYXVkIjpbInVuaXQiLCJ0ZXN0IiwiNTU1NjY2Il0sImV4cCI6MTY3Nzg0MDQzMSwiaWF0IjoxNjc3ODQwMzcwLCJhdXRoX3RpbWUiOjE2Nzc4NDAzMTAsIm5vbmNlIjoiMTIzNDUiLCJhY3IiOiJzb21ldGhpbmciLCJhbXIiOlsiZm9vIiwiYmFyIl0sImF6cCI6IjU1NTY2NiJ9.DtZmvVkuE4Hw48ijBMhRJbxEWCr_WEYuPQBMY73J9TP6MmfeNFkjVJf4nh4omjB9gVLnQ-xhEkNOe62FS5P0BB2VOxPuHZUj34dNspCgG3h98fGxyiMb5vlIYAHDF9T-w_LntlYItohv63MmdYR-hPpAqjXE7KOfErf-wUDGE9R3bfiQ4HpTdyFJB1nsToYrZ9lhP2mzjTCTs58ckZfQ28DFHn_lfHWpR4rJBgvLx7IH4rMrUayr09Ap-PxQLbv0lYMtmgG1z3JK8MXnuYR0UJdZnEIezOzUTlThhCXB-nvuAXYjYxZZTR0FtlgZUHhIpYK0V2abf_Q_Or36akNCUg`
|
|
|
|
// These variables always result in a valid token
|
|
// for the same test run.
|
|
var (
|
|
ValidIssuer = "local.com"
|
|
ValidSubject = "tim@local.com"
|
|
ValidAudience = []string{"unit", "test"}
|
|
ValidAuthTime = time.Now().Add(-time.Minute) // authtime is always 1 minute in the past
|
|
ValidExpiration = ValidAuthTime.Add(2 * time.Minute) // token is always 1 more minute available
|
|
ValidJWTID = "9876"
|
|
ValidNonce = "12345"
|
|
ValidACR = "something"
|
|
ValidAMR = []string{"foo", "bar"}
|
|
ValidClientID = "555666"
|
|
ValidSkew = time.Second
|
|
)
|
|
|
|
// ValidIDToken returns a token and claims that are in the token.
|
|
// It uses the Valid* global variables and the token always passes
|
|
// verification within the same test run.
|
|
func (k *KeySet) ValidIDToken() (string, *oidc.IDTokenClaims) {
|
|
return k.NewIDToken(ValidIssuer, ValidSubject, ValidAudience, ValidExpiration, ValidAuthTime, ValidNonce, ValidACR, ValidAMR, ValidClientID, ValidSkew, "")
|
|
}
|
|
|
|
// ValidAccessToken returns a token and claims that are in the token.
|
|
// It uses the Valid* global variables and the token always passes
|
|
// verification within the same test run.
|
|
func (k *KeySet) ValidAccessToken() (string, *oidc.AccessTokenClaims) {
|
|
return k.NewAccessToken(ValidIssuer, ValidSubject, ValidAudience, ValidExpiration, ValidJWTID, ValidClientID, ValidSkew)
|
|
}
|
|
|
|
// VerifySignature implments op.KeySet.
|
|
func (k *KeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (payload []byte, err error) {
|
|
if ctx.Err() != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return jws.Verify(k.Public)
|
|
}
|
|
|
|
// ACRVerify is a oidc.ACRVerifier func.
|
|
func ACRVerify(acr string) error {
|
|
if acr != ValidACR {
|
|
return errors.New("invalid acr")
|
|
}
|
|
return nil
|
|
}
|