From 25b34cd9f7ec86888eca19f6a5a956b18492a7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 30 Nov 2023 19:53:32 +0100 Subject: [PATCH] Reduce state size (#78) --- go.sum | 1 + internal/state/state.go | 87 +++++++++++++++++++++++++++--------- internal/state/state_test.go | 4 +- 3 files changed, 69 insertions(+), 23 deletions(-) diff --git a/go.sum b/go.sum index 3b103b22..d4217ee8 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= +golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/internal/state/state.go b/internal/state/state.go index e95a0c7d..2d8f0e40 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -1,35 +1,37 @@ package state import ( - "encoding/json" + "bytes" + "encoding/base64" "fmt" + "strings" "time" "github.com/zitadel/oidc/v3/pkg/crypto" ) type State struct { - Client ClientIdentifier `json:"c"` - Ipaddr string `json:"ip"` - CommonName string `json:"cn"` - Issued time.Time `json:"iss"` + Client ClientIdentifier + Ipaddr string + CommonName string + Issued int64 encoded string } type ClientIdentifier struct { - Cid uint64 `json:"c"` - Kid uint64 `json:"k"` - AuthFailedReasonFile string `json:"afr"` - AuthControlFile string `json:"ac"` + Cid uint64 + Kid uint64 + AuthFailedReasonFile string + AuthControlFile string } -func New(client ClientIdentifier, ipaddr, commonName string) *State { - return &State{ +func New(client ClientIdentifier, ipaddr, commonName string) State { + return State{ Client: client, Ipaddr: ipaddr, CommonName: commonName, - Issued: time.Now(), + Issued: time.Now().Round(time.Second).Unix(), } } @@ -44,36 +46,79 @@ func (state *State) Encoded() string { } func (state *State) Decode(secretKey string) error { - jsonState, err := crypto.DecryptAES(state.encoded, secretKey) + encrypted, err := base64.RawURLEncoding.DecodeString(state.encoded) + if err != nil { + return fmt.Errorf("base64 decode %s: %w", state.encoded, err) + } + + data, err := crypto.DecryptBytesAES(encrypted, secretKey) if err != nil { return fmt.Errorf("invalid state %s: %w", state.encoded, err) } - if err := json.Unmarshal([]byte(jsonState), &state); err != nil { - return fmt.Errorf("json decode: %w", err) + _, err = fmt.Fscanln(bytes.NewReader(data), + &state.Client.Cid, + &state.Client.Kid, + &state.Client.AuthFailedReasonFile, + &state.Client.AuthControlFile, + &state.Ipaddr, + &state.CommonName, + &state.Issued, + ) + + if err != nil { + return fmt.Errorf("decode: %w", err) } - issuedSince := time.Since(state.Issued) + state.Client.AuthFailedReasonFile = decodeString(state.Client.AuthFailedReasonFile) + state.Client.AuthControlFile = decodeString(state.Client.AuthControlFile) + state.CommonName = decodeString(state.CommonName) + + issuedSince := time.Since(time.Unix(state.Issued, 0)) if issuedSince >= time.Minute*2 { - return fmt.Errorf("%w: expired after 2 minutes, issued at: %s", ErrInvalid, state.Issued.String()) + return fmt.Errorf("%w: expired after 2 minutes, issued at: %s", ErrInvalid, issuedSince.String()) } else if issuedSince <= time.Second*-5 { - return fmt.Errorf("%w: issued in future, issued at: %s", ErrInvalid, state.Issued.String()) + return fmt.Errorf("%w: issued in future, issued at: %s", ErrInvalid, issuedSince.String()) } return nil } func (state *State) Encode(secretKey string) error { - jsonState, err := json.Marshal(state) + var data bytes.Buffer + + _, err := fmt.Fprintln(&data, + state.Client.Cid, + state.Client.Kid, + encodeString(state.Client.AuthFailedReasonFile), + encodeString(state.Client.AuthControlFile), + state.Ipaddr, + encodeString(state.CommonName), + state.Issued, + ) if err != nil { - return fmt.Errorf("json encode: %w", err) + return fmt.Errorf("encode: %w", err) } - state.encoded, err = crypto.EncryptAES(string(jsonState), secretKey) + encrypted, err := crypto.EncryptBytesAES(data.Bytes(), secretKey) if err != nil { return fmt.Errorf("encrypt aes: %w", err) } + state.encoded = base64.RawURLEncoding.EncodeToString(encrypted) + return nil } + +func encodeString(text string) string { + if text == "" { + return "\x00" + } + + return strings.ReplaceAll(text, " ", "\x00") +} + +func decodeString(text string) string { + return strings.ReplaceAll(text, "\x00", " ") +} diff --git a/internal/state/state_test.go b/internal/state/state_test.go index aa2cc6b7..0585f689 100644 --- a/internal/state/state_test.go +++ b/internal/state/state_test.go @@ -44,7 +44,7 @@ func TestStateInvalid_Future(t *testing.T) { encryptionKey := testutils.HTTPSecret token := state.New(state.ClientIdentifier{Cid: 1, Kid: 2}, "127.0.0.1", "test") - token.Issued = time.Now().Add(time.Hour) + token.Issued = time.Now().Add(time.Hour).Unix() require.NoError(t, token.Encode(encryptionKey)) assert.Contains(t, token.Decode(encryptionKey).Error(), "invalid state: issued in future, issued at:") @@ -56,7 +56,7 @@ func TestStateInvalid_TooOld(t *testing.T) { encryptionKey := testutils.HTTPSecret token := state.New(state.ClientIdentifier{Cid: 1, Kid: 2}, "127.0.0.1", "test") - token.Issued = time.Now().Add(-1 * time.Hour) + token.Issued = time.Now().Add(-1 * time.Hour).Unix() require.NoError(t, token.Encode(encryptionKey)) assert.Contains(t, token.Decode(encryptionKey).Error(), "invalid state: expired after 2 minutes, issued at:")