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

Merge pull request #100 from mozilla/feature-editor-mode

Editor mode
This commit is contained in:
Julien Vehent [:ulfr]
2016-09-05 08:26:28 -04:00
committed by GitHub
5 changed files with 286 additions and 76 deletions

View File

@@ -19,6 +19,11 @@ type encryptedValue struct {
const nonceSize int = 32
type stashData struct {
iv []byte
plaintext interface{}
}
// Cipher encrypts and decrypts data keys with AES GCM 256
type Cipher struct{}
@@ -46,54 +51,67 @@ func parse(value string) (*encryptedValue, error) {
return &encryptedValue{data, iv, tag, datatype}, nil
}
// Decrypt takes a sops-format value string and a key and returns the decrypted value.
func (c Cipher) Decrypt(value string, key []byte, additionalAuthData []byte) (interface{}, 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, path string) (plaintext interface{}, stash interface{}, err error) {
encryptedValue, err := parse(value)
if err != nil {
return "", err
return "", nil, err
}
aescipher, err := cryptoaes.NewCipher(key)
if err != nil {
return "", err
return "", nil, err
}
gcm, err := cipher.NewGCMWithNonceSize(aescipher, len(encryptedValue.iv))
if err != nil {
return "", err
return "", nil, err
}
stashValue := stashData{iv: encryptedValue.iv}
data := append(encryptedValue.data, encryptedValue.tag...)
decryptedBytes, err := gcm.Open(nil, encryptedValue.iv, data, additionalAuthData)
decryptedBytes, err := gcm.Open(nil, encryptedValue.iv, data, []byte(path))
if err != nil {
return "", 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":
return decryptedValue, nil
stashValue.plaintext = decryptedValue
return decryptedValue, stashValue, nil
case "int":
return strconv.Atoi(decryptedValue)
plaintext, err = strconv.Atoi(decryptedValue)
stashValue.plaintext = plaintext
return plaintext, stashValue, err
case "float":
return strconv.ParseFloat(decryptedValue, 64)
plaintext, err = strconv.ParseFloat(decryptedValue, 64)
stashValue.plaintext = plaintext
return plaintext, stashValue, err
case "bytes":
return decryptedValue, nil
stashValue.plaintext = decryptedBytes
return decryptedBytes, stashValue, nil
case "bool":
return strconv.ParseBool(decryptedValue)
plaintext, err = strconv.ParseBool(decryptedValue)
stashValue.plaintext = plaintext
return plaintext, stashValue, err
default:
return nil, fmt.Errorf("Unknown datatype: %s", encryptedValue.datatype)
return nil, nil, fmt.Errorf("Unknown datatype: %s", encryptedValue.datatype)
}
}
// 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, additionalAuthData []byte) (string, error) {
func (c Cipher) Encrypt(value interface{}, key []byte, path string, stash interface{}) (string, error) {
aescipher, err := cryptoaes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("Could not initialize AES GCM encryption cipher: %s", err)
}
iv := make([]byte, nonceSize)
_, err = rand.Read(iv)
if err != nil {
return "", fmt.Errorf("Could not generate random bytes for IV: %s", err)
var iv []byte
if stash, ok := stash.(stashData); !ok || stash.plaintext != value {
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
}
gcm, err := cipher.NewGCMWithNonceSize(aescipher, nonceSize)
if err != nil {
@@ -117,7 +135,7 @@ func (c Cipher) Encrypt(value interface{}, key []byte, additionalAuthData []byte
default:
return "", fmt.Errorf("Value to encrypt has unsupported type %T", value)
}
out := gcm.Seal(nil, iv, plaintext, additionalAuthData)
out := gcm.Seal(nil, iv, plaintext, []byte(path))
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),

View File

@@ -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, []byte("bar:"))
decryption, _, err := Cipher{}.Decrypt(message, key, "bar:")
if err != nil {
t.Errorf("%s", err)
}
@@ -23,25 +23,25 @@ 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)), []byte(""))
_, _, err := Cipher{}.Decrypt(message, []byte(strings.Repeat("f", 32)), "")
if err == nil {
t.Errorf("Decrypting with an invalid AAC should fail")
}
}
func TestRoundtripString(t *testing.T) {
f := func(x string, aad []byte) bool {
f := func(x, aad string) bool {
key := make([]byte, 32)
rand.Read(key)
if x == "" {
return true
}
s, err := Cipher{}.Encrypt(x, key, aad)
s, err := Cipher{}.Encrypt(x, key, aad, nil)
if err != nil {
fmt.Println(err)
return false
}
d, err := Cipher{}.Decrypt(s, key, aad)
d, _, err := Cipher{}.Decrypt(s, key, aad)
if err != nil {
return false
}
@@ -55,12 +55,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, []byte(""))
s, err := Cipher{}.Encrypt(x, key, "", nil)
if err != nil {
fmt.Println(err)
return false
}
d, err := Cipher{}.Decrypt(s, key, []byte(""))
d, _, err := Cipher{}.Decrypt(s, key, "")
if err != nil {
return false
}
@@ -74,12 +74,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, []byte(""))
s, err := Cipher{}.Encrypt(x, key, "", nil)
if err != nil {
fmt.Println(err)
return false
}
d, err := Cipher{}.Decrypt(s, key, []byte(""))
d, _, err := Cipher{}.Decrypt(s, key, "")
if err != nil {
return false
}
@@ -93,12 +93,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, []byte(""))
s, err := Cipher{}.Encrypt(x, key, "", nil)
if err != nil {
fmt.Println(err)
return false
}
d, err := Cipher{}.Decrypt(s, key, []byte(""))
d, _, err := Cipher{}.Decrypt(s, key, "")
if err != nil {
return false
}

