mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-28 18:12:16 +00:00
fix(oidc): use authenticated encryption for opaque tokens (#12017)
# Which Problems Are Solved Opaque tokens now use authenticated encryption. # How the Problems Are Solved - Upgrade zitadel/oidc to v3.47 - Copy crypto implementation for refresh and session tokens (internal to zitadel) - Added config that allows validating old tokens for gradual roll-out # Additional Changes - Set NX cache for `integration-test-build` to `false`, working on a seperate fix. # Additional Context - closes #11315 --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: muhlemmer <[email protected]>
This commit is contained in:
@@ -2,7 +2,6 @@ package authz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -16,14 +15,10 @@ const (
|
||||
SessionTokenFormat = SessionTokenPrefix + "%s:%s"
|
||||
)
|
||||
|
||||
func SessionTokenVerifier(algorithm crypto.EncryptionAlgorithm) func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
|
||||
func SessionTokenVerifier(algorithm crypto.AuthAlgorithm) func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
|
||||
return func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
|
||||
decodedToken, err := base64.RawURLEncoding.DecodeString(sessionToken)
|
||||
if err != nil {
|
||||
return zerrors.ThrowInvalidArgument(err, "COMMAND-hi6Ph", "Errors.Session.Token.Invalid")
|
||||
}
|
||||
_, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
|
||||
token, err := algorithm.DecryptString(decodedToken, algorithm.EncryptionKeyID())
|
||||
token, err := algorithm.DecryptToken(sessionToken)
|
||||
spanPasswordComparison.EndWithError(err)
|
||||
if err != nil || token != fmt.Sprintf(SessionTokenFormat, sessionID, tokenID) {
|
||||
return zerrors.ThrowPermissionDenied(err, "COMMAND-sGr42", "Errors.Session.Token.Invalid")
|
||||
@@ -32,14 +27,10 @@ func SessionTokenVerifier(algorithm crypto.EncryptionAlgorithm) func(ctx context
|
||||
}
|
||||
}
|
||||
|
||||
func SessionTokenDecryptor(algorithm crypto.EncryptionAlgorithm) func(ctx context.Context, sessionToken string) (sessionID, tokenID string, err error) {
|
||||
func SessionTokenDecryptor(algorithm crypto.AuthAlgorithm) func(ctx context.Context, sessionToken string) (sessionID, tokenID string, err error) {
|
||||
return func(ctx context.Context, sessionToken string) (sessionID, tokenID string, err error) {
|
||||
decodedToken, err := base64.RawURLEncoding.DecodeString(sessionToken)
|
||||
if err != nil {
|
||||
return "", "", zerrors.ThrowInvalidArgument(err, "AUTHZ-hi6Ph", "Errors.Session.Token.Invalid")
|
||||
}
|
||||
_, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
|
||||
token, err := algorithm.DecryptString(decodedToken, algorithm.EncryptionKeyID())
|
||||
token, err := algorithm.DecryptToken(sessionToken)
|
||||
spanPasswordComparison.EndWithError(err)
|
||||
if err != nil {
|
||||
return "", "", zerrors.ThrowPermissionDenied(err, "AUTHZ-sGr42", "Errors.Session.Token.Invalid")
|
||||
|
||||
@@ -2,7 +2,6 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/zitadel/logging"
|
||||
@@ -46,13 +45,13 @@ func (s *Server) GetDeviceAuthorizationRequest(ctx context.Context, req *connect
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encrypted, err := s.encryption.Encrypt([]byte(deviceRequest.DeviceCode))
|
||||
encrypted, err := s.authAlg.EncryptToken(deviceRequest.DeviceCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&oidc_pb.GetDeviceAuthorizationRequestResponse{
|
||||
DeviceAuthorizationRequest: &oidc_pb.DeviceAuthorizationRequest{
|
||||
Id: base64.RawURLEncoding.EncodeToString(encrypted),
|
||||
Id: encrypted,
|
||||
ClientId: deviceRequest.ClientID,
|
||||
Scope: deviceRequest.Scopes,
|
||||
AppName: deviceRequest.AppName,
|
||||
@@ -62,7 +61,7 @@ func (s *Server) GetDeviceAuthorizationRequest(ctx context.Context, req *connect
|
||||
}
|
||||
|
||||
func (s *Server) AuthorizeOrDenyDeviceAuthorization(ctx context.Context, req *connect.Request[oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest]) (*connect.Response[oidc_pb.AuthorizeOrDenyDeviceAuthorizationResponse], error) {
|
||||
deviceCode, err := s.deviceCodeFromID(req.Msg.GetDeviceAuthorizationId())
|
||||
deviceCode, err := s.authAlg.DecryptToken(req.Msg.GetDeviceAuthorizationId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -258,11 +257,3 @@ func errorReasonToOIDC(reason oidc_pb.ErrorReason) string {
|
||||
return "server_error"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) deviceCodeFromID(deviceAuthID string) (string, error) {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(deviceAuthID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.encryption.DecryptString(decoded, s.encryption.EncryptionKeyID())
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ type Server struct {
|
||||
|
||||
op *oidc.Server
|
||||
externalSecure bool
|
||||
encryption crypto.EncryptionAlgorithm
|
||||
authAlg crypto.AuthAlgorithm
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
@@ -34,14 +34,14 @@ func CreateServer(
|
||||
query *query.Queries,
|
||||
op *oidc.Server,
|
||||
externalSecure bool,
|
||||
encryption crypto.EncryptionAlgorithm,
|
||||
authAlg crypto.AuthAlgorithm,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
op: op,
|
||||
externalSecure: externalSecure,
|
||||
encryption: encryption,
|
||||
authAlg: authAlg,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -204,15 +203,6 @@ func (o *OPStorage) AuthRequestByCode(ctx context.Context, code string) (_ op.Au
|
||||
panic(o.panicErr("AuthRequestByCode"))
|
||||
}
|
||||
|
||||
// decryptGrant decrypts a code or refresh_token
|
||||
func (o *OPStorage) decryptGrant(grant string) (string, error) {
|
||||
decodedGrant, err := base64.RawURLEncoding.DecodeString(grant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return o.encAlg.DecryptString(decodedGrant, o.encAlg.EncryptionKeyID())
|
||||
}
|
||||
|
||||
func (o *OPStorage) SaveAuthCode(ctx context.Context, id, code string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() {
|
||||
@@ -529,7 +519,7 @@ func (o *OPStorage) GetRefreshTokenInfo(ctx context.Context, clientID string, to
|
||||
span.EndWithError(err)
|
||||
}()
|
||||
|
||||
plainToken, err := o.decryptGrant(token)
|
||||
plainToken, err := o.authAlg.DecryptToken(token)
|
||||
if err != nil {
|
||||
return "", "", op.ErrInvalidRefreshToken
|
||||
}
|
||||
|
||||
+20
-8
@@ -83,7 +83,7 @@ type OPStorage struct {
|
||||
defaultIdTokenLifetime time.Duration
|
||||
defaultRefreshTokenIdleExpiration time.Duration
|
||||
defaultRefreshTokenExpiration time.Duration
|
||||
encAlg crypto.EncryptionAlgorithm
|
||||
authAlg crypto.AuthAlgorithm
|
||||
assetAPIPrefix func(ctx context.Context) string
|
||||
contextToIssuer func(context.Context) string
|
||||
federateLogoutCache cache.Cache[federatedlogout.Index, string, *federatedlogout.FederatedLogout]
|
||||
@@ -118,7 +118,7 @@ func NewServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
repo repository.Repository,
|
||||
encryptionAlg crypto.EncryptionAlgorithm,
|
||||
authAlg crypto.AuthAlgorithm,
|
||||
targetEncryptionAlgorithm crypto.EncryptionAlgorithm,
|
||||
cryptoKey []byte,
|
||||
es *eventstore.Eventstore,
|
||||
@@ -132,12 +132,24 @@ func NewServer(
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
|
||||
}
|
||||
storage := newStorage(config, command, query, repo, encryptionAlg, es, ContextToIssuer, federatedLogoutCache)
|
||||
storage := newStorage(config, command, query, repo, authAlg, es, ContextToIssuer, federatedLogoutCache)
|
||||
keyCache := newPublicKeyCache(ctx, config.PublicKeyCacheMaxAge, queryKeyFunc(query))
|
||||
accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true))
|
||||
idTokenHintKeySet := newOidcKeySet(keyCache)
|
||||
|
||||
var options []op.Option
|
||||
alg := op.NewAES256GCMCrypto(opConfig.CryptoKey, "")
|
||||
if authAlg.LegacyTokenEnabled() {
|
||||
alg = op.NewCompositeCrypto(
|
||||
alg,
|
||||
[]op.Decrypter{
|
||||
alg,
|
||||
op.NewAESCrypto(opConfig.CryptoKey),
|
||||
},
|
||||
)
|
||||
}
|
||||
options := []op.Option{
|
||||
op.WithCrypto(alg),
|
||||
}
|
||||
if !externalSecure {
|
||||
options = append(options, op.WithAllowInsecure())
|
||||
}
|
||||
@@ -173,9 +185,9 @@ func NewServer(
|
||||
jwksCacheControlMaxAge: config.JWKSCacheControlMaxAge,
|
||||
fallbackLogger: fallbackLogger,
|
||||
hasher: hasher,
|
||||
encAlg: encryptionAlg,
|
||||
encAlg: authAlg,
|
||||
targetEncryptionAlgorithm: targetEncryptionAlgorithm,
|
||||
opCrypto: op.NewAESCrypto(opConfig.CryptoKey),
|
||||
opCrypto: alg,
|
||||
assetAPIPrefix: assets.AssetAPI(),
|
||||
}
|
||||
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
|
||||
@@ -247,7 +259,7 @@ func newStorage(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
repo repository.Repository,
|
||||
encAlg crypto.EncryptionAlgorithm,
|
||||
authAlg crypto.AuthAlgorithm,
|
||||
es *eventstore.Eventstore,
|
||||
contextToIssuer func(context.Context) string,
|
||||
federateLogoutCache cache.Cache[federatedlogout.Index, string, *federatedlogout.FederatedLogout],
|
||||
@@ -264,7 +276,7 @@ func newStorage(
|
||||
defaultIdTokenLifetime: config.DefaultIdTokenLifetime,
|
||||
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
|
||||
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
|
||||
encAlg: encAlg,
|
||||
authAlg: authAlg,
|
||||
assetAPIPrefix: assets.AssetAPI(),
|
||||
contextToIssuer: contextToIssuer,
|
||||
federateLogoutCache: federateLogoutCache,
|
||||
|
||||
@@ -39,7 +39,7 @@ type Server struct {
|
||||
fallbackLogger *slog.Logger
|
||||
hasher *crypto.Hasher
|
||||
signingKeyAlgorithm string
|
||||
encAlg crypto.EncryptionAlgorithm
|
||||
encAlg crypto.AuthAlgorithm
|
||||
targetEncryptionAlgorithm crypto.EncryptionAlgorithm
|
||||
opCrypto op.Crypto
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
@@ -145,15 +144,3 @@ func (s *Server) createJWT(ctx context.Context, client op.Client, session *comma
|
||||
|
||||
return crypto.Sign(claims, signer)
|
||||
}
|
||||
|
||||
// decryptCode decrypts a code or refresh_token
|
||||
func (s *Server) decryptCode(ctx context.Context, code string) (_ string, err error) {
|
||||
_, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(code)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.encAlg.DecryptString(decoded, s.encAlg.EncryptionKeyID())
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *Server) CodeExchange(ctx context.Context, r *op.ClientRequest[oidc.Acce
|
||||
return nil, zerrors.ThrowInternal(nil, "OIDC-Ae2ph", "Error.Internal")
|
||||
}
|
||||
|
||||
plainCode, err := s.decryptCode(ctx, r.Data.Code)
|
||||
plainCode, err := s.encAlg.DecryptToken(r.Data.Code)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "OIDC-ahLi2", "Errors.User.Code.Invalid")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user