introspect and client assertion
This commit is contained in:
parent
50ab51bb46
commit
960be5af1f
19 changed files with 413 additions and 156 deletions
|
@ -24,7 +24,7 @@ func NewSHACodeChallenge(code string) string {
|
|||
|
||||
func VerifyCodeChallenge(c *CodeChallenge, codeVerifier string) bool {
|
||||
if c == nil {
|
||||
return false //TODO: ?
|
||||
return false
|
||||
}
|
||||
if c.Method == CodeChallengeMethodS256 {
|
||||
codeVerifier = NewSHACodeChallenge(codeVerifier)
|
||||
|
|
|
@ -5,23 +5,23 @@ const (
|
|||
)
|
||||
|
||||
type DiscoveryConfiguration struct {
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
|
||||
TokenEndpoint string `json:"token_endpoint,omitempty"`
|
||||
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
|
||||
CheckSessionIframe string `json:"check_session_iframe,omitempty"`
|
||||
JwksURI string `json:"jwks_uri,omitempty"`
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
||||
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
||||
TokenEndpointAuthMethodsSupported []AuthMethod `json:"token_endpoint_auth_methods_supported,omitempty"`
|
||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
|
||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
|
||||
TokenEndpoint string `json:"token_endpoint,omitempty"`
|
||||
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
|
||||
CheckSessionIframe string `json:"check_session_iframe,omitempty"`
|
||||
JwksURI string `json:"jwks_uri,omitempty"`
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
||||
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
||||
TokenEndpointAuthMethodsSupported []AuthMethod `json:"token_endpoint_auth_methods_supported,omitempty"`
|
||||
CodeChallengeMethodsSupported []CodeChallengeMethod `json:"code_challenge_methods_supported,omitempty"`
|
||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||
}
|
||||
|
||||
type AuthMethod string
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
package tokenexchange
|
||||
|
||||
import (
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
AccessTokenType = "urn:ietf:params:oauth:token-type:access_token"
|
||||
RefreshTokenType = "urn:ietf:params:oauth:token-type:refresh_token"
|
||||
|
@ -26,22 +22,6 @@ type TokenExchangeRequest struct {
|
|||
requestedTokenType string `schema:"requested_token_type"`
|
||||
}
|
||||
|
||||
type JWTProfileRequest struct {
|
||||
Assertion string `schema:"assertion"`
|
||||
Scope oidc.Scopes `schema:"scope"`
|
||||
GrantType oidc.GrantType `schema:"grant_type"`
|
||||
}
|
||||
|
||||
//ClientCredentialsGrantBasic creates an oauth2 `Client Credentials` Grant
|
||||
//sneding client_id and client_secret as basic auth header
|
||||
func NewJWTProfileRequest(assertion string, scopes ...string) *JWTProfileRequest {
|
||||
return &JWTProfileRequest{
|
||||
GrantType: oidc.GrantTypeBearer,
|
||||
Assertion: assertion,
|
||||
Scope: scopes,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTokenExchangeRequest(subjectToken, subjectTokenType string, opts ...TokenExchangeOption) *TokenExchangeRequest {
|
||||
t := &TokenExchangeRequest{
|
||||
grantType: TokenExchangeGrantType,
|
||||
|
|
|
@ -251,5 +251,9 @@ func (i *introspectionResponse) UnmarshalJSON(data []byte) error {
|
|||
|
||||
i.UpdatedAt = Time(time.Unix(a.UpdatedAt, 0).UTC())
|
||||
|
||||
if err := json.Unmarshal(data, &i.claims); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
18
pkg/oidc/jwt_profile.go
Normal file
18
pkg/oidc/jwt_profile.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package oidc
|
||||
|
||||
type JWTProfileGrantRequest struct {
|
||||
Assertion string `schema:"assertion"`
|
||||
Scope Scopes `schema:"scope"`
|
||||
GrantType GrantType `schema:"grant_type"`
|
||||
}
|
||||
|
||||
//NewJWTProfileGrantRequest creates an oauth2 `JSON Web Token (JWT) Profile` Grant
|
||||
//`urn:ietf:params:oauth:grant-type:jwt-bearer`
|
||||
//sending a self-signed jwt as assertion
|
||||
func NewJWTProfileGrantRequest(assertion string, scopes ...string) *JWTProfileGrantRequest {
|
||||
return &JWTProfileGrantRequest{
|
||||
GrantType: GrantTypeBearer,
|
||||
Assertion: assertion,
|
||||
Scope: scopes,
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
|
@ -14,6 +17,8 @@ import (
|
|||
const (
|
||||
//BearerToken defines the token_type `Bearer`, which is returned in a successful token response
|
||||
BearerToken = "Bearer"
|
||||
|
||||
PrefixBearer = BearerToken + " "
|
||||
)
|
||||
|
||||
type Tokens struct {
|
||||
|
@ -397,7 +402,7 @@ type AccessTokenResponse struct {
|
|||
type JWTProfileAssertion struct {
|
||||
PrivateKeyID string `json:"-"`
|
||||
PrivateKey []byte `json:"-"`
|
||||
Issuer string `json:"issuer"`
|
||||
Issuer string `json:"iss"`
|
||||
Subject string `json:"sub"`
|
||||
Audience Audience `json:"aud"`
|
||||
Expiration Time `json:"exp"`
|
||||
|
@ -412,6 +417,19 @@ func NewJWTProfileAssertionFromKeyJSON(filename string, audience []string) (*JWT
|
|||
return NewJWTProfileAssertionFromFileData(data, audience)
|
||||
}
|
||||
|
||||
func NewJWTProfileAssertionStringFromFileData(data []byte, audience []string) (string, error) {
|
||||
keyData := new(struct {
|
||||
KeyID string `json:"keyId"`
|
||||
Key string `json:"key"`
|
||||
UserID string `json:"userId"`
|
||||
})
|
||||
err := json.Unmarshal(data, keyData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return generateJWTProfileToken(NewJWTProfileAssertion(keyData.UserID, keyData.KeyID, audience, []byte(keyData.Key)))
|
||||
}
|
||||
|
||||
func NewJWTProfileAssertionFromFileData(data []byte, audience []string) (*JWTProfileAssertion, error) {
|
||||
keyData := new(struct {
|
||||
KeyID string `json:"keyId"`
|
||||
|
@ -454,3 +472,46 @@ func AppendClientIDToAudience(clientID string, audience []string) []string {
|
|||
}
|
||||
return append(audience, clientID)
|
||||
}
|
||||
|
||||
func generateJWTProfileToken(assertion *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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue