Skip to content

Commit

Permalink
Reduce state size (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkroepke authored Nov 30, 2023
1 parent 9a3429c commit 25b34cd
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 23 deletions.
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
87 changes: 66 additions & 21 deletions internal/state/state.go
Original file line number Diff line number Diff line change
@@ -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(),
}
}

Expand All @@ -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", " ")
}
4 changes: 2 additions & 2 deletions internal/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:")
Expand All @@ -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:")
Expand Down

0 comments on commit 25b34cd

Please sign in to comment.