View File

@@ -3,6 +3,9 @@ package main
import (
"go.mozilla.org/sops"
"bufio"
"bytes"
"crypto/md5"
"fmt"
"go.mozilla.org/sops/aes"
"go.mozilla.org/sops/json"
@@ -14,6 +17,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"path"
"strings"
"time"
)
@@ -141,7 +145,7 @@ func main() {
output = os.Stdout
}
if err != nil {
return cli.NewExitError("Error: could not read file", exitCouldNotReadInputFile)
fileBytes = nil
}
if c.Bool("encrypt") {
return encrypt(c, file, fileBytes, output)
@@ -150,12 +154,12 @@ func main() {
} else if c.Bool("rotate") {
return rotate(c, file, fileBytes, output)
}
return nil
return edit(c, file, fileBytes)
}
app.Run(os.Args)
}
func runEditor(path string) {
func runEditor(path string) error {
editor := os.Getenv("EDITOR")
var cmd *exec.Cmd
if editor == "" {
@@ -168,7 +172,11 @@ func runEditor(path string) {
} else {
cmd = exec.Command(editor, path)
}
cmd.Run()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func store(path string) sops.Store {
@@ -180,36 +188,36 @@ func store(path string) sops.Store {
panic("Unknown file type for file " + path)
}
func decryptFile(store sops.Store, fileBytes []byte, ignoreMac bool) (sops.Tree, error) {
var tree sops.Tree
func decryptFile(store sops.Store, fileBytes []byte, ignoreMac bool) (tree sops.Tree, stash map[string][]interface{}, err error) {
metadata, err := store.UnmarshalMetadata(fileBytes)
if err != nil {
return tree, cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), exitCouldNotReadInputFile)
return tree, nil, cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), exitCouldNotReadInputFile)
}
key, err := metadata.GetDataKey()
if err != nil {
return tree, cli.NewExitError(err.Error(), exitCouldNotRetrieveKey)
return tree, nil, cli.NewExitError(err.Error(), exitCouldNotRetrieveKey)
}
branch, err := store.Unmarshal(fileBytes)
if err != nil {
return tree, cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), exitCouldNotReadInputFile)
return tree, nil, cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), exitCouldNotReadInputFile)
}
tree = sops.Tree{Branch: branch, Metadata: metadata}
cipher := aes.Cipher{}
mac, err := tree.Decrypt(key, cipher)
stash = make(map[string][]interface{})
mac, err := tree.Decrypt(key, cipher, stash)
if err != nil {
return tree, cli.NewExitError(fmt.Sprintf("Error decrypting tree: %s", err), exitErrorDecryptingTree)
return tree, nil, cli.NewExitError(fmt.Sprintf("Error decrypting tree: %s", err), exitErrorDecryptingTree)
}
originalMac, err := cipher.Decrypt(metadata.MessageAuthenticationCode, key, []byte(metadata.LastModified.Format(time.RFC3339)))
originalMac, _, err := cipher.Decrypt(metadata.MessageAuthenticationCode, key, metadata.LastModified.Format(time.RFC3339))
if originalMac != mac && !ignoreMac {
return tree, cli.NewExitError(fmt.Sprintf("MAC mismatch. File has %s, computed %s", originalMac, mac), 9)
return tree, nil, cli.NewExitError(fmt.Sprintf("MAC mismatch. File has %s, computed %s", originalMac, mac), 9)
}
return tree, nil
return tree, stash, nil
}
func decrypt(c *cli.Context, file string, fileBytes []byte, output io.Writer) error {
store := store(file)
tree, err := decryptFile(store, fileBytes, c.Bool("ignore-mac"))
tree, _, err := decryptFile(store, fileBytes, c.Bool("ignore-mac"))
if err != nil {
return err
}
@@ -240,15 +248,7 @@ func decrypt(c *cli.Context, file string, fileBytes []byte, output io.Writer) er
return nil
}
func encrypt(c *cli.Context, file string, fileBytes []byte, output io.Writer) error {
store := store(file)
branch, err := store.Unmarshal(fileBytes)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), exitCouldNotReadInputFile)
}
var metadata sops.Metadata
metadata.UnencryptedSuffix = c.String("unencrypted-suffix")
metadata.Version = "2.0.0"
func getKeysources(c *cli.Context, file string) ([]sops.KeySource, error) {
var kmsKeys []sops.MasterKey
var pgpKeys []sops.MasterKey
@@ -262,13 +262,13 @@ func encrypt(c *cli.Context, file string, fileBytes []byte, output io.Writer) er
pgpKeys = append(pgpKeys, &k)
}
}
var err error
if c.String("kms") == "" && c.String("pgp") == "" {
var confBytes []byte
if c.String("config") != "" {
confBytes, err = ioutil.ReadFile(c.String("config"))
if err != nil {
return cli.NewExitError(fmt.Sprintf("Error loading config file: %s", err), exitErrorReadingConfig)
return nil, cli.NewExitError(fmt.Sprintf("Error loading config file: %s", err), exitErrorReadingConfig)
}
}
kmsString, pgpString, err := yaml.MasterKeyStringsForFile(file, confBytes)
@@ -283,16 +283,32 @@ func encrypt(c *cli.Context, file string, fileBytes []byte, output io.Writer) er
}
kmsKs := sops.KeySource{Name: "kms", Keys: kmsKeys}
pgpKs := sops.KeySource{Name: "pgp", Keys: pgpKeys}
metadata.KeySources = append(metadata.KeySources, kmsKs)
metadata.KeySources = append(metadata.KeySources, pgpKs)
return []sops.KeySource{kmsKs, pgpKs}, nil
}
func encrypt(c *cli.Context, file string, fileBytes []byte, output io.Writer) error {
store := store(file)
branch, err := store.Unmarshal(fileBytes)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), exitCouldNotReadInputFile)
}
ks, err := getKeysources(c, file)
if err != nil {
return err
}
metadata := sops.Metadata{
UnencryptedSuffix: c.String("unencrypted-suffix"),
Version: "2.0.0",
KeySources: ks,
}
tree := sops.Tree{Branch: branch, Metadata: metadata}
key, err := tree.GenerateDataKey()
if err != nil {
return cli.NewExitError(err.Error(), exitCouldNotRetrieveKey)
}
cipher := aes.Cipher{}
mac, err := tree.Encrypt(key, cipher)
encryptedMac, err := cipher.Encrypt(mac, key, []byte(metadata.LastModified.Format(time.RFC3339)))
mac, err := tree.Encrypt(key, cipher, nil)
encryptedMac, err := cipher.Encrypt(mac, key, metadata.LastModified.Format(time.RFC3339), nil)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not encrypt MAC: %s", err), exitErrorEncryptingTree)
}
@@ -307,7 +323,7 @@ func encrypt(c *cli.Context, file string, fileBytes []byte, output io.Writer) er
func rotate(c *cli.Context, file string, fileBytes []byte, output io.Writer) error {
store := store(file)
tree, err := decryptFile(store, fileBytes, c.Bool("ignore-mac"))
tree, _, err := decryptFile(store, fileBytes, c.Bool("ignore-mac"))
if err != nil {
return err
}
@@ -316,7 +332,7 @@ func rotate(c *cli.Context, file string, fileBytes []byte, output io.Writer) err
return cli.NewExitError(err.Error(), exitCouldNotRetrieveKey)
}
cipher := aes.Cipher{}
_, err = tree.Encrypt(newKey, cipher)
_, err = tree.Encrypt(newKey, cipher, nil)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Error encrypting tree: %s", err), exitErrorEncryptingTree)
}
@@ -333,3 +349,168 @@ func rotate(c *cli.Context, file string, fileBytes []byte, output io.Writer) err
}
return nil
}
func hashFile(filePath string) ([]byte, error) {
var result []byte
file, err := os.Open(filePath)
if err != nil {
return result, err
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return result, err
}
return hash.Sum(result), nil
}
const exampleYaml = `
# Welcome to SOPS. This is the default template.
# Remove these lines and add your data.
# Don't modify the 'sops' section, it contains key material.
example_key: example_value
example_array:
- example_value1
- example_value2
example_multiline: |
this is a
multiline
entry
example_number: 1234.5678
example:
nested:
values: delete_me
example_booleans:
- true
- false
`
func loadExample(c *cli.Context, file string) (sops.Tree, error) {
var in []byte
var tree sops.Tree
if strings.HasSuffix(file, ".yaml") {
in = []byte(exampleYaml)
}
branch, _ := store(file).Unmarshal(in)
tree.Branch = branch
ks, err := getKeysources(c, file)
if err != nil {
return tree, err
}
tree.Metadata.UnencryptedSuffix = c.String("unencrypted-suffix")
tree.Metadata.Version = "2.0.0"
tree.Metadata.KeySources = ks
key, err := tree.GenerateDataKey()
if err != nil {
return tree, cli.NewExitError(err.Error(), exitCouldNotRetrieveKey)
}
tree.Metadata.UpdateMasterKeys(key)
return tree, nil
}
func edit(c *cli.Context, file string, fileBytes []byte) error {
var tree sops.Tree
var stash map[string][]interface{}
var err error
if fileBytes == nil {
tree, err = loadExample(c, file)
} else {
tree, stash, err = decryptFile(store(file), fileBytes, c.Bool("ignore-mac"))
}
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not load file: %s", err), exitCouldNotReadInputFile)
}
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not create temporary directory: %s", err), exitCouldNotWriteOutputFile)
}
defer os.RemoveAll(tmpdir)
tmpfile, err := os.Create(path.Join(tmpdir, path.Base(file)))
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not create temporary file: %s", err), exitCouldNotWriteOutputFile)
}
var out []byte
if c.Bool("show-master-keys") {
out, err = store(file).MarshalWithMetadata(tree.Branch, tree.Metadata)
} else {
out, err = store(file).Marshal(tree.Branch)
}
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), exitErrorDumpingTree)
}
_, err = tmpfile.Write(out)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not write output file: %s", err), exitCouldNotWriteOutputFile)
}
origHash, err := hashFile(tmpfile.Name())
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not hash file: %s", err), exitCouldNotReadInputFile)
}
for {
err = runEditor(tmpfile.Name())
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not run editor: %s", err), exitNoEditorFound)
}
newHash, err := hashFile(tmpfile.Name())
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not hash file: %s", err), exitCouldNotReadInputFile)
}
if bytes.Equal(newHash, origHash) {
return cli.NewExitError("File has not changed, exiting.", exitFileHasNotBeenModified)
}
edited, err := ioutil.ReadFile(tmpfile.Name())
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not read edited file: %s", err), exitCouldNotReadInputFile)
}
newBranch, err := store(file).Unmarshal(edited)
if err != nil {
fmt.Printf("Could not load tree: %s\nProbably invalid syntax. Press a key to return to the editor, or Ctrl+C to exit.", err)
bufio.NewReader(os.Stdin).ReadByte()
continue
}
if c.Bool("show-master-keys") {
metadata, err := store(file).UnmarshalMetadata(edited)
if err != nil {
fmt.Printf("sops branch is invalid: %s.\nPress a key to return to the editor, or Ctrl+C to exit.", err)
bufio.NewReader(os.Stdin).ReadByte()
continue
}
tree.Metadata = metadata
}
tree.Branch = newBranch
if tree.Metadata.MasterKeyCount() == 0 {
fmt.Println("No master keys were provided, so sops can't encrypt the file.\nPress a key to return to the editor, or Ctrl+C to exit.")
bufio.NewReader(os.Stdin).ReadByte()
continue
}
break
}
key, err := tree.Metadata.GetDataKey()
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not retrieve data key: %s", err), exitCouldNotRetrieveKey)
}
cipher := aes.Cipher{}
mac, err := tree.Encrypt(key, cipher, stash)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not encrypt tree: %s", err), exitErrorEncryptingTree)
}
encryptedMac, err := cipher.Encrypt(mac, key, tree.Metadata.LastModified.Format(time.RFC3339), stash)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not encrypt MAC: %s", err), exitErrorEncryptingTree)
}
tree.Metadata.MessageAuthenticationCode = encryptedMac
out, err = store(file).MarshalWithMetadata(tree.Branch, tree.Metadata)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), exitErrorDumpingTree)
}
output, err := os.Create(file)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not open output file for writing: %s", err), exitCouldNotWriteOutputFile)
}
_, err = output.Write(out)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not write output file: %s", err), exitCouldNotWriteOutputFile)
}
return nil
}

