1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2024-12-14 11:23:21 +02:00
oauth2-proxy/pkg/encryption/cipher.go

133 lines
3.4 KiB
Go
Raw Normal View History

2019-05-24 18:06:48 +02:00
package encryption
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
// Cipher provides methods to encrypt and decrypt
type Cipher interface {
Encrypt(value []byte) ([]byte, error)
Decrypt(ciphertext []byte) ([]byte, error)
}
type base64Cipher struct {
Cipher Cipher
}
// NewBase64Cipher returns a new AES Cipher for encrypting cookie values
// and wrapping them in Base64 -- Supports Legacy encryption scheme
Reduce SessionState size better with MessagePack + LZ4 (#632) * Encode sessions with MsgPack + LZ4 Assumes ciphers are now mandatory per #414. Cookie & Redis sessions can fallback to V5 style JSON in error cases. TODO: session_state.go unit tests & new unit tests for Legacy fallback scenarios. * Only compress encoded sessions with Cookie Store * Cleanup msgpack + lz4 error handling * Change NewBase64Cipher to take in an existing Cipher * Add msgpack & lz4 session state tests * Add required options for oauthproxy tests More aggressively assert.NoError on all validation.Validate(opts) calls to enforce legal options in all our tests. Add additional NoError checks wherever error return values were ignored. * Remove support for uncompressed session state fields * Improve error verbosity & add session state tests * Ensure all marshalled sessions are valid Invalid CFB decryptions can result in garbage data that 1/100 times might cause message pack unmarshal to not fail and instead return an empty session. This adds more rigor to make sure legacy sessions cause appropriate errors. * Add tests for legacy V5 session decoding Refactor common legacy JSON test cases to a legacy helpers area under session store tests. * Make ValidateSession a struct method & add CHANGELOG entry * Improve SessionState error & comments verbosity * Move legacy session test helpers to sessions pkg Placing these helpers under the sessions pkg removed all the circular import uses in housing it under the session store area. * Improve SignatureAuthenticator test helper formatting * Make redis.legacyV5DecodeSession internal * Make LegacyV5TestCase test table public for linter
2020-07-13 21:56:05 +02:00
func NewBase64Cipher(c Cipher) Cipher {
return &base64Cipher{Cipher: c}
}
// Encrypt encrypts a value with the embedded Cipher & Base64 encodes it
func (c *base64Cipher) Encrypt(value []byte) ([]byte, error) {
2020-06-02 01:19:27 +02:00
encrypted, err := c.Cipher.Encrypt(value)
if err != nil {
return nil, err
}
return []byte(base64.StdEncoding.EncodeToString(encrypted)), nil
}
// Decrypt Base64 decodes a value & decrypts it with the embedded Cipher
func (c *base64Cipher) Decrypt(ciphertext []byte) ([]byte, error) {
encrypted, err := base64.StdEncoding.DecodeString(string(ciphertext))
if err != nil {
return nil, fmt.Errorf("failed to base64 decode value %s", err)
}
return c.Cipher.Decrypt(encrypted)
}
type cfbCipher struct {
cipher.Block
}
// NewCFBCipher returns a new AES CFB Cipher
func NewCFBCipher(secret []byte) (Cipher, error) {
c, err := aes.NewCipher(secret)
if err != nil {
return nil, err
}
return &cfbCipher{Block: c}, err
}
// Encrypt with AES CFB
func (c *cfbCipher) Encrypt(value []byte) ([]byte, error) {
ciphertext := make([]byte, aes.BlockSize+len(value))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, fmt.Errorf("failed to create initialization vector %s", err)
}
stream := cipher.NewCFBEncrypter(c.Block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], value)
return ciphertext, nil
}
// Decrypt an AES CFB ciphertext
func (c *cfbCipher) Decrypt(ciphertext []byte) ([]byte, error) {
if len(ciphertext) < aes.BlockSize {
return nil, fmt.Errorf("encrypted value should be at least %d bytes, but is only %d bytes", aes.BlockSize, len(ciphertext))
}
iv, ciphertext := ciphertext[:aes.BlockSize], ciphertext[aes.BlockSize:]
plaintext := make([]byte, len(ciphertext))
stream := cipher.NewCFBDecrypter(c.Block, iv)
stream.XORKeyStream(plaintext, ciphertext)
return plaintext, nil
}
type gcmCipher struct {
cipher.Block
}
// NewGCMCipher returns a new AES GCM Cipher
func NewGCMCipher(secret []byte) (Cipher, error) {
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
}
// Using nonce as Seal's dst argument results in it being the first
// chunk of bytes in the ciphertext. Decrypt retrieves the nonce/IV from this.
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
}