initial commit
This commit is contained in:
202
vendor/github.com/ericchiang/oidc/LICENSE
generated
vendored
Normal file
202
vendor/github.com/ericchiang/oidc/LICENSE
generated
vendored
Normal file
@@ -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.
|
||||
|
99
vendor/github.com/ericchiang/oidc/README.md
generated
vendored
Normal file
99
vendor/github.com/ericchiang/oidc/README.md
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
# OpenID Connect client support for Go
|
||||
|
||||
[](https://godoc.org/github.com/ericchiang/oidc)
|
||||
|
||||
This package implements OpenID Connect client logic for the golang.org/x/oauth2 package.
|
||||
|
||||
```go
|
||||
provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure an OpenID Connect aware OAuth2 client.
|
||||
oauth2Config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectURL,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
```
|
||||
|
||||
OAuth2 redirects are unchanged.
|
||||
|
||||
```go
|
||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
|
||||
})
|
||||
```
|
||||
|
||||
For callbacks the provider can be used to query for [user information](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) such as email.
|
||||
|
||||
```go
|
||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify state...
|
||||
|
||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
userinfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
Or the provider can be used to verify and inspect the OpenID Connect
|
||||
[ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) in the
|
||||
[token response](https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse).
|
||||
|
||||
```go
|
||||
verifier := provider.NewVerifier(ctx)
|
||||
```
|
||||
|
||||
The returned verifier can be used to ensure the ID Token (a JWT) is signed by the provider.
|
||||
|
||||
```go
|
||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify state...
|
||||
|
||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the ID Token from oauth2 token.
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
http.Error(w, "No ID Token found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify that the ID Token is signed by the provider.
|
||||
payload, err := verifier.Verify(rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal ID Token for expected custom claims.
|
||||
var idToken struct {
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &idToken); err != nil {
|
||||
http.Error(w, "Failed to unmarshal ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// ...
|
||||
})
|
||||
```
|
145
vendor/github.com/ericchiang/oidc/doc.go
generated
vendored
Normal file
145
vendor/github.com/ericchiang/oidc/doc.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure an OpenID Connect aware OAuth2 client.
|
||||
oauth2Config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectURL,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
|
||||
OAuth2 redirects are unchanged.
|
||||
|
||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
|
||||
})
|
||||
|
||||
For callbacks the provider can be used to query for user information such as email.
|
||||
|
||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify state...
|
||||
|
||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
userinfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// ...
|
||||
})
|
||||
|
||||
The provider also has the ability to verify ID Tokens.
|
||||
|
||||
verifier := provider.NewVerifier(ctx)
|
||||
|
||||
The returned verifier can be used to perform basic validation on ID Token issued by the provider,
|
||||
including verifying the JWT signature. It then returns the payload.
|
||||
|
||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify state...
|
||||
|
||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the ID Token from oauth2 token.
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
http.Error(w, "No ID Token found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify that the ID Token is signed by the provider.
|
||||
payload, err := verifier.Verify(rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal ID Token for expected custom claims.
|
||||
var idToken struct {
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &idToken); err != nil {
|
||||
http.Error(w, "Failed to unmarshal ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// ...
|
||||
})
|
||||
|
||||
ID Token nonces are supported.
|
||||
|
||||
First, provide a nonce source for nonce validation. This will then be used to wrap the existing
|
||||
provider ID Token verifier.
|
||||
|
||||
// A verifier which boths verifies the ID Token signature and nonce.
|
||||
nonceEnabledVerifier := provider.NewVerifier(ctx, oidc.VerifyNonce(nonceSource))
|
||||
|
||||
For the redirect provide a nonce auth code option. This will be placed as a URL parameter during
|
||||
the client redirect.
|
||||
|
||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
nonce, err := newNonce()
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
// Provide a nonce for the OpenID Connect ID Token.
|
||||
http.Redirect(w, r, oauth2Config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound)
|
||||
})
|
||||
|
||||
The nonce enabled verifier can then be used to verify the nonce while unpacking the ID Token.
|
||||
|
||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify state...
|
||||
|
||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the ID Token from oauth2 token.
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
http.Error(w, "No ID Token found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify that the ID Token is signed by the provider and verify the nonce.
|
||||
payload, err := nonceEnabledVerifier.Verify(rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Continue as above...
|
||||
})
|
||||
|
||||
This package uses contexts to derive HTTP clients in the same way as the oauth2 package. To configure
|
||||
a custom client, use the oauth2 packages HTTPClient context key when constructing the context.
|
||||
|
||||
myClient := &http.Client{}
|
||||
|
||||
myCtx := context.WithValue(parentCtx, oauth2.HTTPClient, myClient)
|
||||
|
||||
// NewProvider will use myClient to make the request.
|
||||
provider, err := oidc.NewProvider(myCtx, "https://accounts.example.com")
|
||||
*/
|
||||
package oidc
|
15
vendor/github.com/ericchiang/oidc/examples/README.md
generated
vendored
Normal file
15
vendor/github.com/ericchiang/oidc/examples/README.md
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Examples
|
||||
|
||||
These are example uses of the oidc package. Each requires a Google account and the
|
||||
client ID and secret of a registered OAuth2 application. The client ID and secret
|
||||
should be set as the following environment variables:
|
||||
|
||||
```
|
||||
GOOGLE_OAUTH2_CLIENT_ID
|
||||
GOOGLE_OAUTH2_CLIENT_SECRET
|
||||
```
|
||||
|
||||
See Google's documentation on how to set up an OAuth2 app:
|
||||
https://developers.google.com/identity/protocols/OpenIDConnect?hl=en
|
||||
|
||||
Note that one of the redirect URL's must be "http://127.0.0.1:5556/auth/google/callback"
|
86
vendor/github.com/ericchiang/oidc/examples/idtoken/app.go
generated
vendored
Normal file
86
vendor/github.com/ericchiang/oidc/examples/idtoken/app.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
This is an example application to demonstrate parsing an ID Token.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ericchiang/oidc"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
clientID = os.Getenv("GOOGLE_OAUTH2_CLIENT_ID")
|
||||
clientSecret = os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
verifier := provider.NewVerifier(ctx)
|
||||
|
||||
config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
RedirectURL: "http://127.0.0.1:5556/auth/google/callback",
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
|
||||
state := "foobar" // Don't do this in production.
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
|
||||
})
|
||||
|
||||
http.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("state") != state {
|
||||
http.Error(w, "state did not match", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Println(rawIDToken)
|
||||
idTokenPayload, err := verifier.Verify(rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
oauth2Token.AccessToken = "*REDACTED*"
|
||||
|
||||
rawMessage := json.RawMessage(idTokenPayload)
|
||||
resp := struct {
|
||||
OAuth2Token *oauth2.Token
|
||||
IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
|
||||
}{oauth2Token, &rawMessage}
|
||||
data, err := json.MarshalIndent(resp, "", " ")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
})
|
||||
|
||||
log.Printf("listening on http://%s/", "127.0.0.1:5556")
|
||||
log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
|
||||
}
|
99
vendor/github.com/ericchiang/oidc/examples/nonce/app.go
generated
vendored
Normal file
99
vendor/github.com/ericchiang/oidc/examples/nonce/app.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
This is an example application to demonstrate verifying an ID Token with a nonce.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ericchiang/oidc"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
clientID = os.Getenv("GOOGLE_OAUTH2_CLIENT_ID")
|
||||
clientSecret = os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET")
|
||||
)
|
||||
|
||||
const appNonce = "a super secret nonce"
|
||||
|
||||
// Create a nonce source.
|
||||
type nonceSource struct{}
|
||||
|
||||
func (n nonceSource) ClaimNonce(nonce string) error {
|
||||
if nonce != appNonce {
|
||||
return errors.New("unregonized nonce")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Use the nonce source to create a custom ID Token verifier.
|
||||
nonceEnabledVerifier := provider.NewVerifier(ctx, oidc.VerifyNonce(nonceSource{}))
|
||||
|
||||
config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
RedirectURL: "http://127.0.0.1:5556/auth/google/callback",
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
|
||||
state := "foobar" // Don't do this in production.
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, config.AuthCodeURL(state, oidc.Nonce(appNonce)), http.StatusFound)
|
||||
})
|
||||
|
||||
http.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("state") != state {
|
||||
http.Error(w, "state did not match", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Verify the ID Token signature and nonce.
|
||||
idTokenPayload, err := nonceEnabledVerifier.Verify(rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rawMessage := json.RawMessage(idTokenPayload)
|
||||
resp := struct {
|
||||
OAuth2Token *oauth2.Token
|
||||
IDToken *json.RawMessage // ID Token payload is just JSON.
|
||||
}{oauth2Token, &rawMessage}
|
||||
data, err := json.MarshalIndent(resp, "", " ")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
})
|
||||
|
||||
log.Printf("listening on http://%s/", "127.0.0.1:5556")
|
||||
log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
|
||||
}
|
76
vendor/github.com/ericchiang/oidc/examples/userinfo/app.go
generated
vendored
Normal file
76
vendor/github.com/ericchiang/oidc/examples/userinfo/app.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
This is an example application to demonstrate querying the user info endpoint.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ericchiang/oidc"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
clientID = os.Getenv("GOOGLE_OAUTH2_CLIENT_ID")
|
||||
clientSecret = os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
RedirectURL: "http://127.0.0.1:5556/auth/google/callback",
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
|
||||
state := "foobar" // Don't do this in production.
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
|
||||
})
|
||||
|
||||
http.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("state") != state {
|
||||
http.Error(w, "state did not match", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
OAuth2Token *oauth2.Token
|
||||
UserInfo *oidc.UserInfo
|
||||
}{oauth2Token, userInfo}
|
||||
data, err := json.MarshalIndent(resp, "", " ")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
})
|
||||
|
||||
log.Printf("listening on http://%s/", "127.0.0.1:5556")
|
||||
log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
|
||||
}
|
7
vendor/github.com/ericchiang/oidc/internal/oidc.go
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/internal/oidc.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// Package internal contains support packages for the oidc package.
|
||||
package internal
|
||||
|
||||
// ContextKey is just an empty struct. It exists so context keys can be an immutable
|
||||
// public variable with a unique type. It's immutable because nobody else can create
|
||||
// a ContextKey, being unexported.
|
||||
type ContextKey struct{}
|
188
vendor/github.com/ericchiang/oidc/jwks.go
generated
vendored
Normal file
188
vendor/github.com/ericchiang/oidc/jwks.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/cachecontrol"
|
||||
"golang.org/x/net/context"
|
||||
jose "gopkg.in/square/go-jose.v1"
|
||||
)
|
||||
|
||||
// No matter what insist on caching keys. This is so our request code can be
|
||||
// asynchronous from matching keys. If the request code retrieved keys that
|
||||
// expired immediately, the goroutine to match a JWT to a key would always see
|
||||
// expired keys.
|
||||
//
|
||||
// TODO(ericchiang): Review this logic.
|
||||
var minCache = 2 * time.Minute
|
||||
|
||||
type cachedKeys struct {
|
||||
keys map[string]jose.JsonWebKey // immutable
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
type remoteKeySet struct {
|
||||
client *http.Client
|
||||
|
||||
// "jwks_uri" from discovery.
|
||||
keysURL string
|
||||
|
||||
// The value is always of type *cachedKeys.
|
||||
//
|
||||
// To ensure consistency always call keyCache.Store when holding cond.L.
|
||||
keyCache atomic.Value
|
||||
|
||||
// cond.L guards all following fields. sync.Cond is used in place of a mutex
|
||||
// so multiple processes can wait on a single request to update keys.
|
||||
cond sync.Cond
|
||||
// Is there an existing request to get the remote keys?
|
||||
inflight bool
|
||||
// If the last attempt to refresh keys failed, the error will be saved here.
|
||||
//
|
||||
// TODO(ericchiang): If a routine sets this before calling cond.Broadcast(),
|
||||
// there's no guarentee that a routine calling cond.Wait() will actual see
|
||||
// the error called by the previous routine. Since Broadcast() unlocks
|
||||
// cond.L and Wait() must reacquire the lock, other routines waiting on the
|
||||
// lock might acquire it first. Maybe just log the error?
|
||||
lastErr error
|
||||
}
|
||||
|
||||
func newRemoteKeySet(ctx context.Context, jwksURL string) *remoteKeySet {
|
||||
r := &remoteKeySet{
|
||||
client: contextClient(ctx),
|
||||
keysURL: jwksURL,
|
||||
cond: sync.Cond{L: new(sync.Mutex)},
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *remoteKeySet) verifyJWT(jwt string) (payload []byte, err error) {
|
||||
jws, err := jose.ParseSigned(jwt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing jwt: %v", err)
|
||||
}
|
||||
keyIDs := make([]string, len(jws.Signatures))
|
||||
for i, signature := range jws.Signatures {
|
||||
keyIDs[i] = signature.Header.KeyID
|
||||
}
|
||||
key, err := r.getKey(keyIDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oidc: %s", err)
|
||||
}
|
||||
return jws.Verify(key)
|
||||
}
|
||||
|
||||
func (r *remoteKeySet) getKeyFromCache(keyIDs []string) (*jose.JsonWebKey, bool) {
|
||||
cachedKeys, ok := r.keyCache.Load().(*cachedKeys)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if time.Now().After(cachedKeys.expiry) {
|
||||
return nil, false
|
||||
}
|
||||
for _, keyID := range keyIDs {
|
||||
if key, ok := cachedKeys.keys[keyID]; ok {
|
||||
return &key, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (r *remoteKeySet) getKey(keyIDs []string) (*jose.JsonWebKey, error) {
|
||||
// Fast path. Just do an atomic load.
|
||||
if key, ok := r.getKeyFromCache(keyIDs); ok {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Didn't find keys, use the slow path.
|
||||
r.cond.L.Lock()
|
||||
defer r.cond.L.Unlock()
|
||||
|
||||
// Check again within the mutex.
|
||||
if key, ok := r.getKeyFromCache(keyIDs); ok {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Keys have expired or we're trying to verify a JWT we don't have a key for.
|
||||
|
||||
if !r.inflight {
|
||||
// There isn't currently an inflight request to update keys, start a
|
||||
// goroutine to do so.
|
||||
r.inflight = true
|
||||
go func() {
|
||||
newKeys, newExpiry, err := requestKeys(r.client, r.keysURL)
|
||||
|
||||
r.cond.L.Lock()
|
||||
defer r.cond.L.Unlock()
|
||||
|
||||
r.inflight = false
|
||||
if err != nil {
|
||||
r.lastErr = err
|
||||
} else {
|
||||
r.keyCache.Store(&cachedKeys{newKeys, newExpiry})
|
||||
r.lastErr = nil
|
||||
}
|
||||
|
||||
r.cond.Broadcast() // Wake all r.cond.Wait() calls.
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for r.cond.Broadcast() to be called. This unlocks r.cond.L and
|
||||
// reacquires it after its done waiting.
|
||||
r.cond.Wait()
|
||||
|
||||
if key, ok := r.getKeyFromCache(keyIDs); ok {
|
||||
return key, nil
|
||||
}
|
||||
if r.lastErr != nil {
|
||||
return nil, r.lastErr
|
||||
}
|
||||
return nil, errors.New("no signing keys can validate the signature")
|
||||
}
|
||||
|
||||
func requestKeys(client *http.Client, keysURL string) (map[string]jose.JsonWebKey, time.Time, error) {
|
||||
req, err := http.NewRequest("GET", keysURL, nil)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, fmt.Errorf("can't create request: %v", err)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, fmt.Errorf("can't GET new keys %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err != nil {
|
||||
return nil, time.Time{}, fmt.Errorf("can't fetch new keys: %v", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, time.Time{}, fmt.Errorf("can't fetch new keys: %s %s", resp.Status, body)
|
||||
}
|
||||
|
||||
var keySet jose.JsonWebKeySet
|
||||
if err := json.Unmarshal(body, &keySet); err != nil {
|
||||
return nil, time.Time{}, fmt.Errorf("can't decode keys: %v %s", err, body)
|
||||
}
|
||||
|
||||
keys := make(map[string]jose.JsonWebKey, len(keySet.Keys))
|
||||
for _, key := range keySet.Keys {
|
||||
keys[key.KeyID] = key
|
||||
}
|
||||
|
||||
minExpiry := time.Now().Add(minCache)
|
||||
|
||||
if _, expiry, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{}); err == nil {
|
||||
if minExpiry.Before(expiry) {
|
||||
return keys, expiry, nil
|
||||
}
|
||||
}
|
||||
return keys, minExpiry, nil
|
||||
}
|
285
vendor/github.com/ericchiang/oidc/jwks_test.go
generated
vendored
Normal file
285
vendor/github.com/ericchiang/oidc/jwks_test.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
43
vendor/github.com/ericchiang/oidc/nonce.go
generated
vendored
Normal file
43
vendor/github.com/ericchiang/oidc/nonce.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Nonce returns an auth code option which requires the ID Token created by the
|
||||
// OpenID Connect provider to contain the specified nonce.
|
||||
func Nonce(nonce string) oauth2.AuthCodeOption {
|
||||
return oauth2.SetAuthURLParam("nonce", nonce)
|
||||
}
|
||||
|
||||
// NonceSource represents a source which can verify a nonce is valid and has not
|
||||
// been claimed before.
|
||||
type NonceSource interface {
|
||||
ClaimNonce(nonce string) error
|
||||
}
|
||||
|
||||
// VerifyNonce ensures that the ID Token contains a nonce which can be claimed by the nonce source.
|
||||
func VerifyNonce(source NonceSource) VerificationOption {
|
||||
return nonceVerifier{source}
|
||||
}
|
||||
|
||||
type nonceVerifier struct {
|
||||
nonceSource NonceSource
|
||||
}
|
||||
|
||||
func (n nonceVerifier) verifyIDTokenPayload(payload []byte) error {
|
||||
var token struct {
|
||||
Nonce string `json:"nonce"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &token); err != nil {
|
||||
return fmt.Errorf("oidc: failed to unmarshal nonce: %v", err)
|
||||
}
|
||||
if token.Nonce == "" {
|
||||
return errors.New("oidc: no nonce present in ID Token")
|
||||
}
|
||||
return n.nonceSource.ClaimNonce(token.Nonce)
|
||||
}
|
236
vendor/github.com/ericchiang/oidc/oidc.go
generated
vendored
Normal file
236
vendor/github.com/ericchiang/oidc/oidc.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrTokenExpired indicates that a token parsed by a verifier has expired.
|
||||
ErrTokenExpired = errors.New("ID Token expired")
|
||||
// ErrNotSupported indicates that the requested optional OpenID Connect endpoint is not supported by the provider.
|
||||
ErrNotSupported = errors.New("endpoint not supported")
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
|
||||
ScopeOpenID = "openid"
|
||||
|
||||
// ScopeOfflineAccess is an optional scope defined by OpenID Connect for requesting
|
||||
// OAuth2 refresh tokens.
|
||||
//
|
||||
// Support for this scope differs between OpenID Connect providers. For instance
|
||||
// Google rejects it, favoring appending "access_type=offline" as part of the
|
||||
// authorization request instead.
|
||||
//
|
||||
// See: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
||||
ScopeOfflineAccess = "offline_access"
|
||||
)
|
||||
|
||||
// Provider contains the subset of the OpenID Connect provider metadata needed to request
|
||||
// and verify ID Tokens.
|
||||
type Provider struct {
|
||||
Issuer string `json:"issuer"`
|
||||
AuthURL string `json:"authorization_endpoint"`
|
||||
TokenURL string `json:"token_endpoint"`
|
||||
JWKSURL string `json:"jwks_uri"`
|
||||
UserInfoURL string `json:"userinfo_endpoint"`
|
||||
|
||||
// Optionally contains extra claims.
|
||||
raw map[string]interface{}
|
||||
}
|
||||
|
||||
// NewProvider uses the OpenID Connect disovery mechanism to construct a Provider.
|
||||
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
|
||||
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
|
||||
resp, err := contextClient(ctx).Get(wellKnown)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var p Provider
|
||||
if err := json.Unmarshal(body, &p); err != nil {
|
||||
return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
|
||||
}
|
||||
// raw claims do not get error checks
|
||||
json.Unmarshal(body, &p.raw)
|
||||
if p.Issuer != issuer {
|
||||
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// Extra returns additional fields returned by the server during discovery.
|
||||
func (p *Provider) Extra(key string) interface{} {
|
||||
if p.raw != nil {
|
||||
return p.raw[key]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
|
||||
func (p *Provider) Endpoint() oauth2.Endpoint {
|
||||
return oauth2.Endpoint{AuthURL: p.AuthURL, TokenURL: p.TokenURL}
|
||||
}
|
||||
|
||||
// UserInfo represents the OpenID Connect userinfo claims.
|
||||
type UserInfo struct {
|
||||
Subject string `json:"sub"`
|
||||
Profile string `json:"profile"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
|
||||
// Optionally contains extra claims.
|
||||
raw map[string]interface{}
|
||||
}
|
||||
|
||||
// Extra returns additional claims returned by the server.
|
||||
func (u *UserInfo) Extra(key string) interface{} {
|
||||
if u.raw != nil {
|
||||
return u.raw[key]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserInfo uses the token source to query the provider's user info endpoint.
|
||||
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
|
||||
if p.UserInfoURL == "" {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
cli := oauth2.NewClient(ctx, tokenSource)
|
||||
resp, err := cli.Get(p.UserInfoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
||||
}
|
||||
|
||||
var userInfo UserInfo
|
||||
if err := json.Unmarshal(body, &userInfo); err != nil {
|
||||
return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err)
|
||||
}
|
||||
// raw claims do not get error checks
|
||||
json.Unmarshal(body, &userInfo.raw)
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
// IDTokenVerifier provides verification for ID Tokens.
|
||||
type IDTokenVerifier struct {
|
||||
issuer string
|
||||
keySet *remoteKeySet
|
||||
options []VerificationOption
|
||||
}
|
||||
|
||||
// Verify parse the raw ID Token, verifies it's been signed by the provider, preforms
|
||||
// additional verification, such as checking the expiration, and returns the claims.
|
||||
func (v *IDTokenVerifier) Verify(rawIDToken string) (payload []byte, err error) {
|
||||
payload, err = v.keySet.verifyJWT(rawIDToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var token struct {
|
||||
Exp float64 `json:"exp"` // JSON numbers are always float64s.
|
||||
Issuer string `json:"iss"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &token); err != nil {
|
||||
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
|
||||
}
|
||||
if v.issuer != token.Issuer {
|
||||
return nil, fmt.Errorf("oidc: iss field did not match provider issuer")
|
||||
}
|
||||
if time.Unix(int64(token.Exp), 0).Before(time.Now().Round(time.Second)) {
|
||||
return nil, ErrTokenExpired
|
||||
}
|
||||
for _, option := range v.options {
|
||||
if err := option.verifyIDTokenPayload(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// NewVerifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
|
||||
//
|
||||
// The verifier queries the provider to update keys when a signature cannot be verified by the
|
||||
// set of keys cached from the previous request.
|
||||
func (p *Provider) NewVerifier(ctx context.Context, options ...VerificationOption) *IDTokenVerifier {
|
||||
return &IDTokenVerifier{
|
||||
issuer: p.Issuer,
|
||||
keySet: newRemoteKeySet(ctx, p.JWKSURL),
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// VerificationOption is an option provided to Provider.NewVerifier.
|
||||
type VerificationOption interface {
|
||||
verifyIDTokenPayload(raw []byte) error
|
||||
}
|
||||
|
||||
// VerifyAudience ensures that an ID Token was issued for the specific client.
|
||||
//
|
||||
// Note that a verified token may be valid for other clients, as OpenID Connect allows a token to have
|
||||
// multiple audiences.
|
||||
func VerifyAudience(clientID string) VerificationOption {
|
||||
return clientVerifier{clientID}
|
||||
}
|
||||
|
||||
type clientVerifier struct {
|
||||
clientID string
|
||||
}
|
||||
|
||||
func (c clientVerifier) verifyIDTokenPayload(payload []byte) error {
|
||||
var token struct {
|
||||
Aud string `json:"aud"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &token); err == nil {
|
||||
if token.Aud != c.clientID {
|
||||
return errors.New("oidc: id token aud field did not match client_id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Aud can optionally be an array of strings
|
||||
var token2 struct {
|
||||
Aud []string `json:"aud"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &token2); err != nil {
|
||||
return fmt.Errorf("oidc: failed to unmarshal aud claim: %v", err)
|
||||
}
|
||||
for _, aud := range token2.Aud {
|
||||
if aud == c.clientID {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("oidc: id token aud field did not match client_id")
|
||||
}
|
||||
|
||||
// This method is internal to golang.org/x/oauth2. Just copy it.
|
||||
func contextClient(ctx context.Context) *http.Client {
|
||||
if ctx != nil {
|
||||
if hc, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
|
||||
return hc
|
||||
}
|
||||
}
|
||||
return http.DefaultClient
|
||||
}
|
54
vendor/github.com/ericchiang/oidc/oidc_test.go
generated
vendored
Normal file
54
vendor/github.com/ericchiang/oidc/oidc_test.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package oidc
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestClientVerifier(t *testing.T) {
|
||||
tests := []struct {
|
||||
clientID string
|
||||
payload string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
clientID: "1",
|
||||
payload: `{"aud":"1"}`,
|
||||
},
|
||||
{
|
||||
clientID: "1",
|
||||
payload: `{"aud":"2"}`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
clientID: "1",
|
||||
payload: `{"aud":["1"]}`,
|
||||
},
|
||||
{
|
||||
clientID: "1",
|
||||
payload: `{"aud":["1", "2"]}`,
|
||||
},
|
||||
{
|
||||
clientID: "3",
|
||||
payload: `{"aud":["1", "2"]}`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
clientID: "3",
|
||||
payload: `{"aud":}`, // invalid JSON
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
clientID: "1",
|
||||
payload: `{}`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
err := (clientVerifier{tc.clientID}).verifyIDTokenPayload([]byte(tc.payload))
|
||||
if err != nil && !tc.wantErr {
|
||||
t.Errorf("case %d: %v", i)
|
||||
}
|
||||
if err == nil && tc.wantErr {
|
||||
t.Errorf("case %d: expected error")
|
||||
}
|
||||
}
|
||||
}
|
283
vendor/github.com/ericchiang/oidc/oidcproxy/main.go
generated
vendored
Normal file
283
vendor/github.com/ericchiang/oidc/oidcproxy/main.go
generated
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ericchiang/oidc"
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
cookieName = "oidc-proxy"
|
||||
// This header will be set by oidcproxy during authentication and
|
||||
// passed to the backend.
|
||||
emailHeaderName = "X-User-Email"
|
||||
)
|
||||
|
||||
// Session represents a logged in user's active session.
|
||||
type Session struct {
|
||||
Email string
|
||||
Expires time.Time
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(&Session{})
|
||||
}
|
||||
|
||||
var (
|
||||
// Flags.
|
||||
issuer string
|
||||
backend string
|
||||
scopes string
|
||||
allow string
|
||||
httpAddr string
|
||||
httpsAddr string
|
||||
cookieExp time.Duration
|
||||
|
||||
// Set up during initial configuration.
|
||||
oauth2Config = new(oauth2.Config)
|
||||
oidcProvider *oidc.Provider
|
||||
backendHandler *httputil.ReverseProxy
|
||||
verifier *oidc.IDTokenVerifier
|
||||
|
||||
// Regexps of emails to allow.
|
||||
allowEmail []*regexp.Regexp
|
||||
|
||||
nonceSource *memNonceSource
|
||||
|
||||
cookieEncrypter *securecookie.SecureCookie
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&issuer, "issuer", "https://accounts.google.com", "The issuer URL of the OpenID Connect provider.")
|
||||
flag.StringVar(&backend, "backend", "", "The URL of the backened to proxy to.")
|
||||
flag.StringVar(&oauth2Config.RedirectURL, "redirect-url", "", "A full OAuth2 redirect URL.")
|
||||
flag.StringVar(&oauth2Config.ClientID, "client-id", "", "The client ID of the OAuth2 client.")
|
||||
flag.StringVar(&oauth2Config.ClientSecret, "client-secret", "", "The client secret of the OAuth2 client.")
|
||||
flag.StringVar(&scopes, "scopes", "openid,email,profile", `A comma seprated list of OAuth2 scopes to request ("openid" required).`)
|
||||
flag.StringVar(&allow, "allow-email", ".*", "Comma seperated list of email regexp's to match for access to the backend.")
|
||||
flag.StringVar(&httpAddr, "http", "127.0.0.1:5556", "Default address to listen on.")
|
||||
flag.DurationVar(&cookieExp, "cookie-exp", time.Hour*24, "Duration for which a login cookie is valid for.")
|
||||
flag.Parse()
|
||||
|
||||
// Set flags from environment variables.
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
if f.Value.String() != f.DefValue {
|
||||
return
|
||||
}
|
||||
|
||||
// Convert flag name, e.g. "redirect-url" becomes "OIDC_PROXY_REDIRECT_URL"
|
||||
envVar := "OIDC_PROXY_" + strings.ToUpper(strings.Replace(f.Name, "-", "_", -1))
|
||||
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
if err := flag.Set(f.Name, envVal); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
// All flags are manditory.
|
||||
if f.Value.String() == "" {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
})
|
||||
|
||||
// compile email regexps
|
||||
for _, expr := range strings.Split(allow, ",") {
|
||||
allowEmailRegexp, err := regexp.Compile(expr)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid regexp: %q %v", expr, err)
|
||||
}
|
||||
allowEmail = append(allowEmail, allowEmailRegexp)
|
||||
}
|
||||
|
||||
// configure reverse proxy
|
||||
backendURL, err := url.Parse(backend)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse backend: %v", err)
|
||||
}
|
||||
backendHandler = httputil.NewSingleHostReverseProxy(backendURL)
|
||||
|
||||
redirectURL, err := url.Parse(oauth2Config.RedirectURL)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse redirect URL: %v", err)
|
||||
}
|
||||
|
||||
// Query for the provider.
|
||||
oidcProvider, err = oidc.NewProvider(context.TODO(), issuer)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get provider: %v", err)
|
||||
}
|
||||
|
||||
nonceSource = newNonceSource(context.TODO())
|
||||
verifier = oidcProvider.NewVerifier(context.TODO(), oidc.VerifyNonce(nonceSource))
|
||||
|
||||
oauth2Config.Endpoint = oidcProvider.Endpoint()
|
||||
oauth2Config.Scopes = strings.Split(scopes, ",")
|
||||
|
||||
// Initialize secure cookies.
|
||||
// TODO(ericchiang): make these configurable
|
||||
hashKey := make([]byte, 64)
|
||||
blockKey := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, hashKey); err != nil {
|
||||
log.Fatalf("failed to initialize hash key: %v", err)
|
||||
}
|
||||
if _, err := io.ReadFull(rand.Reader, blockKey); err != nil {
|
||||
log.Fatalf("failed to initialize block key: %v", err)
|
||||
}
|
||||
cookieEncrypter = securecookie.New(hashKey, blockKey)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", handleProxy)
|
||||
mux.HandleFunc("/login", handleRedirect)
|
||||
mux.HandleFunc("/logout", handleLogout)
|
||||
mux.HandleFunc(redirectURL.Path, handleCallback)
|
||||
|
||||
log.Printf("Listening on: %s", httpAddr)
|
||||
http.ListenAndServe(httpAddr, mux)
|
||||
}
|
||||
|
||||
// httpRedirect returns a handler which redirects to the provided path.
|
||||
func httpRedirect(path string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, path, http.StatusFound)
|
||||
})
|
||||
}
|
||||
|
||||
// httpError returns a handler which presents an error to the end user.
|
||||
func httpError(status int, format string, a ...interface{}) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, fmt.Sprintf(format, a...), http.StatusInternalServerError)
|
||||
})
|
||||
}
|
||||
|
||||
func handleCallback(w http.ResponseWriter, r *http.Request) {
|
||||
func() http.Handler {
|
||||
state := r.URL.Query().Get("state")
|
||||
if state == "" {
|
||||
log.Printf("State not set")
|
||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
||||
}
|
||||
if err := nonceSource.ClaimNonce(state); err != nil {
|
||||
log.Printf("Failed to claim nonce: %v", err)
|
||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
||||
}
|
||||
|
||||
oauth2Token, err := oauth2Config.Exchange(context.TODO(), r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
log.Printf("Failed to exchange token: %v", err)
|
||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
||||
}
|
||||
|
||||
// Extract the ID Token from oauth2 token.
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
log.Println("No ID Token found")
|
||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
||||
}
|
||||
|
||||
payload, err := verifier.Verify(rawIDToken)
|
||||
if err != nil {
|
||||
log.Printf("Failed to verify token: %v", err)
|
||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
||||
}
|
||||
var claims struct {
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &claims); err != nil {
|
||||
log.Printf("Failed to decode claims: %v", err)
|
||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
||||
}
|
||||
if !claims.EmailVerified || claims.Email == "" {
|
||||
log.Println("Failed to verify email")
|
||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
||||
}
|
||||
|
||||
s := Session{Email: claims.Email, Expires: time.Now().Add(cookieExp)}
|
||||
encoded, err := cookieEncrypter.Encode(cookieName, s)
|
||||
if err != nil {
|
||||
log.Printf("Failed to encrypt session: %v", err)
|
||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
||||
}
|
||||
|
||||
// Set the encoded cookie
|
||||
cookie := &http.Cookie{Name: cookieName, Value: encoded, HttpOnly: true, Path: "/"}
|
||||
http.SetCookie(w, cookie)
|
||||
return httpRedirect("/")
|
||||
|
||||
}().ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO(ericchiang): since arbitrary requests can create nonces, rate limit this endpoint.
|
||||
func() http.Handler {
|
||||
nonce, err := nonceSource.Nonce()
|
||||
if err != nil {
|
||||
log.Printf("Failed to create nonce: %v", err)
|
||||
return httpError(http.StatusInternalServerError, "Failed to generate redirect")
|
||||
}
|
||||
state, err := nonceSource.Nonce()
|
||||
if err != nil {
|
||||
log.Printf("Failed to create state: %v", err)
|
||||
return httpError(http.StatusInternalServerError, "Failed to generate redirect")
|
||||
}
|
||||
return httpRedirect(oauth2Config.AuthCodeURL(state, oauth2.ApprovalForce, oidc.Nonce(nonce)))
|
||||
}().ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := &http.Cookie{Name: cookieName, Value: "", HttpOnly: true, Path: "/"}
|
||||
http.SetCookie(w, cookie)
|
||||
httpRedirect("/login").ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func handleProxy(w http.ResponseWriter, r *http.Request) {
|
||||
func() http.Handler {
|
||||
cookie, err := r.Cookie(cookieName)
|
||||
if err != nil {
|
||||
// Only error can be ErrNoCookie https://goo.gl/o5fZ49
|
||||
return httpRedirect("/login")
|
||||
}
|
||||
var s Session
|
||||
if err := cookieEncrypter.Decode(cookieName, cookie.Value, &s); err != nil {
|
||||
log.Printf("Failed to decode cookie: %v", err)
|
||||
return http.HandlerFunc(handleLogout) // clear the cookie
|
||||
}
|
||||
if time.Now().After(s.Expires) {
|
||||
log.Printf("Cookie for %q expired", s.Email)
|
||||
return http.HandlerFunc(handleLogout) // clear the cookie
|
||||
}
|
||||
|
||||
for _, allow := range allowEmail {
|
||||
if allow.MatchString(s.Email) {
|
||||
r.Header.Set(emailHeaderName, s.Email)
|
||||
return backendHandler
|
||||
}
|
||||
}
|
||||
log.Printf("Denying %q", s.Email)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
resp := []byte(`<html><head></head><body>Provided email does not have permission to login. <a href="/logout">Try a different account.</a></body></html>`)
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(resp)))
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write(resp)
|
||||
})
|
||||
}().ServeHTTP(w, r)
|
||||
}
|
72
vendor/github.com/ericchiang/oidc/oidcproxy/nonce.go
generated
vendored
Normal file
72
vendor/github.com/ericchiang/oidc/oidcproxy/nonce.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
gcInterval = time.Minute
|
||||
expiresIn = time.Minute * 10
|
||||
)
|
||||
|
||||
type memNonceSource struct {
|
||||
mu sync.Mutex
|
||||
nonces map[string]time.Time
|
||||
}
|
||||
|
||||
func newNonceSource(ctx context.Context) *memNonceSource {
|
||||
s := &memNonceSource{nonces: make(map[string]time.Time)}
|
||||
go s.garbageCollect(ctx)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *memNonceSource) Nonce() (string, error) {
|
||||
buff := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, buff); err != nil {
|
||||
return "", err
|
||||
}
|
||||
nonce := base64.RawURLEncoding.EncodeToString(buff)
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.nonces[nonce] = time.Now().Add(expiresIn)
|
||||
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
func (s *memNonceSource) ClaimNonce(nonce string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if _, ok := s.nonces[nonce]; ok {
|
||||
delete(s.nonces, nonce)
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid nonce")
|
||||
}
|
||||
|
||||
func (s *memNonceSource) garbageCollect(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(gcInterval):
|
||||
s.mu.Lock()
|
||||
now := time.Now()
|
||||
|
||||
for nonce, exp := range s.nonces {
|
||||
if now.After(exp) {
|
||||
delete(s.nonces, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_1.pem
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_1.pem
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIA3zFNhDsB70vMBOzeK48Zn6oic5wfx9xto4ErduEVKFST0aJEfjLO
|
||||
/kzNrKDgXArCEl2KYJQfb8J9lslA7cLvpVSgBwYFK4EEACOhgYkDgYYABABxyN4Y
|
||||
6VxH/86lgejSlHGrjKVSzn6YeOukabBSiU8PS/o/wfGXKX4eKCkJYqVq18zGAfcL
|
||||
q+UM09ZQv/De7mGkXwC67qQv7fS7tJ/t0uFcxriQNtVGPsL4/+YmWrFJBTlK0OgD
|
||||
mkSBG7ERdb5x/JzNFbajSbX0wKzs4VOZU0VVj/2DJw==
|
||||
-----END EC PRIVATE KEY-----
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_2.pem
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_2.pem
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIAtlsqZlQW4bWtSmDgLjbhcCgmmWEVwtzHMTZRoQnUv4rdTxCeKLY3
|
||||
hjxzd5hPCmtfP68GyJhKQgKofKYD/DgQLc2gBwYFK4EEACOhgYkDgYYABAABrcbo
|
||||
t8KEfgzslg4Bb7t0khCgFrT2hX5htSWnwwHiScs1yO9egRcftZg/WAoIo/QDID+i
|
||||
OB4f5Flg5PygZpm/SwE4B1E8dGpyLRmBg3cC0/PfuRkGZ2E5POKZqsiRU5TkvC2D
|
||||
AkwVr6UPpXPheStrp2qh6ptBUtZRzn8Q4lVFKoKe9g==
|
||||
-----END EC PRIVATE KEY-----
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_3.pem
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_3.pem
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIAzUq62J5hiC8B5xw9M/e9KlSeO66uq9PHRSGcFY4d9MFgFKILKU0u
|
||||
cfUBCwbhOnDWkdUTp1DkLWeNhE0UUvN2FgegBwYFK4EEACOhgYkDgYYABAHH7ZyR
|
||||
YXJ9oDJ/KohiQFFXqkvspk3ljvBAFUFRyfL4Q40TtgKGt5YBNmU3SHNHn3fJdjQy
|
||||
xe2OZcmKYxzwCjx6mgACI0IoiIdgZN0RBy8UMhgn810C/iDg+nOZScl7P0t3DFcv
|
||||
H3K5+tkVWe8lLBIUOkqEyHmmfHGYcn6Kc5jHEnAebA==
|
||||
-----END EC PRIVATE KEY-----
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_4.pem
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_4.pem
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIBt+T9wRbTfN3T5kSqfT5nqCt65w+SGAQ5DXQgcf7gCXId+Ux/57MA
|
||||
/Dld+PvG+T8mobr1/jaFiGOLLRsjtnc5Ml+gBwYFK4EEACOhgYkDgYYABAE/ka2T
|
||||
p7MsBezSgeATljES2xBY4wDOcjMmI6MzHdiO9hU/xcIQnhc2tjML2QZSMTuLy1ZQ
|
||||
Yjhu0ZRg5Dxj4m7mgAFp2f/FqtOSAR5vuikaYPzHwosvNFIIpRDJCZ23j6qbtemF
|
||||
5qXUlSXf2+W491rfb2njNwTWx8BLn1M3fFhobK+O9Q==
|
||||
-----END EC PRIVATE KEY-----
|
15
vendor/github.com/ericchiang/oidc/testdata/gen.sh
generated
vendored
Executable file
15
vendor/github.com/ericchiang/oidc/testdata/gen.sh
generated
vendored
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
for i in $(seq 1 4); do
|
||||
openssl ecparam -out ecdsa_521_${i}.pem -name secp521r1 -genkey -noout
|
||||
done
|
||||
|
||||
for i in $(seq 1 4); do
|
||||
openssl genrsa -out rsa_2048_${i}.pem 2048
|
||||
done
|
||||
|
||||
for i in $(seq 1 4); do
|
||||
openssl genrsa -out rsa_4096_${i}.pem 4096
|
||||
done
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_1.pem
generated
vendored
Normal file
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_1.pem
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA05kMh0SJfLTbcAhpq1w8jpTuo+Shy95WgvYc1KzxcH6ol/nq
|
||||
vFSn1VCT/OHyg1t8BGeIfeuckIppT+cRtTjrsqS6FEmJA3lAiRqJZLWbewyXgoaZ
|
||||
eCsmMS0s3w8KUon4Z9t8rFVqv2fkki6p7FtLPjPD0PsTRNCPwtOGM8Ci+0qFuCrt
|
||||
4flUr6DpiALkqN1PSJCAwL22y8C86S6PPBU2seR3TWdD+iMAmr3Rezh+J/JqYXK+
|
||||
4qORzE6mA3hjn+goULQif5fUbYG0vRSyEp8UrlzevS88+ZzxyS9iTZ6H1ympLcsV
|
||||
PeqvCIXPd1OJXn6ZGSuIgOlgZuaieKeuYLHDCQIDAQABAoIBAHrxC+R0H+YDNxRq
|
||||
7uqPlufJBLbZGmDXeDBzSuEO8uFH1jEnFgoCrdk1Dib6KOvFddMhTJ7NDJS2tuWj
|
||||
/hfrUJblOvCaoS8Rfjuq3XVUR1hBQq6mAfleKLyd4NphZL/8RgYh8tg2cOVxOc7t
|
||||
qfEYQimL7hQ4LUPoYf7y46CiJpAV+BIwrR74k9Y3vcpumLwWrkwlfLTMWmcaiJE+
|
||||
gQBVl5+CQZmVKohTPDfCnQ9+ISzxvh4nesiQORMljG/ssQaZi5h7VEJDqGOaDgVB
|
||||
CsFp9fxLrQzx1Gjxv/uEhG/k45uAU1qgNZcL3/XQhyCgadUsuMM3yOndIaiQNbNN
|
||||
7bm1b9kCgYEA6XICJjg6vRbIO7nS5VYW1efFhLlqYjFimjv5f5vaTWA3FM8S1crL
|
||||
HG4Q8yh+CBOwY0mtcwr8RjlnX5Dsi+wGJgFjaL7OG0MDojv9/YKZse4dIHELUPKC
|
||||
Wj1zxiE23JsMAWKXvkhgGJgkC4mksZHVVszcLOEvmn2uZghDtTxhpw8CgYEA6Aqr
|
||||
NipU2MPPOe0Hu2Ar6Kb/EaJyJA2G8lABWILwyI6Wv13dpZz1FfjcpnsZVRNBuYla
|
||||
O3OKZun537kQOfHMWJfZyj2fOrJ0z3rsWZ+nbbUy1um8Z6jHTPqQyNoelE5/QTvs
|
||||
CJ0jDzotbUYsvE4TdOH2EzSneecAgBN7Brz/tGcCgYEAuSpcOBKbzMZYVr+DX7NU
|
||||
c6Dek/M6Rd6kNnBh620k0AEET7YcW4X6a3eGbEjvBtsPKwIS2VCaX91CeJQMfMPe
|
||||
8KBjSH8oHomeRT3Orhm8bVzQr53a+v8QlCFwRnSr/nnhIOwiLqVby8ZJuPkZsFtb
|
||||
W/ksn1CSoLkV7wqZIhVd49MCgYEArkFU0hh4H1DtDlMyu0Q9tTmz00pq7Sg7bz0l
|
||||
xZKPwA1Up+GV0glNBHMfQOaw33LWqL69RGhAR4juXVRdGya6js16gKZGLY5Wqnll
|
||||
hOigk4K/6yUcl7vn76c7k5o53KYWaqbVWqKm8Yh/FNDeR4takSwf38xq+ODBP21h
|
||||
tm24mYECgYBUS4Ox2dzDBPIKVfxyuV+FviM9wYKBd9G2xUYavF5Id/0byYOkOy11
|
||||
K9L4NJI4Xztzw6KZw7ngUsBmK9AN60mLO1SkHyMr7dLanyt03X46jhtEQem+wDqf
|
||||
HgKcqIz0gxaU6+widaEM33/cTi+kafH2uLr13aHU23ZfMV7oeRk3XQ==
|
||||
-----END RSA PRIVATE KEY-----
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_2.pem
generated
vendored
Normal file
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_2.pem
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAsB/GrXd+Ygz/Gq0nxKX06cLh4ECe+AfINVz+LdBCY1R8ossV
|
||||
1v2Rk6M59EMZwsR3XuzCoioEGzI+QyEUUQO+FpsDMpZ3iL4+dTCZIo9XXfHyQEnq
|
||||
JCy2w6Al2Xr6qTvfHBCMn61IGwAgDhDDb0djclJhqLhgxAVwXyxt3+pVSnUqdKtt
|
||||
h0tgLqyEgqX19QjDEh0xhFT+zB/IHmolaJO1DelvDLYmyaxoOCizGb2WyMcjkZ6m
|
||||
sdV/rYpzBW1BjA9N1+gnTsPF1cQ6wJJTtDRjovKWtqCQSmmPnQSVNwIz8+zI9FUc
|
||||
qPM6gP6v56bcpmqPtPylY6T3GbGxBQtW+1Z2/QIDAQABAoIBAFNvikiNTlMXAxdZ
|
||||
JnjTgfXn++en1Wd9EEyvdD6x5XF3CeB5QyxpTbjaX88mpqKNPlu63+3A59cWc0aL
|
||||
+jrzAe9lmhsyCwi9z4rm7fTgYSxBPVlVatWeVSrRyHyB9RONKIH8GRJgHcOkyIrB
|
||||
SESEVklHW7p5NmZGiVidDKRCOAugMpnYbOB2Nf7wI7cxHT8QcxcKFaQTdCSnYv0D
|
||||
eMWpEmP8vsEmnme8Q6Uax470yBHQQvI7JfWUIbh3wZVdDLllbr+E17ej8EHatCyD
|
||||
A6J1lLZ72DJy37G/n3kLLtHy0oWjVk9ZT2m+HEQHSEnAqgHz0UzGKGVoeSnbAxUw
|
||||
FATBNYUCgYEA3KowvUgRyc1ydi2L6YqQQg8Mhq6SdSPkC91p3pI8pMJGwco0tI2y
|
||||
95EFMsFUg3m+v2URDKxjt3itx+vqWxSqeQBgUzKk+0TRuKt0+8dav5BUA3njtLEk
|
||||
VdlzwofbvagUnE+slg0lQKsK/IzODPCckcOjBcUyJI1PNYdDjfQjv7cCgYEAzFO1
|
||||
vtexZEUw1QGsrxlxrwBu7ui3NvpfC7rGqJIiUxm7cLrfDbOSHJxutCNOZH90zDZl
|
||||
xiVBTc3tvpdXdj+zuUkf9Q4dKNTIq3+Hwm3iGXS+yw6rT2C2I3h+IaSCF1YpKakw
|
||||
MSjOwlmnYIckeYGHe2kfEgNiL7qRD+SxJCjSVusCgYEA2/3Mc5h7K35oQ8tqtl1P
|
||||
LpyEN22pU6GBhBasqpmOXg/VrPPjkbHHH6tzzEMT97OTaIrg8YqYK1zjm/HmBgHX
|
||||
ZqTqY2eVNXBJyVseWLlKDrtcFs8ZJZaJDBGrp9/8QdtlGOURwdK/NfaQEHJsJlhn
|
||||
L6ckSudq8yfyNQJyZf5k+YcCgYEArSPqCBNiICN5Y6YNnDqlWLO3TP8p8Y5rZ9cX
|
||||
a9SY/W36pWXUiRm3IEN2k3KvhP10DW+zAhqjobh0U2KPHIaSVtmeGNui3eyhNqHU
|
||||
em7+fq+s1QhTJeo/rQL3bq6mBfxe2Qyi56U6vvmVmXgq8kNOeMb1KyBu3R7suVkC
|
||||
ui9VPY0CgYEArXpn5sZz8ERHXjeRHTVxaTk2djvgYtzVKjEGR1LzX6Bi7drbw/lF
|
||||
M1Fjqog/k0tsDM7pC30EoxpRR10hSFdC7dQ+STTeTr1gmB6xJmA8boe1jpAfhFXm
|
||||
sjjrsFsw3mUpsJD3Ck482T3BA0iZP3NvC/+ge0IkRUC1/j8KP0zQKZI=
|
||||
-----END RSA PRIVATE KEY-----
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_3.pem
generated
vendored
Normal file
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_3.pem
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAv9Dpk+nfW9lOSHdt8UO5f/ELRA+JM4vX9GkbJy9/M/NTv7gg
|
||||
HBNpxYA/RhIMnifeXFAomLYMov0Gxh4+85Dv4YT3lhxlUiwi2yaOIXlgy15R8kGR
|
||||
OCFVKgfqfQw3WnWhjGyH3Rb4Znk4IGh2qj+w2lBlnbeKCxXq+SMB/Kc3TMmJg4Cx
|
||||
P73vVmT3VifW9goOO4DDNAv0uTl/KgWnaVoPc8eRmYxBc9chTxjlmoLHsCy8z9+p
|
||||
pn33eTyfcNF5DS310sMHLcvpP25fPMpkbzDDjnCzBouI0YkJJ1F2m1GPEqnsN0is
|
||||
1F9TGJppcAOpb9aaPmtU2wlzMs5Rwm5vQ6/9wQIDAQABAoIBAQCPczl7+QeltRoq
|
||||
b8a1DCUKXcZDHCtLdWYHzyMTZx4GSA917cl1tb8AiSzIxm7RSJevCfOSYXOJ4RjT
|
||||
yYLivJ3pVnuis5HCpmda5badqhyNevhl6EsmYydBy7G92wj6icZLMk9ZNPiIClfD
|
||||
RNyZ7g/g9QdJsB14tOeJcnjl7lgY/8RQOv52mWC2AfvCRGf6fHoopMP4ZlSTDP3q
|
||||
2LMGNUW+cEwjZg+AIheJCoCOs51pvm/B2DXeb9T/GMzway3qFBKU/RYi+ceOm6UF
|
||||
+BZwStxpXMRbnXrXv9wS3S6260+NSdgIVF7ErzYjinLhZL9Wc6khoRVOCg2ESewr
|
||||
+2sJqoABAoGBAOST6/PgAfvODs0m3tzb8dDYMcCtD7p3QoC3HiG7mEu6vnb/GdZZ
|
||||
6B1XHiK36xLL0/8tf2BtJIqmrDqOJ8pdiT1sbQUtv8lmbdr2PoV2P86v11F8ZBkq
|
||||
szjpQ6gGaItT3dAjJxSGCiwdvMw4za5GJAUPTQZk+t1XasiaAQznMimBAoGBANbT
|
||||
9tv+mqwvzK4/Pw/gyIJkIQrbgXTFdMjhZJkNVxCm2R7YnNDRLSxmng0nJCB2Tgkp
|
||||
EouWNYi9rsWnmR+AOinSSoAb6znQwOZhQzQb2KzkfBnlZ4XFUxvW4OZmJDZILmyr
|
||||
/8uHnEcT7xwT7L90j9cSxq/+WCbB7GGpFmmVpXRBAoGAd7l/ElsXzuOsXwpoGzjd
|
||||
HS3QSYKcRWfoHnFLyBFxgOEMmFmgF+U5rfyOnVLGPy8iGHulR0WDqVgJyBXjg5yg
|
||||
oNqk89x1ozESg2kNcGxymXkDB/xmlcQG4d1UgbLxmWDRQw7Wjmpy846T8Egke47j
|
||||
mP7dsma7+6mpFe+Mc0y5uoECgYBp1D+u/oz5uA5v5G5Phx+fxG3WqG3stX0jnI1v
|
||||
LHgwltEs9e7Cm9lSHzdLKXYNm9ozfw1IwHWc6DyZ2EeBkiyU/6h91cMaVzFADLgL
|
||||
ipBCE8jjBPTrnFqlw0RFnBnIt+RO2qiHfkXJahOH1HTzmBtoCzLf7j9E0JF/Rsno
|
||||
t7SrQQKBgDxAKKHMLT2zm0kz6eTSLSB7WDNxZ14+u32fZj2ln4JMERTk3byGojPX
|
||||
zf9poWwCWJOLd7uVtLl2dQZFUZG9GJuZXO15qmKAaXMI5yVwH1ygzzZvDLOlH8Nn
|
||||
19ZjFhRLreVeAFLMTyOapEH+5QZxaszMR8Xzna9BJnschht/kPM7
|
||||
-----END RSA PRIVATE KEY-----
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_4.pem
generated
vendored
Normal file
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_4.pem
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEArmoiX5G36MKPiVGS1sicruEaGRrbhPbIKOf97aGGQRjXVngo
|
||||
Knwd2L4T9CRyABgQm3tLHHcT5crODoy46wX2g9onTZWViWWuhJ5wxXNmUbCAPWHb
|
||||
j9SunW53WuLYZ/IJLNZt5XYCAFPjAakWp8uMuuDwWo5EyFaw85X3FSMhVmmaYDd0
|
||||
cn+1H4+NS/52wX7tWmyvGUNJ8lzjFAnnOtBJByvkyIC7HDphkLQV4j//sMNY1mPX
|
||||
HbsYgFv2J/LIJtkjdYO2UoDhZG3Gvj16fMy2JE2owA8IX4/s+XAmA2PiTfd0J5b4
|
||||
drAKEcdDl83G6L3depEkTkfvp0ZLsh9xupAvIwIDAQABAoIBABKGgWonPyKA7+AF
|
||||
AxS/MC0/CZebC6/+ylnV8lm4K1tkuRKdJp8EmeL4pYPsDxPFepYZLWwzlbB1rxdK
|
||||
iSWld36fwEb0WXLDkxrQ/Wdrj3Wjyqs6ZqjLTVS5dAH6UEQSKDlT+U5DD4lbX6RA
|
||||
goCGFUeQNtdXfyTMWHU2+4yKM7NKzUpczFky+0d10Mg0ANj3/4IILdr3hqkmMSI9
|
||||
1TB9ksWBXJxt3nGxAjzSFihQFUlc231cey/HhYbvAX5fN0xhLxOk88adDcdXE7br
|
||||
3Ser1q6XaaFQSMj4oi1+h3RAT9MUjJ6johEqjw0PbEZtOqXvA1x5vfFdei6SqgKn
|
||||
Am3BspkCgYEA2lIiKEkT/Je6ZH4Omhv9atbGoBdETAstL3FnNQjkyVau9f6bxQkl
|
||||
4/sz985JpaiasORQBiTGY8JDT/hXjROkut91agi2Vafhr29L/mto7KZglfDsT4b2
|
||||
9z/EZH8wHw7eYhvdoBbMbqNDSI8RrGa4mpLpuN+E0wsFTzSZEL+QMQUCgYEAzIQh
|
||||
xnreQvDAhNradMqLmxRpayn1ORaPReD4/off+mi7hZRLKtP0iNgEVEWHJ6HEqqi1
|
||||
r38XAc8ap/lfOVMar2MLyCFOhYspdHZ+TGLZfr8gg/Fzeq9IRGKYadmIKVwjMeyH
|
||||
REPqg1tyrvMOE0HI5oqkko8JTDJ0OyVC0Vc6+AcCgYAqCzkywugLc/jcU35iZVOH
|
||||
WLdFq1Vmw5w/D7rNdtoAgCYPj6nV5y4Z2o2mgl6ifXbU7BMRK9Hc8lNeOjg6HfdS
|
||||
WahV9DmRA1SuIWPkKjE5qczd81i+9AHpmakrpWbSBF4FTNKAewOBpwVVGuBPcDTK
|
||||
59IE3V7J+cxa9YkotYuCNQKBgCwGla7AbHBEm2z+H+DcaUktD7R+B8gOTzFfyLoi
|
||||
Tdj+CsAquDO0BQQgXG43uWySql+CifoJhc5h4v8d853HggsXa0XdxaWB256yk2Wm
|
||||
MePTCRDePVm/ufLetqiyp1kf+IOaw1Oyux0j5oA62mDS3Iikd+EE4Z+BjPvefY/L
|
||||
E2qpAoGAZo5Wwwk7q8b1n9n/ACh4LpE+QgbFdlJxlfFLJCKstl37atzS8UewOSZj
|
||||
FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ
|
||||
Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo=
|
||||
-----END RSA PRIVATE KEY-----
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_1.pem
generated
vendored
Normal file
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_1.pem
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_2.pem
generated
vendored
Normal file
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_2.pem
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKgIBAAKCAgEAobIQNPQNtGZlPSpbtZC/c/gb5OxoJfoq07h6X8kyeR3xq9qv
|
||||
js8u8YQMt/AtgYw1tSdgNeaYzsvMgaSVgexf8ysqys5obWjcB/sjRR6UJY4hgSm5
|
||||
oLoGy8fi8lbSEtpthHStA7UVUGU95Ypga8r++Kkizk84wDq+0jZAGaEPvhO7RJne
|
||||
nbEKhhZVIFb13LlkaX1eNOAxAcTszFgX/DaZWFcfzKop6gS7agU5NLCtyAjKdSRz
|
||||
CXYMVyVnXhZaxZtLawk9Ld8qIqwmytiPwnlRugoFhTrCPIPfAb1UaESKj4EuupUi
|
||||
brlByun/ENiWvbzTZyGLSuYA6rruyfUll4oDT/SwsDMJ0jZiu5C1bTgA+gY6kB+I
|
||||
A3EvcP2Eh1uiUvwy5adAr29wlP7/8YwWvyEAAaO9SKbh36xcbsJH9OIQbFati99L
|
||||
kn7jfyktUGKxgmwYilF2ehtB1tPOMKRor8HJGoMEcrjiu4Qvc3ygaZtomfY4zxL1
|
||||
uNeZ8c51pm5lmo30kJ5q5aWX6iS4Sp+ncFiPjxj7bV7nXOGqwmxK23tk4qdUOK4s
|
||||
z26uc+CChP6VdOwLU1uu2FdRvBCPSnvBFIk5qaFZkhbk/gA+iS8R82TUf1DWfvZ4
|
||||
why6XJDPQRnscySiIckN3X30TD68dpJ5VtzpRzCvNN9GxoRFY6apcJXoV0sCAwEA
|
||||
AQKCAgAKR01scE8mtpOc7cJiqk7hSlZLmRONxndOeh2dVSbWOCcSq5YZV+Y+CAze
|
||||
7G+YGpeXamddRclU6/OWEiZG2gXHaWkQ90oAGnhSMY6uaCE2ufA7S7G3G9wuvAgb
|
||||
K5WzCRuJHfmZkLtIHwduPfufHopSuD20K6kJ3zIeHsC4YFql1I9E7xsNnyFyIJ1M
|
||||
rvp2C3rskcGZTt8Oo7wByV/M8pOQ4Ajvc6mybJaVSLu4M7r4SkbEZ4rAgTaLm58U
|
||||
hgtDIHoM1cuDzPnatmLI5jdNP3UIhHaRX4jVW/SjIavp7OF5+dZEmhJUQ4aBJZrH
|
||||
MV1ztjsiBSnbmv9X7IYdZG39UhKfuqph20l3NQQ9lGhJH68/V5YEt/301O7Jz2jo
|
||||
hPP56TvvP8sNJZqkiD3JQ9Do4V4s0pUR6RQiim34gdivhRNntPjL1hfBZVoIIxOY
|
||||
Ek//7OmsfrPwHEatZP6UT+IJTVZRbGp6a5Qu0YmQUh1/MVK+h2PU+FVpkgYALm1u
|
||||
6e2dXqRK1NI0v0icFMUTBZAw8h7mob7IlkoA/uLWjgkVZf48/2wCiCtxjDiU31Hu
|
||||
QGV3AZFccjKPwjc+lcWjjH3+nM/q4Z9b2r9x5XaPOlq4mXF4QnheSgQ7URhiEG2I
|
||||
RI1pmV20HCU/qmAiiGGBWOBYlOSBuRbun5/eSOY8c3FXIMOckQKCAQEAy7F3eqYR
|
||||
0tXCJRzR9EQld13QVDeP4PpADbMVzuGXbG6+DDjXgRQiDh0gnS2hSKATd61ZRg+m
|
||||
RHVEPAf9lfEyDutr6qZRZJs14auI0Z4eBfdq+pL23e5tcmj5GuqBZ6qR5ZYHblz+
|
||||
OT0pEa0VfCW63e0XU/1iG0y6PGbSQiHmqas6E3s7ccDjZZE7lRCBn4+RouBBxtFo
|
||||
leugrTK+ahObFeSYSQqjG5vGP13gatui5hib8cGG6QUV6GQvezvLvo/rBmCNFrjm
|
||||
wtQ4vfICifadKYYxyTNNB/EKDrXB1E0NyDM8pmXcfFlajQU0fucvSCNmXIDF8nnL
|
||||
TCB6mGQBX5VUPwKCAQEAyze5K7YQXe5lp7PLz9IHYyY5dtc4wzwHI4PGe2TAKEtQ
|
||||
jEOjSi4bgUYGyalJ8vqmOrGkCBW4NCF8Ywb6wNHgK/O6BGbaSl8N2cYqA9wSnVYB
|
||||
c57ieUCg1A/3mdEwO+jHng1G6Me2Tf7VzkO/XPXyA2Fy6XhIg4xf+mxlW010dbKS
|
||||
kn4CWljY8XhuX1DZMjA+UAsMzaI6DFcxbIcgcVMP6lp9mnk0Fnm5T5x/EpNjuAHV
|
||||
D0m9x9TlQ86akFdf+FljEsj/drDIZ8mkU4JTKiH88wrwfKHrOqhv1NX7hLCXs7wb
|
||||
tkxVF71M2qOcYl5TAvJz9uv2U4O9G5l1jL9wT9eJ9QKCAQEAniUAwGajO+/eNfY0
|
||||
Q9OMyyo5Dsm8mU1x4bEC44ZejD9GqjKPjpXVAuQ2aBH/QGWX97jMsQqBanEpMvp5
|
||||
Nar31IGPXbUXSGcA5F7LcQO0B6nakwT7Sb9NliBOF0mugo/5iih7SIJGlqYXdrPN
|
||||
FIAunxLuo7T8MHnXtgGWiOXNMjnQc0OgGWdKpZamjcss+Hb8+Vnnd7cp3gv8ybu1
|
||||
/qGOLOc4HK13iX3d42C9VfmEdeTxXjeEyPG72pu+CY2ZWDBgpqjboaKY9vbRvxdg
|
||||
RUEFMDISAUYlLl9EEbun626Pnrm5Au/eyWSOWyKJaWWQXg+t72/DP8izwD0PMbWj
|
||||
I1TK/QKCAQEAiYSK5R6OUtIprmPILzlE0H6kclxQSCXN+uWIoiXatynINzLqRB+R
|
||||
c1is7TiHF0swxBVEGEiCX5ytbOHjPCqKVZPYNHRZkexjFhS4h+YcHqZ90v0Y6s6m
|
||||
RvsLJebeihwLQVRgwNOs9XjWvH8x9zlj7Y+7UGyaPZL3vCIwMKnofmE6OLHW68am
|
||||
ADnsDspKQGFPOaFQp7L5LzKt+nAyrx1zbraPusH8Up1Knqobf7mHyJRM1syjBaB3
|
||||
CPy9saG/CvOKTMMBxRL6eumELxLJLoDTiLDFbsGvygEDtHadfvx1nCZWZnWfO7JZ
|
||||
WLdQ82w7JoplmRmylm9WwF+HoZhG63DDJQKCAQEAjkYiyHgh/drr0tQLlpELy3+8
|
||||
3CfKgijIs4Q+UGBsygEkPUzS/oFYl9oJ/yFSSOCsyGTYF9ojXYbdaLyHFS1YcpWN
|
||||
pcPcZRmd68RdVk2gOMAHBAvT2YN56OQJiDwghMA0bIoY2BI7+h5mFLAgudSZXHY8
|
||||
RYt9IjEaFai9cvR88p7p9bQJNuIXWXia9PcdSldHJ01S+GhWHNbd6J0BfPhfAO+R
|
||||
VJl98+U6jLyXyY1Ma1v9AfI6dC43SUKG4Z8b2B0ynxMANiOWXCj823owTk542yFi
|
||||
ylWbhUGqQ4uMn8ojPArNY+Ndpmlgc8MpgXjaEzMil7BfnhlTHz5sQkMM4ByhaA==
|
||||
-----END RSA PRIVATE KEY-----
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_3.pem
generated
vendored
Normal file
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_3.pem
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_4.pem
generated
vendored
Normal file
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_4.pem
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
Reference in New Issue
Block a user