23
sops.go
View File

@@ -27,8 +27,8 @@ 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, additionalAuthData []byte) (string, error)
Decrypt(value string, key []byte, additionalAuthData []byte) (interface{}, error)
Encrypt(value interface{}, key []byte, path string, stash interface{}) (string, error)
Decrypt(value string, key []byte, path string) (plaintext interface{}, stashValue interface{}, err error)
}
// TreeItem is an item inside sops's tree
@@ -118,13 +118,21 @@ func (tree TreeBranch) walkBranch(in TreeBranch, path []string, onLeaves func(in
}
// 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) (string, error) {
func (tree Tree) Encrypt(key []byte, cipher DataKeyCipher, stash map[string][]interface{}) (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)
if !strings.HasSuffix(path[len(path)-1], tree.Metadata.UnencryptedSuffix) {
var err error
in, err = cipher.Encrypt(in, key, []byte(strings.Join(path, ":")+":"))
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)
if err != nil {
return nil, fmt.Errorf("Could not encrypt value: %s", err)
}
@@ -142,16 +150,19 @@ func (tree Tree) Encrypt(key []byte, cipher DataKeyCipher) (string, error) {
}
// 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) (string, error) {
func (tree Tree) Decrypt(key []byte, cipher DataKeyCipher, stash map[string][]interface{}) (string, error) {
hash := sha512.New()
_, err := tree.Branch.walkBranch(tree.Branch, make([]string, 0), func(in interface{}, path []string) (interface{}, error) {
var v interface{}
if !strings.HasSuffix(path[len(path)-1], tree.Metadata.UnencryptedSuffix) {
var err error
v, err = cipher.Decrypt(in.(string), key, []byte(strings.Join(path, ":")+":"))
var stashValue interface{}
pathString := strings.Join(path, ":") + ":"
v, stashValue, 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
}

View File

@@ -23,14 +23,14 @@ func TestUnencryptedSuffix(t *testing.T) {
},
}
cipher := aes.Cipher{}
_, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher)
_, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher, nil)
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)
_, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher, nil)
if err != nil {
t.Errorf("Decrypting the tree failed: %s", err)
}
@@ -41,12 +41,12 @@ func TestUnencryptedSuffix(t *testing.T) {
type MockCipher struct{}
func (m MockCipher) Encrypt(value interface{}, key []byte, additionalAuthData []byte) (string, error) {
func (m MockCipher) Encrypt(value interface{}, key []byte, path string, stashValue interface{}) (string, error) {
return "a", nil
}
func (m MockCipher) Decrypt(value string, key []byte, additionalAuthData []byte) (interface{}, error) {
return "a", nil
func (m MockCipher) Decrypt(value string, key []byte, path string) (interface{}, interface{}, error) {
return "a", nil, nil
}
func TestEncrypt(t *testing.T) {
@@ -97,7 +97,7 @@ func TestEncrypt(t *testing.T) {
},
}
tree := Tree{Branch: branch, Metadata: Metadata{UnencryptedSuffix: DefaultUnencryptedSuffix}}
tree.Encrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{})
tree.Encrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{}, make(map[string][]interface{}))
if !reflect.DeepEqual(tree.Branch, expected) {
t.Errorf("%s does not equal expected tree: %s", tree.Branch, expected)
}
@@ -151,7 +151,7 @@ func TestDecrypt(t *testing.T) {
},
}
tree := Tree{Branch: branch, Metadata: Metadata{UnencryptedSuffix: DefaultUnencryptedSuffix}}
tree.Decrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{})
tree.Decrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{}, make(map[string][]interface{}))
if !reflect.DeepEqual(tree.Branch, expected) {
t.Errorf("%s does not equal expected tree: %s", tree.Branch, expected)
}