git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add GCM Cipher support

During the upcoming encoded session refactor, AES GCM is ideal to use as the Redis (and other DB like stores) encryption wrapper around the session because each session is encrypted with a distinct secret that is passed by the session ticket.

commitb6931aa4ea5d1edad0f181e5e3af03f38bb2b476 authorNick Meves <nick.meves@greenhouse.io> dateSat 9 May 2020, 17:34 (6 years ago) parentf7cca1d
2 files changed+153−70
pkg/encryption/cipher.go+67−23 pkg/encryption/cipher_test.go+86−47
pkg/encryption/cipher.go+67−23
@@ -117,9 +117,27 @@ type Cipher interface {
117117 DecryptInto(s *string) error
118118}
119119
120+type DefaultCipher struct {}
121+
122+// Encrypt is a dummy method for CommonCipher.EncryptInto support
123+func (c *DefaultCipher) Encrypt(value []byte) ([]byte, error) { return value, nil }
124+
125+// Decrypt is a dummy method for CommonCipher.DecryptInto support
126+func (c *DefaultCipher) Decrypt(ciphertext []byte) ([]byte, error) { return ciphertext, nil }
127+
128+// EncryptInto encrypts the value and stores it back in the string pointer
129+func (c *DefaultCipher) EncryptInto(s *string) error {
130+ return into(c.Encrypt, s)
131+}
132+
133+// DecryptInto decrypts the value and stores it back in the string pointer
134+func (c *DefaultCipher) DecryptInto(s *string) error {
135+ return into(c.Decrypt, s)
136+}
137+
120138// NewCipher returns a new aes Cipher for encrypting cookie values
121139// This defaults to the Base64 Cipher to align with legacy Encrypt/Decrypt functionality
122−func NewCipher(secret []byte) (*Base64Cipher, error) {
140+func NewCipher(secret []byte) (Cipher, error) {
123141 cfb, err := NewCFBCipher(secret)
124142 if err != nil {
125143 return nil, err
@@ -128,12 +146,13 @@ func NewCipher(secret []byte) (*Base64Cipher, error) {
128146}
129147
130148type Base64Cipher struct {
149+ DefaultCipher
131150 Cipher Cipher
132151}
133152
134−// NewBase64Cipher returns a new AES CFB Cipher for encrypting cookie values
135−// And wrapping them in Base64 -- Supports Legacy encryption scheme
136−func NewBase64Cipher(c Cipher) (*Base64Cipher, error) {
153+// NewBase64Cipher returns a new AES Cipher for encrypting cookie values
154+// and wrapping them in Base64 -- Supports Legacy encryption scheme
155+func NewBase64Cipher(c Cipher) (Cipher, error) {
137156 return &Base64Cipher{Cipher: c}, nil
138157}
139158
@@ -157,22 +176,13 @@ func (c *Base64Cipher) Decrypt(ciphertext []byte) ([]byte, error) {
157176 return c.Cipher.Decrypt(encrypted)
158177}
159178
160−// EncryptInto encrypts the value and stores it back in the string pointer
161−func (c *Base64Cipher) EncryptInto(s *string) error {
162− return into(c.Encrypt, s)
163−}
164−
165−// DecryptInto decrypts the value and stores it back in the string pointer
166−func (c *Base64Cipher) DecryptInto(s *string) error {
167− return into(c.Decrypt, s)
168−}
169−
170179type CFBCipher struct {
180+ DefaultCipher
171181 cipher.Block
172182}
173183
174184// NewCFBCipher returns a new AES CFB Cipher
175−func NewCFBCipher(secret []byte) (*CFBCipher, error) {
185+func NewCFBCipher(secret []byte) (Cipher, error) {
176186 c, err := aes.NewCipher(secret)
177187 if err != nil {
178188 return nil, err
@@ -193,7 +203,7 @@ func (c *CFBCipher) Encrypt(value []byte) ([]byte, error) {
193203 return ciphertext, nil
194204}
195205
196−// Decrypt a AES CFB ciphertext
206+// Decrypt an AES CFB ciphertext
197207func (c *CFBCipher) Decrypt(ciphertext []byte) ([]byte, error) {
198208 if len(ciphertext) < aes.BlockSize {
199209 return nil, fmt.Errorf("encrypted value should be "+
@@ -209,20 +219,54 @@ func (c *CFBCipher) Decrypt(ciphertext []byte) ([]byte, error) {
209219 return ciphertext, nil
210220}
211221
212−// EncryptInto encrypts the value and stores it back in the string pointer
213−func (c *CFBCipher) EncryptInto(s *string) error {
214− return into(c.Encrypt, s)
222+type GCMCipher struct {
223+ DefaultCipher
224+ cipher.Block
215225}
216226
217−// DecryptInto decrypts the value and stores it back in the string pointer
218−func (c *CFBCipher) DecryptInto(s *string) error {
219− return into(c.Decrypt, s)
227+// NewGCMCipher returns a new AES GCM Cipher
228+func NewGCMCipher(secret []byte) (Cipher, error) {
229+ c, err := aes.NewCipher(secret)
230+ if err != nil {
231+ return nil, err
232+ }
233+ return &GCMCipher{Block: c}, err
234+}
235+
236+// Encrypt with AES GCM on raw bytes
237+func (c *GCMCipher) Encrypt(value []byte) ([]byte, error) {
238+ gcm, err := cipher.NewGCM(c.Block)
239+ if err != nil {
240+ return nil, err
241+ }
242+ nonce := make([]byte, gcm.NonceSize())
243+ if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
244+ return nil, err
245+ }
246+ ciphertext := gcm.Seal(nonce, nonce, value, nil)
247+ return ciphertext, nil
248+}
249+
250+// Decrypt an AES GCM ciphertext
251+func (c *GCMCipher) Decrypt(ciphertext []byte) ([]byte, error) {
252+ gcm, err := cipher.NewGCM(c.Block)
253+ if err != nil {
254+ return nil, err
255+ }
256+
257+ nonceSize := gcm.NonceSize()
258+ nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
259+
260+ plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
261+ if err != nil {
262+ return nil, err
263+ }
264+ return plaintext, nil
220265}
221266
222267// codecFunc is a function that takes a string and encodes/decodes it
223268type codecFunc func([]byte) ([]byte, error)
224269
225−
226270func into(f codecFunc, s *string) error {
227271 // Do not encrypt/decrypt nil or empty strings
228272 if s == nil || *s == "" {
pkg/encryption/cipher_test.go+86−47
@@ -134,45 +134,70 @@ func TestEncodeAndDecodeAccessTokenB64(t *testing.T) {
134134 assert.Equal(t, []byte(token), decoded)
135135}
136136
137−func TestEncryptAndDecryptBase64(t *testing.T) {
137+func TestEncryptAndDecrypt(t *testing.T) {
138138 var err error
139139
140− // Test all 3 valid AES sizes
141− for _, secretSize := range []int{16, 24, 32} {
142− secret := make([]byte, secretSize)
143− _, err = io.ReadFull(rand.Reader, secret)
144− assert.Equal(t, nil, err)
145−
146− // NewCipher creates a Base64 wrapper of CFBCipher
147− c, err := NewCipher(secret)
148− assert.Equal(t, nil, err)
149−
150− // Test various sizes sessions might be
151− for _, dataSize := range []int{10, 100, 1000, 5000, 10000} {
152− data := make([]byte, dataSize)
153− _, err := io.ReadFull(rand.Reader, data)
140+ // Test our 3 cipher types
141+ for _, initCipher := range []func([]byte) (Cipher, error){NewCipher, NewCFBCipher, NewGCMCipher} {
142+ // Test all 3 valid AES sizes
143+ for _, secretSize := range []int{16, 24, 32} {
144+ secret := make([]byte, secretSize)
145+ _, err = io.ReadFull(rand.Reader, secret)
154146 assert.Equal(t, nil, err)
155147
156− encrypted, err := c.Encrypt(data)
148+ c, err := initCipher(secret)
157149 assert.Equal(t, nil, err)
158150
159− decrypted, err := c.Decrypt(encrypted)
160− assert.Equal(t, nil, err)
161− assert.Equal(t, data, decrypted)
151+ // Test various sizes sessions might be
152+ for _, dataSize := range []int{10, 100, 1000, 5000, 10000} {
153+ data := make([]byte, dataSize)
154+ _, err := io.ReadFull(rand.Reader, data)
155+ assert.Equal(t, nil, err)
156+
157+ encrypted, err := c.Encrypt(data)
158+ assert.Equal(t, nil, err)
159+ assert.NotEqual(t, encrypted, data)
160+
161+ decrypted, err := c.Decrypt(encrypted)
162+ assert.Equal(t, nil, err)
163+ assert.Equal(t, data, decrypted)
164+ assert.NotEqual(t, encrypted, decrypted)
165+ }
162166 }
163167 }
164168}
165169
166−func TestDecryptBase64WrongSecret(t *testing.T) {
167− var err error
170+func TestDecryptWrongSecret(t *testing.T) {
171+ secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
172+ secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
173+
174+ // Test CFB & Base64 (GCM is authenticated, it errors differently)
175+ for _, initCipher := range []func([]byte) (Cipher, error){NewCipher, NewCFBCipher} {
176+ c1, err := initCipher(secret1)
177+ assert.Equal(t, nil, err)
178+
179+ c2, err := initCipher(secret2)
180+ assert.Equal(t, nil, err)
181+
182+ data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df")
168183
184+ ciphertext, err := c1.Encrypt(data)
185+ assert.Equal(t, nil, err)
186+
187+ wrongData, err := c2.Decrypt(ciphertext)
188+ assert.Equal(t, nil, err)
189+ assert.NotEqual(t, data, wrongData)
190+ }
191+}
192+
193+func TestDecryptGCMWrongSecret(t *testing.T) {
169194 secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
170195 secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
171196
172− c1, err := NewCipher(secret1)
197+ c1, err := NewGCMCipher(secret1)
173198 assert.Equal(t, nil, err)
174199
175− c2, err := NewCipher(secret2)
200+ c2, err := NewGCMCipher(secret2)
176201 assert.Equal(t, nil, err)
177202
178203 data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df")
@@ -180,21 +205,25 @@ func TestDecryptBase64WrongSecret(t *testing.T) {
180205 ciphertext, err := c1.Encrypt(data)
181206 assert.Equal(t, nil, err)
182207
183− wrongData, err := c2.Decrypt(ciphertext)
184− assert.Equal(t, nil, err)
185− assert.NotEqual(t, data, wrongData)
208+ // GCM is authenticated - this should lead to message authentication failed
209+ _, err = c2.Decrypt(ciphertext)
210+ assert.Error(t, err)
186211}
187212
188−func TestEncryptAndDecryptCFB(t *testing.T) {
213+func TestIntermixCiphersErrors(t *testing.T) {
189214 var err error
190215
216+ // Encrypt with GCM, Decrypt with CFB: Results in Garbage data
191217 // Test all 3 valid AES sizes
192218 for _, secretSize := range []int{16, 24, 32} {
193219 secret := make([]byte, secretSize)
194220 _, err = io.ReadFull(rand.Reader, secret)
195221 assert.Equal(t, nil, err)
196222
197− c, err := NewCFBCipher(secret)
223+ gcm, err := NewGCMCipher(secret)
224+ assert.Equal(t, nil, err)
225+
226+ cfb, err := NewCFBCipher(secret)
198227 assert.Equal(t, nil, err)
199228
200229 // Test various sizes sessions might be
@@ -203,36 +232,46 @@ func TestEncryptAndDecryptCFB(t *testing.T) {
203232 _, err := io.ReadFull(rand.Reader, data)
204233 assert.Equal(t, nil, err)
205234
206− encrypted, err := c.Encrypt(data)
235+ encrypted, err := gcm.Encrypt(data)
207236 assert.Equal(t, nil, err)
237+ assert.NotEqual(t, encrypted, data)
208238
209− decrypted, err := c.Decrypt(encrypted)
239+ decrypted, err := cfb.Decrypt(encrypted)
210240 assert.Equal(t, nil, err)
211− assert.Equal(t, data, decrypted)
241+ // Data is mangled
242+ assert.NotEqual(t, data, decrypted)
243+ assert.NotEqual(t, encrypted, decrypted)
212244 }
213245 }
214−}
215246
216−func TestDecryptCFBWrongSecret(t *testing.T) {
217− var err error
218−
219− secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
220− secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
247+ // Encrypt with CFB, Decrypt with GCM: Results in errors
248+ // Test all 3 valid AES sizes
249+ for _, secretSize := range []int{16, 24, 32} {
250+ secret := make([]byte, secretSize)
251+ _, err = io.ReadFull(rand.Reader, secret)
252+ assert.Equal(t, nil, err)
221253
222− c1, err := NewCFBCipher(secret1)
223− assert.Equal(t, nil, err)
254+ gcm, err := NewGCMCipher(secret)
255+ assert.Equal(t, nil, err)
224256
225− c2, err := NewCFBCipher(secret2)
226− assert.Equal(t, nil, err)
257+ cfb, err := NewCFBCipher(secret)
258+ assert.Equal(t, nil, err)
227259
228− data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df")
260+ // Test various sizes sessions might be
261+ for _, dataSize := range []int{10, 100, 1000, 5000, 10000} {
262+ data := make([]byte, dataSize)
263+ _, err := io.ReadFull(rand.Reader, data)
264+ assert.Equal(t, nil, err)
229265
230− ciphertext, err := c1.Encrypt(data)
231− assert.Equal(t, nil, err)
266+ encrypted, err := cfb.Encrypt(data)
267+ assert.Equal(t, nil, err)
268+ assert.NotEqual(t, encrypted, data)
232269
233− wrongData, err := c2.Decrypt(ciphertext)
234− assert.Equal(t, nil, err)
235− assert.NotEqual(t, data, wrongData)
270+ // GCM is authenticated - this should lead to message authentication failed
271+ _, err = gcm.Decrypt(encrypted)
272+ assert.Error(t, err)
273+ }
274+ }
236275}
237276
238277func TestEncodeIntoAndDecodeIntoAccessToken(t *testing.T) {