From efd8521436abcd0526641f46d4202a05028b1147 Mon Sep 17 00:00:00 2001 From: Dov Reshef Date: Sun, 8 Apr 2018 12:43:43 +0300 Subject: [PATCH] added the --encrypted-suffix option --- cmd/sops/edit.go | 2 ++ cmd/sops/encrypt.go | 2 ++ cmd/sops/main.go | 6 +++++ sops.go | 59 +++++++++++++++++++++++++++++++++--------- sops_test.go | 63 +++++++++++++++++++++++++++++++++++++++------ stores/stores.go | 3 +++ 6 files changed, 115 insertions(+), 20 deletions(-) diff --git a/cmd/sops/edit.go b/cmd/sops/edit.go index 3cdab2dde..9d2c3a1d5 100644 --- a/cmd/sops/edit.go +++ b/cmd/sops/edit.go @@ -36,6 +36,7 @@ type editOpts struct { type editExampleOpts struct { editOpts UnencryptedSuffix string + EncryptedSuffix string KeyGroups []sops.KeyGroup GroupThreshold int } @@ -96,6 +97,7 @@ func editExample(opts editExampleOpts) ([]byte, error) { tree.Metadata = sops.Metadata{ KeyGroups: opts.KeyGroups, UnencryptedSuffix: opts.UnencryptedSuffix, + EncryptedSuffix: opts.EncryptedSuffix, Version: version, ShamirThreshold: opts.GroupThreshold, } diff --git a/cmd/sops/encrypt.go b/cmd/sops/encrypt.go index 5c0fc2fb5..e6fde69c3 100644 --- a/cmd/sops/encrypt.go +++ b/cmd/sops/encrypt.go @@ -18,6 +18,7 @@ type encryptOpts struct { InputPath string KeyServices []keyservice.KeyServiceClient UnencryptedSuffix string + EncryptedSuffix string KeyGroups []sops.KeyGroup GroupThreshold int } @@ -37,6 +38,7 @@ func encrypt(opts encryptOpts) (encryptedFile []byte, err error) { tree.Metadata = sops.Metadata{ KeyGroups: opts.KeyGroups, UnencryptedSuffix: opts.UnencryptedSuffix, + EncryptedSuffix: opts.EncryptedSuffix, Version: version, ShamirThreshold: opts.GroupThreshold, } diff --git a/cmd/sops/main.go b/cmd/sops/main.go index d331dc2c4..d3e7f9a95 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -328,6 +328,10 @@ func main() { Usage: "override the unencrypted key suffix.", Value: sops.DefaultUnencryptedSuffix, }, + cli.StringFlag{ + Name: "encrypted-suffix", + Usage: "override the encrypted key suffix. When empty, all keys will be encrypted, unless otherwise marked with unencrypted-suffix.", + }, cli.StringFlag{ Name: "config", Usage: "path to sops' config file. If set, sops will not search for the config file recursively.", @@ -392,6 +396,7 @@ func main() { InputPath: fileName, Cipher: aes.NewCipher(), UnencryptedSuffix: c.String("unencrypted-suffix"), + EncryptedSuffix: c.String("encrypted-suffix"), KeyServices: svcs, KeyGroups: groups, GroupThreshold: threshold, @@ -498,6 +503,7 @@ func main() { output, err = editExample(editExampleOpts{ editOpts: opts, UnencryptedSuffix: c.String("unencrypted-suffix"), + EncryptedSuffix: c.String("encrypted-suffix"), KeyGroups: groups, GroupThreshold: threshold, }) diff --git a/sops.go b/sops.go index 11e242957..717146745 100644 --- a/sops.go +++ b/sops.go @@ -107,7 +107,7 @@ func valueFromPathAndLeaf(path []interface{}, leaf interface{}) interface{} { leaf, } } else { - return []interface{} { + return []interface{}{ valueFromPathAndLeaf(path[1:], leaf), } } @@ -115,14 +115,14 @@ func valueFromPathAndLeaf(path []interface{}, leaf interface{}) interface{} { if len(path) == 1 { return TreeBranch{ TreeItem{ - Key: component, + Key: component, Value: leaf, }, } } else { return TreeBranch{ TreeItem{ - Key: component, + Key: component, Value: valueFromPathAndLeaf(path[1:], leaf), }, } @@ -171,7 +171,7 @@ func set(branch interface{}, path []interface{}, value interface{}) interface{} } } -func (branch TreeBranch) Set(path []interface{}, value interface{}) (TreeBranch) { +func (branch TreeBranch) Set(path []interface{}, value interface{}) TreeBranch { return set(branch, path, value).(TreeBranch) } @@ -281,7 +281,8 @@ func (branch TreeBranch) walkBranch(in TreeBranch, path []string, onLeaves func( return in, nil } -// 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. +// 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, or those not ending with EncryptedSuffix, if EncryptedSuffix is provided (by default it is not). +// If encryption is successful, it returns the MAC for the encrypted tree. 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) { @@ -294,11 +295,27 @@ func (tree Tree) Encrypt(key []byte, cipher Cipher) (string, error) { hash.Write(bytes) } encrypted := true - for _, v := range path { - if strings.HasSuffix(v, tree.Metadata.UnencryptedSuffix) { - encrypted = false + if tree.Metadata.UnencryptedSuffix != "" { + for _, v := range path { + if strings.HasSuffix(v, tree.Metadata.UnencryptedSuffix) { + encrypted = false + break + } } } + if tree.Metadata.EncryptedSuffix != "" { + encryptedSuffixFound := false + for _, v := range path { + if strings.HasSuffix(v, tree.Metadata.EncryptedSuffix) { + encryptedSuffixFound = true + if !encrypted { + return nil, fmt.Errorf("Cannot use both encrypted_suffix and unencrypted_suffix in the same file") + } + break + } + } + encrypted = encryptedSuffixFound + } if encrypted { var err error pathString := strings.Join(path, ":") + ":" @@ -315,17 +332,34 @@ func (tree Tree) Encrypt(key []byte, cipher Cipher) (string, error) { return fmt.Sprintf("%X", hash.Sum(nil)), nil } -// 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. +// 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 or those not ending with EncryptedSuffix, if EncryptedSuffix is provided (by default it is not). +// If decryption is successful, it returns the MAC for the decrypted tree. func (tree Tree) Decrypt(key []byte, cipher Cipher) (string, error) { log.Debug("Decrypting tree") hash := sha512.New() _, err := tree.Branch.walkBranch(tree.Branch, make([]string, 0), func(in interface{}, path []string) (interface{}, error) { encrypted := true - for _, p := range path { - if strings.HasSuffix(p, tree.Metadata.UnencryptedSuffix) { - encrypted = false + if tree.Metadata.UnencryptedSuffix != "" { + for _, p := range path { + if strings.HasSuffix(p, tree.Metadata.UnencryptedSuffix) { + encrypted = false + break + } } } + if tree.Metadata.EncryptedSuffix != "" { + encryptedSuffixFound := false + for _, p := range path { + if strings.HasSuffix(p, tree.Metadata.EncryptedSuffix) { + encryptedSuffixFound = true + if !encrypted { + return nil, fmt.Errorf("Cannot use both encrypted_suffix and unencrypted_suffix in the same file") + } + break + } + } + encrypted = encryptedSuffixFound + } var v interface{} if encrypted { var err error @@ -390,6 +424,7 @@ func (tree *Tree) GenerateDataKeyWithKeyServices(svcs []keyservice.KeyServiceCli type Metadata struct { LastModified time.Time UnencryptedSuffix string + EncryptedSuffix string MessageAuthenticationCode string Version string KeyGroups []KeyGroup diff --git a/sops_test.go b/sops_test.go index 22f4ae819..84a767a8b 100644 --- a/sops_test.go +++ b/sops_test.go @@ -83,6 +83,56 @@ func TestUnencryptedSuffix(t *testing.T) { } } +func TestEncryptedSuffix(t *testing.T) { + branch := TreeBranch{ + TreeItem{ + Key: "foo_encrypted", + Value: "bar", + }, + TreeItem{ + Key: "bar", + Value: TreeBranch{ + TreeItem{ + Key: "foo", + Value: "bar", + }, + }, + }, + } + tree := Tree{Branch: branch, Metadata: Metadata{EncryptedSuffix: "_encrypted"}} + expected := TreeBranch{ + TreeItem{ + Key: "foo_encrypted", + Value: "rab", + }, + TreeItem{ + Key: "bar", + Value: TreeBranch{ + TreeItem{ + Key: "foo", + Value: "bar", + }, + }, + }, + } + cipher := reverseCipher{} + _, 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) + if err != nil { + t.Errorf("Decrypting the tree failed: %s", err) + } + expected[0].Value = "bar" + if !reflect.DeepEqual(tree.Branch, expected) { + t.Errorf("Trees don't match: \ngot\t\t\t%+v,\nexpected\t\t%+v", tree.Branch, expected) + } +} + type MockCipher struct{} func (m MockCipher) Encrypt(value interface{}, key []byte, path string) (string, error) { @@ -248,7 +298,6 @@ func TestTruncateTree(t *testing.T) { assert.Equal(t, expected, result) } - func TestEncryptComments(t *testing.T) { tree := Tree{ Branch: TreeBranch{ @@ -327,7 +376,7 @@ func TestSetNewKey(t *testing.T) { Key: "bar", Value: TreeBranch{ TreeItem{ - Key: "baz", + Key: "baz", Value: "foobar", }, }, @@ -356,7 +405,7 @@ func TestSetArrayDeepNew(t *testing.T) { func TestSetNewKeyDeep(t *testing.T) { branch := TreeBranch{ TreeItem{ - Key: "foo", + Key: "foo", Value: "bar", }, } @@ -364,7 +413,6 @@ func TestSetNewKeyDeep(t *testing.T) { assert.Equal(t, "hello", set[0].Value.(TreeBranch)[0].Value.(TreeBranch)[0].Value) } - func TestSetNewKeyOnEmptyBranch(t *testing.T) { branch := TreeBranch{} set := branch.Set([]interface{}{"foo", "bar", "baz"}, "hello") @@ -386,7 +434,6 @@ func TestSetArray(t *testing.T) { assert.Equal(t, "uno", set[0].Value.([]interface{})[0]) } - func TestSetArrayNew(t *testing.T) { branch := TreeBranch{} set := branch.Set([]interface{}{"foo", 0, 0}, "uno") @@ -396,7 +443,7 @@ func TestSetArrayNew(t *testing.T) { func TestSetExisting(t *testing.T) { branch := TreeBranch{ TreeItem{ - Key: "foo", + Key: "foo", Value: "foobar", }, } @@ -407,7 +454,7 @@ func TestSetExisting(t *testing.T) { func TestSetArrayLeafNewItem(t *testing.T) { branch := TreeBranch{ TreeItem{ - Key: "array", + Key: "array", Value: []interface{}{}, }, } @@ -438,7 +485,7 @@ func TestSetArrayNonLeaf(t *testing.T) { Value: []interface{}{ TreeBranch{ TreeItem{ - Key: "hello", + Key: "hello", Value: "hello", }, }, diff --git a/stores/stores.go b/stores/stores.go index 9ad3ef725..9acf1f15e 100644 --- a/stores/stores.go +++ b/stores/stores.go @@ -42,6 +42,7 @@ type Metadata struct { MessageAuthenticationCode string `yaml:"mac" json:"mac"` PGPKeys []pgpkey `yaml:"pgp" json:"pgp"` UnencryptedSuffix string `yaml:"unencrypted_suffix" json:"unencrypted_suffix"` + EncryptedSuffix string `yaml:"encrypted_suffix,omitempty" json:"encrypted_suffix,omitempty"` Version string `yaml:"version" json:"version"` } @@ -76,6 +77,7 @@ func MetadataFromInternal(sopsMetadata sops.Metadata) Metadata { var m Metadata m.LastModified = sopsMetadata.LastModified.Format(time.RFC3339) m.UnencryptedSuffix = sopsMetadata.UnencryptedSuffix + m.EncryptedSuffix = sopsMetadata.EncryptedSuffix m.MessageAuthenticationCode = sopsMetadata.MessageAuthenticationCode m.Version = sopsMetadata.Version m.ShamirThreshold = sopsMetadata.ShamirThreshold @@ -159,6 +161,7 @@ func (m *Metadata) ToInternal() (sops.Metadata, error) { Version: m.Version, MessageAuthenticationCode: m.MessageAuthenticationCode, UnencryptedSuffix: m.UnencryptedSuffix, + EncryptedSuffix: m.EncryptedSuffix, LastModified: lastModified, }, nil }