git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add EncryptInto/DecryptInto Unit Tests
commit014fa682be348bafce6b29d0a570ee35d44d9ea2 authorNick Meves <nick.meves@greenhouse.io> dateMon 1 Jun 2020, 16:19 (6 years ago) parente43c65c
6 files changed+124−77
CHANGELOG.md+1 pkg/apis/sessions/session_state.go+10−25 pkg/apis/sessions/session_state_test.go+6−6 pkg/encryption/cipher.go+32−23 pkg/encryption/cipher_test.go+75−22 pkg/encryption/utils.go−1
CHANGELOG.md+1
@@ -55,6 +55,7 @@
5555
5656## Changes since v5.1.1
5757
58+- [#539](https://github.com/oauth2-proxy/oauth2-proxy/pull/539) Refactor encryption ciphers and add AES-GCM support (@NickMeves)
5859- [#601](https://github.com/oauth2-proxy/oauth2-proxy/pull/601) Ensure decrypted user/email are valid UTF8 (@JoelSpeed)
5960- [#560](https://github.com/oauth2-proxy/oauth2-proxy/pull/560) Fallback to UserInfo is User ID claim not present (@JoelSpeed)
6061- [#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) {
104104 PreferredUsername: ss.PreferredUsername,
105105 }
106106 } else {
107− // Backward compatibility with using unencrypted Email
108− if ss.Email != "" {
109− decryptedEmail, errEmail := stringDecrypt(ss.Email, c)
110− if errEmail == nil {
111− if !utf8.ValidString(decryptedEmail) {
112− return nil, errors.New("invalid value for decrypted email")
113− }
114− ss.Email = decryptedEmail
107+ // Backward compatibility with using unencrypted Email or User
108+ // Decryption errors will leave original string
109+ err = c.DecryptInto(&ss.Email)
110+ if err == nil {
111+ if !utf8.ValidString(ss.Email) {
112+ return nil, errors.New("invalid value for decrypted email")
115113 }
116114 }
117− // Backward compatibility with using unencrypted User
118− if ss.User != "" {
119− decryptedUser, errUser := stringDecrypt(ss.User, c)
120− if errUser == nil {
121− if !utf8.ValidString(decryptedUser) {
122− return nil, errors.New("invalid value for decrypted user")
123− }
124− ss.User = decryptedUser
115+ err = c.DecryptInto(&ss.User)
116+ if err == nil {
117+ if !utf8.ValidString(ss.User) {
118+ return nil, errors.New("invalid value for decrypted user")
125119 }
126120 }
127121
@@ -139,12 +133,3 @@ func DecodeSessionState(v string, c encryption.Cipher) (*SessionState, error) {
139133 }
140134 return &ss, nil
141135}
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 {
1717 return &t
1818}
1919
20−func NewCipher(secret []byte) (encryption.Cipher, error) {
20+func newTestCipher(secret []byte) (encryption.Cipher, error) {
2121 return encryption.NewBase64Cipher(encryption.NewCFBCipher, secret)
2222}
2323
2424func TestSessionStateSerialization(t *testing.T) {
25− c, err := NewCipher([]byte(secret))
25+ c, err := newTestCipher([]byte(secret))
2626 assert.Equal(t, nil, err)
27− c2, err := NewCipher([]byte(altSecret))
27+ c2, err := newTestCipher([]byte(altSecret))
2828 assert.Equal(t, nil, err)
2929 s := &sessions.SessionState{
3030 Email: "user@domain.com",
@@ -57,9 +57,9 @@ func TestSessionStateSerialization(t *testing.T) {
5757}
5858
5959func TestSessionStateSerializationWithUser(t *testing.T) {
60− c, err := NewCipher([]byte(secret))
60+ c, err := newTestCipher([]byte(secret))
6161 assert.Equal(t, nil, err)
62− c2, err := NewCipher([]byte(altSecret))
62+ c2, err := newTestCipher([]byte(altSecret))
6363 assert.Equal(t, nil, err)
6464 s := &sessions.SessionState{
6565 User: "just-user",
@@ -205,7 +205,7 @@ func TestDecodeSessionState(t *testing.T) {
205205 eJSON, _ := e.MarshalJSON()
206206 eString := string(eJSON)
207207
208− c, err := NewCipher([]byte(secret))
208+ c, err := newTestCipher([]byte(secret))
209209 assert.NoError(t, err)
210210
211211 testCases := []testCase{
pkg/encryption/cipher.go+32−23
@@ -13,30 +13,11 @@ import (
1313type Cipher interface {
1414 Encrypt(value []byte) ([]byte, error)
1515 Decrypt(ciphertext []byte) ([]byte, error)
16− EncryptInto(s *string) error
16+ EncryptInto(s *string) error
1717 DecryptInto(s *string) error
1818}
1919
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−
3820type Base64Cipher struct {
39− DefaultCipher
4021 Cipher Cipher
4122}
4223
@@ -52,7 +33,7 @@ func NewBase64Cipher(initCipher func([]byte) (Cipher, error), secret []byte) (Ci
5233
5334// Encrypt encrypts a value with the embedded Cipher & Base64 encodes it
5435func (c *Base64Cipher) Encrypt(value []byte) ([]byte, error) {
55− encrypted, err := c.Cipher.Encrypt([]byte(value))
36+ encrypted, err := c.Cipher.Encrypt(value)
5637 if err != nil {
5738 return nil, err
5839 }
@@ -70,8 +51,17 @@ func (c *Base64Cipher) Decrypt(ciphertext []byte) ([]byte, error) {
7051 return c.Cipher.Decrypt(encrypted)
7152}
7253
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+
7364type CFBCipher struct {
74− DefaultCipher
7565 cipher.Block
7666}
7767
@@ -111,8 +101,17 @@ func (c *CFBCipher) Decrypt(ciphertext []byte) ([]byte, error) {
111101 return plaintext, nil
112102}
113103
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+
114114type GCMCipher struct {
115− DefaultCipher
116115 cipher.Block
117116}
118117
@@ -158,6 +157,16 @@ func (c *GCMCipher) Decrypt(ciphertext []byte) ([]byte, error) {
158157 return plaintext, nil
159158}
160159
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+
161170// codecFunc is a function that takes a string and encodes/decodes it
162171type codecFunc func([]byte) ([]byte, error)
163172
pkg/encryption/cipher_test.go+75−22
@@ -5,6 +5,7 @@ import (
55 "encoding/base64"
66 "fmt"
77 "io"
8+ mathrand "math/rand"
89 "testing"
910
1011 "github.com/stretchr/testify/assert"
@@ -117,6 +118,80 @@ func runEncryptAndDecrypt(t *testing.T, c Cipher, dataSize int) {
117118 assert.NotEqual(t, encrypted, decrypted)
118119}
119120
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+
120195func TestDecryptCFBWrongSecret(t *testing.T) {
121196 secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
122197 secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
@@ -228,25 +303,3 @@ func TestCFBtoGCMErrors(t *testing.T) {
228303 })
229304 }
230305}
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 {
104104 }
105105 return false
106106}
107−