verify.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. package oidc
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/base64"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "net/http"
  11. "strings"
  12. "time"
  13. "golang.org/x/oauth2"
  14. jose "gopkg.in/square/go-jose.v2"
  15. )
  16. const (
  17. issuerGoogleAccounts = "https://accounts.google.com"
  18. issuerGoogleAccountsNoScheme = "accounts.google.com"
  19. )
  20. // KeySet is a set of publc JSON Web Keys that can be used to validate the signature
  21. // of JSON web tokens. This is expected to be backed by a remote key set through
  22. // provider metadata discovery or an in-memory set of keys delivered out-of-band.
  23. type KeySet interface {
  24. // VerifySignature parses the JSON web token, verifies the signature, and returns
  25. // the raw payload. Header and claim fields are validated by other parts of the
  26. // package. For example, the KeySet does not need to check values such as signature
  27. // algorithm, issuer, and audience since the IDTokenVerifier validates these values
  28. // independently.
  29. //
  30. // If VerifySignature makes HTTP requests to verify the token, it's expected to
  31. // use any HTTP client associated with the context through ClientContext.
  32. VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
  33. }
  34. // IDTokenVerifier provides verification for ID Tokens.
  35. type IDTokenVerifier struct {
  36. keySet KeySet
  37. config *Config
  38. issuer string
  39. }
  40. // NewVerifier returns a verifier manually constructed from a key set and issuer URL.
  41. //
  42. // It's easier to use provider discovery to construct an IDTokenVerifier than creating
  43. // one directly. This method is intended to be used with provider that don't support
  44. // metadata discovery, or avoiding round trips when the key set URL is already known.
  45. //
  46. // This constructor can be used to create a verifier directly using the issuer URL and
  47. // JSON Web Key Set URL without using discovery:
  48. //
  49. // keySet := oidc.NewRemoteKeySet(ctx, "https://www.googleapis.com/oauth2/v3/certs")
  50. // verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
  51. //
  52. // Since KeySet is an interface, this constructor can also be used to supply custom
  53. // public key sources. For example, if a user wanted to supply public keys out-of-band
  54. // and hold them statically in-memory:
  55. //
  56. // // Custom KeySet implementation.
  57. // keySet := newStatisKeySet(publicKeys...)
  58. //
  59. // // Verifier uses the custom KeySet implementation.
  60. // verifier := oidc.NewVerifier("https://auth.example.com", keySet, config)
  61. //
  62. func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier {
  63. return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL}
  64. }
  65. // Config is the configuration for an IDTokenVerifier.
  66. type Config struct {
  67. // Expected audience of the token. For a majority of the cases this is expected to be
  68. // the ID of the client that initialized the login flow. It may occasionally differ if
  69. // the provider supports the authorizing party (azp) claim.
  70. //
  71. // If not provided, users must explicitly set SkipClientIDCheck.
  72. ClientID string
  73. // If specified, only this set of algorithms may be used to sign the JWT.
  74. //
  75. // Since many providers only support RS256, SupportedSigningAlgs defaults to this value.
  76. SupportedSigningAlgs []string
  77. // If true, no ClientID check performed. Must be true if ClientID field is empty.
  78. SkipClientIDCheck bool
  79. // If true, token expiry is not checked.
  80. SkipExpiryCheck bool
  81. // SkipIssuerCheck is intended for specialized cases where the the caller wishes to
  82. // defer issuer validation. When enabled, callers MUST independently verify the Token's
  83. // Issuer is a known good value.
  84. //
  85. // Mismatched issuers often indicate client mis-configuration. If mismatches are
  86. // unexpected, evaluate if the provided issuer URL is incorrect instead of enabling
  87. // this option.
  88. SkipIssuerCheck bool
  89. // Time function to check Token expiry. Defaults to time.Now
  90. Now func() time.Time
  91. }
  92. // Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
  93. //
  94. // The returned IDTokenVerifier is tied to the Provider's context and its behavior is
  95. // undefined once the Provider's context is canceled.
  96. func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
  97. return NewVerifier(p.issuer, p.remoteKeySet, config)
  98. }
  99. func parseJWT(p string) ([]byte, error) {
  100. parts := strings.Split(p, ".")
  101. if len(parts) < 2 {
  102. return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
  103. }
  104. payload, err := base64.RawURLEncoding.DecodeString(parts[1])
  105. if err != nil {
  106. return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
  107. }
  108. return payload, nil
  109. }
  110. func contains(sli []string, ele string) bool {
  111. for _, s := range sli {
  112. if s == ele {
  113. return true
  114. }
  115. }
  116. return false
  117. }
  118. // Returns the Claims from the distributed JWT token
  119. func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src claimSource) ([]byte, error) {
  120. req, err := http.NewRequest("GET", src.Endpoint, nil)
  121. if err != nil {
  122. return nil, fmt.Errorf("malformed request: %v", err)
  123. }
  124. if src.AccessToken != "" {
  125. req.Header.Set("Authorization", "Bearer "+src.AccessToken)
  126. }
  127. resp, err := doRequest(ctx, req)
  128. if err != nil {
  129. return nil, fmt.Errorf("oidc: Request to endpoint failed: %v", err)
  130. }
  131. defer resp.Body.Close()
  132. body, err := ioutil.ReadAll(resp.Body)
  133. if err != nil {
  134. return nil, fmt.Errorf("unable to read response body: %v", err)
  135. }
  136. if resp.StatusCode != http.StatusOK {
  137. return nil, fmt.Errorf("oidc: request failed: %v", resp.StatusCode)
  138. }
  139. token, err := verifier.Verify(ctx, string(body))
  140. if err != nil {
  141. return nil, fmt.Errorf("malformed response body: %v", err)
  142. }
  143. return token.claims, nil
  144. }
  145. func parseClaim(raw []byte, name string, v interface{}) error {
  146. var parsed map[string]json.RawMessage
  147. if err := json.Unmarshal(raw, &parsed); err != nil {
  148. return err
  149. }
  150. val, ok := parsed[name]
  151. if !ok {
  152. return fmt.Errorf("claim doesn't exist: %s", name)
  153. }
  154. return json.Unmarshal([]byte(val), v)
  155. }
  156. // Verify parses a raw ID Token, verifies it's been signed by the provider, preforms
  157. // any additional checks depending on the Config, and returns the payload.
  158. //
  159. // Verify does NOT do nonce validation, which is the callers responsibility.
  160. //
  161. // See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
  162. //
  163. // oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
  164. // if err != nil {
  165. // // handle error
  166. // }
  167. //
  168. // // Extract the ID Token from oauth2 token.
  169. // rawIDToken, ok := oauth2Token.Extra("id_token").(string)
  170. // if !ok {
  171. // // handle error
  172. // }
  173. //
  174. // token, err := verifier.Verify(ctx, rawIDToken)
  175. //
  176. func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
  177. jws, err := jose.ParseSigned(rawIDToken)
  178. if err != nil {
  179. return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
  180. }
  181. // Throw out tokens with invalid claims before trying to verify the token. This lets
  182. // us do cheap checks before possibly re-syncing keys.
  183. payload, err := parseJWT(rawIDToken)
  184. if err != nil {
  185. return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
  186. }
  187. var token idToken
  188. if err := json.Unmarshal(payload, &token); err != nil {
  189. return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
  190. }
  191. distributedClaims := make(map[string]claimSource)
  192. //step through the token to map claim names to claim sources"
  193. for cn, src := range token.ClaimNames {
  194. if src == "" {
  195. return nil, fmt.Errorf("oidc: failed to obtain source from claim name")
  196. }
  197. s, ok := token.ClaimSources[src]
  198. if !ok {
  199. return nil, fmt.Errorf("oidc: source does not exist")
  200. }
  201. distributedClaims[cn] = s
  202. }
  203. t := &IDToken{
  204. Issuer: token.Issuer,
  205. Subject: token.Subject,
  206. Audience: []string(token.Audience),
  207. Expiry: time.Time(token.Expiry),
  208. IssuedAt: time.Time(token.IssuedAt),
  209. Nonce: token.Nonce,
  210. AccessTokenHash: token.AtHash,
  211. claims: payload,
  212. distributedClaims: distributedClaims,
  213. }
  214. // Check issuer.
  215. if !v.config.SkipIssuerCheck && t.Issuer != v.issuer {
  216. // Google sometimes returns "accounts.google.com" as the issuer claim instead of
  217. // the required "https://accounts.google.com". Detect this case and allow it only
  218. // for Google.
  219. //
  220. // We will not add hooks to let other providers go off spec like this.
  221. if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) {
  222. return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer)
  223. }
  224. }
  225. // If a client ID has been provided, make sure it's part of the audience. SkipClientIDCheck must be true if ClientID is empty.
  226. //
  227. // This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
  228. if !v.config.SkipClientIDCheck {
  229. if v.config.ClientID != "" {
  230. if !contains(t.Audience, v.config.ClientID) {
  231. return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
  232. }
  233. } else {
  234. return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
  235. }
  236. }
  237. // If a SkipExpiryCheck is false, make sure token is not expired.
  238. if !v.config.SkipExpiryCheck {
  239. now := time.Now
  240. if v.config.Now != nil {
  241. now = v.config.Now
  242. }
  243. nowTime := now()
  244. if t.Expiry.Before(nowTime) {
  245. return nil, fmt.Errorf("oidc: token is expired (Token Expiry: %v)", t.Expiry)
  246. }
  247. // If nbf claim is provided in token, ensure that it is indeed in the past.
  248. if token.NotBefore != nil {
  249. nbfTime := time.Time(*token.NotBefore)
  250. leeway := 1 * time.Minute
  251. if nowTime.Add(leeway).Before(nbfTime) {
  252. return nil, fmt.Errorf("oidc: current time %v before the nbf (not before) time: %v", nowTime, nbfTime)
  253. }
  254. }
  255. }
  256. switch len(jws.Signatures) {
  257. case 0:
  258. return nil, fmt.Errorf("oidc: id token not signed")
  259. case 1:
  260. default:
  261. return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
  262. }
  263. sig := jws.Signatures[0]
  264. supportedSigAlgs := v.config.SupportedSigningAlgs
  265. if len(supportedSigAlgs) == 0 {
  266. supportedSigAlgs = []string{RS256}
  267. }
  268. if !contains(supportedSigAlgs, sig.Header.Algorithm) {
  269. return nil, fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm)
  270. }
  271. t.sigAlgorithm = sig.Header.Algorithm
  272. gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
  273. if err != nil {
  274. return nil, fmt.Errorf("failed to verify signature: %v", err)
  275. }
  276. // Ensure that the payload returned by the square actually matches the payload parsed earlier.
  277. if !bytes.Equal(gotPayload, payload) {
  278. return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
  279. }
  280. return t, nil
  281. }
  282. // Nonce returns an auth code option which requires the ID Token created by the
  283. // OpenID Connect provider to contain the specified nonce.
  284. func Nonce(nonce string) oauth2.AuthCodeOption {
  285. return oauth2.SetAuthURLParam("nonce", nonce)
  286. }