@@ -0,0 +1,249 @@
1+package oidc
2+
3+import (
4+ "context"
5+ "crypto/rand"
6+ "crypto/rsa"
7+ "encoding/json"
8+ "fmt"
9+
10+ "github.com/coreos/go-oidc/v3/oidc"
11+ . "github.com/onsi/ginkgo"
12+ . "github.com/onsi/gomega"
13+ "gopkg.in/square/go-jose.v2"
14+)
15+
16+var _ = Describe("Verify", func() {
17+ ctx := context.Background()
18+
19+ It("Succeeds with default aud behavior", func() {
20+ result, err := verify(ctx, &IDTokenVerificationOptions{
21+ AudienceClaims: []string{"aud"},
22+ ClientID: "1226737",
23+ ExtraAudiences: []string{},
24+ }, payload{
25+ Iss: "https://foo",
26+ Aud: "1226737",
27+ })
28+
29+ Expect(err).ToNot(HaveOccurred())
30+ Expect(result.Issuer).To(Equal("https://foo"))
31+ Expect(result.Audience).To(Equal([]string{"1226737"}))
32+ })
33+
34+ It("Fails with default aud behavior", func() {
35+ result, err := verify(ctx, &IDTokenVerificationOptions{
36+ AudienceClaims: []string{"aud"},
37+ ClientID: "7817818",
38+ ExtraAudiences: []string{},
39+ }, payload{
40+ Iss: "https://foo",
41+ Aud: "1226737",
42+ })
43+ Expect(err).To(MatchError("audience from claim aud with value [1226737] does not match with " +
44+ "any of allowed audiences map[7817818:{}]"))
45+ Expect(result).To(BeNil())
46+ })
47+
48+ It("Succeeds with extra audiences", func() {
49+ result, err := verify(ctx, &IDTokenVerificationOptions{
50+ AudienceClaims: []string{"aud"},
51+ ClientID: "7817818",
52+ ExtraAudiences: []string{"xyz", "1226737"},
53+ }, payload{
54+ Iss: "https://foo",
55+ Aud: "1226737",
56+ })
57+
58+ Expect(err).ToNot(HaveOccurred())
59+ Expect(result.Issuer).To(Equal("https://foo"))
60+ Expect(result.Audience).To(Equal([]string{"1226737"}))
61+ })
62+
63+ It("Fails with extra audiences", func() {
64+ result, err := verify(ctx, &IDTokenVerificationOptions{
65+ AudienceClaims: []string{"aud"},
66+ ClientID: "7817818",
67+ ExtraAudiences: []string{"xyz", "abc"},
68+ }, payload{
69+ Iss: "https://foo",
70+ Aud: "1226737",
71+ })
72+
73+ Expect(err).To(MatchError("audience from claim aud with value [1226737] does not match with any " +
74+ "of allowed audiences map[7817818:{} abc:{} xyz:{}]"))
75+ Expect(result).To(BeNil())
76+ })
77+
78+ It("Succeeds with non default aud behavior", func() {
79+ result, err := verify(ctx, &IDTokenVerificationOptions{
80+ AudienceClaims: []string{"client_id"},
81+ ClientID: "1226737",
82+ ExtraAudiences: []string{},
83+ }, payload{
84+ Iss: "https://foo",
85+ ClientID: "1226737",
86+ })
87+
88+ Expect(err).ToNot(HaveOccurred())
89+ Expect(result.Issuer).To(Equal("https://foo"))
90+ Expect(result.Audience).To(Equal([]string{"1226737"}))
91+ })
92+
93+ It("Fails with non default aud behavior", func() {
94+ result, err := verify(ctx, &IDTokenVerificationOptions{
95+ AudienceClaims: []string{"client_id"},
96+ ClientID: "7817818",
97+ ExtraAudiences: []string{},
98+ }, payload{
99+ Iss: "https://foo",
100+ ClientID: "1226737",
101+ })
102+ Expect(err).To(MatchError("audience from claim client_id with value [1226737] does not match with " +
103+ "any of allowed audiences map[7817818:{}]"))
104+ Expect(result).To(BeNil())
105+ })
106+
107+ It("Succeeds with non default aud behavior and extra audiences", func() {
108+ result, err := verify(ctx, &IDTokenVerificationOptions{
109+ AudienceClaims: []string{"client_id"},
110+ ClientID: "7817818",
111+ ExtraAudiences: []string{"xyz", "1226737"},
112+ }, payload{
113+ Iss: "https://foo",
114+ ClientID: "1226737",
115+ })
116+
117+ Expect(err).ToNot(HaveOccurred())
118+ Expect(result.Issuer).To(Equal("https://foo"))
119+ Expect(result.Audience).To(Equal([]string{"1226737"}))
120+ })
121+
122+ It("Fails with non default aud behavior and extra audiences", func() {
123+ result, err := verify(ctx, &IDTokenVerificationOptions{
124+ AudienceClaims: []string{"client_id"},
125+ ClientID: "7817818",
126+ ExtraAudiences: []string{"xyz", "abc"},
127+ }, payload{
128+ Iss: "https://foo",
129+ ClientID: "1226737",
130+ })
131+
132+ Expect(err).To(MatchError("audience from claim client_id with value [1226737] does not match with any " +
133+ "of allowed audiences map[7817818:{} abc:{} xyz:{}]"))
134+ Expect(result).To(BeNil())
135+ })
136+
137+ It("Fails if audience claim does not exist", func() {
138+ result, err := verify(ctx, &IDTokenVerificationOptions{
139+ AudienceClaims: []string{"not_exists"},
140+ ClientID: "7817818",
141+ ExtraAudiences: []string{"xyz", "abc"},
142+ }, payload{
143+ Iss: "https://foo",
144+ ClientID: "1226737",
145+ Aud: "1226737",
146+ })
147+
148+ Expect(err).To(MatchError("audience claims [not_exists] do not exist in claims: " +
149+ "map[aud:1226737 client_id:1226737 iss:https://foo]"))
150+ Expect(result).To(BeNil())
151+ })
152+
153+ It("Succeeds with multiple audiences", func() {
154+ var result, err = verify(ctx, &IDTokenVerificationOptions{
155+ AudienceClaims: []string{"client_id", "aud"},
156+ ClientID: "123456789",
157+ ExtraAudiences: []string{"1226737"},
158+ }, payload{
159+ Iss: "https://foo",
160+ ClientID: "123456789",
161+ Aud: []string{"1226737", "123456789"},
162+ })
163+
164+ Expect(err).ToNot(HaveOccurred())
165+ Expect(result.Issuer).To(Equal("https://foo"))
166+ Expect(result.Audience).To(Equal([]string{"123456789"}))
167+ })
168+
169+ It("Succeeds if aud claim match", func() {
170+ result, err := verify(ctx, &IDTokenVerificationOptions{
171+ AudienceClaims: []string{"client_id", "aud"},
172+ ClientID: "1226737",
173+ ExtraAudiences: []string{"xyz", "abc"},
174+ }, payload{
175+ Iss: "https://foo",
176+ ClientID: "1226737",
177+ Aud: "1226737",
178+ })
179+
180+ Expect(err).ToNot(HaveOccurred())
181+ Expect(result.Issuer).To(Equal("https://foo"))
182+ Expect(result.Audience).To(Equal([]string{"1226737"}))
183+ })
184+})
185+
186+type payload struct {
187+ Iss string `json:"iss,omitempty"`
188+ Aud interface{} `json:"aud,omitempty"`
189+ ClientID string `json:"client_id,omitempty"`
190+}
191+
192+type jwtToken struct {
193+ PrivateKey jose.JSONWebKey
194+ PublicKey jose.JSONWebKey
195+ Token string
196+}
197+
198+type testVerifier struct {
199+ jwk jose.JSONWebKey
200+}
201+
202+func (t *testVerifier) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
203+ jws, err := jose.ParseSigned(jwt)
204+ if err != nil {
205+ return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
206+ }
207+ return jws.Verify(&t.jwk)
208+}
209+
210+func verify(ctx context.Context, verificationOptions *IDTokenVerificationOptions, payload payload) (*oidc.IDToken, error) {
211+ config := &oidc.Config{
212+ ClientID: "1226737",
213+ SkipClientIDCheck: true,
214+ SkipExpiryCheck: true, // required to not run in expired Token error during testing
215+ }
216+ rawToken, err := json.Marshal(payload)
217+ if err != nil {
218+ return nil, err
219+ }
220+ token, _ := createToken(rawToken)
221+ verifier := NewVerifier(oidc.NewVerifier("https://foo", &testVerifier{jwk: token.PublicKey}, config), verificationOptions)
222+ return verifier.Verify(ctx, token.Token)
223+}
224+
225+func createToken(payload []byte) (*jwtToken, error) {
226+ privateKey, err := rsa.GenerateKey(rand.Reader, 1028)
227+ if err != nil {
228+ return nil, err
229+ }
230+ privateWebKey := jose.JSONWebKey{Key: privateKey, Algorithm: string(jose.RS256), KeyID: ""}
231+ publicWebKey := jose.JSONWebKey{Key: privateKey.Public(), Use: "sig", Algorithm: string(jose.RS256), KeyID: ""}
232+ signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: privateWebKey}, nil)
233+ if err != nil {
234+ return nil, err
235+ }
236+ jws, err := signer.Sign(payload)
237+ if err != nil {
238+ return nil, err
239+ }
240+ data, err := jws.CompactSerialize()
241+ if err != nil {
242+ return nil, err
243+ }
244+ return &jwtToken{
245+ PrivateKey: privateWebKey,
246+ PublicKey: publicWebKey,
247+ Token: data,
248+ }, nil
249+}