Skip to content

Commit 40940d0

Browse files
committed
Encrypt and decrypt time.Time objects.
Signed-off-by: Felix Fontein <felix@fontein.de>
1 parent 8122a30 commit 40940d0

File tree

4 files changed

+151
-1
lines changed

4 files changed

+151
-1
lines changed

aes/cipher.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"regexp"
1313
"strconv"
14+
"time"
1415

1516
"github.com/getsops/sops/v3"
1617
"github.com/getsops/sops/v3/logging"
@@ -110,6 +111,10 @@ func (c Cipher) Decrypt(ciphertext string, key []byte, additionalData string) (p
110111
plaintext = decryptedBytes
111112
case "bool":
112113
plaintext, err = strconv.ParseBool(decryptedValue)
114+
case "time":
115+
var value time.Time
116+
err = value.UnmarshalText(decryptedBytes)
117+
plaintext = value
113118
case "comment":
114119
plaintext = sops.Comment{Value: decryptedValue}
115120
default:
@@ -176,6 +181,12 @@ func (c Cipher) Encrypt(plaintext interface{}, key []byte, additionalData string
176181
} else {
177182
plainBytes = []byte("False")
178183
}
184+
case time.Time:
185+
encryptedType = "time"
186+
plainBytes, err = value.MarshalText()
187+
if err != nil {
188+
return "", fmt.Errorf("Error marshaling timestamp %q: %w", value, err)
189+
}
179190
case sops.Comment:
180191
encryptedType = "comment"
181192
plainBytes = []byte(value.Value)

aes/cipher_test.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package aes
22

33
import (
4+
"bytes"
45
"crypto/rand"
6+
"reflect"
57
"strings"
68
"testing"
79
"testing/quick"
10+
"time"
811

9-
"github.com/stretchr/testify/assert"
1012
"github.com/getsops/sops/v3"
13+
"github.com/stretchr/testify/assert"
1114
)
1215

1316
func TestDecrypt(t *testing.T) {
@@ -108,6 +111,36 @@ func TestRoundtripBool(t *testing.T) {
108111
}
109112
}
110113

114+
func TestRoundtripTime(t *testing.T) {
115+
key := []byte(strings.Repeat("f", 32))
116+
parsedTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
117+
assert.Nil(t, err)
118+
loc := time.FixedZone("", 12300) // offset must be divisible by 60, otherwise won't survive a round-trip
119+
values := []time.Time{
120+
time.UnixMilli(0).In(time.UTC),
121+
time.UnixMilli(123456).In(time.UTC),
122+
time.UnixMilli(123456).In(loc),
123+
time.UnixMilli(123456789).In(time.UTC),
124+
time.UnixMilli(123456789).In(time.Local),
125+
time.UnixMilli(1234567890).In(time.UTC),
126+
time.UnixMilli(1234567890).In(loc),
127+
parsedTime,
128+
}
129+
for _, value := range values {
130+
s, err := NewCipher().Encrypt(value, key, "foo")
131+
assert.Nil(t, err)
132+
if err != nil {
133+
continue
134+
}
135+
d, err := NewCipher().Decrypt(s, key, "foo")
136+
assert.Nil(t, err)
137+
if err != nil {
138+
continue
139+
}
140+
assert.Equal(t, value, d)
141+
}
142+
}
143+
111144
func TestEncryptEmptyComment(t *testing.T) {
112145
key := []byte(strings.Repeat("f", 32))
113146
s, err := NewCipher().Encrypt(sops.Comment{}, key, "")
@@ -121,3 +154,61 @@ func TestDecryptEmptyValue(t *testing.T) {
121154
assert.Nil(t, err)
122155
assert.Equal(t, "", s)
123156
}
157+
158+
// This test would belong more in sops_test.go, but from there we cannot access
159+
// the aes package to get a cipher which can actually handle time.Time objects.
160+
func TestTimestamps(t *testing.T) {
161+
unixTime := time.UnixMilli(123456789).In(time.UTC)
162+
parsedTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
163+
assert.Nil(t, err)
164+
branches := sops.TreeBranches{
165+
sops.TreeBranch{
166+
sops.TreeItem{
167+
Key: "foo",
168+
Value: unixTime,
169+
},
170+
sops.TreeItem{
171+
Key: "bar",
172+
Value: sops.TreeBranch{
173+
sops.TreeItem{
174+
Key: "foo",
175+
Value: parsedTime,
176+
},
177+
},
178+
},
179+
},
180+
}
181+
tree := sops.Tree{Branches: branches, Metadata: sops.Metadata{UnencryptedSuffix: "_unencrypted"}}
182+
expected := sops.TreeBranch{
183+
sops.TreeItem{
184+
Key: "foo",
185+
Value: unixTime,
186+
},
187+
sops.TreeItem{
188+
Key: "bar",
189+
Value: sops.TreeBranch{
190+
sops.TreeItem{
191+
Key: "foo",
192+
Value: parsedTime,
193+
},
194+
},
195+
},
196+
}
197+
cipher := NewCipher()
198+
_, err = tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher)
199+
if err != nil {
200+
t.Errorf("Encrypting the tree failed: %s", err)
201+
}
202+
if reflect.DeepEqual(tree.Branches[0], expected) {
203+
t.Errorf("Trees do match: \ngot \t\t%+v,\n not expected \t\t%+v", tree.Branches[0], expected)
204+
}
205+
_, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher)
206+
if err != nil {
207+
t.Errorf("Decrypting the tree failed: %s", err)
208+
}
209+
assert.Equal(t, tree.Branches[0][0].Value, unixTime)
210+
assert.Equal(t, tree.Branches[0], expected)
211+
if !reflect.DeepEqual(tree.Branches[0], expected) {
212+
t.Errorf("Trees don't match: \ngot\t\t\t%+v,\nexpected\t\t%+v", tree.Branches[0], expected)
213+
}
214+
}

functional-tests/src/lib.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,50 @@ b: ba"#
550550
}
551551
}
552552

553+
#[test]
554+
fn test_yaml_time() {
555+
let file_path = prepare_temp_file(
556+
"test_time.yaml",
557+
r#"a: 2024-01-01
558+
b: 2006-01-02T15:04:05+07:06"#
559+
.as_bytes(),
560+
);
561+
assert!(
562+
Command::new(SOPS_BINARY_PATH)
563+
.arg("encrypt")
564+
.arg("-i")
565+
.arg(file_path.clone())
566+
.output()
567+
.expect("Error running sops")
568+
.status
569+
.success(),
570+
"sops didn't exit successfully"
571+
);
572+
let output = Command::new(SOPS_BINARY_PATH)
573+
.arg("decrypt")
574+
.arg("-i")
575+
.arg(file_path.clone())
576+
.output()
577+
.expect("Error running sops");
578+
println!(
579+
"stdout: {}, stderr: {}",
580+
String::from_utf8_lossy(&output.stdout),
581+
String::from_utf8_lossy(&output.stderr)
582+
);
583+
assert!(output.status.success(), "sops didn't exit successfully");
584+
let mut s = String::new();
585+
File::open(file_path)
586+
.unwrap()
587+
.read_to_string(&mut s)
588+
.unwrap();
589+
assert_eq!(
590+
s,
591+
r#"a: 2024-01-01T00:00:00Z
592+
b: 2006-01-02T15:04:05+07:06
593+
"#
594+
);
595+
}
596+
553597
#[test]
554598
fn unset_json_file() {
555599
// Test removal of tree branch

sops.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ func (branch TreeBranch) walkValue(in interface{}, path []string, commentsStack
297297
return onLeaves(in, path, commentsStack)
298298
case float64:
299299
return onLeaves(in, path, commentsStack)
300+
case time.Time:
301+
return onLeaves(in, path, commentsStack)
300302
case Comment:
301303
return onLeaves(in, path, commentsStack)
302304
case TreeBranch:
@@ -918,6 +920,8 @@ func ToBytes(in interface{}) ([]byte, error) {
918920
return boolB, nil
919921
case []byte:
920922
return in, nil
923+
case time.Time:
924+
return in.MarshalText()
921925
case Comment:
922926
return ToBytes(in.Value)
923927
default:

0 commit comments

Comments
 (0)