mirror of
https://github.com/grafana/grafana.git
synced 2025-01-16 11:42:35 -06:00
Encryption: Add support for decrypting ciphertexts with algorithm metadata (#49312)
This commit is contained in:
parent
258b3ab18b
commit
15605b6c80
@ -1,11 +1,13 @@
|
||||
package ossencryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -22,11 +24,20 @@ func ProvideService() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
const saltLength = 8
|
||||
const (
|
||||
saltLength = 8
|
||||
aesCfb = "aes-cfb"
|
||||
encryptionAlgorithmDelimiter = '*'
|
||||
)
|
||||
|
||||
func (s *Service) Decrypt(_ context.Context, payload []byte, secret string) ([]byte, error) {
|
||||
alg, payload, err := deriveEncryptionAlgorithm(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(payload) < saltLength {
|
||||
return nil, fmt.Errorf("unable to compute salt")
|
||||
return nil, errors.New("unable to compute salt")
|
||||
}
|
||||
salt := payload[:saltLength]
|
||||
key, err := encryptionKeyToBytes(secret, string(salt))
|
||||
@ -39,11 +50,49 @@ func (s *Service) Decrypt(_ context.Context, payload []byte, secret string) ([]b
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The IV needs to be unique, but not secure. Therefore it's common to
|
||||
switch alg {
|
||||
case aesCfb:
|
||||
return decryptCFB(block, payload)
|
||||
default:
|
||||
return nil, errors.New("unsupported encryption algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
func deriveEncryptionAlgorithm(payload []byte) (string, []byte, error) {
|
||||
if len(payload) == 0 {
|
||||
return "", nil, fmt.Errorf("unable to derive encryption algorithm")
|
||||
}
|
||||
|
||||
if payload[0] != encryptionAlgorithmDelimiter {
|
||||
return aesCfb, payload, nil // backwards compatibility
|
||||
}
|
||||
|
||||
payload = payload[1:]
|
||||
algDelim := bytes.Index(payload, []byte{encryptionAlgorithmDelimiter})
|
||||
if algDelim == -1 {
|
||||
return aesCfb, payload, nil // backwards compatibility
|
||||
}
|
||||
|
||||
algB64 := payload[:algDelim]
|
||||
payload = payload[algDelim+1:]
|
||||
|
||||
alg := make([]byte, base64.RawStdEncoding.DecodedLen(len(algB64)))
|
||||
|
||||
_, err := base64.RawStdEncoding.Decode(alg, algB64)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return string(alg), payload, nil
|
||||
}
|
||||
|
||||
func decryptCFB(block cipher.Block, payload []byte) ([]byte, error) {
|
||||
// The IV needs to be unique, but not secure. Therefore, it's common to
|
||||
// include it at the beginning of the ciphertext.
|
||||
if len(payload) < aes.BlockSize {
|
||||
return nil, errors.New("payload too short")
|
||||
}
|
||||
|
||||
iv := payload[saltLength : saltLength+aes.BlockSize]
|
||||
payload = payload[saltLength+aes.BlockSize:]
|
||||
payloadDst := make([]byte, len(payload))
|
||||
@ -65,6 +114,7 @@ func (s *Service) Encrypt(_ context.Context, payload []byte, secret string) ([]b
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -37,6 +37,32 @@ func TestEncryption(t *testing.T) {
|
||||
_, err := svc.Decrypt(context.Background(), []byte(""), "1234")
|
||||
require.Error(t, err)
|
||||
|
||||
assert.Equal(t, "unable to compute salt", err.Error())
|
||||
assert.Equal(t, "unable to derive encryption algorithm", err.Error())
|
||||
})
|
||||
|
||||
t.Run("decrypting ciphertext with aes-gcm as encryption algorithm should return error", func(t *testing.T) {
|
||||
// Raw slice of bytes that corresponds to the following ciphertext:
|
||||
// - 'grafana' as payload
|
||||
// - '1234' as secret
|
||||
// - 'aes-gcm' as encryption algorithm
|
||||
// With no encryption algorithm metadata.
|
||||
ciphertext := []byte{42, 89, 87, 86, 122, 76, 87, 100, 106, 98, 81, 42, 48, 99, 55, 50, 51, 48, 83, 66, 20, 99, 47, 238, 61, 44, 129, 125, 14, 37, 162, 230, 47, 31, 104, 70, 144, 223, 26, 51, 180, 17, 76, 52, 36, 93, 17, 203, 99, 158, 219, 102, 74, 173, 74}
|
||||
_, err := svc.Decrypt(context.Background(), ciphertext, "1234")
|
||||
require.Error(t, err)
|
||||
|
||||
assert.Equal(t, "unsupported encryption algorithm", err.Error())
|
||||
})
|
||||
|
||||
t.Run("decrypting ciphertext with aes-cfb as encryption algorithm do not fail", func(t *testing.T) {
|
||||
// Raw slice of bytes that corresponds to the following ciphertext:
|
||||
// - 'grafana' as payload
|
||||
// - '1234' as secret
|
||||
// - 'aes-cfb' as encryption algorithm
|
||||
// With no encryption algorithm metadata.
|
||||
ciphertext := []byte{42, 89, 87, 86, 122, 76, 87, 78, 109, 89, 103, 42, 73, 71, 50, 57, 121, 110, 90, 109, 115, 23, 237, 13, 130, 188, 151, 118, 98, 103, 80, 209, 79, 143, 22, 122, 44, 40, 102, 41, 136, 16, 27}
|
||||
decrypted, err := svc.Decrypt(context.Background(), ciphertext, "1234")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []byte("grafana"), decrypted)
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user