mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-04-21 12:17:22 +02:00
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.
This commit is contained in:
parent
f7cca1d0b3
commit
b6931aa4ea
@ -117,9 +117,27 @@ type Cipher interface {
|
|||||||
DecryptInto(s *string) error
|
DecryptInto(s *string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DefaultCipher struct {}
|
||||||
|
|
||||||
|
// Encrypt is a dummy method for CommonCipher.EncryptInto support
|
||||||
|
func (c *DefaultCipher) Encrypt(value []byte) ([]byte, error) { return value, nil }
|
||||||
|
|
||||||
|
// Decrypt is a dummy method for CommonCipher.DecryptInto support
|
||||||
|
func (c *DefaultCipher) Decrypt(ciphertext []byte) ([]byte, error) { return ciphertext, nil }
|
||||||
|
|
||||||
|
// EncryptInto encrypts the value and stores it back in the string pointer
|
||||||
|
func (c *DefaultCipher) EncryptInto(s *string) error {
|
||||||
|
return into(c.Encrypt, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptInto decrypts the value and stores it back in the string pointer
|
||||||
|
func (c *DefaultCipher) DecryptInto(s *string) error {
|
||||||
|
return into(c.Decrypt, s)
|
||||||
|
}
|
||||||
|
|
||||||
// NewCipher returns a new aes Cipher for encrypting cookie values
|
// NewCipher returns a new aes Cipher for encrypting cookie values
|
||||||
// This defaults to the Base64 Cipher to align with legacy Encrypt/Decrypt functionality
|
// This defaults to the Base64 Cipher to align with legacy Encrypt/Decrypt functionality
|
||||||
func NewCipher(secret []byte) (*Base64Cipher, error) {
|
func NewCipher(secret []byte) (Cipher, error) {
|
||||||
cfb, err := NewCFBCipher(secret)
|
cfb, err := NewCFBCipher(secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -128,12 +146,13 @@ func NewCipher(secret []byte) (*Base64Cipher, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Base64Cipher struct {
|
type Base64Cipher struct {
|
||||||
|
DefaultCipher
|
||||||
Cipher Cipher
|
Cipher Cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBase64Cipher returns a new AES CFB Cipher for encrypting cookie values
|
// NewBase64Cipher returns a new AES Cipher for encrypting cookie values
|
||||||
// And wrapping them in Base64 -- Supports Legacy encryption scheme
|
// and wrapping them in Base64 -- Supports Legacy encryption scheme
|
||||||
func NewBase64Cipher(c Cipher) (*Base64Cipher, error) {
|
func NewBase64Cipher(c Cipher) (Cipher, error) {
|
||||||
return &Base64Cipher{Cipher: c}, nil
|
return &Base64Cipher{Cipher: c}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,22 +176,13 @@ func (c *Base64Cipher) Decrypt(ciphertext []byte) ([]byte, error) {
|
|||||||
return c.Cipher.Decrypt(encrypted)
|
return c.Cipher.Decrypt(encrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptInto encrypts the value and stores it back in the string pointer
|
|
||||||
func (c *Base64Cipher) EncryptInto(s *string) error {
|
|
||||||
return into(c.Encrypt, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptInto decrypts the value and stores it back in the string pointer
|
|
||||||
func (c *Base64Cipher) DecryptInto(s *string) error {
|
|
||||||
return into(c.Decrypt, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CFBCipher struct {
|
type CFBCipher struct {
|
||||||
|
DefaultCipher
|
||||||
cipher.Block
|
cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCFBCipher returns a new AES CFB Cipher
|
// NewCFBCipher returns a new AES CFB Cipher
|
||||||
func NewCFBCipher(secret []byte) (*CFBCipher, error) {
|
func NewCFBCipher(secret []byte) (Cipher, error) {
|
||||||
c, err := aes.NewCipher(secret)
|
c, err := aes.NewCipher(secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -193,7 +203,7 @@ func (c *CFBCipher) Encrypt(value []byte) ([]byte, error) {
|
|||||||
return ciphertext, nil
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt a AES CFB ciphertext
|
// Decrypt an AES CFB ciphertext
|
||||||
func (c *CFBCipher) Decrypt(ciphertext []byte) ([]byte, error) {
|
func (c *CFBCipher) Decrypt(ciphertext []byte) ([]byte, error) {
|
||||||
if len(ciphertext) < aes.BlockSize {
|
if len(ciphertext) < aes.BlockSize {
|
||||||
return nil, fmt.Errorf("encrypted value should be "+
|
return nil, fmt.Errorf("encrypted value should be "+
|
||||||
@ -209,20 +219,54 @@ func (c *CFBCipher) Decrypt(ciphertext []byte) ([]byte, error) {
|
|||||||
return ciphertext, nil
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptInto encrypts the value and stores it back in the string pointer
|
type GCMCipher struct {
|
||||||
func (c *CFBCipher) EncryptInto(s *string) error {
|
DefaultCipher
|
||||||
return into(c.Encrypt, s)
|
cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecryptInto decrypts the value and stores it back in the string pointer
|
// NewGCMCipher returns a new AES GCM Cipher
|
||||||
func (c *CFBCipher) DecryptInto(s *string) error {
|
func NewGCMCipher(secret []byte) (Cipher, error) {
|
||||||
return into(c.Decrypt, s)
|
c, err := aes.NewCipher(secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &GCMCipher{Block: c}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt with AES GCM on raw bytes
|
||||||
|
func (c *GCMCipher) Encrypt(value []byte) ([]byte, error) {
|
||||||
|
gcm, err := cipher.NewGCM(c.Block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ciphertext := gcm.Seal(nonce, nonce, value, nil)
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt an AES GCM ciphertext
|
||||||
|
func (c *GCMCipher) Decrypt(ciphertext []byte) ([]byte, error) {
|
||||||
|
gcm, err := cipher.NewGCM(c.Block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonceSize := gcm.NonceSize()
|
||||||
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||||
|
|
||||||
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plaintext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// codecFunc is a function that takes a string and encodes/decodes it
|
// codecFunc is a function that takes a string and encodes/decodes it
|
||||||
type codecFunc func([]byte) ([]byte, error)
|
type codecFunc func([]byte) ([]byte, error)
|
||||||
|
|
||||||
|
|
||||||
func into(f codecFunc, s *string) error {
|
func into(f codecFunc, s *string) error {
|
||||||
// Do not encrypt/decrypt nil or empty strings
|
// Do not encrypt/decrypt nil or empty strings
|
||||||
if s == nil || *s == "" {
|
if s == nil || *s == "" {
|
||||||
|
@ -134,17 +134,18 @@ func TestEncodeAndDecodeAccessTokenB64(t *testing.T) {
|
|||||||
assert.Equal(t, []byte(token), decoded)
|
assert.Equal(t, []byte(token), decoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncryptAndDecryptBase64(t *testing.T) {
|
func TestEncryptAndDecrypt(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// Test our 3 cipher types
|
||||||
|
for _, initCipher := range []func([]byte) (Cipher, error){NewCipher, NewCFBCipher, NewGCMCipher} {
|
||||||
// Test all 3 valid AES sizes
|
// Test all 3 valid AES sizes
|
||||||
for _, secretSize := range []int{16, 24, 32} {
|
for _, secretSize := range []int{16, 24, 32} {
|
||||||
secret := make([]byte, secretSize)
|
secret := make([]byte, secretSize)
|
||||||
_, err = io.ReadFull(rand.Reader, secret)
|
_, err = io.ReadFull(rand.Reader, secret)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
// NewCipher creates a Base64 wrapper of CFBCipher
|
c, err := initCipher(secret)
|
||||||
c, err := NewCipher(secret)
|
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
// Test various sizes sessions might be
|
// Test various sizes sessions might be
|
||||||
@ -155,24 +156,27 @@ func TestEncryptAndDecryptBase64(t *testing.T) {
|
|||||||
|
|
||||||
encrypted, err := c.Encrypt(data)
|
encrypted, err := c.Encrypt(data)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
assert.NotEqual(t, encrypted, data)
|
||||||
|
|
||||||
decrypted, err := c.Decrypt(encrypted)
|
decrypted, err := c.Decrypt(encrypted)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Equal(t, data, decrypted)
|
assert.Equal(t, data, decrypted)
|
||||||
|
assert.NotEqual(t, encrypted, decrypted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecryptBase64WrongSecret(t *testing.T) {
|
func TestDecryptWrongSecret(t *testing.T) {
|
||||||
var err error
|
|
||||||
|
|
||||||
secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
|
secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
|
||||||
secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
|
secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
|
||||||
|
|
||||||
c1, err := NewCipher(secret1)
|
// Test CFB & Base64 (GCM is authenticated, it errors differently)
|
||||||
|
for _, initCipher := range []func([]byte) (Cipher, error){NewCipher, NewCFBCipher} {
|
||||||
|
c1, err := initCipher(secret1)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
c2, err := NewCipher(secret2)
|
c2, err := initCipher(secret2)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df")
|
data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df")
|
||||||
@ -184,17 +188,42 @@ func TestDecryptBase64WrongSecret(t *testing.T) {
|
|||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.NotEqual(t, data, wrongData)
|
assert.NotEqual(t, data, wrongData)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncryptAndDecryptCFB(t *testing.T) {
|
func TestDecryptGCMWrongSecret(t *testing.T) {
|
||||||
|
secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
|
||||||
|
secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
|
||||||
|
|
||||||
|
c1, err := NewGCMCipher(secret1)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
c2, err := NewGCMCipher(secret2)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df")
|
||||||
|
|
||||||
|
ciphertext, err := c1.Encrypt(data)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
// GCM is authenticated - this should lead to message authentication failed
|
||||||
|
_, err = c2.Decrypt(ciphertext)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntermixCiphersErrors(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// Encrypt with GCM, Decrypt with CFB: Results in Garbage data
|
||||||
// Test all 3 valid AES sizes
|
// Test all 3 valid AES sizes
|
||||||
for _, secretSize := range []int{16, 24, 32} {
|
for _, secretSize := range []int{16, 24, 32} {
|
||||||
secret := make([]byte, secretSize)
|
secret := make([]byte, secretSize)
|
||||||
_, err = io.ReadFull(rand.Reader, secret)
|
_, err = io.ReadFull(rand.Reader, secret)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
c, err := NewCFBCipher(secret)
|
gcm, err := NewGCMCipher(secret)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
cfb, err := NewCFBCipher(secret)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
// Test various sizes sessions might be
|
// Test various sizes sessions might be
|
||||||
@ -203,36 +232,46 @@ func TestEncryptAndDecryptCFB(t *testing.T) {
|
|||||||
_, err := io.ReadFull(rand.Reader, data)
|
_, err := io.ReadFull(rand.Reader, data)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
encrypted, err := c.Encrypt(data)
|
encrypted, err := gcm.Encrypt(data)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
assert.NotEqual(t, encrypted, data)
|
||||||
|
|
||||||
decrypted, err := c.Decrypt(encrypted)
|
decrypted, err := cfb.Decrypt(encrypted)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Equal(t, data, decrypted)
|
// Data is mangled
|
||||||
|
assert.NotEqual(t, data, decrypted)
|
||||||
|
assert.NotEqual(t, encrypted, decrypted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encrypt with CFB, Decrypt with GCM: Results in errors
|
||||||
|
// Test all 3 valid AES sizes
|
||||||
|
for _, secretSize := range []int{16, 24, 32} {
|
||||||
|
secret := make([]byte, secretSize)
|
||||||
|
_, err = io.ReadFull(rand.Reader, secret)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
gcm, err := NewGCMCipher(secret)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
cfb, err := NewCFBCipher(secret)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
// Test various sizes sessions might be
|
||||||
|
for _, dataSize := range []int{10, 100, 1000, 5000, 10000} {
|
||||||
|
data := make([]byte, dataSize)
|
||||||
|
_, err := io.ReadFull(rand.Reader, data)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
encrypted, err := cfb.Encrypt(data)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.NotEqual(t, encrypted, data)
|
||||||
|
|
||||||
|
// GCM is authenticated - this should lead to message authentication failed
|
||||||
|
_, err = gcm.Decrypt(encrypted)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecryptCFBWrongSecret(t *testing.T) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
secret1 := []byte("0123456789abcdefghijklmnopqrstuv")
|
|
||||||
secret2 := []byte("9876543210abcdefghijklmnopqrstuv")
|
|
||||||
|
|
||||||
c1, err := NewCFBCipher(secret1)
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
|
|
||||||
c2, err := NewCFBCipher(secret2)
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
|
|
||||||
data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df")
|
|
||||||
|
|
||||||
ciphertext, err := c1.Encrypt(data)
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
|
|
||||||
wrongData, err := c2.Decrypt(ciphertext)
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
assert.NotEqual(t, data, wrongData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeIntoAndDecodeIntoAccessToken(t *testing.T) {
|
func TestEncodeIntoAndDecodeIntoAccessToken(t *testing.T) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user