feat: service account token exchange

This commit is contained in:
adlerhurst 2020-09-02 17:52:22 +02:00
parent c828290ef1
commit 7a109a763d
7 changed files with 71 additions and 10 deletions

View file

@ -3,11 +3,12 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"github.com/caos/oidc/pkg/cli" "github.com/caos/oidc/pkg/cli"
"github.com/caos/oidc/pkg/rp" "github.com/caos/oidc/pkg/rp"
"github.com/google/go-github/v31/github" "github.com/google/go-github/v31/github"
githubOAuth "golang.org/x/oauth2/github" githubOAuth "golang.org/x/oauth2/github"
"os"
) )
var ( var (
@ -37,7 +38,6 @@ func main() {
if err != nil { if err != nil {
fmt.Println("OAuth flow failed") fmt.Println("OAuth flow failed")
} else { } else {
fmt.Println("OAuth flow success") fmt.Println("OAuth flow success")
} }
} }

View file

@ -170,7 +170,7 @@ func (s *AuthStorage) GetKeySet(_ context.Context) (*jose.JSONWebKeySet, error)
pubkey := s.key.Public() pubkey := s.key.Public()
return &jose.JSONWebKeySet{ return &jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{ Keys: []jose.JSONWebKey{
jose.JSONWebKey{Key: pubkey, Use: "sig", Algorithm: "RS256", KeyID: "1"}, {Key: pubkey, Use: "sig", Algorithm: "RS256", KeyID: "1"},
}, },
}, nil }, nil
} }

View file

