1
0
mirror of https://github.com/getsops/sops.git synced 2026-02-05 12:45:21 +01:00
Files
sops/aes/cipher.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

203 lines
5.7 KiB
Go
Raw Normal View History

2017-09-12 20:01:12 -07:00
/*
Package aes defines a Cipher that uses 256-bit AES-GCM authenticated encryption to encrypt values the SOPS tree.
*/
package aes //import "github.com/getsops/sops/v3/aes"
2016-08-02 14:02:01 -07:00
import (
2016-08-22 12:38:30 -07:00
cryptoaes "crypto/aes"
2016-08-02 14:02:01 -07:00
"crypto/cipher"
2016-08-11 18:14:27 -07:00
"crypto/rand"
2016-08-02 14:02:01 -07:00
"encoding/base64"
2016-08-11 15:47:42 -07:00
"fmt"
2016-08-02 14:02:01 -07:00
"regexp"
"strconv"
"time"
2017-09-06 17:36:39 -07:00
"github.com/getsops/sops/v3"
"github.com/getsops/sops/v3/logging"
"github.com/sirupsen/logrus"
2016-08-02 14:02:01 -07:00
)
2017-09-06 17:36:39 -07:00
var log *logrus.Logger
func init() {
2017-09-07 10:49:27 -07:00
log = logging.NewLogger("AES")
2017-09-06 17:36:39 -07:00
}
2016-08-24 10:29:28 -07:00
type encryptedValue struct {
2016-08-02 14:02:01 -07:00
data []byte
iv []byte
tag []byte
datatype string
}
const nonceSize int = 32
2016-08-24 12:50:35 -07:00
2017-09-12 20:41:02 -07:00
type stashKey struct {
additionalData string
plaintext interface{}
}
2016-08-24 15:46:49 -07:00
// Cipher encrypts and decrypts data keys with AES GCM 256
2017-09-12 20:41:02 -07:00
type Cipher struct {
// stash is a map that stores IVs for reuse, so that the ciphertext doesn't change when decrypting and reencrypting
// the same values.
stash map[stashKey][]byte
}
2019-07-08 15:32:33 -07:00
// NewCipher is the constructor for a new Cipher object
2017-09-12 20:41:02 -07:00
func NewCipher() Cipher {
return Cipher{
stash: make(map[stashKey][]byte),
}
}
2016-08-24 14:38:05 -07:00
var encre = regexp.MustCompile(`^ENC\[AES256_GCM,data:(.+),iv:(.+),tag:(.+),type:(.+)\]`)
2016-08-24 10:29:28 -07:00
func parse(value string) (*encryptedValue, error) {
matches := encre.FindStringSubmatch(value)
2016-08-02 14:02:01 -07:00
if matches == nil {
2016-08-22 12:38:30 -07:00
return nil, fmt.Errorf("Input string %s does not match sops' data format", value)
2016-08-02 14:02:01 -07:00
}
data, err := base64.StdEncoding.DecodeString(matches[1])
2016-08-02 14:02:01 -07:00
if err != nil {
2016-08-22 18:16:51 -07:00
return nil, fmt.Errorf("Error base64-decoding data: %s", err)
2016-08-02 14:02:01 -07:00
}
iv, err := base64.StdEncoding.DecodeString(matches[2])
2016-08-02 14:02:01 -07:00
if err != nil {
2016-08-22 18:16:51 -07:00
return nil, fmt.Errorf("Error base64-decoding iv: %s", err)
2016-08-02 14:02:01 -07:00
}
tag, err := base64.StdEncoding.DecodeString(matches[3])
2016-08-02 14:02:01 -07:00
if err != nil {
2016-08-22 18:16:51 -07:00
return nil, fmt.Errorf("Error base64-decoding tag: %s", err)
2016-08-02 14:02:01 -07:00
}
2016-08-23 12:49:18 -07:00
datatype := string(matches[4])
2016-08-02 14:02:01 -07:00
2016-08-24 10:29:28 -07:00
return &encryptedValue{data, iv, tag, datatype}, nil
2016-08-02 14:02:01 -07:00
}
// Decrypt takes a sops-format value string and a key and returns the decrypted value and a stash value
2017-09-12 20:41:02 -07:00
func (c Cipher) Decrypt(ciphertext string, key []byte, additionalData string) (plaintext interface{}, err error) {
if isEmpty(ciphertext) {
2017-09-12 20:41:02 -07:00
return "", nil
}
2017-09-12 20:41:02 -07:00
encryptedValue, err := parse(ciphertext)
2016-08-02 14:02:01 -07:00
if err != nil {
2017-09-12 20:41:02 -07:00
return nil, err
2016-08-02 14:02:01 -07:00
}
aescipher, err := cryptoaes.NewCipher(key)
2016-08-02 14:02:01 -07:00
if err != nil {
2017-09-12 20:41:02 -07:00
return nil, err
2016-08-02 14:02:01 -07:00
}
gcm, err := cipher.NewGCMWithNonceSize(aescipher, len(encryptedValue.iv))
2016-08-02 14:02:01 -07:00
if err != nil {
2017-09-12 20:41:02 -07:00
return nil, err
2016-08-02 14:02:01 -07:00
}
data := append(encryptedValue.data, encryptedValue.tag...)
decryptedBytes, err := gcm.Open(nil, encryptedValue.iv, data, []byte(additionalData))
2016-08-02 14:02:01 -07:00
if err != nil {
2017-09-12 20:41:02 -07:00
return nil, fmt.Errorf("Could not decrypt with AES_GCM: %s", err)
2016-08-02 14:02:01 -07:00
}
2016-08-22 18:16:51 -07:00
decryptedValue := string(decryptedBytes)
switch encryptedValue.datatype {
case "str":
2017-09-12 20:41:02 -07:00
plaintext = decryptedValue
case "int":
plaintext, err = strconv.Atoi(decryptedValue)
case "float":
plaintext, err = strconv.ParseFloat(decryptedValue, 64)
case "bytes":
2017-09-12 20:41:02 -07:00
plaintext = decryptedBytes
case "bool":
plaintext, err = strconv.ParseBool(decryptedValue)
case "time":
var value time.Time
err = value.UnmarshalText(decryptedBytes)
plaintext = value
2017-08-15 13:48:55 -07:00
case "comment":
plaintext = sops.Comment{Value: decryptedValue}
default:
2017-09-12 20:41:02 -07:00
return nil, fmt.Errorf("Unknown datatype: %s", encryptedValue.datatype)
}
2017-09-12 20:41:02 -07:00
c.stash[stashKey{plaintext: plaintext, additionalData: additionalData}] = encryptedValue.iv
return plaintext, err
2016-08-02 14:02:01 -07:00
}
2016-08-11 18:14:27 -07:00
func isEmpty(value interface{}) bool {
switch value := value.(type) {
case string:
return value == ""
case []byte:
return len(value) == 0
case sops.Comment:
return isEmpty(value.Value)
default:
return false
}
}
2016-08-24 10:29:28 -07:00
// Encrypt takes one of (string, int, float, bool) and encrypts it with the provided key and additional auth data, returning a sops-format encrypted string.
2017-09-12 20:41:02 -07:00
func (c Cipher) Encrypt(plaintext interface{}, key []byte, additionalData string) (ciphertext string, err error) {
if isEmpty(plaintext) {
return "", nil
}
aescipher, err := cryptoaes.NewCipher(key)
2016-08-11 18:14:27 -07:00
if err != nil {
2016-08-22 18:16:51 -07:00
return "", fmt.Errorf("Could not initialize AES GCM encryption cipher: %s", err)
2016-08-11 18:14:27 -07:00
}
var iv []byte
2017-09-12 20:41:02 -07:00
if stash, ok := c.stash[stashKey{plaintext: plaintext, additionalData: additionalData}]; !ok {
iv = make([]byte, nonceSize)
_, err = rand.Read(iv)
if err != nil {
return "", fmt.Errorf("Could not generate random bytes for IV: %s", err)
}
} else {
2017-09-12 20:41:02 -07:00
iv = stash
2016-08-11 18:14:27 -07:00
}
2016-08-24 12:50:35 -07:00
gcm, err := cipher.NewGCMWithNonceSize(aescipher, nonceSize)
2016-08-11 18:14:27 -07:00
if err != nil {
return "", fmt.Errorf("Could not create GCM: %s", err)
}
2017-09-12 20:41:02 -07:00
var plainBytes []byte
2016-08-24 12:52:10 -07:00
var encryptedType string
2017-09-12 20:41:02 -07:00
switch value := plaintext.(type) {
2016-08-11 18:14:27 -07:00
case string:
2016-08-24 12:52:10 -07:00
encryptedType = "str"
2017-09-12 20:41:02 -07:00
plainBytes = []byte(value)
2016-08-11 18:14:27 -07:00
case int:
2016-08-24 12:52:10 -07:00
encryptedType = "int"
2017-09-12 20:41:02 -07:00
plainBytes = []byte(strconv.Itoa(value))
2016-08-11 18:14:27 -07:00
case float64:
2016-08-24 12:52:10 -07:00
encryptedType = "float"
// The Python version encodes floats without padding 0s after the decimal point.
2017-09-12 20:41:02 -07:00
plainBytes = []byte(strconv.FormatFloat(value, 'f', -1, 64))
2016-08-11 18:14:27 -07:00
case bool:
2016-08-24 12:52:10 -07:00
encryptedType = "bool"
// The Python version encodes booleans with Titlecase
if value {
plainBytes = []byte("True")
} else {
plainBytes = []byte("False")
}
case time.Time:
encryptedType = "time"
plainBytes, err = value.MarshalText()
if err != nil {
return "", fmt.Errorf("Error marshaling timestamp %q: %w", value, err)
}
2017-08-15 13:48:55 -07:00
case sops.Comment:
encryptedType = "comment"
plainBytes = []byte(value.Value)
2016-08-11 18:14:27 -07:00
default:
return "", fmt.Errorf("Value to encrypt has unsupported type %T", value)
}
2017-09-12 20:41:02 -07:00
out := gcm.Seal(nil, iv, plainBytes, []byte(additionalData))
2016-08-22 18:16:51 -07:00
return fmt.Sprintf("ENC[AES256_GCM,data:%s,iv:%s,tag:%s,type:%s]",
base64.StdEncoding.EncodeToString(out[:len(out)-cryptoaes.BlockSize]),
base64.StdEncoding.EncodeToString(iv),
base64.StdEncoding.EncodeToString(out[len(out)-cryptoaes.BlockSize:]),
2016-08-24 12:52:10 -07:00
encryptedType), nil
2016-08-11 18:14:27 -07:00
}