aboutsummaryrefslogtreecommitdiffstats
path: root/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/verification.go
blob: 002413047949ee685e759a4f4cb5a4d047d49427 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package oidc

import (
	"errors"
	"fmt"
	"time"

	"github.com/jonboulle/clockwork"

	"github.com/coreos/go-oidc/jose"
	"github.com/coreos/go-oidc/key"
)

func VerifySignature(jwt jose.JWT, keys []key.PublicKey) (bool, error) {
	jwtBytes := []byte(jwt.Data())
	for _, k := range keys {
		v, err := k.Verifier()
		if err != nil {
			return false, err
		}
		if v.Verify(jwt.Signature, jwtBytes) == nil {
			return true, nil
		}
	}
	return false, nil
}

// containsString returns true if the given string(needle) is found
// in the string array(haystack).
func containsString(needle string, haystack []string) bool {
	for _, v := range haystack {
		if v == needle {
			return true
		}
	}
	return false
}

// Verify claims in accordance with OIDC spec
// http://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
func VerifyClaims(jwt jose.JWT, issuer, clientID string) error {
	now := time.Now().UTC()

	claims, err := jwt.Claims()
	if err != nil {
		return err
	}

	ident, err := IdentityFromClaims(claims)
	if err != nil {
		return err
	}

	if ident.ExpiresAt.Before(now) {
		return errors.New("token is expired")
	}

	// iss REQUIRED. Issuer Identifier for the Issuer of the response.
	// The iss value is a case sensitive URL using the https scheme that contains scheme,
	// host, and optionally, port number and path components and no query or fragment components.
	if iss, exists := claims["iss"].(string); exists {
		if !urlEqual(iss, issuer) {
			return fmt.Errorf("invalid claim value: 'iss'. expected=%s, found=%s.", issuer, iss)
		}
	} else {
		return errors.New("missing claim: 'iss'")
	}

	// iat REQUIRED. Time at which the JWT was issued.
	// Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z
	// as measured in UTC until the date/time.
	if _, exists := claims["iat"].(float64); !exists {
		return errors.New("missing claim: 'iat'")
	}

	// aud REQUIRED. Audience(s) that this ID Token is intended for.
	// It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value.
	// It MAY also contain identifiers for other audiences. In the general case, the aud
	// value is an array of case sensitive strings. In the common special case when there
	// is one audience, the aud value MAY be a single case sensitive string.
	if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
		if aud != clientID {
			return fmt.Errorf("invalid claims, 'aud' claim and 'client_id' do not match, aud=%s, client_id=%s", aud, clientID)
		}
	} else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
		if !containsString(clientID, aud) {
			return fmt.Errorf("invalid claims, cannot find 'client_id' in 'aud' claim, aud=%v, client_id=%s", aud, clientID)
		}
	} else {
		return errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
	}

	return nil
}

// VerifyClientClaims verifies all the required claims are valid for a "client credentials" JWT.
// Returns the client ID if valid, or an error if invalid.
func VerifyClientClaims(jwt jose.JWT, issuer string) (string, error) {
	claims, err := jwt.Claims()
	if err != nil {
		return "", fmt.Errorf("failed to parse JWT claims: %v", err)
	}

	iss, ok, err := claims.StringClaim("iss")
	if err != nil {
		return "", fmt.Errorf("failed to parse 'iss' claim: %v", err)
	} else if !ok {
		return "", errors.New("missing required 'iss' claim")
	} else if !urlEqual(iss, issuer) {
		return "", fmt.Errorf("'iss' claim does not match expected issuer, iss=%s", iss)
	}

	sub, ok, err := claims.StringClaim("sub")
	if err != nil {
		return "", fmt.Errorf("failed to parse 'sub' claim: %v", err)
	} else if !ok {
		return "", errors.New("missing required 'sub' claim")
	}

	if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
		if aud != sub {
			return "", fmt.Errorf("invalid claims, 'aud' claim and 'sub' claim do not match, aud=%s, sub=%s", aud, sub)
		}
	} else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
		if !containsString(sub, aud) {
			return "", fmt.Errorf("invalid claims, cannot find 'sud' in 'aud' claim, aud=%v, sub=%s", aud, sub)
		}
	} else {
		return "", errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
	}

	now := time.Now().UTC()
	exp, ok, err := claims.TimeClaim("exp")
	if err != nil {
		return "", fmt.Errorf("failed to parse 'exp' claim: %v", err)
	} else if !ok {
		return "", errors.New("missing required 'exp' claim")
	} else if exp.Before(now) {
		return "", fmt.Errorf("token already expired at: %v", exp)
	}

	return sub, nil
}

type JWTVerifier struct {
	issuer   string
	clientID string
	syncFunc func() error
	keysFunc func() []key.PublicKey
	clock    clockwork.Clock
}

func NewJWTVerifier(issuer, clientID string, syncFunc func() error, keysFunc func() []key.PublicKey) JWTVerifier {
	return JWTVerifier{
		issuer:   issuer,
		clientID: clientID,
		syncFunc: syncFunc,
		keysFunc: keysFunc,
		clock:    clockwork.NewRealClock(),
	}
}

func (v *JWTVerifier) Verify(jwt jose.JWT) error {
	ok, err := VerifySignature(jwt, v.keysFunc())
	if ok {
		goto SignatureVerified
	} else if err != nil {
		return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
	}

	if err = v.syncFunc(); err != nil {
		return fmt.Errorf("oidc: failed syncing KeySet: %v", err)
	}

	ok, err = VerifySignature(jwt, v.keysFunc())
	if err != nil {
		return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
	} else if !ok {
		return errors.New("oidc: unable to verify JWT signature: no matching keys")
	}

SignatureVerified:
	if err := VerifyClaims(jwt, v.issuer, v.clientID); err != nil {
		return fmt.Errorf("oidc: JWT claims invalid: %v", err)
	}

	return nil
}