@ -3,6 +3,7 @@ package oidc
import ( import (
"errors" "errors"
"strings" "strings"
"time"
"golang.org/x/text/language" "golang.org/x/text/language"
) )
@ -24,7 +25,8 @@ const (
PromptConsent Prompt = "consent" PromptConsent Prompt = "consent"
PromptSelectAccount Prompt = "select_account" PromptSelectAccount Prompt = "select_account"
GrantTypeCode GrantType = "authorization_code" GrantTypeCode GrantType = "authorization_code"
GrantTypeBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
BearerToken = "Bearer" BearerToken = "Bearer"
) )
@ -100,6 +102,13 @@ type AccessTokenResponse struct {
IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"` IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"`
} }
type JWTTokenRequest struct {
Scopes Scopes `schema:"scope"`
Audience []string `schema:"aud"`
IssuedAt time.Time `schema:"iat"`
ExpiresAt time.Time `schema:"exp"`
}
type TokenExchangeRequest struct { type TokenExchangeRequest struct {
subjectToken string `schema:"subject_token"` subjectToken string `schema:"subject_token"`
subjectTokenType string `schema:"subject_token_type"` subjectTokenType string `schema:"subject_token_type"`

View file

@ -273,6 +273,11 @@ func (p *DefaultOP) Signer() Signer {
func (p *DefaultOP) Crypto() Crypto { func (p *DefaultOP) Crypto() Crypto {
return p.crypto return p.crypto
} }
func (p *DefaultOP) Verifier() rp.Verifier {
return p.verifier
}
func (p *DefaultOP) HandleReady(w http.ResponseWriter, r *http.Request) { func (p *DefaultOP) HandleReady(w http.ResponseWriter, r *http.Request) {
probes := []ProbesFn{ probes := []ProbesFn{
ReadySigner(p.Signer()), ReadySigner(p.Signer()),
@ -299,9 +304,13 @@ func (p *DefaultOP) HandleExchange(w http.ResponseWriter, r *http.Request) {
RequestError(w, r, ErrInvalidRequest("grant_type missing")) RequestError(w, r, ErrInvalidRequest("grant_type missing"))
return return
} }
if reqType == string(oidc.GrantTypeCode) { switch reqType {
case string(oidc.GrantTypeCode):
CodeExchange(w, r, p) CodeExchange(w, r, p)
return return
case string(oidc.GrantTypeBearer):
JWTExchange(w, r, p)
return
} }
TokenExchange(w, r, p) TokenExchange(w, r, p)
} }

View file

@ -3,11 +3,13 @@ package op
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net/http" "net/http"
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/rp"
"github.com/caos/oidc/pkg/utils" "github.com/caos/oidc/pkg/utils"
) )
@ -20,6 +22,11 @@ type Exchanger interface {
AuthMethodPostSupported() bool AuthMethodPostSupported() bool
} }
type VerifyExchanger interface {
Exchanger
Verifier() rp.Verifier
}
func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
tokenReq, err := ParseAccessTokenRequest(r, exchanger.Decoder()) tokenReq, err := ParseAccessTokenRequest(r, exchanger.Decoder())
if err != nil { if err != nil {
@ -116,6 +123,33 @@ func AuthorizeCodeChallenge(ctx context.Context, tokenReq *oidc.AccessTokenReque
return authReq, nil return authReq, nil
} }
func JWTExchange(w http.ResponseWriter, r *http.Request, exchanger VerifyExchanger) {
assertion, err := ParseJWTTokenRequest(r, exchanger.Decoder())
if err != nil {
RequestError(w, r, err)
}
claims, err := exchanger.Verifier().Verify(r.Context(), "", assertion)
fmt.Println(claims, err)
_ = assertion
}
func ParseJWTTokenRequest(r *http.Request, decoder *schema.Decoder) (string, error) {
err := r.ParseForm()
if err != nil {
return "", ErrInvalidRequest("error parsing form")
}
tokenReq := new(struct {
Token string `schema:"assertion"`
})
err = decoder.Decode(tokenReq, r.Form)
if err != nil {
return "", ErrInvalidRequest("error decoding form")
}
//TODO: validations
return tokenReq.Token, nil
}
func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
tokenRequest, err := ParseTokenExchangeRequest(w, r) tokenRequest, err := ParseTokenExchangeRequest(w, r)
if err != nil { if err != nil {

View file

@ -148,14 +148,25 @@ func DefaultACRVerifier(possibleValues []string) ACRVerifier {
//and https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation //and https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation
func (v *DefaultVerifier) Verify(ctx context.Context, accessToken, idTokenString string) (*oidc.IDTokenClaims, error) { func (v *DefaultVerifier) Verify(ctx context.Context, accessToken, idTokenString string) (*oidc.IDTokenClaims, error) {
v.config.now = time.Now().UTC() v.config.now = time.Now().UTC()
idToken, err := v.VerifyIDToken(ctx, idTokenString) // idToken, err := v.VerifyIDToken(ctx, idTokenString)
// if err != nil {
// return nil, err
// }
// if err := v.verifyAccessToken(accessToken, idToken.AccessTokenHash, idToken.Signature); err != nil { //TODO: sig from token
// return nil, err
// }
// return idToken, nil
// TODO: verifiy
decrypted, err := v.decryptToken(idTokenString)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := v.verifyAccessToken(accessToken, idToken.AccessTokenHash, idToken.Signature); err != nil { //TODO: sig from token claims, _, err := v.parseToken(decrypted)
if err != nil {
return nil, err return nil, err
} }
return idToken, nil return claims, nil
} }
func (v *DefaultVerifier) now() time.Time { func (v *DefaultVerifier) now() time.Time {

View file

@ -19,7 +19,6 @@ func EncryptAES(data string, key string) (string, error) {
} }
func EncryptBytesAES(plainText []byte, key string) ([]byte, error) { func EncryptBytesAES(plainText []byte, key string) ([]byte, error) {
block, err := aes.NewCipher([]byte(key)) block, err := aes.NewCipher([]byte(key))
if err != nil { if err != nil {
return nil, err return nil, err
@ -50,7 +49,6 @@ func DecryptAES(data string, key string) (string, error) {
} }
func DecryptBytesAES(cipherText []byte, key string) ([]byte, error) { func DecryptBytesAES(cipherText []byte, key string) ([]byte, error) {
block, err := aes.NewCipher([]byte(key)) block, err := aes.NewCipher([]byte(key))
if err != nil { if err != nil {
return nil, err return nil, err