diff --git a/cmd/start/start.go b/cmd/start/start.go index 6df8501bd6..f5418a81d6 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -105,7 +105,7 @@ func startZitadel(config *Config, masterKey string) error { return fmt.Errorf("cannot start queries: %w", err) } - authZRepo, err := authz.Start(queries, dbClient, keys.OIDC) + authZRepo, err := authz.Start(queries, dbClient, keys.OIDC, config.ExternalSecure) if err != nil { return fmt.Errorf("error starting authz repo: %w", err) } diff --git a/internal/authz/authz.go b/internal/authz/authz.go index d5c0b8300a..268ea3aec1 100644 --- a/internal/authz/authz.go +++ b/internal/authz/authz.go @@ -9,6 +9,6 @@ import ( "github.com/zitadel/zitadel/internal/query" ) -func Start(queries *query.Queries, dbClient *sql.DB, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) (repository.Repository, error) { - return eventsourcing.Start(queries, dbClient, keyEncryptionAlgorithm) +func Start(queries *query.Queries, dbClient *sql.DB, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, externalSecure bool) (repository.Repository, error) { + return eventsourcing.Start(queries, dbClient, keyEncryptionAlgorithm, externalSecure) } diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index 650be34bae..6c856640ef 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -3,12 +3,17 @@ package eventstore import ( "context" "encoding/base64" + "fmt" "strings" "time" "github.com/zitadel/logging" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" + "gopkg.in/square/go-jose.v2" "github.com/zitadel/zitadel/internal/api/authz" + http_util "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/authz/repository/eventsourcing/view" "github.com/zitadel/zitadel/internal/crypto" caos_errs "github.com/zitadel/zitadel/internal/errors" @@ -27,6 +32,7 @@ type TokenVerifierRepo struct { Eventstore v1.Eventstore View *view.View Query *query.Queries + ExternalSecure bool } func (repo *TokenVerifierRepo) Health() error { @@ -52,7 +58,7 @@ func (repo *TokenVerifierRepo) tokenByID(ctx context.Context, tokenID, userID st } if esErr != nil { - logging.Log("EVENT-5Nm9s").WithError(viewErr).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("error retrieving new events") + logging.WithError(viewErr).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("error retrieving new events") return model.TokenViewToModel(token), nil } viewToken := *token @@ -71,21 +77,13 @@ func (repo *TokenVerifierRepo) tokenByID(ctx context.Context, tokenID, userID st func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenString, verifierClientID, projectID string) (userID string, agentID string, clientID, prefLang, resourceOwner string, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - tokenData, err := base64.RawURLEncoding.DecodeString(tokenString) - if err != nil { - return "", "", "", "", "", caos_errs.ThrowUnauthenticated(nil, "APP-ASdgg", "invalid token") - } - tokenIDSubject, err := repo.TokenVerificationKey.DecryptString(tokenData, repo.TokenVerificationKey.EncryptionKeyID()) - if err != nil { - return "", "", "", "", "", caos_errs.ThrowUnauthenticated(nil, "APP-8EF0zZ", "invalid token") - } - splittedToken := strings.Split(tokenIDSubject, ":") - if len(splittedToken) != 2 { - return "", "", "", "", "", caos_errs.ThrowUnauthenticated(nil, "APP-GDg3a", "invalid token") + tokenID, subject, ok := repo.getTokenIDAndSubject(ctx, tokenString) + if !ok { + return "", "", "", "", "", caos_errs.ThrowUnauthenticated(nil, "APP-Reb32", "invalid token") } _, tokenSpan := tracing.NewNamedSpan(ctx, "token") - token, err := repo.tokenByID(ctx, splittedToken[0], splittedToken[1]) + token, err := repo.tokenByID(ctx, tokenID, subject) tokenSpan.EndWithError(err) if err != nil { return "", "", "", "", "", caos_errs.ThrowUnauthenticated(err, "APP-BxUSiL", "invalid token") @@ -128,12 +126,82 @@ func (repo *TokenVerifierRepo) VerifierClientID(ctx context.Context, appName str return clientID, app.ProjectID, nil } -func (r *TokenVerifierRepo) getUserEvents(ctx context.Context, userID, instanceID string, sequence uint64) (_ []*models.Event, err error) { +func (repo *TokenVerifierRepo) getUserEvents(ctx context.Context, userID, instanceID string, sequence uint64) (_ []*models.Event, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() query, err := usr_view.UserByIDQuery(userID, instanceID, sequence) if err != nil { return nil, err } - return r.Eventstore.FilterEvents(ctx, query) + return repo.Eventstore.FilterEvents(ctx, query) +} + +//getTokenIDAndSubject returns the TokenID and Subject of both opaque tokens and JWTs +func (repo *TokenVerifierRepo) getTokenIDAndSubject(ctx context.Context, accessToken string) (tokenID string, subject string, valid bool) { + // accessToken can be either opaque or JWT + // let's try opaque first: + tokenIDSubject, err := repo.decryptAccessToken(accessToken) + if err != nil { + // if decryption did not work, it might be a JWT + accessTokenClaims, err := op.VerifyAccessToken(ctx, accessToken, repo.jwtTokenVerifier(ctx)) + if err != nil { + return "", "", false + } + return accessTokenClaims.GetTokenID(), accessTokenClaims.GetSubject(), true + } + splitToken := strings.Split(tokenIDSubject, ":") + if len(splitToken) != 2 { + return "", "", false + } + return splitToken[0], splitToken[1], true +} + +func (repo *TokenVerifierRepo) jwtTokenVerifier(ctx context.Context) op.AccessTokenVerifier { + keySet := &openIDKeySet{repo.Query} + issuer := http_util.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), repo.ExternalSecure) + return op.NewAccessTokenVerifier(issuer, keySet) +} + +func (repo *TokenVerifierRepo) decryptAccessToken(token string) (string, error) { + tokenData, err := base64.RawURLEncoding.DecodeString(token) + if err != nil { + return "", caos_errs.ThrowUnauthenticated(nil, "APP-ASdgg", "invalid token") + } + tokenIDSubject, err := repo.TokenVerificationKey.DecryptString(tokenData, repo.TokenVerificationKey.EncryptionKeyID()) + if err != nil { + return "", caos_errs.ThrowUnauthenticated(nil, "APP-8EF0zZ", "invalid token") + } + return tokenIDSubject, nil +} + +type openIDKeySet struct { + *query.Queries +} + +//VerifySignature implements the oidc.KeySet interface +//providing an implementation for the keys retrieved directly from Queries +func (o *openIDKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) { + keySet, err := o.Queries.ActivePublicKeys(ctx, time.Now()) + if err != nil { + return nil, fmt.Errorf("error fetching keys: %w", err) + } + keyID, alg := oidc.GetKeyIDAndAlg(jws) + key, err := oidc.FindMatchingKey(keyID, oidc.KeyUseSignature, alg, jsonWebKeys(keySet.Keys)...) + if err != nil { + return nil, fmt.Errorf("invalid signature: %w", err) + } + return jws.Verify(&key) +} + +func jsonWebKeys(keys []query.PublicKey) []jose.JSONWebKey { + webKeys := make([]jose.JSONWebKey, len(keys)) + for i, key := range keys { + webKeys[i] = jose.JSONWebKey{ + KeyID: key.ID(), + Algorithm: key.Algorithm(), + Use: key.Use().String(), + Key: key.Key(), + } + } + return webKeys } diff --git a/internal/authz/repository/eventsourcing/repository.go b/internal/authz/repository/eventsourcing/repository.go index 71af744bf9..3e8f8200f3 100644 --- a/internal/authz/repository/eventsourcing/repository.go +++ b/internal/authz/repository/eventsourcing/repository.go @@ -18,7 +18,7 @@ type EsRepository struct { eventstore.TokenVerifierRepo } -func Start(queries *query.Queries, dbClient *sql.DB, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) (repository.Repository, error) { +func Start(queries *query.Queries, dbClient *sql.DB, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, externalSecure bool) (repository.Repository, error) { es, err := v1.Start(dbClient) if err != nil { return nil, err @@ -39,6 +39,7 @@ func Start(queries *query.Queries, dbClient *sql.DB, keyEncryptionAlgorithm cryp Eventstore: es, View: view, Query: queries, + ExternalSecure: externalSecure, }, }, nil }