From 3811698f54b94e93392fcf06491d4300c178446c Mon Sep 17 00:00:00 2001 From: Adrian Utrilla Date: Tue, 12 Sep 2017 20:41:02 -0700 Subject: [PATCH] Encapsulate stash inside cipher --- aes/{decryptor.go => cipher.go} | 73 ++++++++++++----------- aes/{decryptor_test.go => cipher_test.go} | 20 +++---- cmd/sops/common/common.go | 16 ++--- cmd/sops/decrypt.go | 3 +- cmd/sops/edit.go | 14 ++--- cmd/sops/encrypt.go | 3 +- cmd/sops/main.go | 10 ++-- cmd/sops/rotate.go | 6 +- cmd/sops/set.go | 5 +- decrypt/decrypt.go | 7 +-- sops.go | 30 ++++------ sops_test.go | 16 ++--- 12 files changed, 96 insertions(+), 107 deletions(-) rename aes/{decryptor.go => cipher.go} (69%) rename aes/{decryptor_test.go => cipher_test.go} (79%) diff --git a/aes/decryptor.go b/aes/cipher.go similarity index 69% rename from aes/decryptor.go rename to aes/cipher.go index 7715e6b69..6e5c86766 100644 --- a/aes/decryptor.go +++ b/aes/cipher.go @@ -23,13 +23,23 @@ type encryptedValue struct { const nonceSize int = 32 -type stashData struct { - iv []byte - plaintext interface{} +type stashKey struct { + additionalData string + plaintext interface{} } // Cipher encrypts and decrypts data keys with AES GCM 256 -type Cipher struct{} +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 +} + +func NewCipher() Cipher { + return Cipher{ + stash: make(map[stashKey][]byte), + } +} var encre = regexp.MustCompile(`^ENC\[AES256_GCM,data:(.+),iv:(.+),tag:(.+),type:(.+)\]`) @@ -56,57 +66,50 @@ func parse(value string) (*encryptedValue, error) { } // Decrypt takes a sops-format value string and a key and returns the decrypted value and a stash value -func (c Cipher) Decrypt(value string, key []byte, additionalData string) (plaintext interface{}, stash interface{}, err error) { - if value == "" { - return "", nil, nil +func (c Cipher) Decrypt(ciphertext string, key []byte, additionalData string) (plaintext interface{}, err error) { + if ciphertext == "" { + return "", nil } - encryptedValue, err := parse(value) + encryptedValue, err := parse(ciphertext) if err != nil { - return "", nil, err + return nil, err } aescipher, err := cryptoaes.NewCipher(key) if err != nil { - return "", nil, err + return nil, err } gcm, err := cipher.NewGCMWithNonceSize(aescipher, len(encryptedValue.iv)) if err != nil { - return "", nil, err + return nil, err } - stashValue := stashData{iv: encryptedValue.iv} data := append(encryptedValue.data, encryptedValue.tag...) decryptedBytes, err := gcm.Open(nil, encryptedValue.iv, data, []byte(additionalData)) if err != nil { - return "", nil, fmt.Errorf("Could not decrypt with AES_GCM: %s", err) + return nil, fmt.Errorf("Could not decrypt with AES_GCM: %s", err) } decryptedValue := string(decryptedBytes) switch encryptedValue.datatype { case "str": - stashValue.plaintext = decryptedValue - return decryptedValue, stashValue, nil + plaintext = decryptedValue case "int": plaintext, err = strconv.Atoi(decryptedValue) - stashValue.plaintext = plaintext - return plaintext, stashValue, err case "float": plaintext, err = strconv.ParseFloat(decryptedValue, 64) - stashValue.plaintext = plaintext - return plaintext, stashValue, err case "bytes": - stashValue.plaintext = decryptedBytes - return decryptedBytes, stashValue, nil + plaintext = decryptedBytes case "bool": plaintext, err = strconv.ParseBool(decryptedValue) - stashValue.plaintext = plaintext - return plaintext, stashValue, err default: - return nil, nil, fmt.Errorf("Unknown datatype: %s", encryptedValue.datatype) + return nil, fmt.Errorf("Unknown datatype: %s", encryptedValue.datatype) } + c.stash[stashKey{plaintext: plaintext, additionalData: additionalData}] = encryptedValue.iv + return plaintext, err } // 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. -func (c Cipher) Encrypt(value interface{}, key []byte, additionalData string, stash interface{}) (string, error) { - if value == "" { +func (c Cipher) Encrypt(plaintext interface{}, key []byte, additionalData string) (ciphertext string, err error) { + if plaintext == "" { return "", nil } aescipher, err := cryptoaes.NewCipher(key) @@ -114,40 +117,40 @@ func (c Cipher) Encrypt(value interface{}, key []byte, additionalData string, st return "", fmt.Errorf("Could not initialize AES GCM encryption cipher: %s", err) } var iv []byte - if stash, ok := stash.(stashData); !ok || stash.plaintext != value { + 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 { - iv = stash.iv + iv = stash } gcm, err := cipher.NewGCMWithNonceSize(aescipher, nonceSize) if err != nil { return "", fmt.Errorf("Could not create GCM: %s", err) } - var plaintext []byte + var plainBytes []byte var encryptedType string - switch value := value.(type) { + switch value := plaintext.(type) { case string: encryptedType = "str" - plaintext = []byte(value) + plainBytes = []byte(value) case int: encryptedType = "int" - plaintext = []byte(strconv.Itoa(value)) + plainBytes = []byte(strconv.Itoa(value)) case float64: encryptedType = "float" // The Python version encodes floats without padding 0s after the decimal point. - plaintext = []byte(strconv.FormatFloat(value, 'f', -1, 64)) + plainBytes = []byte(strconv.FormatFloat(value, 'f', -1, 64)) case bool: encryptedType = "bool" // The Python version encodes booleans with Titlecase - plaintext = []byte(strings.Title(strconv.FormatBool(value))) + plainBytes = []byte(strings.Title(strconv.FormatBool(value))) default: return "", fmt.Errorf("Value to encrypt has unsupported type %T", value) } - out := gcm.Seal(nil, iv, plaintext, []byte(additionalData)) + out := gcm.Seal(nil, iv, plainBytes, []byte(additionalData)) 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), diff --git a/aes/decryptor_test.go b/aes/cipher_test.go similarity index 79% rename from aes/decryptor_test.go rename to aes/cipher_test.go index 4e1b24445..641b09807 100644 --- a/aes/decryptor_test.go +++ b/aes/cipher_test.go @@ -12,7 +12,7 @@ func TestDecrypt(t *testing.T) { expected := "foo" key := []byte(strings.Repeat("f", 32)) message := `ENC[AES256_GCM,data:oYyi,iv:MyIDYbT718JRr11QtBkcj3Dwm4k1aCGZBVeZf0EyV8o=,tag:t5z2Z023Up0kxwCgw1gNxg==,type:str]` - decryption, _, err := Cipher{}.Decrypt(message, key, "bar:") + decryption, err := NewCipher().Decrypt(message, key, "bar:") if err != nil { t.Errorf("%s", err) } @@ -23,7 +23,7 @@ func TestDecrypt(t *testing.T) { func TestDecryptInvalidAad(t *testing.T) { message := `ENC[AES256_GCM,data:oYyi,iv:MyIDYbT718JRr11QtBkcj3Dwm4k1aCGZBVeZf0EyV8o=,tag:t5z2Z023Up0kxwCgw1gNxg==,type:str]` - _, _, err := Cipher{}.Decrypt(message, []byte(strings.Repeat("f", 32)), "") + _, err := NewCipher().Decrypt(message, []byte(strings.Repeat("f", 32)), "") if err == nil { t.Errorf("Decrypting with an invalid AAC should fail") } @@ -33,12 +33,12 @@ func TestRoundtripString(t *testing.T) { f := func(x, aad string) bool { key := make([]byte, 32) rand.Read(key) - s, err := Cipher{}.Encrypt(x, key, aad, nil) + s, err := NewCipher().Encrypt(x, key, aad) if err != nil { log.Println(err) return false } - d, _, err := Cipher{}.Decrypt(s, key, aad) + d, err := NewCipher().Decrypt(s, key, aad) if err != nil { return false } @@ -52,12 +52,12 @@ func TestRoundtripString(t *testing.T) { func TestRoundtripFloat(t *testing.T) { key := []byte(strings.Repeat("f", 32)) f := func(x float64) bool { - s, err := Cipher{}.Encrypt(x, key, "", nil) + s, err := NewCipher().Encrypt(x, key, "") if err != nil { log.Println(err) return false } - d, _, err := Cipher{}.Decrypt(s, key, "") + d, err := NewCipher().Decrypt(s, key, "") if err != nil { return false } @@ -71,12 +71,12 @@ func TestRoundtripFloat(t *testing.T) { func TestRoundtripInt(t *testing.T) { key := []byte(strings.Repeat("f", 32)) f := func(x int) bool { - s, err := Cipher{}.Encrypt(x, key, "", nil) + s, err := NewCipher().Encrypt(x, key, "") if err != nil { log.Println(err) return false } - d, _, err := Cipher{}.Decrypt(s, key, "") + d, err := NewCipher().Decrypt(s, key, "") if err != nil { return false } @@ -90,12 +90,12 @@ func TestRoundtripInt(t *testing.T) { func TestRoundtripBool(t *testing.T) { key := []byte(strings.Repeat("f", 32)) f := func(x bool) bool { - s, err := Cipher{}.Encrypt(x, key, "", nil) + s, err := NewCipher().Encrypt(x, key, "") if err != nil { log.Println(err) return false } - d, _, err := Cipher{}.Decrypt(s, key, "") + d, err := NewCipher().Decrypt(s, key, "") if err != nil { return false } diff --git a/cmd/sops/common/common.go b/cmd/sops/common/common.go index e59d74040..2ee268f66 100644 --- a/cmd/sops/common/common.go +++ b/cmd/sops/common/common.go @@ -16,14 +16,12 @@ import ( type DecryptTreeOpts struct { // Tree is the tree to be decrypted Tree *sops.Tree - // Stash is a map to save information between encryption-decryption round-trips, such as IVs - Stash map[string][]interface{} // KeyServices are the key services to be used for decryption of the data key KeyServices []keyservice.KeyServiceClient // IgnoreMac is whether or not to ignore the Message Authentication Code included in the SOPS tree IgnoreMac bool // Cipher is the cryptographic cipher to use to decrypt the values inside the tree - Cipher sops.DataKeyCipher + Cipher sops.Cipher } // DecryptTree decrypts the tree passed in through the DecryptTreeOpts and additionally returns the decrypted data key @@ -32,11 +30,11 @@ func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) { if err != nil { return nil, cli.NewExitError(err.Error(), codes.CouldNotRetrieveKey) } - computedMac, err := opts.Tree.Decrypt(dataKey, opts.Cipher, opts.Stash) + computedMac, err := opts.Tree.Decrypt(dataKey, opts.Cipher) if err != nil { return nil, cli.NewExitError(fmt.Sprintf("Error decrypting tree: %s", err), codes.ErrorDecryptingTree) } - fileMac, _, err := opts.Cipher.Decrypt(opts.Tree.Metadata.MessageAuthenticationCode, dataKey, opts.Tree.Metadata.LastModified.Format(time.RFC3339)) + fileMac, err := opts.Cipher.Decrypt(opts.Tree.Metadata.MessageAuthenticationCode, dataKey, opts.Tree.Metadata.LastModified.Format(time.RFC3339)) if !opts.IgnoreMac { if fileMac != computedMac { // If the file has an empty MAC, display "no MAC" instead of not displaying anything @@ -53,22 +51,20 @@ func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) { type EncryptTreeOpts struct { // Tree is the tree to be encrypted Tree *sops.Tree - // Stash is a map to save information between encryption-decryption round-trips, such as IVs - Stash map[string][]interface{} // Cipher is the cryptographic cipher to use to encrypt the values inside the tree - Cipher sops.DataKeyCipher + Cipher sops.Cipher // DataKey is the key the cipher should use to encrypt the values inside the tree DataKey []byte } // EncryptTree encrypts the tree passed in through the EncryptTreeOpts func EncryptTree(opts EncryptTreeOpts) error { - unencryptedMac, err := opts.Tree.Encrypt(opts.DataKey, opts.Cipher, opts.Stash) + unencryptedMac, err := opts.Tree.Encrypt(opts.DataKey, opts.Cipher) if err != nil { return cli.NewExitError(fmt.Sprintf("Error encrypting tree: %s", err), codes.ErrorEncryptingTree) } opts.Tree.Metadata.LastModified = time.Now().UTC() - opts.Tree.Metadata.MessageAuthenticationCode, err = opts.Cipher.Encrypt(unencryptedMac, opts.DataKey, opts.Tree.Metadata.LastModified.Format(time.RFC3339), opts.Stash) + opts.Tree.Metadata.MessageAuthenticationCode, err = opts.Cipher.Encrypt(unencryptedMac, opts.DataKey, opts.Tree.Metadata.LastModified.Format(time.RFC3339)) if err != nil { return cli.NewExitError(fmt.Sprintf("Could not encrypt MAC: %s", err), codes.ErrorEncryptingMac) } diff --git a/cmd/sops/decrypt.go b/cmd/sops/decrypt.go index eb5c3e45c..665440a03 100644 --- a/cmd/sops/decrypt.go +++ b/cmd/sops/decrypt.go @@ -11,7 +11,7 @@ import ( ) type decryptOpts struct { - Cipher sops.DataKeyCipher + Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string @@ -27,7 +27,6 @@ func decrypt(opts decryptOpts) (decryptedFile []byte, err error) { } _, err = common.DecryptTree(common.DecryptTreeOpts{ - Stash: make(map[string][]interface{}), Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, diff --git a/cmd/sops/edit.go b/cmd/sops/edit.go index 0eb9c764a..993d9b8cb 100644 --- a/cmd/sops/edit.go +++ b/cmd/sops/edit.go @@ -26,7 +26,7 @@ import ( ) type editOpts struct { - Cipher sops.DataKeyCipher + Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string @@ -107,9 +107,8 @@ func editExample(opts editExampleOpts) ([]byte, error) { if len(errs) > 0 { return nil, cli.NewExitError(fmt.Sprintf("Error encrypting the data key with one or more master keys: %s", errs), codes.CouldNotRetrieveKey) } - stash := make(map[string][]interface{}) - return editTree(opts.editOpts, &tree, dataKey, stash) + return editTree(opts.editOpts, &tree, dataKey) } func edit(opts editOpts) ([]byte, error) { @@ -119,18 +118,17 @@ func edit(opts editOpts) ([]byte, error) { return nil, err } // Decrypt the file - stash := make(map[string][]interface{}) dataKey, err := common.DecryptTree(common.DecryptTreeOpts{ - Stash: stash, Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, + Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } - return editTree(opts, tree, dataKey, stash) + return editTree(opts, tree, dataKey) } -func editTree(opts editOpts, tree *sops.Tree, dataKey []byte, stash map[string][]interface{}) ([]byte, error) { +func editTree(opts editOpts, tree *sops.Tree, dataKey []byte) ([]byte, error) { // Create temporary file for editing tmpdir, err := ioutil.TempDir("", "") if err != nil { @@ -173,7 +171,7 @@ func editTree(opts editOpts, tree *sops.Tree, dataKey []byte, stash map[string][ // Encrypt the file err = common.EncryptTree(common.EncryptTreeOpts{ - Stash: stash, DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, + DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, }) if err != nil { return nil, err diff --git a/cmd/sops/encrypt.go b/cmd/sops/encrypt.go index a9cc1c1c9..1f6d09128 100644 --- a/cmd/sops/encrypt.go +++ b/cmd/sops/encrypt.go @@ -13,7 +13,7 @@ import ( ) type encryptOpts struct { - Cipher sops.DataKeyCipher + Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string @@ -48,7 +48,6 @@ func encrypt(opts encryptOpts) (encryptedFile []byte, err error) { } err = common.EncryptTree(common.EncryptTreeOpts{ - Stash: make(map[string][]interface{}), DataKey: dataKey, Tree: &tree, Cipher: opts.Cipher, diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 47a8c740f..11c88f26c 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -313,7 +313,7 @@ func main() { OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, - Cipher: aes.Cipher{}, + Cipher: aes.NewCipher(), UnencryptedSuffix: c.String("unencrypted-suffix"), KeyServices: svcs, KeyGroups: keyGroups, @@ -333,7 +333,7 @@ func main() { OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, - Cipher: aes.Cipher{}, + Cipher: aes.NewCipher(), Extract: extract, KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), @@ -363,7 +363,7 @@ func main() { OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, - Cipher: aes.Cipher{}, + Cipher: aes.NewCipher(), KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), AddMasterKeys: addMasterKeys, @@ -383,7 +383,7 @@ func main() { OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, - Cipher: aes.Cipher{}, + Cipher: aes.NewCipher(), KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), Value: value, @@ -402,7 +402,7 @@ func main() { OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, - Cipher: aes.Cipher{}, + Cipher: aes.NewCipher(), KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), ShowMasterKeys: c.Bool("show-master-keys"), diff --git a/cmd/sops/rotate.go b/cmd/sops/rotate.go index be38daf8e..d05e0bf2d 100644 --- a/cmd/sops/rotate.go +++ b/cmd/sops/rotate.go @@ -12,7 +12,7 @@ import ( ) type rotateOpts struct { - Cipher sops.DataKeyCipher + Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string @@ -29,7 +29,7 @@ func rotate(opts rotateOpts) ([]byte, error) { } dataKey, err := common.DecryptTree(common.DecryptTreeOpts{ - Stash: make(map[string][]interface{}), Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, + Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { @@ -60,7 +60,7 @@ func rotate(opts rotateOpts) ([]byte, error) { // Reencrypt the file with the new key err = common.EncryptTree(common.EncryptTreeOpts{ - Stash: make(map[string][]interface{}), DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, + DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, }) if err != nil { return nil, err diff --git a/cmd/sops/set.go b/cmd/sops/set.go index 474638418..f63286615 100644 --- a/cmd/sops/set.go +++ b/cmd/sops/set.go @@ -11,7 +11,7 @@ import ( ) type setOpts struct { - Cipher sops.DataKeyCipher + Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string @@ -31,7 +31,6 @@ func set(opts setOpts) ([]byte, error) { // Decrypt the file dataKey, err := common.DecryptTree(common.DecryptTreeOpts{ - Stash: make(map[string][]interface{}), Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, @@ -52,7 +51,7 @@ func set(opts setOpts) ([]byte, error) { tree.Branch = branch.InsertOrReplaceValue(key, opts.Value) err = common.EncryptTree(common.EncryptTreeOpts{ - Stash: make(map[string][]interface{}), DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, + DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, }) if err != nil { return nil, err diff --git a/decrypt/decrypt.go b/decrypt/decrypt.go index f9c81b693..d1a655744 100644 --- a/decrypt/decrypt.go +++ b/decrypt/decrypt.go @@ -60,9 +60,8 @@ func Data(data []byte, format string) (cleartext []byte, err error) { tree := sops.Tree{Branch: branch, Metadata: metadata} // Decrypt the tree - cipher := aes.Cipher{} - stash := make(map[string][]interface{}) - mac, err := tree.Decrypt(key, cipher, stash) + cipher := aes.NewCipher() + mac, err := tree.Decrypt(key, cipher) if err != nil { return nil, err } @@ -70,7 +69,7 @@ func Data(data []byte, format string) (cleartext []byte, err error) { // Compute the hash of the cleartext tree and compare it with // the one that was stored in the document. If they match, // integrity was preserved - originalMac, _, err := cipher.Decrypt( + originalMac, err := cipher.Decrypt( metadata.MessageAuthenticationCode, key, metadata.LastModified.Format(time.RFC3339), diff --git a/sops.go b/sops.go index be4ca73a6..8c4aba6f3 100644 --- a/sops.go +++ b/sops.go @@ -68,10 +68,15 @@ const MacMismatch = sopsError("MAC mismatch") // MetadataNotFound occurs when the input file is malformed and doesn't have sops metadata in it const MetadataNotFound = sopsError("sops metadata not found") -// DataKeyCipher provides a way to encrypt and decrypt the data key used to encrypt and decrypt sops files, so that the data key can be stored alongside the encrypted content. A DataKeyCipher must be able to decrypt the values it encrypts. -type DataKeyCipher interface { - Encrypt(value interface{}, key []byte, additionalData string, stash interface{}) (string, error) - Decrypt(value string, key []byte, additionalData string) (plaintext interface{}, stashValue interface{}, err error) +// Cipher provides a way to encrypt and decrypt the data key used to encrypt and decrypt sops files, so that the +// data key can be stored alongside the encrypted content. A Cipher must be able to decrypt the values it encrypts. +type Cipher interface { + // Encrypt takes a plaintext, a key and additional data and returns the plaintext encrypted with the key, using the + // additional data for authentication + Encrypt(plaintext interface{}, key []byte, additionalData string) (ciphertext string, err error) + // Encrypt takes a ciphertext, a key and additional data and returns the ciphertext encrypted with the key, using + // the additional data for authentication + Decrypt(ciphertext string, key []byte, additionalData string) (plaintext interface{}, err error) } // Comment represents a comment in the sops tree for the file formats that actually support them. @@ -197,7 +202,7 @@ func (branch TreeBranch) walkBranch(in TreeBranch, path []string, onLeaves func( } // Encrypt walks over the tree and encrypts all values with the provided cipher, except those whose key ends with the UnencryptedSuffix specified on the Metadata struct. If encryption is successful, it returns the MAC for the encrypted tree. -func (tree Tree) Encrypt(key []byte, cipher DataKeyCipher, stash map[string][]interface{}) (string, error) { +func (tree Tree) Encrypt(key []byte, cipher Cipher) (string, error) { hash := sha512.New() _, err := tree.Branch.walkBranch(tree.Branch, make([]string, 0), func(in interface{}, path []string) (interface{}, error) { bytes, err := ToBytes(in) @@ -210,14 +215,7 @@ func (tree Tree) Encrypt(key []byte, cipher DataKeyCipher, stash map[string][]in if !unencrypted { var err error pathString := strings.Join(path, ":") + ":" - // Pop from the left of the stash - var stashValue interface{} - if len(stash[pathString]) > 0 { - var newStash []interface{} - stashValue, newStash = stash[pathString][0], stash[pathString][1:len(stash[pathString])] - stash[pathString] = newStash - } - in, err = cipher.Encrypt(in, key, pathString, stashValue) + in, err = cipher.Encrypt(in, key, pathString) if err != nil { return nil, fmt.Errorf("Could not encrypt value: %s", err) } @@ -235,7 +233,7 @@ func (tree Tree) Encrypt(key []byte, cipher DataKeyCipher, stash map[string][]in } // Decrypt walks over the tree and decrypts all values with the provided cipher, except those whose key ends with the UnencryptedSuffix specified on the Metadata struct. If decryption is successful, it returns the MAC for the decrypted tree. -func (tree Tree) Decrypt(key []byte, cipher DataKeyCipher, stash map[string][]interface{}) (string, error) { +func (tree Tree) Decrypt(key []byte, cipher Cipher) (string, error) { log.Print("Decrypting SOPS tree") hash := sha512.New() _, err := tree.Branch.walkBranch(tree.Branch, make([]string, 0), func(in interface{}, path []string) (interface{}, error) { @@ -248,13 +246,11 @@ func (tree Tree) Decrypt(key []byte, cipher DataKeyCipher, stash map[string][]in } if !unencrypted { var err error - var stashValue interface{} pathString := strings.Join(path, ":") + ":" - v, stashValue, err = cipher.Decrypt(in.(string), key, pathString) + v, err = cipher.Decrypt(in.(string), key, pathString) if err != nil { return nil, fmt.Errorf("Could not decrypt value: %s", err) } - stash[pathString] = append(stash[pathString], stashValue) } else { v = in } diff --git a/sops_test.go b/sops_test.go index 47908f736..100256a16 100644 --- a/sops_test.go +++ b/sops_test.go @@ -41,15 +41,15 @@ func TestUnencryptedSuffix(t *testing.T) { }, }, } - cipher := aes.Cipher{} - _, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher, nil) + cipher := aes.NewCipher() + _, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Encrypting the tree failed: %s", err) } if !reflect.DeepEqual(tree.Branch, expected) { t.Errorf("Trees don't match: \ngot \t\t%+v,\n expected \t\t%+v", tree.Branch, expected) } - _, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher, nil) + _, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Decrypting the tree failed: %s", err) } @@ -60,12 +60,12 @@ func TestUnencryptedSuffix(t *testing.T) { type MockCipher struct{} -func (m MockCipher) Encrypt(value interface{}, key []byte, path string, stashValue interface{}) (string, error) { +func (m MockCipher) Encrypt(value interface{}, key []byte, path string) (string, error) { return "a", nil } -func (m MockCipher) Decrypt(value string, key []byte, path string) (interface{}, interface{}, error) { - return "a", nil, nil +func (m MockCipher) Decrypt(value string, key []byte, path string) (interface{}, error) { + return "a", nil } func TestEncrypt(t *testing.T) { @@ -116,7 +116,7 @@ func TestEncrypt(t *testing.T) { }, } tree := Tree{Branch: branch, Metadata: Metadata{UnencryptedSuffix: DefaultUnencryptedSuffix}} - tree.Encrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{}, make(map[string][]interface{})) + tree.Encrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{}) if !reflect.DeepEqual(tree.Branch, expected) { t.Errorf("%s does not equal expected tree: %s", tree.Branch, expected) } @@ -170,7 +170,7 @@ func TestDecrypt(t *testing.T) { }, } tree := Tree{Branch: branch, Metadata: Metadata{UnencryptedSuffix: DefaultUnencryptedSuffix}} - tree.Decrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{}, make(map[string][]interface{})) + tree.Decrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{}) if !reflect.DeepEqual(tree.Branch, expected) { t.Errorf("%s does not equal expected tree: %s", tree.Branch, expected) }