refactoring
This commit is contained in:
parent
138da8a208
commit
0ca2370d48
25 changed files with 698 additions and 511 deletions
|
@ -12,8 +12,8 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/caos/oidc/pkg/client/rs"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/rp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -25,8 +25,9 @@ const (
|
|||
func main() {
|
||||
keyPath := os.Getenv("KEY")
|
||||
port := os.Getenv("PORT")
|
||||
issuer := os.Getenv("ISSUER")
|
||||
|
||||
provider, err := rp.NewResourceServerFromKeyFile(keyPath)
|
||||
provider, err := rs.NewResourceServerFromKeyFile(issuer, keyPath)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating provider %s", err.Error())
|
||||
}
|
||||
|
@ -46,7 +47,7 @@ func main() {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
resp, err := rp.Introspect(r.Context(), provider, token)
|
||||
resp, err := rs.Introspect(r.Context(), provider, token)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
|
@ -67,7 +68,7 @@ func main() {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
resp, err := rp.Introspect(r.Context(), provider, token)
|
||||
resp, err := rs.Introspect(r.Context(), provider, token)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -14,8 +11,8 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/caos/oidc/pkg/client/rp"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/rp"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -32,8 +29,6 @@ func main() {
|
|||
port := os.Getenv("PORT")
|
||||
scopes := strings.Split(os.Getenv("SCOPES"), " ")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
redirectURI := fmt.Sprintf("http://localhost:%v%v", port, callbackPath)
|
||||
cookieHandler := utils.NewCookieHandler(key, key, utils.WithUnsecure())
|
||||
|
||||
|
@ -48,7 +43,7 @@ func main() {
|
|||
options = append(options, rp.WithClientKey(keyPath))
|
||||
}
|
||||
|
||||
provider, err := rp.NewRelayingPartyOIDC(issuer, clientID, clientSecret, redirectURI, scopes, options...)
|
||||
provider, err := rp.NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI, scopes, options...)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating provider %s", err.Error())
|
||||
}
|
||||
|
@ -81,80 +76,6 @@ func main() {
|
|||
//with the returned tokens from the token endpoint
|
||||
http.Handle(callbackPath, rp.CodeExchangeHandler(marshal, provider))
|
||||
|
||||
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
|
||||
tokens, err := rp.ClientCredentials(ctx, provider, "scope")
|
||||
if err != nil {
|
||||
http.Error(w, "failed to exchange token: "+err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(tokens)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
})
|
||||
|
||||
http.HandleFunc("/jwt-profile", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
tpl := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<form method="POST" action="/jwt-profile" enctype="multipart/form-data">
|
||||
<label for="key">Select a key file:</label>
|
||||
<input type="file" accept=".json" id="key" name="key">
|
||||
<button type="submit">Get Token</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>`
|
||||
t, err := template.New("login").Parse(tpl)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = t.Execute(w, nil)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
err := r.ParseMultipartForm(4 << 10)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
file, handler, err := r.FormFile("key")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
key, err := ioutil.ReadAll(file)
|
||||
fmt.Println(handler.Header)
|
||||
assertion, err := oidc.NewJWTProfileAssertionFromFileData(key, []string{issuer})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
token, err := rp.JWTProfileAssertionExchange(ctx, assertion, scopes, provider)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data, err := json.Marshal(token)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
})
|
||||
lis := fmt.Sprintf("127.0.0.1:%s", port)
|
||||
logrus.Infof("listening on http://%s/", lis)
|
||||
logrus.Fatal(http.ListenAndServe("127.0.0.1:"+port, nil))
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
"golang.org/x/oauth2"
|
||||
githubOAuth "golang.org/x/oauth2/github"
|
||||
|
||||
"github.com/caos/oidc/pkg/rp"
|
||||
"github.com/caos/oidc/pkg/rp/cli"
|
||||
"github.com/caos/oidc/pkg/client/rp"
|
||||
"github.com/caos/oidc/pkg/client/rp/cli"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -35,7 +35,7 @@ func main() {
|
|||
|
||||
ctx := context.Background()
|
||||
cookieHandler := utils.NewCookieHandler(key, key, utils.WithUnsecure())
|
||||
relayingParty, err := rp.NewRelayingPartyOAuth(rpConfig, rp.WithCookieHandler(cookieHandler))
|
||||
relyingParty, err := rp.NewRelyingPartyOAuth(rpConfig, rp.WithCookieHandler(cookieHandler))
|
||||
if err != nil {
|
||||
fmt.Printf("error creating relaying party: %v", err)
|
||||
return
|
||||
|
@ -43,9 +43,9 @@ func main() {
|
|||
state := func() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
token := cli.CodeFlow(relayingParty, callbackPath, port, state)
|
||||
token := cli.CodeFlow(relyingParty, callbackPath, port, state)
|
||||
|
||||
client := github.NewClient(relayingParty.OAuthConfig().Client(ctx, token.Token))
|
||||
client := github.NewClient(relyingParty.OAuthConfig().Client(ctx, token.Token))
|
||||
|
||||
_, _, err = client.Users.Get(ctx, "")
|
||||
if err != nil {
|
||||
|
|
196
example/client/service/service.go
Normal file
196
example/client/service/service.go
Normal file
|
@ -0,0 +1,196 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/caos/oidc/pkg/client/profile"
|
||||
)
|
||||
|
||||
var (
|
||||
client *http.Client = http.DefaultClient
|
||||
)
|
||||
|
||||
func main() {
|
||||
//keyPath := os.Getenv("KEY_PATH")
|
||||
issuer := os.Getenv("ISSUER")
|
||||
port := os.Getenv("PORT")
|
||||
scopes := strings.Split(os.Getenv("SCOPES"), " ")
|
||||
//testURL := os.Getenv("TEST_URL")
|
||||
|
||||
//if keyPath != "" {
|
||||
// ts, err := rp.NewJWTProfileTokenSourceFromFile(issuer, keyPath, scopes)
|
||||
// if err != nil {
|
||||
// logrus.Fatalf("error creating token source %s", err.Error())
|
||||
// }
|
||||
// //client = oauth2.NewClient(context.Background(), ts)
|
||||
// resp, err := callExampleEndpoint(client, testURL)
|
||||
// if err != nil {
|
||||
// logrus.Fatalf("error response from test url: %s", err.Error())
|
||||
// }
|
||||
// fmt.Println(resp)
|
||||
//}
|
||||
|
||||
http.HandleFunc("/jwt-profile", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
tpl := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<form method="POST" action="/jwt-profile" enctype="multipart/form-data">
|
||||
<label for="key">Select a key file:</label>
|
||||
<input type="file" accept=".json" id="key" name="key">
|
||||
<button type="submit">Get Token</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>`
|
||||
t, err := template.New("login").Parse(tpl)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = t.Execute(w, nil)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
err := r.ParseMultipartForm(4 << 10)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
file, _, err := r.FormFile("key")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
key, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ts, err := profile.NewJWTProfileTokenSourceFromKeyFile(issuer, key, scopes)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
client = oauth2.NewClient(context.Background(), ts)
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
//assertion, err := oidc.NewJWTProfileAssertionFromFileData(key, []string{issuer})
|
||||
//if err != nil {
|
||||
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
// return
|
||||
//}
|
||||
//token, err := rp.JWTProfileAssertionExchange(ctx, assertion, scopes, provider)
|
||||
//if err != nil {
|
||||
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
// return
|
||||
//}
|
||||
data, err := json.Marshal(token)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
})
|
||||
|
||||
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
|
||||
tpl := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<form method="POST" action="/test">
|
||||
<label for="url">URL for test:</label>
|
||||
<input type="text" id="url" name="url" width="200px">
|
||||
<button type="submit">Test Token</button>
|
||||
</form>
|
||||
{{if .URL}}
|
||||
<p>
|
||||
Result for {{.URL}}: {{.Response}}
|
||||
</p>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>`
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
testURL := r.Form.Get("url")
|
||||
var data struct {
|
||||
URL string
|
||||
Response interface{}
|
||||
}
|
||||
if testURL != "" {
|
||||
data.URL = testURL
|
||||
data.Response, err = callExampleEndpoint(client, testURL)
|
||||
if err != nil {
|
||||
data.Response = err
|
||||
}
|
||||
}
|
||||
t, err := template.New("login").Parse(tpl)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = t.Execute(w, data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
})
|
||||
lis := fmt.Sprintf("127.0.0.1:%s", port)
|
||||
logrus.Infof("listening on http://%s/", lis)
|
||||
logrus.Fatal(http.ListenAndServe("127.0.0.1:"+port, nil))
|
||||
}
|
||||
|
||||
func callExampleEndpoint(client *http.Client, testURL string) (interface{}, error) {
|
||||
req, err := http.NewRequest("GET", testURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
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 "", fmt.Errorf("http status not ok: %s %s", resp.Status, body)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(resp.Header.Get("content-type"), "text/plain") {
|
||||
return string(body), nil
|
||||
}
|
||||
return body, err
|
||||
}
|
90
pkg/client/client.go
Normal file
90
pkg/client/client.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
Encoder = func() utils.Encoder {
|
||||
e := schema.NewEncoder()
|
||||
e.RegisterEncoder(oidc.Scopes{}, func(value reflect.Value) string {
|
||||
return value.Interface().(oidc.Scopes).Encode()
|
||||
})
|
||||
return e
|
||||
}()
|
||||
)
|
||||
|
||||
//Discover calls the discovery endpoint of the provided issuer and returns its configuration
|
||||
func Discover(issuer string, httpClient *http.Client) (*oidc.DiscoveryConfiguration, error) {
|
||||
wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint
|
||||
req, err := http.NewRequest("GET", wellKnown, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
discoveryConfig := new(oidc.DiscoveryConfiguration)
|
||||
err = utils.HttpRequest(httpClient, req, &discoveryConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return discoveryConfig, nil
|
||||
}
|
||||
|
||||
type tokenEndpointCaller interface {
|
||||
TokenEndpoint() string
|
||||
HttpClient() *http.Client
|
||||
}
|
||||
|
||||
func CallTokenEndpoint(request interface{}, caller tokenEndpointCaller) (newToken *oauth2.Token, err error) {
|
||||
return callTokenEndpoint(request, nil, caller)
|
||||
}
|
||||
|
||||
func callTokenEndpoint(request interface{}, authFn interface{}, caller tokenEndpointCaller) (newToken *oauth2.Token, err error) {
|
||||
req, err := utils.FormRequest(caller.TokenEndpoint(), request, Encoder, authFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenRes := new(oidc.AccessTokenResponse)
|
||||
if err := utils.HttpRequest(caller.HttpClient(), req, &tokenRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oauth2.Token{
|
||||
AccessToken: tokenRes.AccessToken,
|
||||
TokenType: tokenRes.TokenType,
|
||||
RefreshToken: tokenRes.RefreshToken,
|
||||
Expiry: time.Now().UTC().Add(time.Duration(tokenRes.ExpiresIn) * time.Second),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewSignerFromPrivateKeyByte(key []byte, keyID string) (jose.Signer, error) {
|
||||
privateKey, err := utils.BytesToPrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signingKey := jose.SigningKey{
|
||||
Algorithm: jose.RS256,
|
||||
Key: &jose.JSONWebKey{Key: privateKey, KeyID: keyID},
|
||||
}
|
||||
return jose.NewSigner(signingKey, &jose.SignerOptions{})
|
||||
}
|
||||
|
||||
func SignedJWTProfileAssertion(clientID string, audience []string, expiration time.Duration, signer jose.Signer) (string, error) {
|
||||
iat := time.Now()
|
||||
exp := iat.Add(expiration)
|
||||
return utils.Sign(&oidc.JWTTokenRequest{
|
||||
Issuer: clientID,
|
||||
Subject: clientID,
|
||||
Audience: audience,
|
||||
ExpiresAt: oidc.Time(exp),
|
||||
IssuedAt: oidc.Time(iat),
|
||||
}, signer)
|
||||
}
|
30
pkg/client/jwt_profile.go
Normal file
30
pkg/client/jwt_profile.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
//JWTProfileExchange handles the oauth2 jwt profile exchange
|
||||
func JWTProfileExchange(ctx context.Context, jwtProfileGrantRequest *oidc.JWTProfileGrantRequest, caller tokenEndpointCaller) (*oauth2.Token, error) {
|
||||
return CallTokenEndpoint(jwtProfileGrantRequest, caller)
|
||||
}
|
||||
|
||||
func ClientAssertionCodeOptions(assertion string) []oauth2.AuthCodeOption {
|
||||
return []oauth2.AuthCodeOption{
|
||||
oauth2.SetAuthURLParam("client_assertion", assertion),
|
||||
oauth2.SetAuthURLParam("client_assertion_type", oidc.ClientAssertionTypeJWTAssertion),
|
||||
}
|
||||
}
|
||||
|
||||
func ClientAssertionFormAuthorization(assertion string) utils.FormAuthorization {
|
||||
return func(values url.Values) {
|
||||
values.Set("client_assertion", assertion)
|
||||
values.Set("client_assertion_type", oidc.ClientAssertionTypeJWTAssertion)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package rp
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -14,7 +14,7 @@ type keyFile struct {
|
|||
Type string `json:"type"` // serviceaccount or application
|
||||
KeyID string `json:"keyId"`
|
||||
Key string `json:"key"`
|
||||
Issuer string `json:"issuer"`
|
||||
Issuer string `json:"issuer"` //not yet in file
|
||||
|
||||
//serviceaccount
|
||||
UserID string `json:"userId"`
|
||||
|
@ -28,6 +28,10 @@ func ConfigFromKeyFile(path string) (*keyFile, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ConfigFromKeyFileData(data)
|
||||
}
|
||||
|
||||
func ConfigFromKeyFileData(data []byte) (*keyFile, error) {
|
||||
var f keyFile
|
||||
if err := json.Unmarshal(data, &f); err != nil {
|
||||
return nil, err
|
85
pkg/client/profile/jwt_profile.go
Normal file
85
pkg/client/profile/jwt_profile.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/client"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
//jwtProfileTokenSource implement the oauth2.TokenSource
|
||||
//it will request a token using the OAuth2 JWT Profile Grant
|
||||
//therefore sending an `assertion` by singing a JWT with the provided private key
|
||||
type jwtProfileTokenSource struct {
|
||||
clientID string
|
||||
audience []string
|
||||
signer jose.Signer
|
||||
scopes []string
|
||||
httpClient *http.Client
|
||||
tokenEndpoint string
|
||||
}
|
||||
|
||||
func NewJWTProfileTokenSourceFromKeyFile(issuer string, data []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (oauth2.TokenSource, error) {
|
||||
keyData, err := client.ConfigFromKeyFileData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewJWTProfileTokenSource(issuer, keyData.UserID, keyData.KeyID, []byte(keyData.Key), scopes, options...)
|
||||
}
|
||||
|
||||
func NewJWTProfileTokenSource(issuer, clientID, keyID string, key []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (oauth2.TokenSource, error) {
|
||||
signer, err := client.NewSignerFromPrivateKeyByte(key, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
source := &jwtProfileTokenSource{
|
||||
clientID: clientID,
|
||||
audience: []string{issuer},
|
||||
signer: signer,
|
||||
scopes: scopes,
|
||||
httpClient: http.DefaultClient,
|
||||
}
|
||||
for _, opt := range options {
|
||||
opt(source)
|
||||
}
|
||||
if source.tokenEndpoint == "" {
|
||||
config, err := client.Discover(issuer, source.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
source.tokenEndpoint = config.TokenEndpoint
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func WithHTTPClient(client *http.Client) func(*jwtProfileTokenSource) {
|
||||
return func(source *jwtProfileTokenSource) {
|
||||
source.httpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
func WithStaticTokenEndpoint(issuer, tokenEndpoint string) func(*jwtProfileTokenSource) {
|
||||
return func(source *jwtProfileTokenSource) {
|
||||
source.tokenEndpoint = tokenEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jwtProfileTokenSource) TokenEndpoint() string {
|
||||
return j.tokenEndpoint
|
||||
}
|
||||
|
||||
func (j *jwtProfileTokenSource) HttpClient() *http.Client {
|
||||
return j.httpClient
|
||||
}
|
||||
|
||||
func (j *jwtProfileTokenSource) Token() (*oauth2.Token, error) {
|
||||
assertion, err := client.SignedJWTProfileAssertion(j.clientID, j.audience, time.Hour, j.signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.JWTProfileExchange(nil, oidc.NewJWTProfileGrantRequest(assertion, j.scopes...), j)
|
||||
}
|
|
@ -4,8 +4,8 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/oidc/pkg/client/rp"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/rp"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -13,7 +13,7 @@ const (
|
|||
loginPath = "/login"
|
||||
)
|
||||
|
||||
func CodeFlow(relayingParty rp.RelayingParty, callbackPath, port string, stateProvider func() string) *oidc.Tokens {
|
||||
func CodeFlow(relyingParty rp.RelyingParty, callbackPath, port string, stateProvider func() string) *oidc.Tokens {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
|
@ -24,8 +24,8 @@ func CodeFlow(relayingParty rp.RelayingParty, callbackPath, port string, statePr
|
|||
msg = msg + "<p>You are authenticated and can now return to the CLI.</p>"
|
||||
w.Write([]byte(msg))
|
||||
}
|
||||
http.Handle(loginPath, rp.AuthURLHandler(stateProvider, relayingParty))
|
||||
http.Handle(callbackPath, rp.CodeExchangeHandler(callback, relayingParty))
|
||||
http.Handle(loginPath, rp.AuthURLHandler(stateProvider, relyingParty))
|
||||
http.Handle(callbackPath, rp.CodeExchangeHandler(callback, relyingParty))
|
||||
|
||||
utils.StartServer(ctx, port)
|
||||
|
|
@ -5,10 +5,12 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
context "context"
|
||||
oidc "github.com/caos/oidc/pkg/oidc"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
// MockVerifier is a mock of Verifier interface
|
|
@ -4,43 +4,32 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/oidc/grants"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/client"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
idTokenKey = "id_token"
|
||||
stateParam = "state"
|
||||
pkceCode = "pkce"
|
||||
jwtProfileKey = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
idTokenKey = "id_token"
|
||||
stateParam = "state"
|
||||
pkceCode = "pkce"
|
||||
)
|
||||
|
||||
var (
|
||||
encoder = func() utils.Encoder {
|
||||
e := schema.NewEncoder()
|
||||
e.RegisterEncoder(oidc.Scopes{}, func(value reflect.Value) string {
|
||||
return value.Interface().(oidc.Scopes).Encode()
|
||||
})
|
||||
return e
|
||||
}()
|
||||
)
|
||||
|
||||
//RelayingParty declares the minimal interface for oidc clients
|
||||
type RelayingParty interface {
|
||||
//RelyingParty declares the minimal interface for oidc clients
|
||||
type RelyingParty interface {
|
||||
//OAuthConfig returns the oauth2 Config
|
||||
OAuthConfig() *oauth2.Config
|
||||
|
||||
//Issuer returns the issuer of the oidc config
|
||||
Issuer() string
|
||||
|
||||
//IsPKCE returns if authorization is done using `Authorization Code Flow with Proof Key for Code Exchange (PKCE)`
|
||||
IsPKCE() bool
|
||||
|
||||
|
@ -53,13 +42,16 @@ type RelayingParty interface {
|
|||
//IsOAuth2Only specifies whether relaying party handles only oauth2 or oidc calls
|
||||
IsOAuth2Only() bool
|
||||
|
||||
ClientKey() []byte
|
||||
ClientKeyID() string
|
||||
//Signer is used if the relaying party uses the JWT Profile
|
||||
Signer() jose.Signer
|
||||
|
||||
//UserinfoEndpoint returns the userinfo
|
||||
UserinfoEndpoint() string
|
||||
|
||||
//IDTokenVerifier returns the verifier interface used for oidc id_token verification
|
||||
IDTokenVerifier() IDTokenVerifier
|
||||
|
||||
//ErrorHandler returns the handler used for callback errors
|
||||
|
||||
ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string)
|
||||
}
|
||||
|
||||
|
@ -71,14 +63,12 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
type relayingParty struct {
|
||||
type relyingParty struct {
|
||||
issuer string
|
||||
endpoints Endpoints
|
||||
oauthConfig *oauth2.Config
|
||||
oauth2Only bool
|
||||
pkce bool
|
||||
clientKey []byte
|
||||
clientKeyID string
|
||||
|
||||
httpClient *http.Client
|
||||
cookieHandler *utils.CookieHandler
|
||||
|
@ -86,72 +76,79 @@ type relayingParty struct {
|
|||
errorHandler func(http.ResponseWriter, *http.Request, string, string, string)
|
||||
idTokenVerifier IDTokenVerifier
|
||||
verifierOpts []VerifierOption
|
||||
signer jose.Signer
|
||||
}
|
||||
|
||||
func (rp *relayingParty) OAuthConfig() *oauth2.Config {
|
||||
func (rp *relyingParty) OAuthConfig() *oauth2.Config {
|
||||
return rp.oauthConfig
|
||||
}
|
||||
|
||||
func (rp *relayingParty) IsPKCE() bool {
|
||||
func (rp *relyingParty) Issuer() string {
|
||||
return rp.issuer
|
||||
}
|
||||
|
||||
func (rp *relyingParty) IsPKCE() bool {
|
||||
return rp.pkce
|
||||
}
|
||||
|
||||
func (rp *relayingParty) CookieHandler() *utils.CookieHandler {
|
||||
func (rp *relyingParty) CookieHandler() *utils.CookieHandler {
|
||||
return rp.cookieHandler
|
||||
}
|
||||
|
||||
func (rp *relayingParty) HttpClient() *http.Client {
|
||||
func (rp *relyingParty) HttpClient() *http.Client {
|
||||
return rp.httpClient
|
||||
}
|
||||
|
||||
func (rp *relayingParty) IsOAuth2Only() bool {
|
||||
func (rp *relyingParty) IsOAuth2Only() bool {
|
||||
return rp.oauth2Only
|
||||
}
|
||||
|
||||
func (rp *relayingParty) ClientKey() []byte {
|
||||
return rp.clientKey
|
||||
func (rp *relyingParty) Signer() jose.Signer {
|
||||
return rp.signer
|
||||
}
|
||||
|
||||
func (rp *relayingParty) ClientKeyID() string {
|
||||
return rp.clientKeyID
|
||||
func (rp *relyingParty) UserinfoEndpoint() string {
|
||||
return rp.endpoints.UserinfoURL
|
||||
}
|
||||
|
||||
func (rp *relayingParty) IDTokenVerifier() IDTokenVerifier {
|
||||
func (rp *relyingParty) IDTokenVerifier() IDTokenVerifier {
|
||||
if rp.idTokenVerifier == nil {
|
||||
rp.idTokenVerifier = NewIDTokenVerifier(rp.issuer, rp.oauthConfig.ClientID, NewRemoteKeySet(rp.httpClient, rp.endpoints.JKWsURL), rp.verifierOpts...)
|
||||
}
|
||||
return rp.idTokenVerifier
|
||||
}
|
||||
|
||||
func (rp *relayingParty) ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string) {
|
||||
func (rp *relyingParty) ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string) {
|
||||
if rp.errorHandler == nil {
|
||||
rp.errorHandler = DefaultErrorHandler
|
||||
}
|
||||
return rp.errorHandler
|
||||
}
|
||||
|
||||
//NewRelayingPartyOAuth creates an (OAuth2) RelayingParty with the given
|
||||
//NewRelyingPartyOAuth creates an (OAuth2) RelyingParty with the given
|
||||
//OAuth2 Config and possible configOptions
|
||||
//it will use the AuthURL and TokenURL set in config
|
||||
func NewRelayingPartyOAuth(config *oauth2.Config, options ...Option) (RelayingParty, error) {
|
||||
rp := &relayingParty{
|
||||
func NewRelyingPartyOAuth(config *oauth2.Config, options ...Option) (RelyingParty, error) {
|
||||
rp := &relyingParty{
|
||||
oauthConfig: config,
|
||||
httpClient: utils.DefaultHTTPClient,
|
||||
oauth2Only: true,
|
||||
}
|
||||
|
||||
for _, optFunc := range options {
|
||||
optFunc(rp)
|
||||
if err := optFunc(rp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return rp, nil
|
||||
}
|
||||
|
||||
//NewRelayingPartyOIDC creates an (OIDC) RelayingParty with the given
|
||||
//NewRelyingPartyOIDC creates an (OIDC) RelyingParty with the given
|
||||
//issuer, clientID, clientSecret, redirectURI, scopes and possible configOptions
|
||||
//it will run discovery on the provided issuer and use the found endpoints
|
||||
func NewRelayingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, scopes []string, options ...Option) (RelayingParty, error) {
|
||||
rp := &relayingParty{
|
||||
func NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, scopes []string, options ...Option) (RelyingParty, error) {
|
||||
rp := &relyingParty{
|
||||
issuer: issuer,
|
||||
oauthConfig: &oauth2.Config{
|
||||
ClientID: clientID,
|
||||
|
@ -164,7 +161,9 @@ func NewRelayingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, sc
|
|||
}
|
||||
|
||||
for _, optFunc := range options {
|
||||
optFunc(rp)
|
||||
if err := optFunc(rp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
endpoints, err := Discover(rp.issuer, rp.httpClient)
|
||||
|
@ -178,12 +177,13 @@ func NewRelayingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, sc
|
|||
}
|
||||
|
||||
//DefaultRPOpts is the type for providing dynamic options to the DefaultRP
|
||||
type Option func(*relayingParty)
|
||||
type Option func(*relyingParty) error
|
||||
|
||||
//WithCookieHandler set a `CookieHandler` for securing the various redirects
|
||||
func WithCookieHandler(cookieHandler *utils.CookieHandler) Option {
|
||||
return func(rp *relayingParty) {
|
||||
return func(rp *relyingParty) error {
|
||||
rp.cookieHandler = cookieHandler
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,40 +191,49 @@ func WithCookieHandler(cookieHandler *utils.CookieHandler) Option {
|
|||
//it also sets a `CookieHandler` for securing the various redirects
|
||||
//and exchanging the code challenge
|
||||
func WithPKCE(cookieHandler *utils.CookieHandler) Option {
|
||||
return func(rp *relayingParty) {
|
||||
return func(rp *relyingParty) error {
|
||||
rp.pkce = true
|
||||
rp.cookieHandler = cookieHandler
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//WithHTTPClient provides the ability to set an http client to be used for the relaying party and verifier
|
||||
func WithHTTPClient(client *http.Client) Option {
|
||||
return func(rp *relayingParty) {
|
||||
return func(rp *relyingParty) error {
|
||||
rp.httpClient = client
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithErrorHandler(errorHandler ErrorHandler) Option {
|
||||
return func(rp *relayingParty) {
|
||||
return func(rp *relyingParty) error {
|
||||
rp.errorHandler = errorHandler
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithVerifierOpts(opts ...VerifierOption) Option {
|
||||
return func(rp *relayingParty) {
|
||||
return func(rp *relyingParty) error {
|
||||
rp.verifierOpts = opts
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithClientKey(path string) Option {
|
||||
return func(rp *relayingParty) {
|
||||
config, _ := ConfigFromKeyFile(path)
|
||||
rp.clientKey = []byte(config.Key)
|
||||
rp.clientKeyID = config.KeyID
|
||||
return func(rp *relyingParty) error {
|
||||
config, err := client.ConfigFromKeyFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rp.signer, err = client.NewSignerFromPrivateKeyByte([]byte(config.Key), config.KeyID)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//Discover calls the discovery endpoint of the provided issuer and returns the found endpoints
|
||||
//
|
||||
//deprecated: use client.Discover
|
||||
func Discover(issuer string, httpClient *http.Client) (Endpoints, error) {
|
||||
wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint
|
||||
req, err := http.NewRequest("GET", wellKnown, nil)
|
||||
|
@ -241,7 +250,7 @@ func Discover(issuer string, httpClient *http.Client) (Endpoints, error) {
|
|||
|
||||
//AuthURL returns the auth request url
|
||||
//(wrapping the oauth2 `AuthCodeURL`)
|
||||
func AuthURL(state string, rp RelayingParty, opts ...AuthURLOpt) string {
|
||||
func AuthURL(state string, rp RelyingParty, opts ...AuthURLOpt) string {
|
||||
authOpts := make([]oauth2.AuthCodeOption, 0)
|
||||
for _, opt := range opts {
|
||||
authOpts = append(authOpts, opt()...)
|
||||
|
@ -251,7 +260,7 @@ func AuthURL(state string, rp RelayingParty, opts ...AuthURLOpt) string {
|
|||
|
||||
//AuthURLHandler extends the `AuthURL` method with a http redirect handler
|
||||
//including handling setting cookie for secure `state` transfer
|
||||
func AuthURLHandler(stateFn func() string, rp RelayingParty) http.HandlerFunc {
|
||||
func AuthURLHandler(stateFn func() string, rp RelyingParty) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
opts := make([]AuthURLOpt, 0)
|
||||
state := stateFn()
|
||||
|
@ -272,7 +281,7 @@ func AuthURLHandler(stateFn func() string, rp RelayingParty) http.HandlerFunc {
|
|||
}
|
||||
|
||||
//GenerateAndStoreCodeChallenge generates a PKCE code challenge and stores its verifier into a secure cookie
|
||||
func GenerateAndStoreCodeChallenge(w http.ResponseWriter, rp RelayingParty) (string, error) {
|
||||
func GenerateAndStoreCodeChallenge(w http.ResponseWriter, rp RelyingParty) (string, error) {
|
||||
codeVerifier := uuid.New().String()
|
||||
if err := rp.CookieHandler().SetCookie(w, pkceCode, codeVerifier); err != nil {
|
||||
return "", err
|
||||
|
@ -282,7 +291,7 @@ func GenerateAndStoreCodeChallenge(w http.ResponseWriter, rp RelayingParty) (str
|
|||
|
||||
//CodeExchange handles the oauth2 code exchange, extracting and validating the id_token
|
||||
//returning it parsed together with the oauth2 tokens (access, refresh)
|
||||
func CodeExchange(ctx context.Context, code string, rp RelayingParty, opts ...CodeExchangeOpt) (tokens *oidc.Tokens, err error) {
|
||||
func CodeExchange(ctx context.Context, code string, rp RelyingParty, opts ...CodeExchangeOpt) (tokens *oidc.Tokens, err error) {
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, rp.HttpClient())
|
||||
codeOpts := make([]oauth2.AuthCodeOption, 0)
|
||||
for _, opt := range opts {
|
||||
|
@ -314,7 +323,7 @@ func CodeExchange(ctx context.Context, code string, rp RelayingParty, opts ...Co
|
|||
//CodeExchangeHandler extends the `CodeExchange` method with a http handler
|
||||
//including cookie handling for secure `state` transfer
|
||||
//and optional PKCE code verifier checking
|
||||
func CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc.Tokens, string), rp RelayingParty) http.HandlerFunc {
|
||||
func CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc.Tokens, string), rp RelyingParty) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
state, err := tryReadStateCookie(w, r, rp)
|
||||
if err != nil {
|
||||
|
@ -335,8 +344,8 @@ func CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc
|
|||
}
|
||||
codeOpts = append(codeOpts, WithCodeVerifier(codeVerifier))
|
||||
}
|
||||
if len(rp.ClientKey()) > 0 {
|
||||
assertion, err := oidc.GenerateJWTProfileToken(oidc.NewJWTProfileAssertion(rp.OAuthConfig().ClientID, rp.ClientKeyID(), []string{"http://localhost:50002/oauth/v2"}, rp.ClientKey()))
|
||||
if rp.Signer() != nil {
|
||||
assertion, err := client.SignedJWTProfileAssertion(rp.OAuthConfig().ClientID, []string{rp.Issuer()}, time.Hour, rp.Signer())
|
||||
if err != nil {
|
||||
http.Error(w, "failed to build assertion: "+err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
|
@ -352,51 +361,21 @@ func CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc
|
|||
}
|
||||
}
|
||||
|
||||
//ClientCredentials is the `RelayingParty` interface implementation
|
||||
//handling the oauth2 client credentials grant
|
||||
func ClientCredentials(ctx context.Context, rp RelayingParty, scopes ...string) (newToken *oauth2.Token, err error) {
|
||||
return CallTokenEndpointAuthorized(grants.ClientCredentialsGrantBasic(scopes...), rp)
|
||||
}
|
||||
|
||||
func CallTokenEndpointAuthorized(request interface{}, rp RelayingParty) (newToken *oauth2.Token, err error) {
|
||||
config := rp.OAuthConfig()
|
||||
var fn interface{} = utils.AuthorizeBasic(config.ClientID, config.ClientSecret)
|
||||
if config.Endpoint.AuthStyle == oauth2.AuthStyleInParams {
|
||||
fn = func(form url.Values) {
|
||||
form.Set("client_id", config.ClientID)
|
||||
form.Set("client_secret", config.ClientSecret)
|
||||
}
|
||||
}
|
||||
return callTokenEndpoint(request, fn, rp)
|
||||
}
|
||||
|
||||
func CallTokenEndpoint(request interface{}, rp RelayingParty) (newToken *oauth2.Token, err error) {
|
||||
return callTokenEndpoint(request, nil, rp)
|
||||
}
|
||||
|
||||
func callTokenEndpoint(request interface{}, authFn interface{}, rp RelayingParty) (newToken *oauth2.Token, err error) {
|
||||
req, err := utils.FormRequest(rp.OAuthConfig().Endpoint.TokenURL, request, encoder, authFn)
|
||||
//Userinfo will call the OIDC Userinfo Endpoint with the provided token
|
||||
func Userinfo(token string, rp RelyingParty) (oidc.UserInfo, error) {
|
||||
req, err := http.NewRequest("GET", rp.UserinfoEndpoint(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokenRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
if err := utils.HttpRequest(rp.HttpClient(), req, &tokenRes); err != nil {
|
||||
req.Header.Set("authorization", token)
|
||||
userinfo := oidc.NewUserInfo()
|
||||
if err := utils.HttpRequest(rp.HttpClient(), req, &userinfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oauth2.Token{
|
||||
AccessToken: tokenRes.AccessToken,
|
||||
TokenType: tokenRes.TokenType,
|
||||
RefreshToken: tokenRes.RefreshToken,
|
||||
Expiry: time.Now().UTC().Add(time.Duration(tokenRes.ExpiresIn) * time.Second),
|
||||
}, nil
|
||||
return userinfo, nil
|
||||
}
|
||||
|
||||
func trySetStateCookie(w http.ResponseWriter, state string, rp RelayingParty) error {
|
||||
func trySetStateCookie(w http.ResponseWriter, state string, rp RelyingParty) error {
|
||||
if rp.CookieHandler() != nil {
|
||||
if err := rp.CookieHandler().SetCookie(w, stateParam, state); err != nil {
|
||||
return err
|
||||
|
@ -405,7 +384,7 @@ func trySetStateCookie(w http.ResponseWriter, state string, rp RelayingParty) er
|
|||
return nil
|
||||
}
|
||||
|
||||
func tryReadStateCookie(w http.ResponseWriter, r *http.Request, rp RelayingParty) (state string, err error) {
|
||||
func tryReadStateCookie(w http.ResponseWriter, r *http.Request, rp RelyingParty) (state string, err error) {
|
||||
if rp.CookieHandler() == nil {
|
||||
return r.FormValue(stateParam), nil
|
||||
}
|
||||
|
@ -417,7 +396,7 @@ func tryReadStateCookie(w http.ResponseWriter, r *http.Request, rp RelayingParty
|
|||
return state, nil
|
||||
}
|
||||
|
||||
type OptionFunc func(RelayingParty)
|
||||
type OptionFunc func(RelyingParty)
|
||||
|
||||
type Endpoints struct {
|
||||
oauth2.Endpoint
|
||||
|
@ -472,9 +451,6 @@ func WithCodeVerifier(codeVerifier string) CodeExchangeOpt {
|
|||
//WithClientAssertionJWT sets the `client_assertion` param in the token request
|
||||
func WithClientAssertionJWT(clientAssertion string) CodeExchangeOpt {
|
||||
return func() []oauth2.AuthCodeOption {
|
||||
return []oauth2.AuthCodeOption{
|
||||
oauth2.SetAuthURLParam("client_assertion", clientAssertion),
|
||||
oauth2.SetAuthURLParam("client_assertion_type", oidc.ClientAssertionTypeJWTAssertion),
|
||||
}
|
||||
return client.ClientAssertionCodeOptions(clientAssertion)
|
||||
}
|
||||
}
|
27
pkg/client/rp/tockenexchange.go
Normal file
27
pkg/client/rp/tockenexchange.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package rp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc/grants/tokenexchange"
|
||||
)
|
||||
|
||||
//TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange`
|
||||
type TokenExchangeRP interface {
|
||||
RelyingParty
|
||||
|
||||
//TokenExchange implement the `Token Exchange Grant` exchanging some token for an other
|
||||
TokenExchange(context.Context, *tokenexchange.TokenExchangeRequest) (*oauth2.Token, error)
|
||||
}
|
||||
|
||||
//DelegationTokenExchangeRP extends the `TokenExchangeRP` interface
|
||||
//for the specific `delegation token` request
|
||||
type DelegationTokenExchangeRP interface {
|
||||
TokenExchangeRP
|
||||
|
||||
//DelegationTokenExchange implement the `Token Exchange Grant`
|
||||
//providing an access token in request for a `delegation` token for a given resource / audience
|
||||
DelegationTokenExchange(context.Context, string, ...tokenexchange.TokenExchangeOption) (*oauth2.Token, error)
|
||||
}
|
|
@ -214,13 +214,3 @@ func (i *idTokenVerifier) ACR() oidc.ACRVerifier {
|
|||
func (i *idTokenVerifier) MaxAge() time.Duration {
|
||||
return i.maxAge
|
||||
}
|
||||
|
||||
//deprecated: Use IDTokenVerifier (or oidc.Verifier)
|
||||
type Verifier interface {
|
||||
|
||||
//Verify checks the access_token and id_token and returns the `id token claims`
|
||||
Verify(ctx context.Context, accessToken, idTokenString string) (*oidc.IDTokenClaims, error)
|
||||
|
||||
//VerifyIDToken checks the id_token only and returns its `id token claims`
|
||||
VerifyIDToken(ctx context.Context, idTokenString string) (*oidc.IDTokenClaims, error)
|
||||
}
|
123
pkg/client/rs/resource_server.go
Normal file
123
pkg/client/rs/resource_server.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package rs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/client"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
type ResourceServer interface {
|
||||
IntrospectionURL() string
|
||||
HttpClient() *http.Client
|
||||
AuthFn() (interface{}, error)
|
||||
}
|
||||
|
||||
type resourceServer struct {
|
||||
issuer string
|
||||
tokenURL string
|
||||
introspectURL string
|
||||
httpClient *http.Client
|
||||
authFn func() (interface{}, error)
|
||||
}
|
||||
|
||||
func (r *resourceServer) IntrospectionURL() string {
|
||||
return r.introspectURL
|
||||
}
|
||||
|
||||
func (r *resourceServer) HttpClient() *http.Client {
|
||||
return r.httpClient
|
||||
}
|
||||
|
||||
func (r *resourceServer) AuthFn() (interface{}, error) {
|
||||
return r.authFn()
|
||||
}
|
||||
|
||||
func NewResourceServerClientCredentials(issuer, clientID, clientSecret string, option Option) (ResourceServer, error) {
|
||||
authorizer := func() (interface{}, error) {
|
||||
return utils.AuthorizeBasic(clientID, clientSecret), nil
|
||||
}
|
||||
return newResourceServer(issuer, authorizer, option)
|
||||
}
|
||||
func NewResourceServerJWTProfile(issuer, clientID, keyID string, key []byte, options ...Option) (ResourceServer, error) {
|
||||
signer, err := client.NewSignerFromPrivateKeyByte(key, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorizer := func() (interface{}, error) {
|
||||
assertion, err := client.SignedJWTProfileAssertion(clientID, []string{issuer}, time.Hour, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.ClientAssertionFormAuthorization(assertion), nil
|
||||
}
|
||||
return newResourceServer(issuer, authorizer, options...)
|
||||
}
|
||||
|
||||
func newResourceServer(issuer string, authorizer func() (interface{}, error), options ...Option) (*resourceServer, error) {
|
||||
rs := &resourceServer{
|
||||
issuer: issuer,
|
||||
httpClient: utils.DefaultHTTPClient,
|
||||
}
|
||||
for _, optFunc := range options {
|
||||
optFunc(rs)
|
||||
}
|
||||
if rs.introspectURL == "" || rs.tokenURL == "" {
|
||||
config, err := client.Discover(rs.issuer, rs.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs.tokenURL = config.TokenEndpoint
|
||||
rs.introspectURL = config.IntrospectionEndpoint
|
||||
}
|
||||
if rs.introspectURL == "" || rs.tokenURL == "" {
|
||||
return nil, errors.New("introspectURL and/or tokenURL is empty: please provide with either `WithStaticEndpoints` or a discovery url")
|
||||
}
|
||||
rs.authFn = authorizer
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func NewResourceServerFromKeyFile(issuer, path string, options ...Option) (ResourceServer, error) {
|
||||
c, err := client.ConfigFromKeyFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewResourceServerJWTProfile(issuer, c.ClientID, c.KeyID, []byte(c.Key), options...)
|
||||
}
|
||||
|
||||
type Option func(*resourceServer)
|
||||
|
||||
//WithClient provides the ability to set an http client to be used for the resource server
|
||||
func WithClient(client *http.Client) Option {
|
||||
return func(server *resourceServer) {
|
||||
server.httpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
//WithStaticEndpoints provides the ability to set static token and introspect URL
|
||||
func WithStaticEndpoints(tokenURL, introspectURL string) Option {
|
||||
return func(server *resourceServer) {
|
||||
server.tokenURL = tokenURL
|
||||
server.introspectURL = introspectURL
|
||||
}
|
||||
}
|
||||
|
||||
func Introspect(ctx context.Context, rp ResourceServer, token string) (oidc.IntrospectionResponse, error) {
|
||||
authFn, err := rp.AuthFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := utils.FormRequest(rp.IntrospectionURL(), &oidc.IntrospectionRequest{Token: token}, client.Encoder, authFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := oidc.NewIntrospectionResponse()
|
||||
if err := utils.HttpRequest(rp.HttpClient(), req, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
|
@ -42,6 +42,7 @@ type DiscoveryConfiguration struct {
|
|||
DisplayValuesSupported []Display `json:"display_values_supported,omitempty"`
|
||||
ClaimTypesSupported []string `json:"claim_types_supported,omitempty"`
|
||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||
ClaimsParameterSupported bool `json:"claims_parameter_supported,omitempty"`
|
||||
CodeChallengeMethodsSupported []CodeChallengeMethod `json:"code_challenge_methods_supported,omitempty"`
|
||||
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
||||
ClaimsLocalesSupported []language.Tag `json:"claims_locales_supported,omitempty"`
|
||||
|
|
|
@ -34,11 +34,6 @@ type OPStorage interface {
|
|||
GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, error)
|
||||
GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
|
||||
ValidateJWTProfileScopes(ctx context.Context, userID string, scope oidc.Scopes) (oidc.Scopes, error)
|
||||
|
||||
//deprecated: use GetUserinfoFromScopes instead
|
||||
GetUserinfoFromScopes(ctx context.Context, userID, clientID string, scopes []string) (oidc.UserInfo, error)
|
||||
//deprecated: use SetUserinfoFromToken instead
|
||||
GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (oidc.UserInfo, error)
|
||||
}
|
||||
|
||||
type Storage interface {
|
||||
|
|
|
@ -114,7 +114,8 @@ func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, vali
|
|||
}
|
||||
}
|
||||
if len(scopes) > 0 {
|
||||
userInfo, err := storage.GetUserinfoFromScopes(ctx, authReq.GetSubject(), authReq.GetClientID(), scopes)
|
||||
userInfo := oidc.NewUserInfo()
|
||||
err := storage.SetUserinfoFromScopes(ctx, userInfo, authReq.GetSubject(), authReq.GetClientID(), scopes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -123,6 +123,9 @@ func AuthorizeClient(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exc
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if client.AuthMethod() == oidc.AuthMethodPrivateKeyJWT {
|
||||
return nil, nil, errors.New("invalid_grant")
|
||||
}
|
||||
if client.AuthMethod() == oidc.AuthMethodNone {
|
||||
authReq, err := AuthorizeCodeChallenge(ctx, tokenReq, exchanger)
|
||||
return authReq, client, err
|
||||
|
|
|
@ -34,7 +34,8 @@ func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoP
|
|||
http.Error(w, "access token invalid", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
info, err := userinfoProvider.Storage().GetUserinfoFromToken(r.Context(), tokenID, subject, r.Header.Get("origin"))
|
||||
info := oidc.NewUserInfo()
|
||||
err = userinfoProvider.Storage().SetUserinfoFromToken(r.Context(), info, tokenID, subject, r.Header.Get("origin"))
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
utils.MarshalJSON(w, err)
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
package rp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
type ResourceServer interface {
|
||||
IntrospectionURL() string
|
||||
HttpClient() *http.Client
|
||||
AuthFn() interface{}
|
||||
}
|
||||
|
||||
type resourceServer struct {
|
||||
issuer string
|
||||
tokenURL string
|
||||
introspectURL string
|
||||
httpClient *http.Client
|
||||
authFn interface{}
|
||||
}
|
||||
|
||||
type jwtAccessTokenSource struct {
|
||||
clientID string
|
||||
audience []string
|
||||
PrivateKey []byte
|
||||
PrivateKeyID string
|
||||
}
|
||||
|
||||
func (j *jwtAccessTokenSource) Token() (*oauth2.Token, error) {
|
||||
iat := time.Now()
|
||||
exp := iat.Add(time.Hour)
|
||||
assertion, err := GenerateJWTProfileToken(&oidc.JWTProfileAssertion{
|
||||
PrivateKeyID: j.PrivateKeyID,
|
||||
PrivateKey: j.PrivateKey,
|
||||
Issuer: j.clientID,
|
||||
Subject: j.clientID,
|
||||
Audience: j.audience,
|
||||
Expiration: oidc.Time(exp),
|
||||
IssuedAt: oidc.Time(iat),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oauth2.Token{AccessToken: assertion, TokenType: "Bearer", Expiry: exp}, nil
|
||||
}
|
||||
|
||||
func (r *resourceServer) IntrospectionURL() string {
|
||||
return r.introspectURL
|
||||
}
|
||||
|
||||
func (r *resourceServer) HttpClient() *http.Client {
|
||||
return r.httpClient
|
||||
}
|
||||
|
||||
func (r *resourceServer) AuthFn() interface{} {
|
||||
return r.authFn
|
||||
}
|
||||
|
||||
func NewResourceServerClientCredentials(issuer, clientID, clientSecret string, option RSOption) (ResourceServer, error) {
|
||||
authorizer := func(tokenURL string) func(context.Context) *http.Client {
|
||||
return (&clientcredentials.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
TokenURL: tokenURL,
|
||||
}).Client
|
||||
}
|
||||
return newResourceServer(issuer, authorizer, option)
|
||||
}
|
||||
func NewResourceServerJWTProfile(issuer, clientID, keyID string, key []byte, options ...RSOption) (ResourceServer, error) {
|
||||
ts := &jwtAccessTokenSource{
|
||||
clientID: clientID,
|
||||
PrivateKey: key,
|
||||
PrivateKeyID: keyID,
|
||||
audience: []string{issuer},
|
||||
}
|
||||
|
||||
//authorizer := func(tokenURL string) func(context.Context) *http.Client {
|
||||
// return func(ctx context.Context) *http.Client {
|
||||
// return oauth2.NewClient(ctx, oauth2.ReuseTokenSource(token, ts))
|
||||
// }
|
||||
//}
|
||||
authorizer := utils.FormAuthorization(func(values url.Values) {
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
//return nil, err
|
||||
}
|
||||
values.Set("client_assertion", token.AccessToken)
|
||||
})
|
||||
return newResourceServer(issuer, authorizer, options...)
|
||||
}
|
||||
|
||||
//
|
||||
//func newResourceServer(issuer string, authorizer func(tokenURL string) func(ctx context.Context) *http.Client, options ...RSOption) (*resourceServer, error) {
|
||||
// rp := &resourceServer{
|
||||
// issuer: issuer,
|
||||
// httpClient: utils.DefaultHTTPClient,
|
||||
// }
|
||||
// for _, optFunc := range options {
|
||||
// optFunc(rp)
|
||||
// }
|
||||
// if rp.introspectURL == "" || rp.tokenURL == "" {
|
||||
// endpoints, err := Discover(rp.issuer, rp.httpClient)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// rp.tokenURL = endpoints.TokenURL
|
||||
// rp.introspectURL = endpoints.IntrospectURL
|
||||
// }
|
||||
// if rp.introspectURL == "" || rp.tokenURL == "" {
|
||||
// return nil, errors.New("introspectURL and/or tokenURL is empty: please provide with either `WithStaticEndpoints` or a discovery url")
|
||||
// }
|
||||
// //rp.httpClient = authorizer(rp.tokenURL)(context.WithValue(context.Background(), oauth2.HTTPClient, rp.HttpClient()))
|
||||
// return rp, nil
|
||||
//}
|
||||
func newResourceServer(issuer string, authorizer interface{}, options ...RSOption) (*resourceServer, error) {
|
||||
rp := &resourceServer{
|
||||
issuer: issuer,
|
||||
httpClient: utils.DefaultHTTPClient,
|
||||
}
|
||||
for _, optFunc := range options {
|
||||
optFunc(rp)
|
||||
}
|
||||
if rp.introspectURL == "" || rp.tokenURL == "" {
|
||||
endpoints, err := Discover(rp.issuer, rp.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rp.tokenURL = endpoints.TokenURL
|
||||
rp.introspectURL = endpoints.IntrospectURL
|
||||
}
|
||||
if rp.introspectURL == "" || rp.tokenURL == "" {
|
||||
return nil, errors.New("introspectURL and/or tokenURL is empty: please provide with either `WithStaticEndpoints` or a discovery url")
|
||||
}
|
||||
//rp.httpClient = authorizer(rp.tokenURL)(context.WithValue(context.Background(), oauth2.HTTPClient, rp.HttpClient()))
|
||||
rp.authFn = authorizer
|
||||
return rp, nil
|
||||
}
|
||||
|
||||
func NewResourceServerFromKeyFile(path string, options ...RSOption) (ResourceServer, error) {
|
||||
c, err := ConfigFromKeyFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Issuer = "http://localhost:50002/oauth/v2"
|
||||
return NewResourceServerJWTProfile(c.Issuer, c.ClientID, c.KeyID, []byte(c.Key), options...)
|
||||
}
|
||||
|
||||
type RSOption func(*resourceServer)
|
||||
|
||||
//WithClient provides the ability to set an http client to be used for the resource server
|
||||
func WithClient(client *http.Client) RSOption {
|
||||
return func(server *resourceServer) {
|
||||
server.httpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
//WithStaticEndpoints provides the ability to set static token and introspect URL
|
||||
func WithStaticEndpoints(tokenURL, introspectURL string) RSOption {
|
||||
return func(server *resourceServer) {
|
||||
server.tokenURL = tokenURL
|
||||
server.introspectURL = introspectURL
|
||||
}
|
||||
}
|
||||
|
||||
func Introspect(ctx context.Context, rp ResourceServer, token string) (oidc.IntrospectionResponse, error) {
|
||||
req, err := utils.FormRequest(rp.IntrospectionURL(), &oidc.IntrospectionRequest{Token: token}, encoder, rp.AuthFn())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := oidc.NewIntrospectionResponse()
|
||||
if err := utils.HttpRequest(rp.HttpClient(), req, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package rp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/oidc/grants/tokenexchange"
|
||||
)
|
||||
|
||||
//TokenExchangeRP extends the `RelayingParty` interface for the *draft* oauth2 `Token Exchange`
|
||||
type TokenExchangeRP interface {
|
||||
RelayingParty
|
||||
|
||||
//TokenExchange implement the `Token Exchange Grant` exchanging some token for an other
|
||||
TokenExchange(context.Context, *tokenexchange.TokenExchangeRequest) (*oauth2.Token, error)
|
||||
}
|
||||
|
||||
//DelegationTokenExchangeRP extends the `TokenExchangeRP` interface
|
||||
//for the specific `delegation token` request
|
||||
type DelegationTokenExchangeRP interface {
|
||||
TokenExchangeRP
|
||||
|
||||
//DelegationTokenExchange implement the `Token Exchange Grant`
|
||||
//providing an access token in request for a `delegation` token for a given resource / audience
|
||||
DelegationTokenExchange(context.Context, string, ...tokenexchange.TokenExchangeOption) (*oauth2.Token, error)
|
||||
}
|
||||
|
||||
//TokenExchange handles the oauth2 token exchange
|
||||
func TokenExchange(ctx context.Context, request *tokenexchange.TokenExchangeRequest, rp RelayingParty) (newToken *oauth2.Token, err error) {
|
||||
return CallTokenEndpoint(request, rp)
|
||||
}
|
||||
|
||||
//DelegationTokenExchange handles the oauth2 token exchange for a delegation token
|
||||
func DelegationTokenExchange(ctx context.Context, subjectToken string, rp RelayingParty, reqOpts ...tokenexchange.TokenExchangeOption) (newToken *oauth2.Token, err error) {
|
||||
return TokenExchange(ctx, DelegationTokenRequest(subjectToken, reqOpts...), rp)
|
||||
}
|
||||
|
||||
//JWTProfileExchange handles the oauth2 jwt profile exchange
|
||||
func JWTProfileExchange(ctx context.Context, jwtProfileGrantRequest *oidc.JWTProfileGrantRequest, rp RelayingParty) (*oauth2.Token, error) {
|
||||
return CallTokenEndpoint(jwtProfileGrantRequest, rp)
|
||||
}
|
||||
|
||||
//JWTProfileExchange handles the oauth2 jwt profile exchange
|
||||
func JWTProfileAssertionExchange(ctx context.Context, assertion *oidc.JWTProfileAssertion, scopes oidc.Scopes, rp RelayingParty) (*oauth2.Token, error) {
|
||||
token, err := GenerateJWTProfileToken(assertion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return JWTProfileExchange(ctx, oidc.NewJWTProfileGrantRequest(token, scopes...), rp)
|
||||
}
|
||||
|
||||
func GenerateJWTProfileToken(assertion *oidc.JWTProfileAssertion) (string, error) {
|
||||
privateKey, err := bytesToPrivateKey(assertion.PrivateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key := jose.SigningKey{
|
||||
Algorithm: jose.RS256,
|
||||
Key: &jose.JSONWebKey{Key: privateKey, KeyID: assertion.PrivateKeyID},
|
||||
}
|
||||
signer, err := jose.NewSigner(key, &jose.SignerOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
marshalledAssertion, err := json.Marshal(assertion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signedAssertion, err := signer.Sign(marshalledAssertion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return signedAssertion.CompactSerialize()
|
||||
}
|
||||
|
||||
func bytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(priv)
|
||||
enc := x509.IsEncryptedPEMBlock(block)
|
||||
b := block.Bytes
|
||||
var err error
|
||||
if enc {
|
||||
b, err = x509.DecryptPEMBlock(block, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
key, err := x509.ParsePKCS1PrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
25
pkg/utils/key.go
Normal file
25
pkg/utils/key.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
)
|
||||
|
||||
func BytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(priv)
|
||||
enc := x509.IsEncryptedPEMBlock(block)
|
||||
b := block.Bytes
|
||||
var err error
|
||||
if enc {
|
||||
b, err = x509.DecryptPEMBlock(block, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
key, err := x509.ParsePKCS1PrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue