Fix KMS encryption context for backwards compatibility with SOPS 1.x

In SOPS 1.x, KMS encryption context was stored as a JSON object, but
SOPS 2.0 stored it as a comma-separated list of key/value pairs:

```
$ jq '.sops.kms | .[].context' encrypted-python
{
  "a": "b",
  "c": "d"
}
> jq '.sops.kms | .[].context' encrypted-go
"a:b,c:d"
```

The two outputs are incompatible with each other and caused a stack
trace when reading files encrypted with SOPS 1.x.

This patch restores read and output compatibility with SOPS 1.x.

Fixes #190.
This commit is contained in:
Andy Freeland 2017-03-22 01:01:41 -07:00
Родитель 176f2baba5
Коммит 89e75471cc
4 изменённых файлов: 65 добавлений и 25 удалений

Просмотреть файл

@ -5,7 +5,6 @@ import (
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
@ -162,8 +161,8 @@ func (key MasterKey) createSession() (*session.Session, error) {
}
// ToMap converts the MasterKey to a map for serialization purposes
func (key MasterKey) ToMap() map[string]string {
out := make(map[string]string)
func (key MasterKey) ToMap() map[string]interface{} {
out := make(map[string]interface{})
out["arn"] = key.Arn
if key.Role != "" {
out["role"] = key.Role
@ -171,28 +170,47 @@ func (key MasterKey) ToMap() map[string]string {
out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339)
out["enc"] = key.EncryptedKey
if key.EncryptionContext != nil {
var outContexts []string
outcontext := make(map[string]string)
for k, v := range key.EncryptionContext {
outContexts = append(outContexts, k+":"+*v)
outcontext[k] = *v
}
sort.Strings(outContexts)
out["context"] = strings.Join(outContexts, ",")
out["context"] = outcontext
}
return out
}
// ParseKMSContext takes a comma-separated list of KMS context key:value pairs and returns a map
func ParseKMSContext(in string) map[string]*string {
if in == "" {
return nil
}
// ParseKMSContext takes either a KMS context map or a comma-separated list of KMS context key:value pairs and returns a map
func ParseKMSContext(in interface{}) map[string]*string {
out := make(map[string]*string)
for _, kv := range strings.Split(in, ",") {
kv := strings.Split(kv, ":")
if len(kv) != 2 {
switch in := in.(type) {
case map[string]interface{}:
if len(in) == 0 {
return nil
}
out[kv[0]] = &kv[1]
for k, v := range in {
var v = v.(string)
out[k] = &v
}
case map[interface{}]interface{}:
if len(in) == 0 {
return nil
}
for k, v := range in {
var v = v.(string)
out[k.(string)] = &v
}
case string:
if in == "" {
return nil
}
for _, kv := range strings.Split(in, ",") {
kv := strings.Split(kv, ":")
if len(kv) != 2 {
return nil
}
out[kv[0]] = &kv[1]
}
}
return out
}

Просмотреть файл

@ -73,6 +73,27 @@ func TestKMSKeySourceFromString(t *testing.T) {
func TestParseEncryptionContext(t *testing.T) {
value1 := "value1"
value2 := "value2"
// map from YAML
var yamlmap = map[interface{}]interface{}{
"key1": value1,
"key2": value2,
}
assert.Equal(t, ParseKMSContext(yamlmap), map[string]*string{
"key1": &value1,
"key2": &value2,
})
assert.Nil(t, ParseKMSContext(map[interface{}]interface{}{}))
// map from JSON
var jsonmap = map[string]interface{}{
"key1": value1,
"key2": value2,
}
assert.Equal(t, ParseKMSContext(jsonmap), map[string]*string{
"key1": &value1,
"key2": &value2,
})
assert.Nil(t, ParseKMSContext(map[string]interface{}{}))
// sops 2.0.x formatted encryption context as a comma-separated list of key:value pairs
assert.Equal(t, ParseKMSContext("key1:value1,key2:value2"), map[string]*string{
"key1": &value1,
"key2": &value2,
@ -87,7 +108,6 @@ func TestParseEncryptionContext(t *testing.T) {
func TestKeyToMap(t *testing.T) {
value1 := "value1"
value2 := "value2"
value3 := "value3"
key := MasterKey{
CreationDate: time.Date(2016, time.October, 31, 10, 0, 0, 0, time.UTC),
Arn: "foo",
@ -96,14 +116,16 @@ func TestKeyToMap(t *testing.T) {
EncryptionContext: map[string]*string{
"key1": &value1,
"key2": &value2,
"AAA_this_key_should_be_first": &value3,
},
}
assert.Equal(t, map[string]string{
assert.Equal(t, map[string]interface{}{
"arn": "foo",
"role": "bar",
"enc": "this is encrypted",
"created_at": "2016-10-31T10:00:00Z",
"context": "AAA_this_key_should_be_first:value3,key1:value1,key2:value2",
"context": map[string]string{
"key1": value1,
"key2": value2,
},
}, key.ToMap())
}

Просмотреть файл

@ -201,8 +201,8 @@ func (key *MasterKey) passphrasePrompt(keys []openpgp.Key, symmetric bool) ([]by
}
// ToMap converts the MasterKey into a map for serialization purposes
func (key MasterKey) ToMap() map[string]string {
out := make(map[string]string)
func (key MasterKey) ToMap() map[string]interface{} {
out := make(map[string]interface{})
out["fp"] = key.Fingerprint
out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339)
out["enc"] = key.EncryptedKey

Просмотреть файл

@ -303,7 +303,7 @@ type MasterKey interface {
Decrypt() ([]byte, error)
NeedsRotation() bool
ToString() string
ToMap() map[string]string
ToMap() map[string]interface{}
}
// Store provides a way to load and save the sops tree along with metadata
@ -426,7 +426,7 @@ func (m *Metadata) ToMap() map[string]interface{} {
out["mac"] = m.MessageAuthenticationCode
out["version"] = m.Version
for _, ks := range m.KeySources {
var keys []map[string]string
var keys []map[string]interface{}
for _, k := range ks.Keys {
keys = append(keys, k.ToMap())
}
@ -550,7 +550,7 @@ func mapKMSEntriesToKeySource(in []interface{}) (KeySource, error) {
return keysource, fmt.Errorf("Could not parse creation date: %s", err)
}
if _, ok := entry["context"]; ok {
key.EncryptionContext = kms.ParseKMSContext(entry["context"].(string))
key.EncryptionContext = kms.ParseKMSContext(entry["context"])
}
key.CreationDate = creationDate
keysource.Keys = append(keysource.Keys, key)