aboutsummaryrefslogtreecommitdiffstats
path: root/src/kube2msb/vendor/github.com/coreos/go-oidc
diff options
context:
space:
mode:
Diffstat (limited to 'src/kube2msb/vendor/github.com/coreos/go-oidc')
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/LICENSE202
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/NOTICE5
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/http/client.go7
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/http/http.go159
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/http/middleware.go14
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/http/url.go29
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/jose/claims.go126
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jose.go112
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jwk.go135
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jws.go51
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jwt.go82
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig.go24
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig_hmac.go67
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig_rsa.go67
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/key/key.go153
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/key/manager.go99
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/key/repo.go55
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/key/rotate.go165
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/key/sync.go91
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oauth2/error.go29
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oauth2/oauth2.go416
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go846
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/identity.go44
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/interface.go3
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/key.go67
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/provider.go688
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/transport.go88
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/util.go109
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/verification.go188
29 files changed, 4121 insertions, 0 deletions
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/LICENSE b/src/kube2msb/vendor/github.com/coreos/go-oidc/LICENSE
new file mode 100644
index 0000000..e06d208
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/LICENSE
@@ -0,0 +1,202 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/NOTICE b/src/kube2msb/vendor/github.com/coreos/go-oidc/NOTICE
new file mode 100644
index 0000000..b39ddfa
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/NOTICE
@@ -0,0 +1,5 @@
+CoreOS Project
+Copyright 2014 CoreOS, Inc
+
+This product includes software developed at CoreOS, Inc.
+(http://www.coreos.com/).
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/http/client.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/http/client.go
new file mode 100644
index 0000000..fd079b4
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/http/client.go
@@ -0,0 +1,7 @@
+package http
+
+import "net/http"
+
+type Client interface {
+ Do(*http.Request) (*http.Response, error)
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/http/http.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/http/http.go
new file mode 100644
index 0000000..f0d051b
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/http/http.go
@@ -0,0 +1,159 @@
+package http
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "net/http"
+ "net/url"
+ "path"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/coreos/pkg/capnslog"
+)
+
+var (
+ log = capnslog.NewPackageLogger("github.com/coreos/go-oidc", "http")
+)
+
+func WriteError(w http.ResponseWriter, code int, msg string) {
+ e := struct {
+ Error string `json:"error"`
+ }{
+ Error: msg,
+ }
+ b, err := json.Marshal(e)
+ if err != nil {
+ log.Errorf("Failed marshaling %#v to JSON: %v", e, err)
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ w.Write(b)
+}
+
+// BasicAuth parses a username and password from the request's
+// Authorization header. This was pulled from golang master:
+// https://codereview.appspot.com/76540043
+func BasicAuth(r *http.Request) (username, password string, ok bool) {
+ auth := r.Header.Get("Authorization")
+ if auth == "" {
+ return
+ }
+
+ if !strings.HasPrefix(auth, "Basic ") {
+ return
+ }
+ c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic "))
+ if err != nil {
+ return
+ }
+ cs := string(c)
+ s := strings.IndexByte(cs, ':')
+ if s < 0 {
+ return
+ }
+ return cs[:s], cs[s+1:], true
+}
+
+func cacheControlMaxAge(hdr string) (time.Duration, bool, error) {
+ for _, field := range strings.Split(hdr, ",") {
+ parts := strings.SplitN(strings.TrimSpace(field), "=", 2)
+ k := strings.ToLower(strings.TrimSpace(parts[0]))
+ if k != "max-age" {
+ continue
+ }
+
+ if len(parts) == 1 {
+ return 0, false, errors.New("max-age has no value")
+ }
+
+ v := strings.TrimSpace(parts[1])
+ if v == "" {
+ return 0, false, errors.New("max-age has empty value")
+ }
+
+ age, err := strconv.Atoi(v)
+ if err != nil {
+ return 0, false, err
+ }
+
+ if age <= 0 {
+ return 0, false, nil
+ }
+
+ return time.Duration(age) * time.Second, true, nil
+ }
+
+ return 0, false, nil
+}
+
+func expires(date, expires string) (time.Duration, bool, error) {
+ if date == "" || expires == "" {
+ return 0, false, nil
+ }
+
+ te, err := time.Parse(time.RFC1123, expires)
+ if err != nil {
+ return 0, false, err
+ }
+
+ td, err := time.Parse(time.RFC1123, date)
+ if err != nil {
+ return 0, false, err
+ }
+
+ ttl := te.Sub(td)
+
+ // headers indicate data already expired, caller should not
+ // have to care about this case
+ if ttl <= 0 {
+ return 0, false, nil
+ }
+
+ return ttl, true, nil
+}
+
+func Cacheable(hdr http.Header) (time.Duration, bool, error) {
+ ttl, ok, err := cacheControlMaxAge(hdr.Get("Cache-Control"))
+ if err != nil || ok {
+ return ttl, ok, err
+ }
+
+ return expires(hdr.Get("Date"), hdr.Get("Expires"))
+}
+
+// MergeQuery appends additional query values to an existing URL.
+func MergeQuery(u url.URL, q url.Values) url.URL {
+ uv := u.Query()
+ for k, vs := range q {
+ for _, v := range vs {
+ uv.Add(k, v)
+ }
+ }
+ u.RawQuery = uv.Encode()
+ return u
+}
+
+// NewResourceLocation appends a resource id to the end of the requested URL path.
+func NewResourceLocation(reqURL *url.URL, id string) string {
+ var u url.URL
+ u = *reqURL
+ u.Path = path.Join(u.Path, id)
+ u.RawQuery = ""
+ u.Fragment = ""
+ return u.String()
+}
+
+// CopyRequest returns a clone of the provided *http.Request.
+// The returned object is a shallow copy of the struct and a
+// deep copy of its Header field.
+func CopyRequest(r *http.Request) *http.Request {
+ r2 := *r
+ r2.Header = make(http.Header)
+ for k, s := range r.Header {
+ r2.Header[k] = s
+ }
+ return &r2
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/http/middleware.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/http/middleware.go
new file mode 100644
index 0000000..270b3bc
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/http/middleware.go
@@ -0,0 +1,14 @@
+package http
+
+import (
+ "net/http"
+)
+
+type LoggingMiddleware struct {
+ Next http.Handler
+}
+
+func (l *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ log.Infof("HTTP %s %v", r.Method, r.URL)
+ l.Next.ServeHTTP(w, r)
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/http/url.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/http/url.go
new file mode 100644
index 0000000..df60eb1
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/http/url.go
@@ -0,0 +1,29 @@
+package http
+
+import (
+ "errors"
+ "net/url"
+)
+
+// ParseNonEmptyURL checks that a string is a parsable URL which is also not empty
+// since `url.Parse("")` does not return an error. Must contian a scheme and a host.
+func ParseNonEmptyURL(u string) (*url.URL, error) {
+ if u == "" {
+ return nil, errors.New("url is empty")
+ }
+
+ ur, err := url.Parse(u)
+ if err != nil {
+ return nil, err
+ }
+
+ if ur.Scheme == "" {
+ return nil, errors.New("url scheme is empty")
+ }
+
+ if ur.Host == "" {
+ return nil, errors.New("url host is empty")
+ }
+
+ return ur, nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/claims.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/claims.go
new file mode 100644
index 0000000..8b48bfd
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/claims.go
@@ -0,0 +1,126 @@
+package jose
+
+import (
+ "encoding/json"
+ "fmt"
+ "math"
+ "time"
+)
+
+type Claims map[string]interface{}
+
+func (c Claims) Add(name string, value interface{}) {
+ c[name] = value
+}
+
+func (c Claims) StringClaim(name string) (string, bool, error) {
+ cl, ok := c[name]
+ if !ok {
+ return "", false, nil
+ }
+
+ v, ok := cl.(string)
+ if !ok {
+ return "", false, fmt.Errorf("unable to parse claim as string: %v", name)
+ }
+
+ return v, true, nil
+}
+
+func (c Claims) StringsClaim(name string) ([]string, bool, error) {
+ cl, ok := c[name]
+ if !ok {
+ return nil, false, nil
+ }
+
+ if v, ok := cl.([]string); ok {
+ return v, true, nil
+ }
+
+ // When unmarshaled, []string will become []interface{}.
+ if v, ok := cl.([]interface{}); ok {
+ var ret []string
+ for _, vv := range v {
+ str, ok := vv.(string)
+ if !ok {
+ return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
+ }
+ ret = append(ret, str)
+ }
+ return ret, true, nil
+ }
+
+ return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
+}
+
+func (c Claims) Int64Claim(name string) (int64, bool, error) {
+ cl, ok := c[name]
+ if !ok {
+ return 0, false, nil
+ }
+
+ v, ok := cl.(int64)
+ if !ok {
+ vf, ok := cl.(float64)
+ if !ok {
+ return 0, false, fmt.Errorf("unable to parse claim as int64: %v", name)
+ }
+ v = int64(vf)
+ }
+
+ return v, true, nil
+}
+
+func (c Claims) Float64Claim(name string) (float64, bool, error) {
+ cl, ok := c[name]
+ if !ok {
+ return 0, false, nil
+ }
+
+ v, ok := cl.(float64)
+ if !ok {
+ vi, ok := cl.(int64)
+ if !ok {
+ return 0, false, fmt.Errorf("unable to parse claim as float64: %v", name)
+ }
+ v = float64(vi)
+ }
+
+ return v, true, nil
+}
+
+func (c Claims) TimeClaim(name string) (time.Time, bool, error) {
+ v, ok, err := c.Float64Claim(name)
+ if !ok || err != nil {
+ return time.Time{}, ok, err
+ }
+
+ s := math.Trunc(v)
+ ns := (v - s) * math.Pow(10, 9)
+ return time.Unix(int64(s), int64(ns)).UTC(), true, nil
+}
+
+func decodeClaims(payload []byte) (Claims, error) {
+ var c Claims
+ if err := json.Unmarshal(payload, &c); err != nil {
+ return nil, fmt.Errorf("malformed JWT claims, unable to decode: %v", err)
+ }
+ return c, nil
+}
+
+func marshalClaims(c Claims) ([]byte, error) {
+ b, err := json.Marshal(c)
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
+func encodeClaims(c Claims) (string, error) {
+ b, err := marshalClaims(c)
+ if err != nil {
+ return "", err
+ }
+
+ return encodeSegment(b), nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jose.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jose.go
new file mode 100644
index 0000000..6209926
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jose.go
@@ -0,0 +1,112 @@
+package jose
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+const (
+ HeaderMediaType = "typ"
+ HeaderKeyAlgorithm = "alg"
+ HeaderKeyID = "kid"
+)
+
+const (
+ // Encryption Algorithm Header Parameter Values for JWS
+ // See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-6
+ AlgHS256 = "HS256"
+ AlgHS384 = "HS384"
+ AlgHS512 = "HS512"
+ AlgRS256 = "RS256"
+ AlgRS384 = "RS384"
+ AlgRS512 = "RS512"
+ AlgES256 = "ES256"
+ AlgES384 = "ES384"
+ AlgES512 = "ES512"
+ AlgPS256 = "PS256"
+ AlgPS384 = "PS384"
+ AlgPS512 = "PS512"
+ AlgNone = "none"
+)
+
+const (
+ // Algorithm Header Parameter Values for JWE
+ // See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-4.1
+ AlgRSA15 = "RSA1_5"
+ AlgRSAOAEP = "RSA-OAEP"
+ AlgRSAOAEP256 = "RSA-OAEP-256"
+ AlgA128KW = "A128KW"
+ AlgA192KW = "A192KW"
+ AlgA256KW = "A256KW"
+ AlgDir = "dir"
+ AlgECDHES = "ECDH-ES"
+ AlgECDHESA128KW = "ECDH-ES+A128KW"
+ AlgECDHESA192KW = "ECDH-ES+A192KW"
+ AlgECDHESA256KW = "ECDH-ES+A256KW"
+ AlgA128GCMKW = "A128GCMKW"
+ AlgA192GCMKW = "A192GCMKW"
+ AlgA256GCMKW = "A256GCMKW"
+ AlgPBES2HS256A128KW = "PBES2-HS256+A128KW"
+ AlgPBES2HS384A192KW = "PBES2-HS384+A192KW"
+ AlgPBES2HS512A256KW = "PBES2-HS512+A256KW"
+)
+
+const (
+ // Encryption Algorithm Header Parameter Values for JWE
+ // See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-22
+ EncA128CBCHS256 = "A128CBC-HS256"
+ EncA128CBCHS384 = "A128CBC-HS384"
+ EncA256CBCHS512 = "A256CBC-HS512"
+ EncA128GCM = "A128GCM"
+ EncA192GCM = "A192GCM"
+ EncA256GCM = "A256GCM"
+)
+
+type JOSEHeader map[string]string
+
+func (j JOSEHeader) Validate() error {
+ if _, exists := j[HeaderKeyAlgorithm]; !exists {
+ return fmt.Errorf("header missing %q parameter", HeaderKeyAlgorithm)
+ }
+
+ return nil
+}
+
+func decodeHeader(seg string) (JOSEHeader, error) {
+ b, err := decodeSegment(seg)
+ if err != nil {
+ return nil, err
+ }
+
+ var h JOSEHeader
+ err = json.Unmarshal(b, &h)
+ if err != nil {
+ return nil, err
+ }
+
+ return h, nil
+}
+
+func encodeHeader(h JOSEHeader) (string, error) {
+ b, err := json.Marshal(h)
+ if err != nil {
+ return "", err
+ }
+
+ return encodeSegment(b), nil
+}
+
+// Decode JWT specific base64url encoding with padding stripped
+func decodeSegment(seg string) ([]byte, error) {
+ if l := len(seg) % 4; l != 0 {
+ seg += strings.Repeat("=", 4-l)
+ }
+ return base64.URLEncoding.DecodeString(seg)
+}
+
+// Encode JWT specific base64url encoding with padding stripped
+func encodeSegment(seg []byte) string {
+ return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jwk.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jwk.go
new file mode 100644
index 0000000..b7a8e23
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jwk.go
@@ -0,0 +1,135 @@
+package jose
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "math/big"
+ "strings"
+)
+
+// JSON Web Key
+// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-36#page-5
+type JWK struct {
+ ID string
+ Type string
+ Alg string
+ Use string
+ Exponent int
+ Modulus *big.Int
+ Secret []byte
+}
+
+type jwkJSON struct {
+ ID string `json:"kid"`
+ Type string `json:"kty"`
+ Alg string `json:"alg"`
+ Use string `json:"use"`
+ Exponent string `json:"e"`
+ Modulus string `json:"n"`
+}
+
+func (j *JWK) MarshalJSON() ([]byte, error) {
+ t := jwkJSON{
+ ID: j.ID,
+ Type: j.Type,
+ Alg: j.Alg,
+ Use: j.Use,
+ Exponent: encodeExponent(j.Exponent),
+ Modulus: encodeModulus(j.Modulus),
+ }
+
+ return json.Marshal(&t)
+}
+
+func (j *JWK) UnmarshalJSON(data []byte) error {
+ var t jwkJSON
+ err := json.Unmarshal(data, &t)
+ if err != nil {
+ return err
+ }
+
+ e, err := decodeExponent(t.Exponent)
+ if err != nil {
+ return err
+ }
+
+ n, err := decodeModulus(t.Modulus)
+ if err != nil {
+ return err
+ }
+
+ j.ID = t.ID
+ j.Type = t.Type
+ j.Alg = t.Alg
+ j.Use = t.Use
+ j.Exponent = e
+ j.Modulus = n
+
+ return nil
+}
+
+type JWKSet struct {
+ Keys []JWK `json:"keys"`
+}
+
+func decodeExponent(e string) (int, error) {
+ decE, err := decodeBase64URLPaddingOptional(e)
+ if err != nil {
+ return 0, err
+ }
+ var eBytes []byte
+ if len(decE) < 8 {
+ eBytes = make([]byte, 8-len(decE), 8)
+ eBytes = append(eBytes, decE...)
+ } else {
+ eBytes = decE
+ }
+ eReader := bytes.NewReader(eBytes)
+ var E uint64
+ err = binary.Read(eReader, binary.BigEndian, &E)
+ if err != nil {
+ return 0, err
+ }
+ return int(E), nil
+}
+
+func encodeExponent(e int) string {
+ b := make([]byte, 8)
+ binary.BigEndian.PutUint64(b, uint64(e))
+ var idx int
+ for ; idx < 8; idx++ {
+ if b[idx] != 0x0 {
+ break
+ }
+ }
+ return base64.URLEncoding.EncodeToString(b[idx:])
+}
+
+// Turns a URL encoded modulus of a key into a big int.
+func decodeModulus(n string) (*big.Int, error) {
+ decN, err := decodeBase64URLPaddingOptional(n)
+ if err != nil {
+ return nil, err
+ }
+ N := big.NewInt(0)
+ N.SetBytes(decN)
+ return N, nil
+}
+
+func encodeModulus(n *big.Int) string {
+ return base64.URLEncoding.EncodeToString(n.Bytes())
+}
+
+// decodeBase64URLPaddingOptional decodes Base64 whether there is padding or not.
+// The stdlib version currently doesn't handle this.
+// We can get rid of this is if this bug:
+// https://github.com/golang/go/issues/4237
+// ever closes.
+func decodeBase64URLPaddingOptional(e string) ([]byte, error) {
+ if m := len(e) % 4; m != 0 {
+ e += strings.Repeat("=", 4-m)
+ }
+ return base64.URLEncoding.DecodeString(e)
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jws.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jws.go
new file mode 100644
index 0000000..1049ece
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jws.go
@@ -0,0 +1,51 @@
+package jose
+
+import (
+ "fmt"
+ "strings"
+)
+
+type JWS struct {
+ RawHeader string
+ Header JOSEHeader
+ RawPayload string
+ Payload []byte
+ Signature []byte
+}
+
+// Given a raw encoded JWS token parses it and verifies the structure.
+func ParseJWS(raw string) (JWS, error) {
+ parts := strings.Split(raw, ".")
+ if len(parts) != 3 {
+ return JWS{}, fmt.Errorf("malformed JWS, only %d segments", len(parts))
+ }
+
+ rawSig := parts[2]
+ jws := JWS{
+ RawHeader: parts[0],
+ RawPayload: parts[1],
+ }
+
+ header, err := decodeHeader(jws.RawHeader)
+ if err != nil {
+ return JWS{}, fmt.Errorf("malformed JWS, unable to decode header, %s", err)
+ }
+ if err = header.Validate(); err != nil {
+ return JWS{}, fmt.Errorf("malformed JWS, %s", err)
+ }
+ jws.Header = header
+
+ payload, err := decodeSegment(jws.RawPayload)
+ if err != nil {
+ return JWS{}, fmt.Errorf("malformed JWS, unable to decode payload: %s", err)
+ }
+ jws.Payload = payload
+
+ sig, err := decodeSegment(rawSig)
+ if err != nil {
+ return JWS{}, fmt.Errorf("malformed JWS, unable to decode signature: %s", err)
+ }
+ jws.Signature = sig
+
+ return jws, nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jwt.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jwt.go
new file mode 100644
index 0000000..3b3e963
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/jwt.go
@@ -0,0 +1,82 @@
+package jose
+
+import "strings"
+
+type JWT JWS
+
+func ParseJWT(token string) (jwt JWT, err error) {
+ jws, err := ParseJWS(token)
+ if err != nil {
+ return
+ }
+
+ return JWT(jws), nil
+}
+
+func NewJWT(header JOSEHeader, claims Claims) (jwt JWT, err error) {
+ jwt = JWT{}
+
+ jwt.Header = header
+ jwt.Header[HeaderMediaType] = "JWT"
+
+ claimBytes, err := marshalClaims(claims)
+ if err != nil {
+ return
+ }
+ jwt.Payload = claimBytes
+
+ eh, err := encodeHeader(header)
+ if err != nil {
+ return
+ }
+ jwt.RawHeader = eh
+
+ ec, err := encodeClaims(claims)
+ if err != nil {
+ return
+ }
+ jwt.RawPayload = ec
+
+ return
+}
+
+func (j *JWT) KeyID() (string, bool) {
+ kID, ok := j.Header[HeaderKeyID]
+ return kID, ok
+}
+
+func (j *JWT) Claims() (Claims, error) {
+ return decodeClaims(j.Payload)
+}
+
+// Encoded data part of the token which may be signed.
+func (j *JWT) Data() string {
+ return strings.Join([]string{j.RawHeader, j.RawPayload}, ".")
+}
+
+// Full encoded JWT token string in format: header.claims.signature
+func (j *JWT) Encode() string {
+ d := j.Data()
+ s := encodeSegment(j.Signature)
+ return strings.Join([]string{d, s}, ".")
+}
+
+func NewSignedJWT(claims Claims, s Signer) (*JWT, error) {
+ header := JOSEHeader{
+ HeaderKeyAlgorithm: s.Alg(),
+ HeaderKeyID: s.ID(),
+ }
+
+ jwt, err := NewJWT(header, claims)
+ if err != nil {
+ return nil, err
+ }
+
+ sig, err := s.Sign([]byte(jwt.Data()))
+ if err != nil {
+ return nil, err
+ }
+ jwt.Signature = sig
+
+ return &jwt, nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig.go
new file mode 100644
index 0000000..7b2b253
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig.go
@@ -0,0 +1,24 @@
+package jose
+
+import (
+ "fmt"
+)
+
+type Verifier interface {
+ ID() string
+ Alg() string
+ Verify(sig []byte, data []byte) error
+}
+
+type Signer interface {
+ Verifier
+ Sign(data []byte) (sig []byte, err error)
+}
+
+func NewVerifier(jwk JWK) (Verifier, error) {
+ if jwk.Type != "RSA" {
+ return nil, fmt.Errorf("unsupported key type %q", jwk.Type)
+ }
+
+ return NewVerifierRSA(jwk)
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig_hmac.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig_hmac.go
new file mode 100644
index 0000000..b3ca3ef
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig_hmac.go
@@ -0,0 +1,67 @@
+package jose
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/hmac"
+ _ "crypto/sha256"
+ "errors"
+ "fmt"
+)
+
+type VerifierHMAC struct {
+ KeyID string
+ Hash crypto.Hash
+ Secret []byte
+}
+
+type SignerHMAC struct {
+ VerifierHMAC
+}
+
+func NewVerifierHMAC(jwk JWK) (*VerifierHMAC, error) {
+ if jwk.Alg != "" && jwk.Alg != "HS256" {
+ return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
+ }
+
+ v := VerifierHMAC{
+ KeyID: jwk.ID,
+ Secret: jwk.Secret,
+ Hash: crypto.SHA256,
+ }
+
+ return &v, nil
+}
+
+func (v *VerifierHMAC) ID() string {
+ return v.KeyID
+}
+
+func (v *VerifierHMAC) Alg() string {
+ return "HS256"
+}
+
+func (v *VerifierHMAC) Verify(sig []byte, data []byte) error {
+ h := hmac.New(v.Hash.New, v.Secret)
+ h.Write(data)
+ if !bytes.Equal(sig, h.Sum(nil)) {
+ return errors.New("invalid hmac signature")
+ }
+ return nil
+}
+
+func NewSignerHMAC(kid string, secret []byte) *SignerHMAC {
+ return &SignerHMAC{
+ VerifierHMAC: VerifierHMAC{
+ KeyID: kid,
+ Secret: secret,
+ Hash: crypto.SHA256,
+ },
+ }
+}
+
+func (s *SignerHMAC) Sign(data []byte) ([]byte, error) {
+ h := hmac.New(s.Hash.New, s.Secret)
+ h.Write(data)
+ return h.Sum(nil), nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig_rsa.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig_rsa.go
new file mode 100644
index 0000000..004e45d
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/jose/sig_rsa.go
@@ -0,0 +1,67 @@
+package jose
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "fmt"
+)
+
+type VerifierRSA struct {
+ KeyID string
+ Hash crypto.Hash
+ PublicKey rsa.PublicKey
+}
+
+type SignerRSA struct {
+ PrivateKey rsa.PrivateKey
+ VerifierRSA
+}
+
+func NewVerifierRSA(jwk JWK) (*VerifierRSA, error) {
+ if jwk.Alg != "" && jwk.Alg != "RS256" {
+ return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
+ }
+
+ v := VerifierRSA{
+ KeyID: jwk.ID,
+ PublicKey: rsa.PublicKey{
+ N: jwk.Modulus,
+ E: jwk.Exponent,
+ },
+ Hash: crypto.SHA256,
+ }
+
+ return &v, nil
+}
+
+func NewSignerRSA(kid string, key rsa.PrivateKey) *SignerRSA {
+ return &SignerRSA{
+ PrivateKey: key,
+ VerifierRSA: VerifierRSA{
+ KeyID: kid,
+ PublicKey: key.PublicKey,
+ Hash: crypto.SHA256,
+ },
+ }
+}
+
+func (v *VerifierRSA) ID() string {
+ return v.KeyID
+}
+
+func (v *VerifierRSA) Alg() string {
+ return "RS256"
+}
+
+func (v *VerifierRSA) Verify(sig []byte, data []byte) error {
+ h := v.Hash.New()
+ h.Write(data)
+ return rsa.VerifyPKCS1v15(&v.PublicKey, v.Hash, h.Sum(nil), sig)
+}
+
+func (s *SignerRSA) Sign(data []byte) ([]byte, error) {
+ h := s.Hash.New()
+ h.Write(data)
+ return rsa.SignPKCS1v15(rand.Reader, &s.PrivateKey, s.Hash, h.Sum(nil))
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/key/key.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/key.go
new file mode 100644
index 0000000..d0142a9
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/key.go
@@ -0,0 +1,153 @@
+package key
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "encoding/base64"
+ "encoding/json"
+ "math/big"
+ "time"
+
+ "github.com/coreos/go-oidc/jose"
+)
+
+func NewPublicKey(jwk jose.JWK) *PublicKey {
+ return &PublicKey{jwk: jwk}
+}
+
+type PublicKey struct {
+ jwk jose.JWK
+}
+
+func (k *PublicKey) MarshalJSON() ([]byte, error) {
+ return json.Marshal(&k.jwk)
+}
+
+func (k *PublicKey) UnmarshalJSON(data []byte) error {
+ var jwk jose.JWK
+ if err := json.Unmarshal(data, &jwk); err != nil {
+ return err
+ }
+ k.jwk = jwk
+ return nil
+}
+
+func (k *PublicKey) ID() string {
+ return k.jwk.ID
+}
+
+func (k *PublicKey) Verifier() (jose.Verifier, error) {
+ return jose.NewVerifierRSA(k.jwk)
+}
+
+type PrivateKey struct {
+ KeyID string
+ PrivateKey *rsa.PrivateKey
+}
+
+func (k *PrivateKey) ID() string {
+ return k.KeyID
+}
+
+func (k *PrivateKey) Signer() jose.Signer {
+ return jose.NewSignerRSA(k.ID(), *k.PrivateKey)
+}
+
+func (k *PrivateKey) JWK() jose.JWK {
+ return jose.JWK{
+ ID: k.KeyID,
+ Type: "RSA",
+ Alg: "RS256",
+ Use: "sig",
+ Exponent: k.PrivateKey.PublicKey.E,
+ Modulus: k.PrivateKey.PublicKey.N,
+ }
+}
+
+type KeySet interface {
+ ExpiresAt() time.Time
+}
+
+type PublicKeySet struct {
+ keys []PublicKey
+ index map[string]*PublicKey
+ expiresAt time.Time
+}
+
+func NewPublicKeySet(jwks []jose.JWK, exp time.Time) *PublicKeySet {
+ keys := make([]PublicKey, len(jwks))
+ index := make(map[string]*PublicKey)
+ for i, jwk := range jwks {
+ keys[i] = *NewPublicKey(jwk)
+ index[keys[i].ID()] = &keys[i]
+ }
+ return &PublicKeySet{
+ keys: keys,
+ index: index,
+ expiresAt: exp,
+ }
+}
+
+func (s *PublicKeySet) ExpiresAt() time.Time {
+ return s.expiresAt
+}
+
+func (s *PublicKeySet) Keys() []PublicKey {
+ return s.keys
+}
+
+func (s *PublicKeySet) Key(id string) *PublicKey {
+ return s.index[id]
+}
+
+type PrivateKeySet struct {
+ keys []*PrivateKey
+ ActiveKeyID string
+ expiresAt time.Time
+}
+
+func NewPrivateKeySet(keys []*PrivateKey, exp time.Time) *PrivateKeySet {
+ return &PrivateKeySet{
+ keys: keys,
+ ActiveKeyID: keys[0].ID(),
+ expiresAt: exp.UTC(),
+ }
+}
+
+func (s *PrivateKeySet) Keys() []*PrivateKey {
+ return s.keys
+}
+
+func (s *PrivateKeySet) ExpiresAt() time.Time {
+ return s.expiresAt
+}
+
+func (s *PrivateKeySet) Active() *PrivateKey {
+ for i, k := range s.keys {
+ if k.ID() == s.ActiveKeyID {
+ return s.keys[i]
+ }
+ }
+
+ return nil
+}
+
+type GeneratePrivateKeyFunc func() (*PrivateKey, error)
+
+func GeneratePrivateKey() (*PrivateKey, error) {
+ pk, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return nil, err
+ }
+
+ k := PrivateKey{
+ KeyID: base64BigInt(pk.PublicKey.N),
+ PrivateKey: pk,
+ }
+
+ return &k, nil
+}
+
+func base64BigInt(b *big.Int) string {
+ return base64.URLEncoding.EncodeToString(b.Bytes())
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/key/manager.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/manager.go
new file mode 100644
index 0000000..476ab6a
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/manager.go
@@ -0,0 +1,99 @@
+package key
+
+import (
+ "errors"
+ "time"
+
+ "github.com/jonboulle/clockwork"
+
+ "github.com/coreos/go-oidc/jose"
+ "github.com/coreos/pkg/health"
+)
+
+type PrivateKeyManager interface {
+ ExpiresAt() time.Time
+ Signer() (jose.Signer, error)
+ JWKs() ([]jose.JWK, error)
+ PublicKeys() ([]PublicKey, error)
+
+ WritableKeySetRepo
+ health.Checkable
+}
+
+func NewPrivateKeyManager() PrivateKeyManager {
+ return &privateKeyManager{
+ clock: clockwork.NewRealClock(),
+ }
+}
+
+type privateKeyManager struct {
+ keySet *PrivateKeySet
+ clock clockwork.Clock
+}
+
+func (m *privateKeyManager) ExpiresAt() time.Time {
+ if m.keySet == nil {
+ return m.clock.Now().UTC()
+ }
+
+ return m.keySet.ExpiresAt()
+}
+
+func (m *privateKeyManager) Signer() (jose.Signer, error) {
+ if err := m.Healthy(); err != nil {
+ return nil, err
+ }
+
+ return m.keySet.Active().Signer(), nil
+}
+
+func (m *privateKeyManager) JWKs() ([]jose.JWK, error) {
+ if err := m.Healthy(); err != nil {
+ return nil, err
+ }
+
+ keys := m.keySet.Keys()
+ jwks := make([]jose.JWK, len(keys))
+ for i, k := range keys {
+ jwks[i] = k.JWK()
+ }
+ return jwks, nil
+}
+
+func (m *privateKeyManager) PublicKeys() ([]PublicKey, error) {
+ jwks, err := m.JWKs()
+ if err != nil {
+ return nil, err
+ }
+ keys := make([]PublicKey, len(jwks))
+ for i, jwk := range jwks {
+ keys[i] = *NewPublicKey(jwk)
+ }
+ return keys, nil
+}
+
+func (m *privateKeyManager) Healthy() error {
+ if m.keySet == nil {
+ return errors.New("private key manager uninitialized")
+ }
+
+ if len(m.keySet.Keys()) == 0 {
+ return errors.New("private key manager zero keys")
+ }
+
+ if m.keySet.ExpiresAt().Before(m.clock.Now().UTC()) {
+ return errors.New("private key manager keys expired")
+ }
+
+ return nil
+}
+
+func (m *privateKeyManager) Set(keySet KeySet) error {
+ privKeySet, ok := keySet.(*PrivateKeySet)
+ if !ok {
+ return errors.New("unable to cast to PrivateKeySet")
+ }
+
+ m.keySet = privKeySet
+ return nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/key/repo.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/repo.go
new file mode 100644
index 0000000..1acdeb3
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/repo.go
@@ -0,0 +1,55 @@
+package key
+
+import (
+ "errors"
+ "sync"
+)
+
+var ErrorNoKeys = errors.New("no keys found")
+
+type WritableKeySetRepo interface {
+ Set(KeySet) error
+}
+
+type ReadableKeySetRepo interface {
+ Get() (KeySet, error)
+}
+
+type PrivateKeySetRepo interface {
+ WritableKeySetRepo
+ ReadableKeySetRepo
+}
+
+func NewPrivateKeySetRepo() PrivateKeySetRepo {
+ return &memPrivateKeySetRepo{}
+}
+
+type memPrivateKeySetRepo struct {
+ mu sync.RWMutex
+ pks PrivateKeySet
+}
+
+func (r *memPrivateKeySetRepo) Set(ks KeySet) error {
+ pks, ok := ks.(*PrivateKeySet)
+ if !ok {
+ return errors.New("unable to cast to PrivateKeySet")
+ } else if pks == nil {
+ return errors.New("nil KeySet")
+ }
+
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ r.pks = *pks
+ return nil
+}
+
+func (r *memPrivateKeySetRepo) Get() (KeySet, error) {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+
+ if r.pks.keys == nil {
+ return nil, ErrorNoKeys
+ }
+ return KeySet(&r.pks), nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/key/rotate.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/rotate.go
new file mode 100644
index 0000000..9c5508b
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/rotate.go
@@ -0,0 +1,165 @@
+package key
+
+import (
+ "errors"
+ "time"
+
+ "github.com/coreos/pkg/capnslog"
+ ptime "github.com/coreos/pkg/timeutil"
+ "github.com/jonboulle/clockwork"
+)
+
+var (
+ log = capnslog.NewPackageLogger("github.com/coreos/go-oidc", "key")
+
+ ErrorPrivateKeysExpired = errors.New("private keys have expired")
+)
+
+func NewPrivateKeyRotator(repo PrivateKeySetRepo, ttl time.Duration) *PrivateKeyRotator {
+ return &PrivateKeyRotator{
+ repo: repo,
+ ttl: ttl,
+
+ keep: 2,
+ generateKey: GeneratePrivateKey,
+ clock: clockwork.NewRealClock(),
+ }
+}
+
+type PrivateKeyRotator struct {
+ repo PrivateKeySetRepo
+ generateKey GeneratePrivateKeyFunc
+ clock clockwork.Clock
+ keep int
+ ttl time.Duration
+}
+
+func (r *PrivateKeyRotator) expiresAt() time.Time {
+ return r.clock.Now().UTC().Add(r.ttl)
+}
+
+func (r *PrivateKeyRotator) Healthy() error {
+ pks, err := r.privateKeySet()
+ if err != nil {
+ return err
+ }
+
+ if r.clock.Now().After(pks.ExpiresAt()) {
+ return ErrorPrivateKeysExpired
+ }
+
+ return nil
+}
+
+func (r *PrivateKeyRotator) privateKeySet() (*PrivateKeySet, error) {
+ ks, err := r.repo.Get()
+ if err != nil {
+ return nil, err
+ }
+
+ pks, ok := ks.(*PrivateKeySet)
+ if !ok {
+ return nil, errors.New("unable to cast to PrivateKeySet")
+ }
+ return pks, nil
+}
+
+func (r *PrivateKeyRotator) nextRotation() (time.Duration, error) {
+ pks, err := r.privateKeySet()
+ if err == ErrorNoKeys {
+ log.Infof("No keys in private key set; must rotate immediately")
+ return 0, nil
+ }
+ if err != nil {
+ return 0, err
+ }
+
+ now := r.clock.Now()
+
+ // Ideally, we want to rotate after half the TTL has elapsed.
+ idealRotationTime := pks.ExpiresAt().Add(-r.ttl / 2)
+
+ // If we are past the ideal rotation time, rotate immediatly.
+ return max(0, idealRotationTime.Sub(now)), nil
+}
+
+func max(a, b time.Duration) time.Duration {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func (r *PrivateKeyRotator) Run() chan struct{} {
+ attempt := func() {
+ k, err := r.generateKey()
+ if err != nil {
+ log.Errorf("Failed generating signing key: %v", err)
+ return
+ }
+
+ exp := r.expiresAt()
+ if err := rotatePrivateKeys(r.repo, k, r.keep, exp); err != nil {
+ log.Errorf("Failed key rotation: %v", err)
+ return
+ }
+
+ log.Infof("Rotated signing keys: id=%s expiresAt=%s", k.ID(), exp)
+ }
+
+ stop := make(chan struct{})
+ go func() {
+ for {
+ var nextRotation time.Duration
+ var sleep time.Duration
+ var err error
+ for {
+ if nextRotation, err = r.nextRotation(); err == nil {
+ break
+ }
+ sleep = ptime.ExpBackoff(sleep, time.Minute)
+ log.Errorf("error getting nextRotation, retrying in %v: %v", sleep, err)
+ time.Sleep(sleep)
+ }
+
+ log.Infof("will rotate keys in %v", nextRotation)
+ select {
+ case <-r.clock.After(nextRotation):
+ attempt()
+ case <-stop:
+ return
+ }
+ }
+ }()
+
+ return stop
+}
+
+func rotatePrivateKeys(repo PrivateKeySetRepo, k *PrivateKey, keep int, exp time.Time) error {
+ ks, err := repo.Get()
+ if err != nil && err != ErrorNoKeys {
+ return err
+ }
+
+ var keys []*PrivateKey
+ if ks != nil {
+ pks, ok := ks.(*PrivateKeySet)
+ if !ok {
+ return errors.New("unable to cast to PrivateKeySet")
+ }
+ keys = pks.Keys()
+ }
+
+ keys = append([]*PrivateKey{k}, keys...)
+ if l := len(keys); l > keep {
+ keys = keys[0:keep]
+ }
+
+ nks := PrivateKeySet{
+ keys: keys,
+ ActiveKeyID: k.ID(),
+ expiresAt: exp,
+ }
+
+ return repo.Set(KeySet(&nks))
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/key/sync.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/sync.go
new file mode 100644
index 0000000..e8d5d03
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/key/sync.go
@@ -0,0 +1,91 @@
+package key
+
+import (
+ "errors"
+ "time"
+
+ "github.com/jonboulle/clockwork"
+
+ "github.com/coreos/pkg/timeutil"
+)
+
+func NewKeySetSyncer(r ReadableKeySetRepo, w WritableKeySetRepo) *KeySetSyncer {
+ return &KeySetSyncer{
+ readable: r,
+ writable: w,
+ clock: clockwork.NewRealClock(),
+ }
+}
+
+type KeySetSyncer struct {
+ readable ReadableKeySetRepo
+ writable WritableKeySetRepo
+ clock clockwork.Clock
+}
+
+func (s *KeySetSyncer) Run() chan struct{} {
+ stop := make(chan struct{})
+ go func() {
+ var failing bool
+ var next time.Duration
+ for {
+ exp, err := syncKeySet(s.readable, s.writable, s.clock)
+ if err != nil || exp == 0 {
+ if !failing {
+ failing = true
+ next = time.Second
+ } else {
+ next = timeutil.ExpBackoff(next, time.Minute)
+ }
+ if exp == 0 {
+ log.Errorf("Synced to already expired key set, retrying in %v: %v", next, err)
+
+ } else {
+ log.Errorf("Failed syncing key set, retrying in %v: %v", next, err)
+ }
+ } else {
+ failing = false
+ next = exp / 2
+ log.Infof("Synced key set, checking again in %v", next)
+ }
+
+ select {
+ case <-s.clock.After(next):
+ continue
+ case <-stop:
+ return
+ }
+ }
+ }()
+
+ return stop
+}
+
+func Sync(r ReadableKeySetRepo, w WritableKeySetRepo) (time.Duration, error) {
+ return syncKeySet(r, w, clockwork.NewRealClock())
+}
+
+// syncKeySet copies the keyset from r to the KeySet at w and returns the duration in which the KeySet will expire.
+// If keyset has already expired, returns a zero duration.
+func syncKeySet(r ReadableKeySetRepo, w WritableKeySetRepo, clock clockwork.Clock) (exp time.Duration, err error) {
+ var ks KeySet
+ ks, err = r.Get()
+ if err != nil {
+ return
+ }
+
+ if ks == nil {
+ err = errors.New("no source KeySet")
+ return
+ }
+
+ if err = w.Set(ks); err != nil {
+ return
+ }
+
+ now := clock.Now()
+ if ks.ExpiresAt().After(now) {
+ exp = ks.ExpiresAt().Sub(now)
+ }
+ return
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oauth2/error.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oauth2/error.go
new file mode 100644
index 0000000..50d8909
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oauth2/error.go
@@ -0,0 +1,29 @@
+package oauth2
+
+const (
+ ErrorAccessDenied = "access_denied"
+ ErrorInvalidClient = "invalid_client"
+ ErrorInvalidGrant = "invalid_grant"
+ ErrorInvalidRequest = "invalid_request"
+ ErrorServerError = "server_error"
+ ErrorUnauthorizedClient = "unauthorized_client"
+ ErrorUnsupportedGrantType = "unsupported_grant_type"
+ ErrorUnsupportedResponseType = "unsupported_response_type"
+)
+
+type Error struct {
+ Type string `json:"error"`
+ Description string `json:"error_description,omitempty"`
+ State string `json:"state,omitempty"`
+}
+
+func (e *Error) Error() string {
+ if e.Description != "" {
+ return e.Type + ": " + e.Description
+ }
+ return e.Type
+}
+
+func NewError(typ string) *Error {
+ return &Error{Type: typ}
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oauth2/oauth2.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oauth2/oauth2.go
new file mode 100644
index 0000000..1c68293
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oauth2/oauth2.go
@@ -0,0 +1,416 @@
+package oauth2
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "mime"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+
+ phttp "github.com/coreos/go-oidc/http"
+)
+
+// ResponseTypesEqual compares two response_type values. If either
+// contains a space, it is treated as an unordered list. For example,
+// comparing "code id_token" and "id_token code" would evaluate to true.
+func ResponseTypesEqual(r1, r2 string) bool {
+ if !strings.Contains(r1, " ") || !strings.Contains(r2, " ") {
+ // fast route, no split needed
+ return r1 == r2
+ }
+
+ // split, sort, and compare
+ r1Fields := strings.Fields(r1)
+ r2Fields := strings.Fields(r2)
+ if len(r1Fields) != len(r2Fields) {
+ return false
+ }
+ sort.Strings(r1Fields)
+ sort.Strings(r2Fields)
+ for i, r1Field := range r1Fields {
+ if r1Field != r2Fields[i] {
+ return false
+ }
+ }
+ return true
+}
+
+const (
+ // OAuth2.0 response types registered by OIDC.
+ //
+ // See: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#RegistryContents
+ ResponseTypeCode = "code"
+ ResponseTypeCodeIDToken = "code id_token"
+ ResponseTypeCodeIDTokenToken = "code id_token token"
+ ResponseTypeIDToken = "id_token"
+ ResponseTypeIDTokenToken = "id_token token"
+ ResponseTypeToken = "token"
+ ResponseTypeNone = "none"
+)
+
+const (
+ GrantTypeAuthCode = "authorization_code"
+ GrantTypeClientCreds = "client_credentials"
+ GrantTypeUserCreds = "password"
+ GrantTypeImplicit = "implicit"
+ GrantTypeRefreshToken = "refresh_token"
+
+ AuthMethodClientSecretPost = "client_secret_post"
+ AuthMethodClientSecretBasic = "client_secret_basic"
+ AuthMethodClientSecretJWT = "client_secret_jwt"
+ AuthMethodPrivateKeyJWT = "private_key_jwt"
+)
+
+type Config struct {
+ Credentials ClientCredentials
+ Scope []string
+ RedirectURL string
+ AuthURL string
+ TokenURL string
+
+ // Must be one of the AuthMethodXXX methods above. Right now, only
+ // AuthMethodClientSecretPost and AuthMethodClientSecretBasic are supported.
+ AuthMethod string
+}
+
+type Client struct {
+ hc phttp.Client
+ creds ClientCredentials
+ scope []string
+ authURL *url.URL
+ redirectURL *url.URL
+ tokenURL *url.URL
+ authMethod string
+}
+
+type ClientCredentials struct {
+ ID string
+ Secret string
+}
+
+func NewClient(hc phttp.Client, cfg Config) (c *Client, err error) {
+ if len(cfg.Credentials.ID) == 0 {
+ err = errors.New("missing client id")
+ return
+ }
+
+ if len(cfg.Credentials.Secret) == 0 {
+ err = errors.New("missing client secret")
+ return
+ }
+
+ if cfg.AuthMethod == "" {
+ cfg.AuthMethod = AuthMethodClientSecretBasic
+ } else if cfg.AuthMethod != AuthMethodClientSecretPost && cfg.AuthMethod != AuthMethodClientSecretBasic {
+ err = fmt.Errorf("auth method %q is not supported", cfg.AuthMethod)
+ return
+ }
+
+ au, err := phttp.ParseNonEmptyURL(cfg.AuthURL)
+ if err != nil {
+ return
+ }
+
+ tu, err := phttp.ParseNonEmptyURL(cfg.TokenURL)
+ if err != nil {
+ return
+ }
+
+ // Allow empty redirect URL in the case where the client
+ // only needs to verify a given token.
+ ru, err := url.Parse(cfg.RedirectURL)
+ if err != nil {
+ return
+ }
+
+ c = &Client{
+ creds: cfg.Credentials,
+ scope: cfg.Scope,
+ redirectURL: ru,
+ authURL: au,
+ tokenURL: tu,
+ hc: hc,
+ authMethod: cfg.AuthMethod,
+ }
+
+ return
+}
+
+// Return the embedded HTTP client
+func (c *Client) HttpClient() phttp.Client {
+ return c.hc
+}
+
+// Generate the url for initial redirect to oauth provider.
+func (c *Client) AuthCodeURL(state, accessType, prompt string) string {
+ v := c.commonURLValues()
+ v.Set("state", state)
+ if strings.ToLower(accessType) == "offline" {
+ v.Set("access_type", "offline")
+ }
+
+ if prompt != "" {
+ v.Set("prompt", prompt)
+ }
+ v.Set("response_type", "code")
+
+ q := v.Encode()
+ u := *c.authURL
+ if u.RawQuery == "" {
+ u.RawQuery = q
+ } else {
+ u.RawQuery += "&" + q
+ }
+ return u.String()
+}
+
+func (c *Client) commonURLValues() url.Values {
+ return url.Values{
+ "redirect_uri": {c.redirectURL.String()},
+ "scope": {strings.Join(c.scope, " ")},
+ "client_id": {c.creds.ID},
+ }
+}
+
+func (c *Client) newAuthenticatedRequest(urlToken string, values url.Values) (*http.Request, error) {
+ var req *http.Request
+ var err error
+ switch c.authMethod {
+ case AuthMethodClientSecretPost:
+ values.Set("client_secret", c.creds.Secret)
+ req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
+ if err != nil {
+ return nil, err
+ }
+ case AuthMethodClientSecretBasic:
+ req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
+ if err != nil {
+ return nil, err
+ }
+ encodedID := url.QueryEscape(c.creds.ID)
+ encodedSecret := url.QueryEscape(c.creds.Secret)
+ req.SetBasicAuth(encodedID, encodedSecret)
+ default:
+ panic("misconfigured client: auth method not supported")
+ }
+
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ return req, nil
+
+}
+
+// ClientCredsToken posts the client id and secret to obtain a token scoped to the OAuth2 client via the "client_credentials" grant type.
+// May not be supported by all OAuth2 servers.
+func (c *Client) ClientCredsToken(scope []string) (result TokenResponse, err error) {
+ v := url.Values{
+ "scope": {strings.Join(scope, " ")},
+ "grant_type": {GrantTypeClientCreds},
+ }
+
+ req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
+ if err != nil {
+ return
+ }
+
+ resp, err := c.hc.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ return parseTokenResponse(resp)
+}
+
+// UserCredsToken posts the username and password to obtain a token scoped to the OAuth2 client via the "password" grant_type
+// May not be supported by all OAuth2 servers.
+func (c *Client) UserCredsToken(username, password string) (result TokenResponse, err error) {
+ v := url.Values{
+ "scope": {strings.Join(c.scope, " ")},
+ "grant_type": {GrantTypeUserCreds},
+ "username": {username},
+ "password": {password},
+ }
+
+ req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
+ if err != nil {
+ return
+ }
+
+ resp, err := c.hc.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ return parseTokenResponse(resp)
+}
+
+// RequestToken requests a token from the Token Endpoint with the specified grantType.
+// If 'grantType' == GrantTypeAuthCode, then 'value' should be the authorization code.
+// If 'grantType' == GrantTypeRefreshToken, then 'value' should be the refresh token.
+func (c *Client) RequestToken(grantType, value string) (result TokenResponse, err error) {
+ v := c.commonURLValues()
+
+ v.Set("grant_type", grantType)
+ v.Set("client_secret", c.creds.Secret)
+ switch grantType {
+ case GrantTypeAuthCode:
+ v.Set("code", value)
+ case GrantTypeRefreshToken:
+ v.Set("refresh_token", value)
+ default:
+ err = fmt.Errorf("unsupported grant_type: %v", grantType)
+ return
+ }
+
+ req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
+ if err != nil {
+ return
+ }
+
+ resp, err := c.hc.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ return parseTokenResponse(resp)
+}
+
+func parseTokenResponse(resp *http.Response) (result TokenResponse, err error) {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ badStatusCode := resp.StatusCode < 200 || resp.StatusCode > 299
+
+ contentType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+ if err != nil {
+ return
+ }
+
+ result = TokenResponse{
+ RawBody: body,
+ }
+
+ newError := func(typ, desc, state string) error {
+ if typ == "" {
+ return fmt.Errorf("unrecognized error %s", body)
+ }
+ return &Error{typ, desc, state}
+ }
+
+ if contentType == "application/x-www-form-urlencoded" || contentType == "text/plain" {
+ var vals url.Values
+ vals, err = url.ParseQuery(string(body))
+ if err != nil {
+ return
+ }
+ if error := vals.Get("error"); error != "" || badStatusCode {
+ err = newError(error, vals.Get("error_description"), vals.Get("state"))
+ return
+ }
+ e := vals.Get("expires_in")
+ if e == "" {
+ e = vals.Get("expires")
+ }
+ if e != "" {
+ result.Expires, err = strconv.Atoi(e)
+ if err != nil {
+ return
+ }
+ }
+ result.AccessToken = vals.Get("access_token")
+ result.TokenType = vals.Get("token_type")
+ result.IDToken = vals.Get("id_token")
+ result.RefreshToken = vals.Get("refresh_token")
+ result.Scope = vals.Get("scope")
+ } else {
+ var r struct {
+ AccessToken string `json:"access_token"`
+ TokenType string `json:"token_type"`
+ IDToken string `json:"id_token"`
+ RefreshToken string `json:"refresh_token"`
+ Scope string `json:"scope"`
+ State string `json:"state"`
+ ExpiresIn int `json:"expires_in"`
+ Expires int `json:"expires"`
+ Error string `json:"error"`
+ Desc string `json:"error_description"`
+ }
+ if err = json.Unmarshal(body, &r); err != nil {
+ return
+ }
+ if r.Error != "" || badStatusCode {
+ err = newError(r.Error, r.Desc, r.State)
+ return
+ }
+ result.AccessToken = r.AccessToken
+ result.TokenType = r.TokenType
+ result.IDToken = r.IDToken
+ result.RefreshToken = r.RefreshToken
+ result.Scope = r.Scope
+ if r.ExpiresIn == 0 {
+ result.Expires = r.Expires
+ } else {
+ result.Expires = r.ExpiresIn
+ }
+ }
+ return
+}
+
+type TokenResponse struct {
+ AccessToken string
+ TokenType string
+ Expires int
+ IDToken string
+ RefreshToken string // OPTIONAL.
+ Scope string // OPTIONAL, if identical to the scope requested by the client, otherwise, REQUIRED.
+ RawBody []byte // In case callers need some other non-standard info from the token response
+}
+
+type AuthCodeRequest struct {
+ ResponseType string
+ ClientID string
+ RedirectURL *url.URL
+ Scope []string
+ State string
+}
+
+func ParseAuthCodeRequest(q url.Values) (AuthCodeRequest, error) {
+ acr := AuthCodeRequest{
+ ResponseType: q.Get("response_type"),
+ ClientID: q.Get("client_id"),
+ State: q.Get("state"),
+ Scope: make([]string, 0),
+ }
+
+ qs := strings.TrimSpace(q.Get("scope"))
+ if qs != "" {
+ acr.Scope = strings.Split(qs, " ")
+ }
+
+ err := func() error {
+ if acr.ClientID == "" {
+ return NewError(ErrorInvalidRequest)
+ }
+
+ redirectURL := q.Get("redirect_uri")
+ if redirectURL != "" {
+ ru, err := url.Parse(redirectURL)
+ if err != nil {
+ return NewError(ErrorInvalidRequest)
+ }
+ acr.RedirectURL = ru
+ }
+
+ return nil
+ }()
+
+ return acr, err
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go
new file mode 100644
index 0000000..7a3cb40
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go
@@ -0,0 +1,846 @@
+package oidc
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/mail"
+ "net/url"
+ "sync"
+ "time"
+
+ phttp "github.com/coreos/go-oidc/http"
+ "github.com/coreos/go-oidc/jose"
+ "github.com/coreos/go-oidc/key"
+ "github.com/coreos/go-oidc/oauth2"
+)
+
+const (
+ // amount of time that must pass after the last key sync
+ // completes before another attempt may begin
+ keySyncWindow = 5 * time.Second
+)
+
+var (
+ DefaultScope = []string{"openid", "email", "profile"}
+
+ supportedAuthMethods = map[string]struct{}{
+ oauth2.AuthMethodClientSecretBasic: struct{}{},
+ oauth2.AuthMethodClientSecretPost: struct{}{},
+ }
+)
+
+type ClientCredentials oauth2.ClientCredentials
+
+type ClientIdentity struct {
+ Credentials ClientCredentials
+ Metadata ClientMetadata
+}
+
+type JWAOptions struct {
+ // SigningAlg specifies an JWA alg for signing JWTs.
+ //
+ // Specifying this field implies different actions depending on the context. It may
+ // require objects be serialized and signed as a JWT instead of plain JSON, or
+ // require an existing JWT object use the specified alg.
+ //
+ // See: http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
+ SigningAlg string
+ // EncryptionAlg, if provided, specifies that the returned or sent object be stored
+ // (or nested) within a JWT object and encrypted with the provided JWA alg.
+ EncryptionAlg string
+ // EncryptionEnc specifies the JWA enc algorithm to use with EncryptionAlg. If
+ // EncryptionAlg is provided and EncryptionEnc is omitted, this field defaults
+ // to A128CBC-HS256.
+ //
+ // If EncryptionEnc is provided EncryptionAlg must also be specified.
+ EncryptionEnc string
+}
+
+func (opt JWAOptions) valid() error {
+ if opt.EncryptionEnc != "" && opt.EncryptionAlg == "" {
+ return errors.New("encryption encoding provided with no encryption algorithm")
+ }
+ return nil
+}
+
+func (opt JWAOptions) defaults() JWAOptions {
+ if opt.EncryptionAlg != "" && opt.EncryptionEnc == "" {
+ opt.EncryptionEnc = jose.EncA128CBCHS256
+ }
+ return opt
+}
+
+var (
+ // Ensure ClientMetadata satisfies these interfaces.
+ _ json.Marshaler = &ClientMetadata{}
+ _ json.Unmarshaler = &ClientMetadata{}
+)
+
+// ClientMetadata holds metadata that the authorization server associates
+// with a client identifier. The fields range from human-facing display
+// strings such as client name, to items that impact the security of the
+// protocol, such as the list of valid redirect URIs.
+//
+// See http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
+//
+// TODO: support language specific claim representations
+// http://openid.net/specs/openid-connect-registration-1_0.html#LanguagesAndScripts
+type ClientMetadata struct {
+ RedirectURIs []url.URL // Required
+
+ // A list of OAuth 2.0 "response_type" values that the client wishes to restrict
+ // itself to. Either "code", "token", or another registered extension.
+ //
+ // If omitted, only "code" will be used.
+ ResponseTypes []string
+ // A list of OAuth 2.0 grant types the client wishes to restrict itself to.
+ // The grant type values used by OIDC are "authorization_code", "implicit",
+ // and "refresh_token".
+ //
+ // If ommitted, only "authorization_code" will be used.
+ GrantTypes []string
+ // "native" or "web". If omitted, "web".
+ ApplicationType string
+
+ // List of email addresses.
+ Contacts []mail.Address
+ // Name of client to be presented to the end-user.
+ ClientName string
+ // URL that references a logo for the Client application.
+ LogoURI *url.URL
+ // URL of the home page of the Client.
+ ClientURI *url.URL
+ // Profile data policies and terms of use to be provided to the end user.
+ PolicyURI *url.URL
+ TermsOfServiceURI *url.URL
+
+ // URL to or the value of the client's JSON Web Key Set document.
+ JWKSURI *url.URL
+ JWKS *jose.JWKSet
+
+ // URL referencing a flie with a single JSON array of redirect URIs.
+ SectorIdentifierURI *url.URL
+
+ SubjectType string
+
+ // Options to restrict the JWS alg and enc values used for server responses and requests.
+ IDTokenResponseOptions JWAOptions
+ UserInfoResponseOptions JWAOptions
+ RequestObjectOptions JWAOptions
+
+ // Client requested authorization method and signing options for the token endpoint.
+ //
+ // Defaults to "client_secret_basic"
+ TokenEndpointAuthMethod string
+ TokenEndpointAuthSigningAlg string
+
+ // DefaultMaxAge specifies the maximum amount of time in seconds before an authorized
+ // user must reauthroize.
+ //
+ // If 0, no limitation is placed on the maximum.
+ DefaultMaxAge int64
+ // RequireAuthTime specifies if the auth_time claim in the ID token is required.
+ RequireAuthTime bool
+
+ // Default Authentication Context Class Reference values for authentication requests.
+ DefaultACRValues []string
+
+ // URI that a third party can use to initiate a login by the relaying party.
+ //
+ // See: http://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
+ InitiateLoginURI *url.URL
+ // Pre-registered request_uri values that may be cached by the server.
+ RequestURIs []url.URL
+}
+
+// Defaults returns a shallow copy of ClientMetadata with default
+// values replacing omitted fields.
+func (m ClientMetadata) Defaults() ClientMetadata {
+ if len(m.ResponseTypes) == 0 {
+ m.ResponseTypes = []string{oauth2.ResponseTypeCode}
+ }
+ if len(m.GrantTypes) == 0 {
+ m.GrantTypes = []string{oauth2.GrantTypeAuthCode}
+ }
+ if m.ApplicationType == "" {
+ m.ApplicationType = "web"
+ }
+ if m.TokenEndpointAuthMethod == "" {
+ m.TokenEndpointAuthMethod = oauth2.AuthMethodClientSecretBasic
+ }
+ m.IDTokenResponseOptions = m.IDTokenResponseOptions.defaults()
+ m.UserInfoResponseOptions = m.UserInfoResponseOptions.defaults()
+ m.RequestObjectOptions = m.RequestObjectOptions.defaults()
+ return m
+}
+
+func (m *ClientMetadata) MarshalJSON() ([]byte, error) {
+ e := m.toEncodableStruct()
+ return json.Marshal(&e)
+}
+
+func (m *ClientMetadata) UnmarshalJSON(data []byte) error {
+ var e encodableClientMetadata
+ if err := json.Unmarshal(data, &e); err != nil {
+ return err
+ }
+ meta, err := e.toStruct()
+ if err != nil {
+ return err
+ }
+ if err := meta.Valid(); err != nil {
+ return err
+ }
+ *m = meta
+ return nil
+}
+
+type encodableClientMetadata struct {
+ RedirectURIs []string `json:"redirect_uris"` // Required
+ ResponseTypes []string `json:"response_types,omitempty"`
+ GrantTypes []string `json:"grant_types,omitempty"`
+ ApplicationType string `json:"application_type,omitempty"`
+ Contacts []string `json:"contacts,omitempty"`
+ ClientName string `json:"client_name,omitempty"`
+ LogoURI string `json:"logo_uri,omitempty"`
+ ClientURI string `json:"client_uri,omitempty"`
+ PolicyURI string `json:"policy_uri,omitempty"`
+ TermsOfServiceURI string `json:"tos_uri,omitempty"`
+ JWKSURI string `json:"jwks_uri,omitempty"`
+ JWKS *jose.JWKSet `json:"jwks,omitempty"`
+ SectorIdentifierURI string `json:"sector_identifier_uri,omitempty"`
+ SubjectType string `json:"subject_type,omitempty"`
+ IDTokenSignedResponseAlg string `json:"id_token_signed_response_alg,omitempty"`
+ IDTokenEncryptedResponseAlg string `json:"id_token_encrypted_response_alg,omitempty"`
+ IDTokenEncryptedResponseEnc string `json:"id_token_encrypted_response_enc,omitempty"`
+ UserInfoSignedResponseAlg string `json:"userinfo_signed_response_alg,omitempty"`
+ UserInfoEncryptedResponseAlg string `json:"userinfo_encrypted_response_alg,omitempty"`
+ UserInfoEncryptedResponseEnc string `json:"userinfo_encrypted_response_enc,omitempty"`
+ RequestObjectSigningAlg string `json:"request_object_signing_alg,omitempty"`
+ RequestObjectEncryptionAlg string `json:"request_object_encryption_alg,omitempty"`
+ RequestObjectEncryptionEnc string `json:"request_object_encryption_enc,omitempty"`
+ TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
+ TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg,omitempty"`
+ DefaultMaxAge int64 `json:"default_max_age,omitempty"`
+ RequireAuthTime bool `json:"require_auth_time,omitempty"`
+ DefaultACRValues []string `json:"default_acr_values,omitempty"`
+ InitiateLoginURI string `json:"initiate_login_uri,omitempty"`
+ RequestURIs []string `json:"request_uris,omitempty"`
+}
+
+func (c *encodableClientMetadata) toStruct() (ClientMetadata, error) {
+ p := stickyErrParser{}
+ m := ClientMetadata{
+ RedirectURIs: p.parseURIs(c.RedirectURIs, "redirect_uris"),
+ ResponseTypes: c.ResponseTypes,
+ GrantTypes: c.GrantTypes,
+ ApplicationType: c.ApplicationType,
+ Contacts: p.parseEmails(c.Contacts, "contacts"),
+ ClientName: c.ClientName,
+ LogoURI: p.parseURI(c.LogoURI, "logo_uri"),
+ ClientURI: p.parseURI(c.ClientURI, "client_uri"),
+ PolicyURI: p.parseURI(c.PolicyURI, "policy_uri"),
+ TermsOfServiceURI: p.parseURI(c.TermsOfServiceURI, "tos_uri"),
+ JWKSURI: p.parseURI(c.JWKSURI, "jwks_uri"),
+ JWKS: c.JWKS,
+ SectorIdentifierURI: p.parseURI(c.SectorIdentifierURI, "sector_identifier_uri"),
+ SubjectType: c.SubjectType,
+ TokenEndpointAuthMethod: c.TokenEndpointAuthMethod,
+ TokenEndpointAuthSigningAlg: c.TokenEndpointAuthSigningAlg,
+ DefaultMaxAge: c.DefaultMaxAge,
+ RequireAuthTime: c.RequireAuthTime,
+ DefaultACRValues: c.DefaultACRValues,
+ InitiateLoginURI: p.parseURI(c.InitiateLoginURI, "initiate_login_uri"),
+ RequestURIs: p.parseURIs(c.RequestURIs, "request_uris"),
+ IDTokenResponseOptions: JWAOptions{
+ c.IDTokenSignedResponseAlg,
+ c.IDTokenEncryptedResponseAlg,
+ c.IDTokenEncryptedResponseEnc,
+ },
+ UserInfoResponseOptions: JWAOptions{
+ c.UserInfoSignedResponseAlg,
+ c.UserInfoEncryptedResponseAlg,
+ c.UserInfoEncryptedResponseEnc,
+ },
+ RequestObjectOptions: JWAOptions{
+ c.RequestObjectSigningAlg,
+ c.RequestObjectEncryptionAlg,
+ c.RequestObjectEncryptionEnc,
+ },
+ }
+ if p.firstErr != nil {
+ return ClientMetadata{}, p.firstErr
+ }
+ return m, nil
+}
+
+// stickyErrParser parses URIs and email addresses. Once it encounters
+// a parse error, subsequent calls become no-op.
+type stickyErrParser struct {
+ firstErr error
+}
+
+func (p *stickyErrParser) parseURI(s, field string) *url.URL {
+ if p.firstErr != nil || s == "" {
+ return nil
+ }
+ u, err := url.Parse(s)
+ if err == nil {
+ if u.Host == "" {
+ err = errors.New("no host in URI")
+ } else if u.Scheme != "http" && u.Scheme != "https" {
+ err = errors.New("invalid URI scheme")
+ }
+ }
+ if err != nil {
+ p.firstErr = fmt.Errorf("failed to parse %s: %v", field, err)
+ return nil
+ }
+ return u
+}
+
+func (p *stickyErrParser) parseURIs(s []string, field string) []url.URL {
+ if p.firstErr != nil || len(s) == 0 {
+ return nil
+ }
+ uris := make([]url.URL, len(s))
+ for i, val := range s {
+ if val == "" {
+ p.firstErr = fmt.Errorf("invalid URI in field %s", field)
+ return nil
+ }
+ if u := p.parseURI(val, field); u != nil {
+ uris[i] = *u
+ }
+ }
+ return uris
+}
+
+func (p *stickyErrParser) parseEmails(s []string, field string) []mail.Address {
+ if p.firstErr != nil || len(s) == 0 {
+ return nil
+ }
+ addrs := make([]mail.Address, len(s))
+ for i, addr := range s {
+ if addr == "" {
+ p.firstErr = fmt.Errorf("invalid email in field %s", field)
+ return nil
+ }
+ a, err := mail.ParseAddress(addr)
+ if err != nil {
+ p.firstErr = fmt.Errorf("invalid email in field %s: %v", field, err)
+ return nil
+ }
+ addrs[i] = *a
+ }
+ return addrs
+}
+
+func (m *ClientMetadata) toEncodableStruct() encodableClientMetadata {
+ return encodableClientMetadata{
+ RedirectURIs: urisToStrings(m.RedirectURIs),
+ ResponseTypes: m.ResponseTypes,
+ GrantTypes: m.GrantTypes,
+ ApplicationType: m.ApplicationType,
+ Contacts: emailsToStrings(m.Contacts),
+ ClientName: m.ClientName,
+ LogoURI: uriToString(m.LogoURI),
+ ClientURI: uriToString(m.ClientURI),
+ PolicyURI: uriToString(m.PolicyURI),
+ TermsOfServiceURI: uriToString(m.TermsOfServiceURI),
+ JWKSURI: uriToString(m.JWKSURI),
+ JWKS: m.JWKS,
+ SectorIdentifierURI: uriToString(m.SectorIdentifierURI),
+ SubjectType: m.SubjectType,
+ IDTokenSignedResponseAlg: m.IDTokenResponseOptions.SigningAlg,
+ IDTokenEncryptedResponseAlg: m.IDTokenResponseOptions.EncryptionAlg,
+ IDTokenEncryptedResponseEnc: m.IDTokenResponseOptions.EncryptionEnc,
+ UserInfoSignedResponseAlg: m.UserInfoResponseOptions.SigningAlg,
+ UserInfoEncryptedResponseAlg: m.UserInfoResponseOptions.EncryptionAlg,
+ UserInfoEncryptedResponseEnc: m.UserInfoResponseOptions.EncryptionEnc,
+ RequestObjectSigningAlg: m.RequestObjectOptions.SigningAlg,
+ RequestObjectEncryptionAlg: m.RequestObjectOptions.EncryptionAlg,
+ RequestObjectEncryptionEnc: m.RequestObjectOptions.EncryptionEnc,
+ TokenEndpointAuthMethod: m.TokenEndpointAuthMethod,
+ TokenEndpointAuthSigningAlg: m.TokenEndpointAuthSigningAlg,
+ DefaultMaxAge: m.DefaultMaxAge,
+ RequireAuthTime: m.RequireAuthTime,
+ DefaultACRValues: m.DefaultACRValues,
+ InitiateLoginURI: uriToString(m.InitiateLoginURI),
+ RequestURIs: urisToStrings(m.RequestURIs),
+ }
+}
+
+func uriToString(u *url.URL) string {
+ if u == nil {
+ return ""
+ }
+ return u.String()
+}
+
+func urisToStrings(urls []url.URL) []string {
+ if len(urls) == 0 {
+ return nil
+ }
+ sli := make([]string, len(urls))
+ for i, u := range urls {
+ sli[i] = u.String()
+ }
+ return sli
+}
+
+func emailsToStrings(addrs []mail.Address) []string {
+ if len(addrs) == 0 {
+ return nil
+ }
+ sli := make([]string, len(addrs))
+ for i, addr := range addrs {
+ sli[i] = addr.String()
+ }
+ return sli
+}
+
+// Valid determines if a ClientMetadata conforms with the OIDC specification.
+//
+// Valid is called by UnmarshalJSON.
+//
+// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
+// URLs fields where the OIDC spec requires it. This may change in future releases
+// of this package. See: https://github.com/coreos/go-oidc/issues/34
+func (m *ClientMetadata) Valid() error {
+ if len(m.RedirectURIs) == 0 {
+ return errors.New("zero redirect URLs")
+ }
+
+ validURI := func(u *url.URL, fieldName string) error {
+ if u.Host == "" {
+ return fmt.Errorf("no host for uri field %s", fieldName)
+ }
+ if u.Scheme != "http" && u.Scheme != "https" {
+ return fmt.Errorf("uri field %s scheme is not http or https", fieldName)
+ }
+ return nil
+ }
+
+ uris := []struct {
+ val *url.URL
+ name string
+ }{
+ {m.LogoURI, "logo_uri"},
+ {m.ClientURI, "client_uri"},
+ {m.PolicyURI, "policy_uri"},
+ {m.TermsOfServiceURI, "tos_uri"},
+ {m.JWKSURI, "jwks_uri"},
+ {m.SectorIdentifierURI, "sector_identifier_uri"},
+ {m.InitiateLoginURI, "initiate_login_uri"},
+ }
+
+ for _, uri := range uris {
+ if uri.val == nil {
+ continue
+ }
+ if err := validURI(uri.val, uri.name); err != nil {
+ return err
+ }
+ }
+
+ uriLists := []struct {
+ vals []url.URL
+ name string
+ }{
+ {m.RedirectURIs, "redirect_uris"},
+ {m.RequestURIs, "request_uris"},
+ }
+ for _, list := range uriLists {
+ for _, uri := range list.vals {
+ if err := validURI(&uri, list.name); err != nil {
+ return err
+ }
+ }
+ }
+
+ options := []struct {
+ option JWAOptions
+ name string
+ }{
+ {m.IDTokenResponseOptions, "id_token response"},
+ {m.UserInfoResponseOptions, "userinfo response"},
+ {m.RequestObjectOptions, "request_object"},
+ }
+ for _, option := range options {
+ if err := option.option.valid(); err != nil {
+ return fmt.Errorf("invalid JWA values for %s: %v", option.name, err)
+ }
+ }
+ return nil
+}
+
+type ClientRegistrationResponse struct {
+ ClientID string // Required
+ ClientSecret string
+ RegistrationAccessToken string
+ RegistrationClientURI string
+ // If IsZero is true, unspecified.
+ ClientIDIssuedAt time.Time
+ // Time at which the client_secret will expire.
+ // If IsZero is true, it will not expire.
+ ClientSecretExpiresAt time.Time
+
+ ClientMetadata
+}
+
+type encodableClientRegistrationResponse struct {
+ ClientID string `json:"client_id"` // Required
+ ClientSecret string `json:"client_secret,omitempty"`
+ RegistrationAccessToken string `json:"registration_access_token,omitempty"`
+ RegistrationClientURI string `json:"registration_client_uri,omitempty"`
+ ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
+ // Time at which the client_secret will expire, in seconds since the epoch.
+ // If 0 it will not expire.
+ ClientSecretExpiresAt int64 `json:"client_secret_expires_at"` // Required
+
+ encodableClientMetadata
+}
+
+func unixToSec(t time.Time) int64 {
+ if t.IsZero() {
+ return 0
+ }
+ return t.Unix()
+}
+
+func (c *ClientRegistrationResponse) MarshalJSON() ([]byte, error) {
+ e := encodableClientRegistrationResponse{
+ ClientID: c.ClientID,
+ ClientSecret: c.ClientSecret,
+ RegistrationAccessToken: c.RegistrationAccessToken,
+ RegistrationClientURI: c.RegistrationClientURI,
+ ClientIDIssuedAt: unixToSec(c.ClientIDIssuedAt),
+ ClientSecretExpiresAt: unixToSec(c.ClientSecretExpiresAt),
+ encodableClientMetadata: c.ClientMetadata.toEncodableStruct(),
+ }
+ return json.Marshal(&e)
+}
+
+func secToUnix(sec int64) time.Time {
+ if sec == 0 {
+ return time.Time{}
+ }
+ return time.Unix(sec, 0)
+}
+
+func (c *ClientRegistrationResponse) UnmarshalJSON(data []byte) error {
+ var e encodableClientRegistrationResponse
+ if err := json.Unmarshal(data, &e); err != nil {
+ return err
+ }
+ if e.ClientID == "" {
+ return errors.New("no client_id in client registration response")
+ }
+ metadata, err := e.encodableClientMetadata.toStruct()
+ if err != nil {
+ return err
+ }
+ *c = ClientRegistrationResponse{
+ ClientID: e.ClientID,
+ ClientSecret: e.ClientSecret,
+ RegistrationAccessToken: e.RegistrationAccessToken,
+ RegistrationClientURI: e.RegistrationClientURI,
+ ClientIDIssuedAt: secToUnix(e.ClientIDIssuedAt),
+ ClientSecretExpiresAt: secToUnix(e.ClientSecretExpiresAt),
+ ClientMetadata: metadata,
+ }
+ return nil
+}
+
+type ClientConfig struct {
+ HTTPClient phttp.Client
+ Credentials ClientCredentials
+ Scope []string
+ RedirectURL string
+ ProviderConfig ProviderConfig
+ KeySet key.PublicKeySet
+}
+
+func NewClient(cfg ClientConfig) (*Client, error) {
+ // Allow empty redirect URL in the case where the client
+ // only needs to verify a given token.
+ ru, err := url.Parse(cfg.RedirectURL)
+ if err != nil {
+ return nil, fmt.Errorf("invalid redirect URL: %v", err)
+ }
+
+ c := Client{
+ credentials: cfg.Credentials,
+ httpClient: cfg.HTTPClient,
+ scope: cfg.Scope,
+ redirectURL: ru.String(),
+ providerConfig: newProviderConfigRepo(cfg.ProviderConfig),
+ keySet: cfg.KeySet,
+ }
+
+ if c.httpClient == nil {
+ c.httpClient = http.DefaultClient
+ }
+
+ if c.scope == nil {
+ c.scope = make([]string, len(DefaultScope))
+ copy(c.scope, DefaultScope)
+ }
+
+ return &c, nil
+}
+
+type Client struct {
+ httpClient phttp.Client
+ providerConfig *providerConfigRepo
+ credentials ClientCredentials
+ redirectURL string
+ scope []string
+ keySet key.PublicKeySet
+ providerSyncer *ProviderConfigSyncer
+
+ keySetSyncMutex sync.RWMutex
+ lastKeySetSync time.Time
+}
+
+func (c *Client) Healthy() error {
+ now := time.Now().UTC()
+
+ cfg := c.providerConfig.Get()
+
+ if cfg.Empty() {
+ return errors.New("oidc client provider config empty")
+ }
+
+ if !cfg.ExpiresAt.IsZero() && cfg.ExpiresAt.Before(now) {
+ return errors.New("oidc client provider config expired")
+ }
+
+ return nil
+}
+
+func (c *Client) OAuthClient() (*oauth2.Client, error) {
+ cfg := c.providerConfig.Get()
+ authMethod, err := chooseAuthMethod(cfg)
+ if err != nil {
+ return nil, err
+ }
+
+ ocfg := oauth2.Config{
+ Credentials: oauth2.ClientCredentials(c.credentials),
+ RedirectURL: c.redirectURL,
+ AuthURL: cfg.AuthEndpoint.String(),
+ TokenURL: cfg.TokenEndpoint.String(),
+ Scope: c.scope,
+ AuthMethod: authMethod,
+ }
+
+ return oauth2.NewClient(c.httpClient, ocfg)
+}
+
+func chooseAuthMethod(cfg ProviderConfig) (string, error) {
+ if len(cfg.TokenEndpointAuthMethodsSupported) == 0 {
+ return oauth2.AuthMethodClientSecretBasic, nil
+ }
+
+ for _, authMethod := range cfg.TokenEndpointAuthMethodsSupported {
+ if _, ok := supportedAuthMethods[authMethod]; ok {
+ return authMethod, nil
+ }
+ }
+
+ return "", errors.New("no supported auth methods")
+}
+
+// SyncProviderConfig starts the provider config syncer
+func (c *Client) SyncProviderConfig(discoveryURL string) chan struct{} {
+ r := NewHTTPProviderConfigGetter(c.httpClient, discoveryURL)
+ s := NewProviderConfigSyncer(r, c.providerConfig)
+ stop := s.Run()
+ s.WaitUntilInitialSync()
+ return stop
+}
+
+func (c *Client) maybeSyncKeys() error {
+ tooSoon := func() bool {
+ return time.Now().UTC().Before(c.lastKeySetSync.Add(keySyncWindow))
+ }
+
+ // ignore request to sync keys if a sync operation has been
+ // attempted too recently
+ if tooSoon() {
+ return nil
+ }
+
+ c.keySetSyncMutex.Lock()
+ defer c.keySetSyncMutex.Unlock()
+
+ // check again, as another goroutine may have been holding
+ // the lock while updating the keys
+ if tooSoon() {
+ return nil
+ }
+
+ cfg := c.providerConfig.Get()
+ r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint.String())
+ w := &clientKeyRepo{client: c}
+ _, err := key.Sync(r, w)
+ c.lastKeySetSync = time.Now().UTC()
+
+ return err
+}
+
+type clientKeyRepo struct {
+ client *Client
+}
+
+func (r *clientKeyRepo) Set(ks key.KeySet) error {
+ pks, ok := ks.(*key.PublicKeySet)
+ if !ok {
+ return errors.New("unable to cast to PublicKey")
+ }
+ r.client.keySet = *pks
+ return nil
+}
+
+func (c *Client) ClientCredsToken(scope []string) (jose.JWT, error) {
+ cfg := c.providerConfig.Get()
+
+ if !cfg.SupportsGrantType(oauth2.GrantTypeClientCreds) {
+ return jose.JWT{}, fmt.Errorf("%v grant type is not supported", oauth2.GrantTypeClientCreds)
+ }
+
+ oac, err := c.OAuthClient()
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ t, err := oac.ClientCredsToken(scope)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ jwt, err := jose.ParseJWT(t.IDToken)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ return jwt, c.VerifyJWT(jwt)
+}
+
+// ExchangeAuthCode exchanges an OAuth2 auth code for an OIDC JWT ID token.
+func (c *Client) ExchangeAuthCode(code string) (jose.JWT, error) {
+ oac, err := c.OAuthClient()
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ t, err := oac.RequestToken(oauth2.GrantTypeAuthCode, code)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ jwt, err := jose.ParseJWT(t.IDToken)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ return jwt, c.VerifyJWT(jwt)
+}
+
+// RefreshToken uses a refresh token to exchange for a new OIDC JWT ID Token.
+func (c *Client) RefreshToken(refreshToken string) (jose.JWT, error) {
+ oac, err := c.OAuthClient()
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ t, err := oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ jwt, err := jose.ParseJWT(t.IDToken)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ return jwt, c.VerifyJWT(jwt)
+}
+
+func (c *Client) VerifyJWT(jwt jose.JWT) error {
+ var keysFunc func() []key.PublicKey
+ if kID, ok := jwt.KeyID(); ok {
+ keysFunc = c.keysFuncWithID(kID)
+ } else {
+ keysFunc = c.keysFuncAll()
+ }
+
+ v := NewJWTVerifier(
+ c.providerConfig.Get().Issuer.String(),
+ c.credentials.ID,
+ c.maybeSyncKeys, keysFunc)
+
+ return v.Verify(jwt)
+}
+
+// keysFuncWithID returns a function that retrieves at most unexpired
+// public key from the Client that matches the provided ID
+func (c *Client) keysFuncWithID(kID string) func() []key.PublicKey {
+ return func() []key.PublicKey {
+ c.keySetSyncMutex.RLock()
+ defer c.keySetSyncMutex.RUnlock()
+
+ if c.keySet.ExpiresAt().Before(time.Now()) {
+ return []key.PublicKey{}
+ }
+
+ k := c.keySet.Key(kID)
+ if k == nil {
+ return []key.PublicKey{}
+ }
+
+ return []key.PublicKey{*k}
+ }
+}
+
+// keysFuncAll returns a function that retrieves all unexpired public
+// keys from the Client
+func (c *Client) keysFuncAll() func() []key.PublicKey {
+ return func() []key.PublicKey {
+ c.keySetSyncMutex.RLock()
+ defer c.keySetSyncMutex.RUnlock()
+
+ if c.keySet.ExpiresAt().Before(time.Now()) {
+ return []key.PublicKey{}
+ }
+
+ return c.keySet.Keys()
+ }
+}
+
+type providerConfigRepo struct {
+ mu sync.RWMutex
+ config ProviderConfig // do not access directly, use Get()
+}
+
+func newProviderConfigRepo(pc ProviderConfig) *providerConfigRepo {
+ return &providerConfigRepo{sync.RWMutex{}, pc}
+}
+
+// returns an error to implement ProviderConfigSetter
+func (r *providerConfigRepo) Set(cfg ProviderConfig) error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ r.config = cfg
+ return nil
+}
+
+func (r *providerConfigRepo) Get() ProviderConfig {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+ return r.config
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/identity.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/identity.go
new file mode 100644
index 0000000..9bfa8e3
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/identity.go
@@ -0,0 +1,44 @@
+package oidc
+
+import (
+ "errors"
+ "time"
+
+ "github.com/coreos/go-oidc/jose"
+)
+
+type Identity struct {
+ ID string
+ Name string
+ Email string
+ ExpiresAt time.Time
+}
+
+func IdentityFromClaims(claims jose.Claims) (*Identity, error) {
+ if claims == nil {
+ return nil, errors.New("nil claim set")
+ }
+
+ var ident Identity
+ var err error
+ var ok bool
+
+ if ident.ID, ok, err = claims.StringClaim("sub"); err != nil {
+ return nil, err
+ } else if !ok {
+ return nil, errors.New("missing required claim: sub")
+ }
+
+ if ident.Email, _, err = claims.StringClaim("email"); err != nil {
+ return nil, err
+ }
+
+ exp, ok, err := claims.TimeClaim("exp")
+ if err != nil {
+ return nil, err
+ } else if ok {
+ ident.ExpiresAt = exp
+ }
+
+ return &ident, nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/interface.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/interface.go
new file mode 100644
index 0000000..248cac0
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/interface.go
@@ -0,0 +1,3 @@
+package oidc
+
+type LoginFunc func(ident Identity, sessionKey string) (redirectURL string, err error)
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/key.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/key.go
new file mode 100644
index 0000000..82a0f56
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/key.go
@@ -0,0 +1,67 @@
+package oidc
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "time"
+
+ phttp "github.com/coreos/go-oidc/http"
+ "github.com/coreos/go-oidc/jose"
+ "github.com/coreos/go-oidc/key"
+)
+
+// DefaultPublicKeySetTTL is the default TTL set on the PublicKeySet if no
+// Cache-Control header is provided by the JWK Set document endpoint.
+const DefaultPublicKeySetTTL = 24 * time.Hour
+
+// NewRemotePublicKeyRepo is responsible for fetching the JWK Set document.
+func NewRemotePublicKeyRepo(hc phttp.Client, ep string) *remotePublicKeyRepo {
+ return &remotePublicKeyRepo{hc: hc, ep: ep}
+}
+
+type remotePublicKeyRepo struct {
+ hc phttp.Client
+ ep string
+}
+
+// Get returns a PublicKeySet fetched from the JWK Set document endpoint. A TTL
+// is set on the Key Set to avoid it having to be re-retrieved for every
+// encryption event. This TTL is typically controlled by the endpoint returning
+// a Cache-Control header, but defaults to 24 hours if no Cache-Control header
+// is found.
+func (r *remotePublicKeyRepo) Get() (key.KeySet, error) {
+ req, err := http.NewRequest("GET", r.ep, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := r.hc.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var d struct {
+ Keys []jose.JWK `json:"keys"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
+ return nil, err
+ }
+
+ if len(d.Keys) == 0 {
+ return nil, errors.New("zero keys in response")
+ }
+
+ ttl, ok, err := phttp.Cacheable(resp.Header)
+ if err != nil {
+ return nil, err
+ }
+ if !ok {
+ ttl = DefaultPublicKeySetTTL
+ }
+
+ exp := time.Now().UTC().Add(ttl)
+ ks := key.NewPublicKeySet(d.Keys, exp)
+ return ks, nil
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/provider.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/provider.go
new file mode 100644
index 0000000..1235890
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/provider.go
@@ -0,0 +1,688 @@
+package oidc
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/coreos/pkg/capnslog"
+ "github.com/coreos/pkg/timeutil"
+ "github.com/jonboulle/clockwork"
+
+ phttp "github.com/coreos/go-oidc/http"
+ "github.com/coreos/go-oidc/oauth2"
+)
+
+var (
+ log = capnslog.NewPackageLogger("github.com/coreos/go-oidc", "http")
+)
+
+const (
+ // Subject Identifier types defined by the OIDC spec. Specifies if the provider
+ // should provide the same sub claim value to all clients (public) or a unique
+ // value for each client (pairwise).
+ //
+ // See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
+ SubjectTypePublic = "public"
+ SubjectTypePairwise = "pairwise"
+)
+
+var (
+ // Default values for omitted provider config fields.
+ //
+ // Use ProviderConfig's Defaults method to fill a provider config with these values.
+ DefaultGrantTypesSupported = []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeImplicit}
+ DefaultResponseModesSupported = []string{"query", "fragment"}
+ DefaultTokenEndpointAuthMethodsSupported = []string{oauth2.AuthMethodClientSecretBasic}
+ DefaultClaimTypesSupported = []string{"normal"}
+)
+
+const (
+ MaximumProviderConfigSyncInterval = 24 * time.Hour
+ MinimumProviderConfigSyncInterval = time.Minute
+
+ discoveryConfigPath = "/.well-known/openid-configuration"
+)
+
+// internally configurable for tests
+var minimumProviderConfigSyncInterval = MinimumProviderConfigSyncInterval
+
+var (
+ // Ensure ProviderConfig satisfies these interfaces.
+ _ json.Marshaler = &ProviderConfig{}
+ _ json.Unmarshaler = &ProviderConfig{}
+)
+
+// ProviderConfig represents the OpenID Provider Metadata specifying what
+// configurations a provider supports.
+//
+// See: http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
+type ProviderConfig struct {
+ Issuer *url.URL // Required
+ AuthEndpoint *url.URL // Required
+ TokenEndpoint *url.URL // Required if grant types other than "implicit" are supported
+ UserInfoEndpoint *url.URL
+ KeysEndpoint *url.URL // Required
+ RegistrationEndpoint *url.URL
+
+ // Servers MAY choose not to advertise some supported scope values even when this
+ // parameter is used, although those defined in OpenID Core SHOULD be listed, if supported.
+ ScopesSupported []string
+ // OAuth2.0 response types supported.
+ ResponseTypesSupported []string // Required
+ // OAuth2.0 response modes supported.
+ //
+ // If omitted, defaults to DefaultResponseModesSupported.
+ ResponseModesSupported []string
+ // OAuth2.0 grant types supported.
+ //
+ // If omitted, defaults to DefaultGrantTypesSupported.
+ GrantTypesSupported []string
+ ACRValuesSupported []string
+ // SubjectTypesSupported specifies strategies for providing values for the sub claim.
+ SubjectTypesSupported []string // Required
+
+ // JWA signing and encryption algorith values supported for ID tokens.
+ IDTokenSigningAlgValues []string // Required
+ IDTokenEncryptionAlgValues []string
+ IDTokenEncryptionEncValues []string
+
+ // JWA signing and encryption algorith values supported for user info responses.
+ UserInfoSigningAlgValues []string
+ UserInfoEncryptionAlgValues []string
+ UserInfoEncryptionEncValues []string
+
+ // JWA signing and encryption algorith values supported for request objects.
+ ReqObjSigningAlgValues []string
+ ReqObjEncryptionAlgValues []string
+ ReqObjEncryptionEncValues []string
+
+ TokenEndpointAuthMethodsSupported []string
+ TokenEndpointAuthSigningAlgValuesSupported []string
+ DisplayValuesSupported []string
+ ClaimTypesSupported []string
+ ClaimsSupported []string
+ ServiceDocs *url.URL
+ ClaimsLocalsSupported []string
+ UILocalsSupported []string
+ ClaimsParameterSupported bool
+ RequestParameterSupported bool
+ RequestURIParamaterSupported bool
+ RequireRequestURIRegistration bool
+
+ Policy *url.URL
+ TermsOfService *url.URL
+
+ // Not part of the OpenID Provider Metadata
+ ExpiresAt time.Time
+}
+
+// Defaults returns a shallow copy of ProviderConfig with default
+// values replacing omitted fields.
+//
+// var cfg oidc.ProviderConfig
+// // Fill provider config with default values for omitted fields.
+// cfg = cfg.Defaults()
+//
+func (p ProviderConfig) Defaults() ProviderConfig {
+ setDefault := func(val *[]string, defaultVal []string) {
+ if len(*val) == 0 {
+ *val = defaultVal
+ }
+ }
+ setDefault(&p.GrantTypesSupported, DefaultGrantTypesSupported)
+ setDefault(&p.ResponseModesSupported, DefaultResponseModesSupported)
+ setDefault(&p.TokenEndpointAuthMethodsSupported, DefaultTokenEndpointAuthMethodsSupported)
+ setDefault(&p.ClaimTypesSupported, DefaultClaimTypesSupported)
+ return p
+}
+
+func (p *ProviderConfig) MarshalJSON() ([]byte, error) {
+ e := p.toEncodableStruct()
+ return json.Marshal(&e)
+}
+
+func (p *ProviderConfig) UnmarshalJSON(data []byte) error {
+ var e encodableProviderConfig
+ if err := json.Unmarshal(data, &e); err != nil {
+ return err
+ }
+ conf, err := e.toStruct()
+ if err != nil {
+ return err
+ }
+ if err := conf.Valid(); err != nil {
+ return err
+ }
+ *p = conf
+ return nil
+}
+
+type encodableProviderConfig struct {
+ Issuer string `json:"issuer"`
+ AuthEndpoint string `json:"authorization_endpoint"`
+ TokenEndpoint string `json:"token_endpoint"`
+ UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
+ KeysEndpoint string `json:"jwks_uri"`
+ RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
+
+ // Use 'omitempty' for all slices as per OIDC spec:
+ // "Claims that return multiple values are represented as JSON arrays.
+ // Claims with zero elements MUST be omitted from the response."
+ // http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
+
+ ScopesSupported []string `json:"scopes_supported,omitempty"`
+ ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
+ ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
+ GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
+ ACRValuesSupported []string `json:"acr_values_supported,omitempty"`
+ SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
+
+ IDTokenSigningAlgValues []string `json:"id_token_signing_alg_values_supported,omitempty"`
+ IDTokenEncryptionAlgValues []string `json:"id_token_encryption_alg_values_supported,omitempty"`
+ IDTokenEncryptionEncValues []string `json:"id_token_encryption_enc_values_supported,omitempty"`
+ UserInfoSigningAlgValues []string `json:"userinfo_signing_alg_values_supported,omitempty"`
+ UserInfoEncryptionAlgValues []string `json:"userinfo_encryption_alg_values_supported,omitempty"`
+ UserInfoEncryptionEncValues []string `json:"userinfo_encryption_enc_values_supported,omitempty"`
+ ReqObjSigningAlgValues []string `json:"request_object_signing_alg_values_supported,omitempty"`
+ ReqObjEncryptionAlgValues []string `json:"request_object_encryption_alg_values_supported,omitempty"`
+ ReqObjEncryptionEncValues []string `json:"request_object_encryption_enc_values_supported,omitempty"`
+
+ TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
+ TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
+
+ DisplayValuesSupported []string `json:"display_values_supported,omitempty"`
+ ClaimTypesSupported []string `json:"claim_types_supported,omitempty"`
+ ClaimsSupported []string `json:"claims_supported,omitempty"`
+ ServiceDocs string `json:"service_documentation,omitempty"`
+ ClaimsLocalsSupported []string `json:"claims_locales_supported,omitempty"`
+ UILocalsSupported []string `json:"ui_locales_supported,omitempty"`
+ ClaimsParameterSupported bool `json:"claims_parameter_supported,omitempty"`
+ RequestParameterSupported bool `json:"request_parameter_supported,omitempty"`
+ RequestURIParamaterSupported bool `json:"request_uri_parameter_supported,omitempty"`
+ RequireRequestURIRegistration bool `json:"require_request_uri_registration,omitempty"`
+
+ Policy string `json:"op_policy_uri,omitempty"`
+ TermsOfService string `json:"op_tos_uri,omitempty"`
+}
+
+func (cfg ProviderConfig) toEncodableStruct() encodableProviderConfig {
+ return encodableProviderConfig{
+ Issuer: uriToString(cfg.Issuer),
+ AuthEndpoint: uriToString(cfg.AuthEndpoint),
+ TokenEndpoint: uriToString(cfg.TokenEndpoint),
+ UserInfoEndpoint: uriToString(cfg.UserInfoEndpoint),
+ KeysEndpoint: uriToString(cfg.KeysEndpoint),
+ RegistrationEndpoint: uriToString(cfg.RegistrationEndpoint),
+ ScopesSupported: cfg.ScopesSupported,
+ ResponseTypesSupported: cfg.ResponseTypesSupported,
+ ResponseModesSupported: cfg.ResponseModesSupported,
+ GrantTypesSupported: cfg.GrantTypesSupported,
+ ACRValuesSupported: cfg.ACRValuesSupported,
+ SubjectTypesSupported: cfg.SubjectTypesSupported,
+ IDTokenSigningAlgValues: cfg.IDTokenSigningAlgValues,
+ IDTokenEncryptionAlgValues: cfg.IDTokenEncryptionAlgValues,
+ IDTokenEncryptionEncValues: cfg.IDTokenEncryptionEncValues,
+ UserInfoSigningAlgValues: cfg.UserInfoSigningAlgValues,
+ UserInfoEncryptionAlgValues: cfg.UserInfoEncryptionAlgValues,
+ UserInfoEncryptionEncValues: cfg.UserInfoEncryptionEncValues,
+ ReqObjSigningAlgValues: cfg.ReqObjSigningAlgValues,
+ ReqObjEncryptionAlgValues: cfg.ReqObjEncryptionAlgValues,
+ ReqObjEncryptionEncValues: cfg.ReqObjEncryptionEncValues,
+ TokenEndpointAuthMethodsSupported: cfg.TokenEndpointAuthMethodsSupported,
+ TokenEndpointAuthSigningAlgValuesSupported: cfg.TokenEndpointAuthSigningAlgValuesSupported,
+ DisplayValuesSupported: cfg.DisplayValuesSupported,
+ ClaimTypesSupported: cfg.ClaimTypesSupported,
+ ClaimsSupported: cfg.ClaimsSupported,
+ ServiceDocs: uriToString(cfg.ServiceDocs),
+ ClaimsLocalsSupported: cfg.ClaimsLocalsSupported,
+ UILocalsSupported: cfg.UILocalsSupported,
+ ClaimsParameterSupported: cfg.ClaimsParameterSupported,
+ RequestParameterSupported: cfg.RequestParameterSupported,
+ RequestURIParamaterSupported: cfg.RequestURIParamaterSupported,
+ RequireRequestURIRegistration: cfg.RequireRequestURIRegistration,
+ Policy: uriToString(cfg.Policy),
+ TermsOfService: uriToString(cfg.TermsOfService),
+ }
+}
+
+func (e encodableProviderConfig) toStruct() (ProviderConfig, error) {
+ p := stickyErrParser{}
+ conf := ProviderConfig{
+ Issuer: p.parseURI(e.Issuer, "issuer"),
+ AuthEndpoint: p.parseURI(e.AuthEndpoint, "authorization_endpoint"),
+ TokenEndpoint: p.parseURI(e.TokenEndpoint, "token_endpoint"),
+ UserInfoEndpoint: p.parseURI(e.UserInfoEndpoint, "userinfo_endpoint"),
+ KeysEndpoint: p.parseURI(e.KeysEndpoint, "jwks_uri"),
+ RegistrationEndpoint: p.parseURI(e.RegistrationEndpoint, "registration_endpoint"),
+ ScopesSupported: e.ScopesSupported,
+ ResponseTypesSupported: e.ResponseTypesSupported,
+ ResponseModesSupported: e.ResponseModesSupported,
+ GrantTypesSupported: e.GrantTypesSupported,
+ ACRValuesSupported: e.ACRValuesSupported,
+ SubjectTypesSupported: e.SubjectTypesSupported,
+ IDTokenSigningAlgValues: e.IDTokenSigningAlgValues,
+ IDTokenEncryptionAlgValues: e.IDTokenEncryptionAlgValues,
+ IDTokenEncryptionEncValues: e.IDTokenEncryptionEncValues,
+ UserInfoSigningAlgValues: e.UserInfoSigningAlgValues,
+ UserInfoEncryptionAlgValues: e.UserInfoEncryptionAlgValues,
+ UserInfoEncryptionEncValues: e.UserInfoEncryptionEncValues,
+ ReqObjSigningAlgValues: e.ReqObjSigningAlgValues,
+ ReqObjEncryptionAlgValues: e.ReqObjEncryptionAlgValues,
+ ReqObjEncryptionEncValues: e.ReqObjEncryptionEncValues,
+ TokenEndpointAuthMethodsSupported: e.TokenEndpointAuthMethodsSupported,
+ TokenEndpointAuthSigningAlgValuesSupported: e.TokenEndpointAuthSigningAlgValuesSupported,
+ DisplayValuesSupported: e.DisplayValuesSupported,
+ ClaimTypesSupported: e.ClaimTypesSupported,
+ ClaimsSupported: e.ClaimsSupported,
+ ServiceDocs: p.parseURI(e.ServiceDocs, "service_documentation"),
+ ClaimsLocalsSupported: e.ClaimsLocalsSupported,
+ UILocalsSupported: e.UILocalsSupported,
+ ClaimsParameterSupported: e.ClaimsParameterSupported,
+ RequestParameterSupported: e.RequestParameterSupported,
+ RequestURIParamaterSupported: e.RequestURIParamaterSupported,
+ RequireRequestURIRegistration: e.RequireRequestURIRegistration,
+ Policy: p.parseURI(e.Policy, "op_policy-uri"),
+ TermsOfService: p.parseURI(e.TermsOfService, "op_tos_uri"),
+ }
+ if p.firstErr != nil {
+ return ProviderConfig{}, p.firstErr
+ }
+ return conf, nil
+}
+
+// Empty returns if a ProviderConfig holds no information.
+//
+// This case generally indicates a ProviderConfigGetter has experienced an error
+// and has nothing to report.
+func (p ProviderConfig) Empty() bool {
+ return p.Issuer == nil
+}
+
+func contains(sli []string, ele string) bool {
+ for _, s := range sli {
+ if s == ele {
+ return true
+ }
+ }
+ return false
+}
+
+// Valid determines if a ProviderConfig conforms with the OIDC specification.
+// If Valid returns successfully it guarantees required field are non-nil and
+// URLs are well formed.
+//
+// Valid is called by UnmarshalJSON.
+//
+// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
+// URLs fields where the OIDC spec requires it. This may change in future releases
+// of this package. See: https://github.com/coreos/go-oidc/issues/34
+func (p ProviderConfig) Valid() error {
+ grantTypes := p.GrantTypesSupported
+ if len(grantTypes) == 0 {
+ grantTypes = DefaultGrantTypesSupported
+ }
+ implicitOnly := true
+ for _, grantType := range grantTypes {
+ if grantType != oauth2.GrantTypeImplicit {
+ implicitOnly = false
+ break
+ }
+ }
+
+ if len(p.SubjectTypesSupported) == 0 {
+ return errors.New("missing required field subject_types_supported")
+ }
+ if len(p.IDTokenSigningAlgValues) == 0 {
+ return errors.New("missing required field id_token_signing_alg_values_supported")
+ }
+
+ if len(p.ScopesSupported) != 0 && !contains(p.ScopesSupported, "openid") {
+ return errors.New("scoped_supported must be unspecified or include 'openid'")
+ }
+
+ if !contains(p.IDTokenSigningAlgValues, "RS256") {
+ return errors.New("id_token_signing_alg_values_supported must include 'RS256'")
+ }
+ if contains(p.TokenEndpointAuthMethodsSupported, "none") {
+ return errors.New("token_endpoint_auth_signing_alg_values_supported cannot include 'none'")
+ }
+
+ uris := []struct {
+ val *url.URL
+ name string
+ required bool
+ }{
+ {p.Issuer, "issuer", true},
+ {p.AuthEndpoint, "authorization_endpoint", true},
+ {p.TokenEndpoint, "token_endpoint", !implicitOnly},
+ {p.UserInfoEndpoint, "userinfo_endpoint", false},
+ {p.KeysEndpoint, "jwks_uri", true},
+ {p.RegistrationEndpoint, "registration_endpoint", false},
+ {p.ServiceDocs, "service_documentation", false},
+ {p.Policy, "op_policy_uri", false},
+ {p.TermsOfService, "op_tos_uri", false},
+ }
+
+ for _, uri := range uris {
+ if uri.val == nil {
+ if !uri.required {
+ continue
+ }
+ return fmt.Errorf("empty value for required uri field %s", uri.name)
+ }
+ if uri.val.Host == "" {
+ return fmt.Errorf("no host for uri field %s", uri.name)
+ }
+ if uri.val.Scheme != "http" && uri.val.Scheme != "https" {
+ return fmt.Errorf("uri field %s schemeis not http or https", uri.name)
+ }
+ }
+ return nil
+}
+
+// Supports determines if provider supports a client given their respective metadata.
+func (p ProviderConfig) Supports(c ClientMetadata) error {
+ if err := p.Valid(); err != nil {
+ return fmt.Errorf("invalid provider config: %v", err)
+ }
+ if err := c.Valid(); err != nil {
+ return fmt.Errorf("invalid client config: %v", err)
+ }
+
+ // Fill default values for omitted fields
+ c = c.Defaults()
+ p = p.Defaults()
+
+ // Do the supported values list the requested one?
+ supports := []struct {
+ supported []string
+ requested string
+ name string
+ }{
+ {p.IDTokenSigningAlgValues, c.IDTokenResponseOptions.SigningAlg, "id_token_signed_response_alg"},
+ {p.IDTokenEncryptionAlgValues, c.IDTokenResponseOptions.EncryptionAlg, "id_token_encryption_response_alg"},
+ {p.IDTokenEncryptionEncValues, c.IDTokenResponseOptions.EncryptionEnc, "id_token_encryption_response_enc"},
+ {p.UserInfoSigningAlgValues, c.UserInfoResponseOptions.SigningAlg, "userinfo_signed_response_alg"},
+ {p.UserInfoEncryptionAlgValues, c.UserInfoResponseOptions.EncryptionAlg, "userinfo_encryption_response_alg"},
+ {p.UserInfoEncryptionEncValues, c.UserInfoResponseOptions.EncryptionEnc, "userinfo_encryption_response_enc"},
+ {p.ReqObjSigningAlgValues, c.RequestObjectOptions.SigningAlg, "request_object_signing_alg"},
+ {p.ReqObjEncryptionAlgValues, c.RequestObjectOptions.EncryptionAlg, "request_object_encryption_alg"},
+ {p.ReqObjEncryptionEncValues, c.RequestObjectOptions.EncryptionEnc, "request_object_encryption_enc"},
+ }
+ for _, field := range supports {
+ if field.requested == "" {
+ continue
+ }
+ if !contains(field.supported, field.requested) {
+ return fmt.Errorf("provider does not support requested value for field %s", field.name)
+ }
+ }
+
+ stringsEqual := func(s1, s2 string) bool { return s1 == s2 }
+
+ // For lists, are the list of requested values a subset of the supported ones?
+ supportsAll := []struct {
+ supported []string
+ requested []string
+ name string
+ // OAuth2.0 response_type can be space separated lists where order doesn't matter.
+ // For example "id_token token" is the same as "token id_token"
+ // Support a custom compare method.
+ comp func(s1, s2 string) bool
+ }{
+ {p.GrantTypesSupported, c.GrantTypes, "grant_types", stringsEqual},
+ {p.ResponseTypesSupported, c.ResponseTypes, "response_type", oauth2.ResponseTypesEqual},
+ }
+ for _, field := range supportsAll {
+ requestLoop:
+ for _, req := range field.requested {
+ for _, sup := range field.supported {
+ if field.comp(req, sup) {
+ continue requestLoop
+ }
+ }
+ return fmt.Errorf("provider does not support requested value for field %s", field.name)
+ }
+ }
+
+ // TODO(ericchiang): Are there more checks we feel comfortable with begin strict about?
+
+ return nil
+}
+
+func (p ProviderConfig) SupportsGrantType(grantType string) bool {
+ var supported []string
+ if len(p.GrantTypesSupported) == 0 {
+ supported = DefaultGrantTypesSupported
+ } else {
+ supported = p.GrantTypesSupported
+ }
+
+ for _, t := range supported {
+ if t == grantType {
+ return true
+ }
+ }
+ return false
+}
+
+type ProviderConfigGetter interface {
+ Get() (ProviderConfig, error)
+}
+
+type ProviderConfigSetter interface {
+ Set(ProviderConfig) error
+}
+
+type ProviderConfigSyncer struct {
+ from ProviderConfigGetter
+ to ProviderConfigSetter
+ clock clockwork.Clock
+
+ initialSyncDone bool
+ initialSyncWait sync.WaitGroup
+}
+
+func NewProviderConfigSyncer(from ProviderConfigGetter, to ProviderConfigSetter) *ProviderConfigSyncer {
+ return &ProviderConfigSyncer{
+ from: from,
+ to: to,
+ clock: clockwork.NewRealClock(),
+ }
+}
+
+func (s *ProviderConfigSyncer) Run() chan struct{} {
+ stop := make(chan struct{})
+
+ var next pcsStepper
+ next = &pcsStepNext{aft: time.Duration(0)}
+
+ s.initialSyncWait.Add(1)
+ go func() {
+ for {
+ select {
+ case <-s.clock.After(next.after()):
+ next = next.step(s.sync)
+ case <-stop:
+ return
+ }
+ }
+ }()
+
+ return stop
+}
+
+func (s *ProviderConfigSyncer) WaitUntilInitialSync() {
+ s.initialSyncWait.Wait()
+}
+
+func (s *ProviderConfigSyncer) sync() (time.Duration, error) {
+ cfg, err := s.from.Get()
+ if err != nil {
+ return 0, err
+ }
+
+ if err = s.to.Set(cfg); err != nil {
+ return 0, fmt.Errorf("error setting provider config: %v", err)
+ }
+
+ if !s.initialSyncDone {
+ s.initialSyncWait.Done()
+ s.initialSyncDone = true
+ }
+
+ log.Infof("Updating provider config: config=%#v", cfg)
+
+ return nextSyncAfter(cfg.ExpiresAt, s.clock), nil
+}
+
+type pcsStepFunc func() (time.Duration, error)
+
+type pcsStepper interface {
+ after() time.Duration
+ step(pcsStepFunc) pcsStepper
+}
+
+type pcsStepNext struct {
+ aft time.Duration
+}
+
+func (n *pcsStepNext) after() time.Duration {
+ return n.aft
+}
+
+func (n *pcsStepNext) step(fn pcsStepFunc) (next pcsStepper) {
+ ttl, err := fn()
+ if err == nil {
+ next = &pcsStepNext{aft: ttl}
+ log.Debugf("Synced provider config, next attempt in %v", next.after())
+ } else {
+ next = &pcsStepRetry{aft: time.Second}
+ log.Errorf("Provider config sync failed, retrying in %v: %v", next.after(), err)
+ }
+ return
+}
+
+type pcsStepRetry struct {
+ aft time.Duration
+}
+
+func (r *pcsStepRetry) after() time.Duration {
+ return r.aft
+}
+
+func (r *pcsStepRetry) step(fn pcsStepFunc) (next pcsStepper) {
+ ttl, err := fn()
+ if err == nil {
+ next = &pcsStepNext{aft: ttl}
+ log.Infof("Provider config sync no longer failing")
+ } else {
+ next = &pcsStepRetry{aft: timeutil.ExpBackoff(r.aft, time.Minute)}
+ log.Errorf("Provider config sync still failing, retrying in %v: %v", next.after(), err)
+ }
+ return
+}
+
+func nextSyncAfter(exp time.Time, clock clockwork.Clock) time.Duration {
+ if exp.IsZero() {
+ return MaximumProviderConfigSyncInterval
+ }
+
+ t := exp.Sub(clock.Now()) / 2
+ if t > MaximumProviderConfigSyncInterval {
+ t = MaximumProviderConfigSyncInterval
+ } else if t < minimumProviderConfigSyncInterval {
+ t = minimumProviderConfigSyncInterval
+ }
+
+ return t
+}
+
+type httpProviderConfigGetter struct {
+ hc phttp.Client
+ issuerURL string
+ clock clockwork.Clock
+}
+
+func NewHTTPProviderConfigGetter(hc phttp.Client, issuerURL string) *httpProviderConfigGetter {
+ return &httpProviderConfigGetter{
+ hc: hc,
+ issuerURL: issuerURL,
+ clock: clockwork.NewRealClock(),
+ }
+}
+
+func (r *httpProviderConfigGetter) Get() (cfg ProviderConfig, err error) {
+ // If the Issuer value contains a path component, any terminating / MUST be removed before
+ // appending /.well-known/openid-configuration.
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
+ discoveryURL := strings.TrimSuffix(r.issuerURL, "/") + discoveryConfigPath
+ req, err := http.NewRequest("GET", discoveryURL, nil)
+ if err != nil {
+ return
+ }
+
+ resp, err := r.hc.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ if err = json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
+ return
+ }
+
+ var ttl time.Duration
+ var ok bool
+ ttl, ok, err = phttp.Cacheable(resp.Header)
+ if err != nil {
+ return
+ } else if ok {
+ cfg.ExpiresAt = r.clock.Now().UTC().Add(ttl)
+ }
+
+ // The issuer value returned MUST be identical to the Issuer URL that was directly used to retrieve the configuration information.
+ // http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation
+ if !urlEqual(cfg.Issuer.String(), r.issuerURL) {
+ err = fmt.Errorf(`"issuer" in config (%v) does not match provided issuer URL (%v)`, cfg.Issuer, r.issuerURL)
+ return
+ }
+
+ return
+}
+
+func FetchProviderConfig(hc phttp.Client, issuerURL string) (ProviderConfig, error) {
+ if hc == nil {
+ hc = http.DefaultClient
+ }
+
+ g := NewHTTPProviderConfigGetter(hc, issuerURL)
+ return g.Get()
+}
+
+func WaitForProviderConfig(hc phttp.Client, issuerURL string) (pcfg ProviderConfig) {
+ return waitForProviderConfig(hc, issuerURL, clockwork.NewRealClock())
+}
+
+func waitForProviderConfig(hc phttp.Client, issuerURL string, clock clockwork.Clock) (pcfg ProviderConfig) {
+ var sleep time.Duration
+ var err error
+ for {
+ pcfg, err = FetchProviderConfig(hc, issuerURL)
+ if err == nil {
+ break
+ }
+
+ sleep = timeutil.ExpBackoff(sleep, time.Minute)
+ fmt.Printf("Failed fetching provider config, trying again in %v: %v\n", sleep, err)
+ time.Sleep(sleep)
+ }
+
+ return
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/transport.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/transport.go
new file mode 100644
index 0000000..61c926d
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/transport.go
@@ -0,0 +1,88 @@
+package oidc
+
+import (
+ "fmt"
+ "net/http"
+ "sync"
+
+ phttp "github.com/coreos/go-oidc/http"
+ "github.com/coreos/go-oidc/jose"
+)
+
+type TokenRefresher interface {
+ // Verify checks if the provided token is currently valid or not.
+ Verify(jose.JWT) error
+
+ // Refresh attempts to authenticate and retrieve a new token.
+ Refresh() (jose.JWT, error)
+}
+
+type ClientCredsTokenRefresher struct {
+ Issuer string
+ OIDCClient *Client
+}
+
+func (c *ClientCredsTokenRefresher) Verify(jwt jose.JWT) (err error) {
+ _, err = VerifyClientClaims(jwt, c.Issuer)
+ return
+}
+
+func (c *ClientCredsTokenRefresher) Refresh() (jwt jose.JWT, err error) {
+ if err = c.OIDCClient.Healthy(); err != nil {
+ err = fmt.Errorf("unable to authenticate, unhealthy OIDC client: %v", err)
+ return
+ }
+
+ jwt, err = c.OIDCClient.ClientCredsToken([]string{"openid"})
+ if err != nil {
+ err = fmt.Errorf("unable to verify auth code with issuer: %v", err)
+ return
+ }
+
+ return
+}
+
+type AuthenticatedTransport struct {
+ TokenRefresher
+ http.RoundTripper
+
+ mu sync.Mutex
+ jwt jose.JWT
+}
+
+func (t *AuthenticatedTransport) verifiedJWT() (jose.JWT, error) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ if t.TokenRefresher.Verify(t.jwt) == nil {
+ return t.jwt, nil
+ }
+
+ jwt, err := t.TokenRefresher.Refresh()
+ if err != nil {
+ return jose.JWT{}, fmt.Errorf("unable to acquire valid JWT: %v", err)
+ }
+
+ t.jwt = jwt
+ return t.jwt, nil
+}
+
+// SetJWT sets the JWT held by the Transport.
+// This is useful for cases in which you want to set an initial JWT.
+func (t *AuthenticatedTransport) SetJWT(jwt jose.JWT) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ t.jwt = jwt
+}
+
+func (t *AuthenticatedTransport) RoundTrip(r *http.Request) (*http.Response, error) {
+ jwt, err := t.verifiedJWT()
+ if err != nil {
+ return nil, err
+ }
+
+ req := phttp.CopyRequest(r)
+ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt.Encode()))
+ return t.RoundTripper.RoundTrip(req)
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/util.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/util.go
new file mode 100644
index 0000000..f2a5a19
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/util.go
@@ -0,0 +1,109 @@
+package oidc
+
+import (
+ "crypto/rand"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/coreos/go-oidc/jose"
+)
+
+// RequestTokenExtractor funcs extract a raw encoded token from a request.
+type RequestTokenExtractor func(r *http.Request) (string, error)
+
+// ExtractBearerToken is a RequestTokenExtractor which extracts a bearer token from a request's
+// Authorization header.
+func ExtractBearerToken(r *http.Request) (string, error) {
+ ah := r.Header.Get("Authorization")
+ if ah == "" {
+ return "", errors.New("missing Authorization header")
+ }
+
+ if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" {
+ return "", errors.New("should be a bearer token")
+ }
+
+ val := ah[7:]
+ if len(val) == 0 {
+ return "", errors.New("bearer token is empty")
+ }
+
+ return val, nil
+}
+
+// CookieTokenExtractor returns a RequestTokenExtractor which extracts a token from the named cookie in a request.
+func CookieTokenExtractor(cookieName string) RequestTokenExtractor {
+ return func(r *http.Request) (string, error) {
+ ck, err := r.Cookie(cookieName)
+ if err != nil {
+ return "", fmt.Errorf("token cookie not found in request: %v", err)
+ }
+
+ if ck.Value == "" {
+ return "", errors.New("token cookie found but is empty")
+ }
+
+ return ck.Value, nil
+ }
+}
+
+func NewClaims(iss, sub string, aud interface{}, iat, exp time.Time) jose.Claims {
+ return jose.Claims{
+ // required
+ "iss": iss,
+ "sub": sub,
+ "aud": aud,
+ "iat": iat.Unix(),
+ "exp": exp.Unix(),
+ }
+}
+
+func GenClientID(hostport string) (string, error) {
+ b, err := randBytes(32)
+ if err != nil {
+ return "", err
+ }
+
+ var host string
+ if strings.Contains(hostport, ":") {
+ host, _, err = net.SplitHostPort(hostport)
+ if err != nil {
+ return "", err
+ }
+ } else {
+ host = hostport
+ }
+
+ return fmt.Sprintf("%s@%s", base64.URLEncoding.EncodeToString(b), host), nil
+}
+
+func randBytes(n int) ([]byte, error) {
+ b := make([]byte, n)
+ got, err := rand.Read(b)
+ if err != nil {
+ return nil, err
+ } else if n != got {
+ return nil, errors.New("unable to generate enough random data")
+ }
+ return b, nil
+}
+
+// urlEqual checks two urls for equality using only the host and path portions.
+func urlEqual(url1, url2 string) bool {
+ u1, err := url.Parse(url1)
+ if err != nil {
+ return false
+ }
+ u2, err := url.Parse(url2)
+ if err != nil {
+ return false
+ }
+
+ return strings.ToLower(u1.Host+u1.Path) == strings.ToLower(u2.Host+u2.Path)
+}
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/verification.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/verification.go
new file mode 100644
index 0000000..0024130
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/verification.go
@@ -0,0 +1,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
+}