package ect import ( "crypto/ecdsa" "errors" "testing" "time" ) func TestParse(t *testing.T) { key, _ := GenerateKey() now := time.Now() payload := &Payload{ Iss: "iss", Aud: []string{"aud"}, Iat: now.Unix(), Exp: now.Add(time.Hour).Unix(), Jti: "jti-parse", ExecAct: "act", Par: []string{}, Pol: "pol", PolDecision: PolDecisionApproved, } compact, err := Create(payload, key, CreateOptions{KeyID: "kid"}) if err != nil { t.Fatal(err) } parsed, err := Parse(compact) if err != nil { t.Fatal(err) } if parsed.Payload.Jti != "jti-parse" { t.Errorf("jti: got %q", parsed.Payload.Jti) } if parsed.Raw != compact { t.Error("Raw should match compact") } } func TestVerify_InvalidTyp(t *testing.T) { // Create token then tamper header to test typ check would need different alg or manual JWS; // instead test Verify with bad token _, err := Verify("not-a-jws", VerifyOptions{}) if err == nil { t.Fatal("expected error for invalid JWS") } } func TestVerify_Expired(t *testing.T) { key, _ := GenerateKey() now := time.Now() payload := &Payload{ Iss: "iss", Aud: []string{"verifier"}, Iat: now.Add(-1 * time.Hour).Unix(), Exp: now.Add(-1 * time.Minute).Unix(), Jti: "jti-exp", ExecAct: "act", Par: []string{}, Pol: "pol", PolDecision: PolDecisionApproved, } compact, _ := Create(payload, key, CreateOptions{KeyID: "kid"}) resolver := func(kid string) (*ecdsa.PublicKey, error) { if kid == "kid" { return &key.PublicKey, nil } return nil, nil } _, err := Verify(compact, VerifyOptions{ VerifierID: "verifier", ResolveKey: resolver, Now: now, }) if err == nil { t.Fatal("expected error for expired token") } } func TestVerify_Replay(t *testing.T) { key, _ := GenerateKey() now := time.Now() payload := &Payload{ Iss: "iss", Aud: []string{"v"}, Iat: now.Unix(), Exp: now.Add(time.Hour).Unix(), Jti: "jti-replay", ExecAct: "act", Par: []string{}, Pol: "p", PolDecision: PolDecisionApproved, } compact, _ := Create(payload, key, CreateOptions{KeyID: "kid"}) resolver := func(kid string) (*ecdsa.PublicKey, error) { if kid == "kid" { return &key.PublicKey, nil } return nil, nil } opts := VerifyOptions{ VerifierID: "v", ResolveKey: resolver, Now: now, JTISeen: func(jti string) bool { return jti == "jti-replay" }, } _, err := Verify(compact, opts) if err == nil { t.Fatal("expected error for replay") } } func TestDefaultVerifyOptions(t *testing.T) { opts := DefaultVerifyOptions() if opts.IATMaxAge != 15*time.Minute { t.Errorf("IATMaxAge: got %v", opts.IATMaxAge) } if opts.DAG.ClockSkewTolerance != DefaultClockSkewTolerance { t.Errorf("DAG: got %d", opts.DAG.ClockSkewTolerance) } } func TestVerify_WITSubjectMismatch(t *testing.T) { key, _ := GenerateKey() now := time.Now() payload := &Payload{ Iss: "iss", Aud: []string{"v"}, Iat: now.Unix(), Exp: now.Add(time.Hour).Unix(), Jti: "jti-wit", ExecAct: "act", Par: []string{}, Pol: "p", PolDecision: PolDecisionApproved, } compact, _ := Create(payload, key, CreateOptions{KeyID: "kid"}) resolver := func(kid string) (*ecdsa.PublicKey, error) { if kid == "kid" { return &key.PublicKey, nil } return nil, nil } _, err := Verify(compact, VerifyOptions{ VerifierID: "v", ResolveKey: resolver, Now: now, WITSubject: "other-iss", }) if err == nil { t.Fatal("expected error for WIT subject mismatch") } } func TestVerify_IATTooFarPast(t *testing.T) { key, _ := GenerateKey() now := time.Now() payload := &Payload{ Iss: "iss", Aud: []string{"v"}, Iat: now.Add(-1 * time.Hour).Unix(), Exp: now.Add(time.Hour).Unix(), Jti: "jti-iat", ExecAct: "act", Par: []string{}, Pol: "p", PolDecision: PolDecisionApproved, } compact, _ := Create(payload, key, CreateOptions{KeyID: "kid"}) resolver := func(kid string) (*ecdsa.PublicKey, error) { if kid == "kid" { return &key.PublicKey, nil } return nil, nil } _, err := Verify(compact, VerifyOptions{ VerifierID: "v", ResolveKey: resolver, Now: now, IATMaxAge: 30 * time.Minute, }) if err == nil { t.Fatal("expected error for iat too far in past") } } func TestVerify_IATInFuture(t *testing.T) { key, _ := GenerateKey() now := time.Now() payload := &Payload{ Iss: "iss", Aud: []string{"v"}, Iat: now.Add(60 * time.Second).Unix(), Exp: now.Add(2 * time.Hour).Unix(), Jti: "jti-fut", ExecAct: "act", Par: []string{}, Pol: "p", PolDecision: PolDecisionApproved, } compact, _ := Create(payload, key, CreateOptions{KeyID: "kid"}) resolver := func(kid string) (*ecdsa.PublicKey, error) { if kid == "kid" { return &key.PublicKey, nil } return nil, nil } _, err := Verify(compact, VerifyOptions{ VerifierID: "v", ResolveKey: resolver, Now: now, IATMaxFuture: 30 * time.Second, }) if err == nil { t.Fatal("expected error for iat in future") } } func TestVerify_ResolveKeyError(t *testing.T) { key, _ := GenerateKey() now := time.Now() payload := &Payload{ Iss: "iss", Aud: []string{"v"}, Iat: now.Unix(), Exp: now.Add(time.Hour).Unix(), Jti: "jti-err", ExecAct: "act", Par: []string{}, Pol: "p", PolDecision: PolDecisionApproved, } compact, _ := Create(payload, key, CreateOptions{KeyID: "kid"}) resolver := func(kid string) (*ecdsa.PublicKey, error) { return nil, errors.New("key lookup failed") } _, err := Verify(compact, VerifyOptions{ VerifierID: "v", ResolveKey: resolver, Now: now, }) if err == nil { t.Fatal("expected error from ResolveKey") } } func TestVerify_WithDAG(t *testing.T) { key, _ := GenerateKey() ledger := NewMemoryLedger() now := time.Now() root := &Payload{ Iss: "iss", Aud: []string{"v"}, Iat: now.Unix(), Exp: now.Add(time.Hour).Unix(), Jti: "jti-root", ExecAct: "act", Par: []string{}, Pol: "p", PolDecision: PolDecisionApproved, } compactRoot, _ := Create(root, key, CreateOptions{KeyID: "kid"}) resolver := func(kid string) (*ecdsa.PublicKey, error) { if kid == "kid" { return &key.PublicKey, nil } return nil, nil } opts := VerifyOptions{ VerifierID: "v", ResolveKey: resolver, Store: ledger, Now: now, } parsed, err := Verify(compactRoot, opts) if err != nil { t.Fatal(err) } _, _ = ledger.Append(compactRoot, parsed.Payload) child := &Payload{ Iss: "iss", Aud: []string{"v"}, Iat: now.Unix() + 1, Exp: now.Add(time.Hour).Unix(), Jti: "jti-child", ExecAct: "act2", Par: []string{"jti-root"}, Pol: "p", PolDecision: PolDecisionApproved, } compactChild, _ := Create(child, key, CreateOptions{KeyID: "kid"}) parsed2, err := Verify(compactChild, opts) if err != nil { t.Fatal(err) } if parsed2.Payload.Jti != "jti-child" { t.Errorf("got %q", parsed2.Payload.Jti) } }