git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add EncryptInto/DecryptInto Unit Tests
014fa68Nick Meves
CHANGELOG.md+1
@@ -55,6 +55,7 @@
55
55
56## Changes since v5.1.1
56## Changes since v5.1.1
57
57
58+- [#539](https://github.com/oauth2-proxy/oauth2-proxy/pull/539) Refactor encryption ciphers and add AES-GCM support (@NickMeves)
58- [#601](https://github.com/oauth2-proxy/oauth2-proxy/pull/601) Ensure decrypted user/email are valid UTF8 (@JoelSpeed)
59- [#601](https://github.com/oauth2-proxy/oauth2-proxy/pull/601) Ensure decrypted user/email are valid UTF8 (@JoelSpeed)
59- [#560](https://github.com/oauth2-proxy/oauth2-proxy/pull/560) Fallback to UserInfo is User ID claim not present (@JoelSpeed)
60- [#560](https://github.com/oauth2-proxy/oauth2-proxy/pull/560) Fallback to UserInfo is User ID claim not present (@JoelSpeed)
60- [#598](https://github.com/oauth2-proxy/oauth2-proxy/pull/598) acr_values no longer sent to IdP when empty (@ScottGuymer)
61- [#598](https://github.com/oauth2-proxy/oauth2-proxy/pull/598) acr_values no longer sent to IdP when empty (@ScottGuymer)
pkg/apis/sessions/session_state.go+10−25
@@ -104,24 +104,18 @@ func DecodeSessionState(v string, c encryption.Cipher) (*SessionState, error) {
104 PreferredUsername: ss.PreferredUsername,
104 PreferredUsername: ss.PreferredUsername,
105 }
105 }
106 } else {
106 } else {
107− // Backward compatibility with using unencrypted Email
107+ // Backward compatibility with using unencrypted Email or User
108− if ss.Email != "" {
108+ // Decryption errors will leave original string
109− decryptedEmail, errEmail := stringDecrypt(ss.Email, c)
109+ err = c.DecryptInto(&ss.Email)
110− if errEmail == nil {
110+ if err == nil {
111− if !utf8.ValidString(decryptedEmail) {
111+ if !utf8.ValidString(ss.Email) {
112− return nil, errors.New("invalid value for decrypted email")
112+ return nil, errors.New("invalid value for decrypted email")
113− }
114− ss.Email = decryptedEmail
115 }
113 }
116 }
114 }
117− // Backward compatibility with using unencrypted User
115+ err = c.DecryptInto(&ss.User)
118− if ss.User != "" {
119− decryptedUser, errUser := stringDecrypt(ss.User, c)
120− if errUser == nil {
116+ if err == nil {
121− if !utf8.ValidString(decryptedUser) {
117+ if !utf8.ValidString(ss.User) {
122− return nil, errors.New("invalid value for decrypted user")
118+ return nil, errors.New("invalid value for decrypted user")
123− }
124− ss.User = decryptedUser
125 }
119 }
126 }
120 }
127
121
@@ -139,12 +133,3 @@ func DecodeSessionState(v string, c encryption.Cipher) (*SessionState, error) {
139 }
133 }
140 return &ss, nil
134 return &ss, nil
141}
135}
142−
143−// stringDecrypt wraps a Base64Cipher to make it string => string
144−func stringDecrypt(ciphertext string, c encryption.Cipher) (string, error) {
145− value, err := c.Decrypt([]byte(ciphertext))
146− if err != nil {
147− return "", err
148− }
149− return string(value), nil
150−}
pkg/apis/sessions/session_state_test.go+6−6
@@ -17,14 +17,14 @@ func timePtr(t time.Time) *time.Time {
17 return &t
17 return &t
18}
18}
19
19
20−func NewCipher(secret []byte) (encryption.Cipher, error) {
20+func newTestCipher(secret []byte) (encryption.Cipher, error) {
21 return encryption.NewBase64Cipher(encryption.NewCFBCipher, secret)
21 return encryption.NewBase64Cipher(encryption.NewCFBCipher, secret)
22}
22}
23
23
24func TestSessionStateSerialization(t *testing.T) {
24func TestSessionStateSerialization(t *testing.T) {
25− c, err := NewCipher([]byte(secret))
25+ c, err := newTestCipher([]byte(secret))
26 assert.Equal(t, nil, err)
26 assert.Equal(t, nil, err)
27− c2, err := NewCipher([]byte(altSecret))
27+ c2, err := newTestCipher([]byte(altSecret))
28 assert.Equal(t, nil, err)
28 assert.Equal(t, nil, err)
29 s := &sessions.SessionState{
29 s := &sessions.SessionState{
30 Email: "user@domain.com",
30 Email: "user@domain.com",
@@ -57,9 +57,9 @@ func TestSessionStateSerialization(t *testing.T) {
57}
57}
58
58
59func TestSessionStateSerializationWithUser(t *testing.T) {
59func TestSessionStateSerializationWithUser(t *testing.T) {
60− c, err := NewCipher([]byte(secret))
60+ c, err := newTestCipher([]byte(secret))
61 assert.Equal(t, nil, err)
61 assert.Equal(t, nil, err)
62− c2, err := NewCipher([]byte(altSecret))
62+ c2, err := newTestCipher([]byte(altSecret))
63 assert.Equal(t, nil, err)
63 assert.Equal(t, nil, err)
64 s := &sessions.SessionState{
64 s := &sessions.SessionState{
65 User: "just-user",
65 User: "just-user",
@@ -205,7 +205,7 @@ func TestDecodeSessionState(t *testing.T) {
205 eJSON, _ := e.MarshalJSON()
205 eJSON, _ := e.MarshalJSON()
206 eString := string(eJSON)
206 eString := string(eJSON)
207
207
208− c, err := NewCipher([]byte(secret))
208+ c, err := newTestCipher([]byte(secret))
209 assert.NoError(t, err)
209 assert.NoError(t, err)
210
210
211 testCases := []testCase{
211 testCases := []testCase{
pkg/encryption/cipher.go+32−23
@@ -13,30 +13,11 @@ import (
13type Cipher interface {
13type Cipher interface {
14 Encrypt(value []byte) ([]byte, error)
14 Encrypt(value []byte) ([]byte, error)
15 Decrypt(ciphertext []byte) ([]byte, error)
15 Decrypt(ciphertext []byte) ([]byte, error)
16− EncryptInto(s *string) error
16+ EncryptInto(s *string) error
17 DecryptInto(s *string) error
17 DecryptInto(s *string) error
18}
18}
19
19
20−type DefaultCipher struct {}
21−
22−// Encrypt is a dummy method for CommonCipher.EncryptInto support
23−func (c *DefaultCipher) Encrypt(value []byte) ([]byte, error) { return value, nil }
24−
25−// Decrypt is a dummy method for CommonCipher.DecryptInto support
26−func (c *DefaultCipher) Decrypt(ciphertext []byte) ([]byte, error) { return ciphertext, nil }
27−
28−// EncryptInto encrypts the value and stores it back in the string pointer
29−func (c *DefaultCipher) EncryptInto(s *string) error {
30− return into(c.Encrypt, s)
31−}
32−
33−// DecryptInto decrypts the value and stores it back in the string pointer
34−func (c *DefaultCipher) DecryptInto(s *string) error {
35− return into(c.Decrypt, s)
36−}
37−
38type Base64Cipher struct {
20type Base64Cipher struct {
39− DefaultCipher
40 Cipher Cipher
21 Cipher Cipher
41}
22}
42
23
@@ -52,7 +33,7 @@ func NewBase64Cipher(initCipher func([]byte) (Cipher, error), secret []byte) (Ci
52
33
53// Encrypt encrypts a value with the embedded Cipher & Base64 encodes it
34// Encrypt encrypts a value with the embedded Cipher & Base64 encodes it
54func (c *Base64Cipher) Encrypt(value []byte) ([]byte, error) {
35func (c *Base64Cipher) Encrypt(value []byte) ([]byte, error) {
55− encrypted, err := c.Cipher.Encrypt([]byte(value))
36+ encrypted, err := c.Cipher.Encrypt(value)
56 if err != nil {
37 if err != nil {
57 return nil, err
38 return nil, err
58 }
39 }
@@ -70,8 +51,17 @@ func (c *Base64Cipher) Decrypt(ciphertext []byte) ([]byte, error) {
70 return c.Cipher.Decrypt(encrypted)
51 return c.Cipher.Decrypt(encrypted)
71}
52}
72
53
54+// EncryptInto encrypts the value and stores it back in the string pointer
55+func (c *Base64Cipher) EncryptInto(s *string) error {
56+ return into(c.Encrypt, s)
57+}
58+
59+// DecryptInto decrypts the value and stores it back in the string pointer
60+func (c *Base64Cipher) DecryptInto(s *string) error {
61+ return into(c.Decrypt, s)
62+}
63+
73type CFBCipher struct {
64type CFBCipher struct {
74− DefaultCipher
75 cipher.Block
65 cipher.Block
76}
66}
77
67
@@ -111,8 +101,17 @@ func (c *CFBCipher) Decrypt(ciphertext []byte) ([]byte, error) {
111 return plaintext, nil
101 return plaintext, nil
112}
102}
113
103
104+// EncryptInto returns an error since the encrypted data is a []byte that isn't string cast-able
105+func (c *CFBCipher) EncryptInto(s *string) error {
106+ return fmt.Errorf("CFBCipher is not a string->string compatible cipher")
107+}
108+
109+// EncryptInto returns an error since the encrypted data needs to be a []byte
110+func (c *CFBCipher) DecryptInto(s *string) error {
111+ return fmt.Errorf("CFBCipher is not a string->string compatible cipher")
112+}
113+
114type GCMCipher struct {
114type GCMCipher struct {
115− DefaultCipher
116 cipher.Block
115 cipher.Block
117}
116}
118
117
@@ -158,6 +157,16 @@ func (c *GCMCipher) Decrypt(ciphertext []byte) ([]byte, error) {
158 return plaintext, nil
157 return plaintext, nil
159}
158}
160
159
160+// EncryptInto returns an error since the encrypted data is a []byte that isn't string cast-able
161+func (c *GCMCipher) EncryptInto(s *string) error {
162+ return fmt.Errorf("CFBCipher is not a string->string compatible cipher")
163+}
164+
165+// EncryptInto returns an error since the encrypted data needs to be a []byte
166+func (c *GCMCipher) DecryptInto(s *string) error {
167+ return fmt.Errorf("CFBCipher is not a string->string compatible cipher")
168+}
169+
161// codecFunc is a function that takes a string and encodes/decodes it
170// codecFunc is a function that takes a string and encodes/decodes it
162type codecFunc func([]byte) ([]byte, error)
171type codecFunc func([]byte) ([]byte, error)
163
172
pkg/encryption/cipher_test.go+75−22
@@ -5,6 +5,7 @@ import (
5 "encoding/base64"
5 "encoding/base64"
6 "fmt"
6 "fmt"
7 "io"
7 "io"
8+ mathrand "math/rand"
8 "testing"
9 "testing"
9
10
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/assert"
@@ -117,6 +118,80 @@ func runEncryptAndDecrypt(t *testing.T, c Cipher, dataSize int) {
117 assert.NotEqual(t, encrypted, decrypted)
118 assert.NotEqual(t, encrypted, decrypted)
118}
119}
119
120
121+func TestEncryptIntoAndDecryptInto(t *testing.T) {
122+ // Test our 2 cipher types
123+ cipherInits := map[string]func([]byte) (Cipher, error){
124+ "CFB": NewCFBCipher,
125+ "GCM": NewGCMCipher,
126+ }
127+ for name, initCipher := range cipherInits {
128+ t.Run(name, func(t *testing.T) {
129+ // Test all 3 valid AES sizes
130+ for _, secretSize := range []int{16, 24, 32} {
131+ t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) {
132+ secret := make([]byte, secretSize)
133+ _, err := io.ReadFull(rand.Reader, secret)
134+ assert.Equal(t, nil, err)
135+
136+ // Test Standard & Base64 wrapped
137+ cstd, err := initCipher(secret)
138+ assert.Equal(t, nil, err)
139+
140+ cb64, err := NewBase64Cipher(initCipher, secret)
141+ assert.Equal(t, nil, err)
142+
143+ ciphers := map[string]Cipher{
144+ "Standard": cstd,
145+ "Base64": cb64,
146+ }
147+
148+ for cName, c := range ciphers {
149+ // Check no errors with empty or nil strings
150+ if cName == "Base64" {
151+ empty := ""
152+ assert.Equal(t, nil, c.EncryptInto(&empty))
153+ assert.Equal(t, nil, c.DecryptInto(&empty))
154+ assert.Equal(t, nil, c.EncryptInto(nil))
155+ assert.Equal(t, nil, c.DecryptInto(nil))
156+ }
157+
158+ t.Run(cName, func(t *testing.T) {
159+ // Test various sizes sessions might be
160+ for _, dataSize := range []int{10, 100, 1000, 5000, 10000} {
161+ t.Run(fmt.Sprintf("%d", dataSize), func(t *testing.T) {
162+ runEncryptIntoAndDecryptInto(t, c, cName, dataSize)
163+ })
164+ }
165+ })
166+ }
167+ })
168+ }
169+ })
170+ }
171+}
172+
173+func runEncryptIntoAndDecryptInto(t *testing.T, c Cipher, cipherType string, dataSize int) {
174+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
175+ b := make([]byte, dataSize)
176+ for i := range b {
177+ b[i] = charset[mathrand.Intn(len(charset))]
178+ }
179+ data := string(b)
180+ originalData := data
181+
182+ // Base64 is the only cipher that supports string->string Encrypt/Decrypt Into methods
183+ if cipherType == "Base64" {
184+ assert.Equal(t, nil, c.EncryptInto(&data))
185+ assert.NotEqual(t, originalData, data)
186+
187+ assert.Equal(t, nil, c.DecryptInto(&data))
188+ assert.Equal(t, originalData, data)
189+ } else {
190+ assert.NotEqual(t, nil, c.EncryptInto(&data))
191+ assert.NotEqual(t, nil, c.DecryptInto(&data))
192+ }
193+}
194+
120func TestDecryptCFBWrongSecret(t *testing.T) {
195func TestDecryptCFBWrongSecret(t *testing.T) {
121 secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
196 secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
122 secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
197 secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
@@ -228,25 +303,3 @@ func TestCFBtoGCMErrors(t *testing.T) {
228 })
303 })
229 }
304 }
230}
305}
231−
232−func TestEncodeIntoAndDecodeIntoAccessToken(t *testing.T) {
233− const secret = "0123456789abcdefghijklmnopqrstuv"
234− c, err := NewCipher([]byte(secret))
235− assert.Equal(t, nil, err)
236−
237− token := "my access token"
238− originalToken := token
239−
240− assert.Equal(t, nil, c.EncryptInto(&token))
241− assert.NotEqual(t, originalToken, token)
242−
243− assert.Equal(t, nil, c.DecryptInto(&token))
244− assert.Equal(t, originalToken, token)
245−
246− // Check no errors with empty or nil strings
247− empty := ""
248− assert.Equal(t, nil, c.EncryptInto(&empty))
249− assert.Equal(t, nil, c.DecryptInto(&empty))
250− assert.Equal(t, nil, c.EncryptInto(nil))
251− assert.Equal(t, nil, c.DecryptInto(nil))
252−}
pkg/encryption/utils.go−1
@@ -104,4 +104,3 @@ func checkHmac(input, expected string) bool {
104 }
104 }
105 return false
105 return false
106}
106}
107−