verify.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. // If the IDTokenVerifier is created from a provider with (*Provider).Verifier, this
  76. // defaults to the set of algorithms the provider supports. Otherwise this values
  77. // defaults to RS256.
  78. SupportedSigningAlgs []string
  79. // If true, no ClientID check performed. Must be true if ClientID field is empty.
  80. SkipClientIDCheck bool
  81. // If true, token expiry is not checked.
  82. SkipExpiryCheck bool
  83. // SkipIssuerCheck is intended for specialized cases where the the caller wishes to
  84. // defer issuer validation. When enabled, callers MUST independently verify the Token's
  85. // Issuer is a known good value.
  86. //
  87. // Mismatched issuers often indicate client mis-configuration. If mismatches are
  88. // unexpected, evaluate if the provided issuer URL is incorrect instead of enabling
  89. // this option.
  90. SkipIssuerCheck bool
  91. // Time function to check Token expiry. Defaults to time.Now
  92. Now func() time.Time
  93. }
  94. // Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
  95. //
  96. // The returned IDTokenVerifier is tied to the Provider's context and its behavior is
  97. // undefined once the Provider's context is canceled.
  98. func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
  99. if len(config.SupportedSigningAlgs) == 0 && len(p.algorithms) > 0 {
  100. // Make a copy so we don't modify the config values.
  101. cp := &Config{}
  102. *cp = *config
  103. cp.SupportedSigningAlgs = p.algorithms
  104. config = cp
  105. }
  106. return NewVerifier(p.issuer, p.remoteKeySet, config)
  107. }
  108. func parseJWT(p string) ([]byte, error) {
  109. parts := strings.Split(p, ".")
  110. if len(parts) < 2 {
  111. return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
  112. }
  113. payload, err := base64.RawURLEncoding.DecodeString(parts[1])
  114. if err != nil {
  115. return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
  116. }
  117. return payload, nil
  118. }
  119. func contains(sli []string, ele string) bool {
  120. for _, s := range sli {
  121. if s == ele {
  122. return true
  123. }
  124. }
  125. return false
  126. }
  127. // Returns the Claims from the distributed JWT token
  128. func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src claimSource) ([]byte, error) {
  129. req, err := http.NewRequest("GET", src.Endpoint, nil)
  130. if err != nil {
  131. return nil, fmt.Errorf("malformed request: %v", err)
  132. }
  133. if src.AccessToken != "" {
  134. req.Header.Set("Authorization", "Bearer "+src.AccessToken)
  135. }
  136. resp, err := doRequest(ctx, req)
  137. if err != nil {
  138. return nil, fmt.Errorf("oidc: Request to endpoint failed: %v", err)
  139. }
  140. defer resp.Body.Close()
  141. body, err := ioutil.ReadAll(resp.Body)
  142. if err != nil {
  143. return nil, fmt.Errorf("unable to read response body: %v", err)
  144. }
  145. if resp.StatusCode != http.StatusOK {
  146. return nil, fmt.Errorf("oidc: request failed: %v", resp.StatusCode)
  147. }
  148. token, err := verifier.Verify(ctx, string(body))
  149. if err != nil {
  150. return nil, fmt.Errorf("malformed response body: %v", err)
  151. }
  152. return token.claims, nil
  153. }
  154. func parseClaim(raw []byte, name string, v interface{}) error {
  155. var parsed map[string]json.RawMessage
  156. if err := json.Unmarshal(raw, &parsed); err != nil {
  157. return err
  158. }
  159. val, ok := parsed[name]
  160. if !ok {
  161. return fmt.Errorf("claim doesn't exist: %s", name)
  162. }
  163. return json.Unmarshal([]byte(val), v)
  164. }
  165. // Verify parses a raw ID Token, verifies it's been signed by the provider, preforms
  166. // any additional checks depending on the Config, and returns the payload.
  167. //
  168. // Verify does NOT do nonce validation, which is the callers responsibility.
  169. //
  170. // See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
  171. //
  172. // oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
  173. // if err != nil {
  174. // // handle error
  175. // }
  176. //
  177. // // Extract the ID Token from oauth2 token.
  178. // rawIDToken, ok := oauth2Token.Extra("id_token").(string)
  179. // if !ok {
  180. // // handle error
  181. // }
  182. //
  183. // token, err := verifier.Verify(ctx, rawIDToken)
  184. //
  185. func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
  186. jws, err := jose.ParseSigned(rawIDToken)
  187. if err != nil {
  188. return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
  189. }
  190. // Throw out tokens with invalid claims before trying to verify the token. This lets
  191. // us do cheap checks before possibly re-syncing keys.
  192. payload, err := parseJWT(rawIDToken)
  193. if err != nil {
  194. return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
  195. }
  196. var token idToken
  197. if err := json.Unmarshal(payload, &token); err != nil {
  198. return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
  199. }
  200. distributedClaims := make(map[string]claimSource)
  201. //step through the token to map claim names to claim sources"
  202. for cn, src := range token.ClaimNames {
  203. if src == "" {
  204. return nil, fmt.Errorf("oidc: failed to obtain source from claim name")
  205. }
  206. s, ok := token.ClaimSources[src]
  207. if !ok {
  208. return nil, fmt.Errorf("oidc: source does not exist")
  209. }
  210. distributedClaims[cn] = s
  211. }
  212. t := &IDToken{
  213. Issuer: token.Issuer,
  214. Subject: token.Subject,
  215. Audience: []string(token.Audience),
  216. Expiry: time.Time(token.Expiry),
  217. IssuedAt: time.Time(token.IssuedAt),
  218. Nonce: token.Nonce,
  219. AccessTokenHash: token.AtHash,
  220. claims: payload,
  221. distributedClaims: distributedClaims,
  222. }
  223. // Check issuer.
  224. if !v.config.SkipIssuerCheck && t.Issuer != v.issuer {
  225. // Google sometimes returns "accounts.google.com" as the issuer claim instead of
  226. // the required "https://accounts.google.com". Detect this case and allow it only
  227. // for Google.
  228. //
  229. // We will not add hooks to let other providers go off spec like this.
  230. if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) {
  231. return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer)
  232. }
  233. }
  234. // If a client ID has been provided, make sure it's part of the audience. SkipClientIDCheck must be true if ClientID is empty.
  235. //
  236. // This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
  237. if !v.config.SkipClientIDCheck {
  238. if v.config.ClientID != "" {
  239. if !contains(t.Audience, v.config.ClientID) {
  240. return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
  241. }
  242. } else {
  243. return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
  244. }
  245. }
  246. // If a SkipExpiryCheck is false, make sure token is not expired.
  247. if !v.config.SkipExpiryCheck {
  248. now := time.Now
  249. if v.config.Now != nil {
  250. now = v.config.Now
  251. }
  252. nowTime := now()
  253. if t.Expiry.Before(nowTime) {
  254. return nil, fmt.Errorf("oidc: token is expired (Token Expiry: %v)", t.Expiry)
  255. }
  256. // If nbf claim is provided in token, ensure that it is indeed in the past.
  257. if token.NotBefore != nil {
  258. nbfTime := time.Time(*token.NotBefore)
  259. leeway := 1 * time.Minute
  260. if nowTime.Add(leeway).Before(nbfTime) {
  261. return nil, fmt.Errorf("oidc: current time %v before the nbf (not before) time: %v", nowTime, nbfTime)
  262. }
  263. }
  264. }
  265. switch len(jws.Signatures) {
  266. case 0:
  267. return nil, fmt.Errorf("oidc: id token not signed")
  268. case 1:
  269. default:
  270. return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
  271. }
  272. sig := jws.Signatures[0]
  273. supportedSigAlgs := v.config.SupportedSigningAlgs
  274. if len(supportedSigAlgs) == 0 {
  275. supportedSigAlgs = []string{RS256}
  276. }
  277. if !contains(supportedSigAlgs, sig.Header.Algorithm) {
  278. return nil, fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm)
  279. }
  280. t.sigAlgorithm = sig.Header.Algorithm
  281. gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
  282. if err != nil {
  283. return nil, fmt.Errorf("failed to verify signature: %v", err)
  284. }
  285. // Ensure that the payload returned by the square actually matches the payload parsed earlier.
  286. if !bytes.Equal(gotPayload, payload) {
  287. return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
  288. }
  289. return t, nil
  290. }
  291. // Nonce returns an auth code option which requires the ID Token created by the
  292. // OpenID Connect provider to contain the specified nonce.
  293. func Nonce(nonce string) oauth2.AuthCodeOption {
  294. return oauth2.SetAuthURLParam("nonce", nonce)
  295. }