Skip to content

Commit d513bd1

Browse files
committed
engineccl: Add data-driven test for ctr_stream
The previous tests verified that encryption was reversible, but there was no verification of the expected results and so there was no guarantee that changes to the implementation remained compatible with data previously written to disk. The new test captures the current results as the baseline. Release note: None Epic: None
1 parent 989c37c commit d513bd1

File tree

2 files changed

+282
-0
lines changed

2 files changed

+282
-0
lines changed

pkg/ccl/storageccl/engineccl/ctr_stream_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,21 @@
99
package engineccl
1010

1111
import (
12+
"bytes"
1213
"context"
1314
"crypto/rand"
1415
"encoding/binary"
16+
"encoding/hex"
1517
"fmt"
1618
"strings"
1719
"testing"
1820

1921
"github.com/cockroachdb/cockroach/pkg/ccl/securityccl/fipsccl"
2022
"github.com/cockroachdb/cockroach/pkg/ccl/storageccl/engineccl/enginepbccl"
2123
"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
24+
"github.com/cockroachdb/cockroach/pkg/testutils/datapathutils"
2225
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
26+
"github.com/cockroachdb/datadriven"
2327
"github.com/kr/pretty"
2428
"github.com/stretchr/testify/require"
2529
)
@@ -46,6 +50,143 @@ func generateKey(encType enginepbccl.EncryptionType) (*enginepbccl.SecretKey, er
4650
return key, err
4751
}
4852

53+
func readHex(s string) ([]byte, error) {
54+
s = strings.ReplaceAll(s, " ", "")
55+
s = strings.ReplaceAll(s, "\n", "")
56+
return hex.DecodeString(s)
57+
}
58+
59+
func writeHex(b []byte) string {
60+
var buf strings.Builder
61+
for i, c := range b {
62+
fmt.Fprintf(&buf, "%02x", c)
63+
if i%16 == 15 {
64+
buf.WriteString("\n")
65+
} else {
66+
buf.WriteString(" ")
67+
}
68+
}
69+
return buf.String()
70+
}
71+
72+
func encryptManySubBlocks(
73+
t *testing.T, fcs *fileCipherStream, baseOffset int64, plaintext, ciphertext []byte,
74+
) {
75+
// Split the text into many different left/right pairs, encrypt each one
76+
// separately, and make sure it matches the corresponding ciphertext.
77+
// This covers various cases such as full and partial blocks, aligned and
78+
// unaligned, etc.
79+
// Since we're only dealing with fairly small data sizes, we can iterate
80+
// through every possible split point and just try them all.
81+
for i := range plaintext {
82+
leftData := append([]byte{}, plaintext[0:i]...)
83+
fcs.Encrypt(baseOffset, leftData)
84+
if !bytes.Equal(leftData, ciphertext[0:i]) {
85+
t.Errorf("encrypting bytes 0:%d did not match full ciphertext", i)
86+
}
87+
rightData := append([]byte{}, plaintext[i:]...)
88+
fcs.Encrypt(baseOffset+int64(i), rightData)
89+
if !bytes.Equal(rightData, ciphertext[i:]) {
90+
t.Errorf("encrypting bytes %d:end did not match full ciphertext", i)
91+
}
92+
}
93+
}
94+
95+
// Running non-fips mode:
96+
// ./dev test pkg/ccl/storageccl/engineccl -f CTRStreamDataDriven --rewrite --stream-output
97+
// Running fips mode:
98+
// ./dev test-binaries --cross=crosslinuxfips pkg/ccl/storageccl/engineccl && mkdir -p fipsbin && tar xf bin/test_binaries.tar.gz -C fipsbin && docker run -v $PWD/fipsbin:/fipsbin -it redhat/ubi9 bash -c 'cd /fipsbin/pkg/ccl/storageccl/engineccl/bin && ./run.sh -test.run CTRStreamDataDriven'
99+
func TestCTRStreamDataDriven(t *testing.T) {
100+
defer leaktest.AfterTest(t)()
101+
var data []byte
102+
keys := map[string]*enginepbccl.SecretKey{}
103+
ivs := map[string][]byte{}
104+
seenCiphertexts := map[string]struct{}{}
105+
datadriven.RunTest(t, datapathutils.TestDataPath(t, "ctr_stream"),
106+
func(t *testing.T, d *datadriven.TestData) string {
107+
fmt.Println(d.Pos)
108+
109+
switch d.Cmd {
110+
case "set-data":
111+
var err error
112+
data, err = readHex(d.Input)
113+
require.NoError(t, err)
114+
return "ok"
115+
116+
case "create-key":
117+
var name string
118+
d.ScanArgs(t, "name", &name)
119+
decoded, err := readHex(d.Input)
120+
require.NoError(t, err)
121+
key := &enginepbccl.SecretKey{
122+
Info: &enginepbccl.KeyInfo{},
123+
Key: decoded,
124+
}
125+
switch len(decoded) {
126+
case 16:
127+
key.Info.EncryptionType = enginepbccl.EncryptionType_AES128_CTR
128+
case 24:
129+
key.Info.EncryptionType = enginepbccl.EncryptionType_AES192_CTR
130+
case 32:
131+
key.Info.EncryptionType = enginepbccl.EncryptionType_AES256_CTR
132+
default:
133+
return fmt.Sprintf("invalid key size %d", len(decoded))
134+
}
135+
keys[name] = key
136+
return "ok"
137+
138+
case "create-iv":
139+
var name string
140+
d.ScanArgs(t, "name", &name)
141+
decoded, err := readHex(d.Input)
142+
require.NoError(t, err)
143+
if len(decoded) != 16 {
144+
return "iv must be 16 bytes"
145+
}
146+
ivs[name] = decoded
147+
return "ok"
148+
149+
case "encrypt":
150+
var offset int64
151+
d.ScanArgs(t, "offset", &offset)
152+
keyName := "default"
153+
d.MaybeScanArgs(t, "key", &keyName)
154+
ivName := "default"
155+
d.MaybeScanArgs(t, "iv", &ivName)
156+
expectDuplicate := false
157+
d.MaybeScanArgs(t, "expect_duplicate", &expectDuplicate)
158+
iv := ivs[ivName]
159+
bcs, err := newCTRBlockCipherStream(keys[keyName], iv[:12], binary.BigEndian.Uint32(iv[12:16]))
160+
require.NoError(t, err)
161+
fcs := &fileCipherStream{bcs: bcs}
162+
// Encrypt() mutates its argument so make a copy of data.
163+
output := append([]byte{}, data...)
164+
fcs.Encrypt(offset, output)
165+
reencrypted := append([]byte{}, output...)
166+
fcs.Decrypt(offset, reencrypted)
167+
if !bytes.Equal(data, reencrypted) {
168+
t.Fatalf("decrypted data didn't match input")
169+
}
170+
171+
outputString := string(output)
172+
_, isDuplicate := seenCiphertexts[outputString]
173+
if isDuplicate && !expectDuplicate {
174+
// Assume that each test is using different parameters; if we see the same
175+
// ciphertext twice something's gone wrong.
176+
t.Fatalf("same ciphertext produced more than once")
177+
} else if expectDuplicate && !isDuplicate {
178+
t.Fatalf("expected duplicate of prior ciphertext")
179+
}
180+
seenCiphertexts[outputString] = struct{}{}
181+
encryptManySubBlocks(t, fcs, offset, data, output)
182+
return writeHex(output)
183+
184+
default:
185+
return fmt.Sprintf("unknown command: %s\n", d.Cmd)
186+
}
187+
})
188+
}
189+
49190
func TestFileCipherStream(t *testing.T) {
50191
defer leaktest.AfterTest(t)()
51192

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# This file contains tests that ctr_stream produces the expected outputs
2+
# for known inputs. This ensures that changes to the implementation do
3+
# not render it incompatible with previously-written data.
4+
#
5+
# This test is inherently going to be full of illegible gibberish, so in
6+
# order to minimize maintenance burden we use the datadriven package with
7+
# its "rewrite" option. To produce random inputs, you can use something like
8+
# head -c 16 /dev/random | od -An -tx1 -v
9+
# (note that macOS `od` uses more whitespacae than linux `od`, so this file
10+
# uses the linux `od` format).
11+
12+
# 128 bytes of random data. In this file we'll verify the entire contents
13+
# of the block. In the test code, we'll try slicing it up in different ways
14+
# so that we test long blocks, short blocks, non-aligned blocks, etc.
15+
set-data
16+
4a 55 17 f3 ff 73 bc d8 7e 51 66 a0 1c 95 cb 20
17+
c2 54 53 b1 40 11 ec aa 12 b1 09 fa a2 69 22 bc
18+
56 9f f6 b0 1b 40 36 b0 d9 e2 de 3e b3 86 6b 60
19+
51 80 b2 c9 ae 05 bd c3 10 0a e0 38 bd 32 4c 5b
20+
e5 b5 4b e9 bc d5 94 9d b4 4f f7 dc 6d 88 9d 00
21+
6a 83 16 42 ec 46 3b 2f bf 34 01 75 f7 ce 82 1c
22+
b5 df e3 4c ce d0 74 ed a4 fd 96 5a be 69 05 51
23+
c6 bf da 38 1f 8e 80 01 bf 81 8c 31 1f 53 12 b8
24+
----
25+
ok
26+
27+
create-key name=default
28+
d6 35 90 0e ce 70 8e d2 d2 47 cd 16 fc fb 26 87
29+
----
30+
ok
31+
32+
create-key name=other
33+
42 d8 8a 0f ed 55 c0 64 ef 48 99 27 e1 a2 8c 2d
34+
----
35+
ok
36+
37+
create-iv name=default
38+
72 ea 17 b6 7a dd 63 bb 01 94 ba 8e bd 84 13 c4
39+
----
40+
ok
41+
42+
create-iv name=other
43+
d7 53 99 7c 89 a2 bf 64 5a 38 65 d8 9a 71 07 2a
44+
----
45+
ok
46+
47+
encrypt offset=0
48+
----
49+
70 54 03 81 d3 f0 e5 f2 ab 28 03 55 f0 53 a3 63
50+
a8 03 e1 f5 06 48 53 3a 86 81 f8 a6 da 35 80 a3
51+
4a fb 80 48 39 cf 77 32 17 1e ec 32 cc 22 e9 4a
52+
3f b7 f9 e4 93 52 89 21 0c bc bc 66 77 3a 23 18
53+
df 79 ab 4a 7a 29 c7 3d 45 41 dd fc e0 d9 10 e0
54+
76 22 e8 78 2c 6a cb 3d 62 af 84 3e 3c 9f ee f7
55+
84 7b 38 01 db fb 98 e5 1d 9b 61 e1 ed ef be 94
56+
93 3a 79 84 c3 dc 9d 26 be 2f ed 3c 83 68 d4 30
57+
58+
# Change the key and the output changes
59+
encrypt offset=0 key=other
60+
----
61+
27 8e 1a 59 1a e2 8c 31 8e 80 8d 35 40 1e 1c b5
62+
01 7f 76 58 67 4b aa 0a 2e e5 23 41 ea f1 31 62
63+
99 70 ac dc 65 a1 4f d6 84 9e dc c3 1c 63 43 8a
64+
d0 04 de 89 3c 2d 8d 12 2f 97 e6 81 0c 16 a0 c1
65+
2e cd 2f f2 f6 1d 46 76 83 78 63 3f 44 3a d5 46
66+
f2 d4 b7 65 be f6 02 88 9f f7 6e 6f 4b 74 dc 4f
67+
ad 29 80 70 5e b1 3e 1e 6e e8 df a0 b8 cf 90 f4
68+
83 29 7d dc 21 a2 31 da d2 47 22 a5 3e 26 12 4c
69+
70+
# The IV also changes the output
71+
encrypt offset=0 iv=other
72+
----
73+
61 8b 1f 76 07 05 78 e9 67 27 c8 fb 3d 75 3b c5
74+
f1 f0 6f a7 6d 81 f9 b3 e4 40 98 62 d4 51 f7 b3
75+
de d6 1b 80 b1 ac 26 81 37 5c 49 b5 bc 30 fa 95
76+
3e ec 12 3d 87 39 6e 0e 3a 6e f7 0e c9 ac 44 bb
77+
24 ce 71 1f 5e aa 98 78 6e 02 a1 d0 cd 65 46 75
78+
e7 f7 3f 8b d8 22 34 d9 fb ef b0 9f d8 e2 7a ad
79+
1f 13 0d 94 8d b7 d1 87 14 96 77 7c 82 c4 6f 6e
80+
0d 0e 42 46 09 29 a2 21 82 42 95 35 1c f7 2a 6e
81+
82+
# And so does the offset. Note that the way this parameter works is to
83+
# logically shift the static test input data into the given offset.
84+
encrypt offset=1
85+
----
86+
4b 41 65 df 7c 2a 96 0d 07 34 93 4c da fd 88 4a
87+
95 e6 17 f7 19 ae 7c 3e 22 40 55 82 fe cb 3d a0
88+
32 e9 0e 92 94 01 b4 7e 25 d0 d2 41 17 04 41 0e
89+
66 cb 9f f4 f9 31 5f df a6 56 be f2 b5 5d 0f 61
90+
29 55 e8 2f 40 86 34 6c ba 65 d7 51 3c 05 7d 1c
91+
cb 7d 2c 82 c0 b6 29 f2 24 b1 4a be a6 a2 69 2d
92+
11 04 ae 59 e5 3c 7c 54 c2 0a 2d 09 38 d2 c0 04
93+
43 1c 66 e4 4d 93 a7 00 11 e0 81 ad 24 95 9a 36
94+
95+
# Test some interesting offsets, namely powers of 256 times the block
96+
# size of 16 (which result in powers of 256 in the internal computation)
97+
encrypt offset=4096
98+
----
99+
af 94 f3 42 3d fa 41 44 ff 9d 06 bf 56 cd 71 65
100+
a2 ec 9c 24 50 06 a7 22 66 51 c7 05 9f 63 71 d0
101+
2c 8e d0 13 4d 2e b2 85 85 59 ed 07 a8 b7 40 43
102+
26 3e 66 72 92 d9 6f 50 f1 13 a0 d5 f0 38 09 8a
103+
ef 57 fd 88 64 99 3b b1 4b 83 bd 01 1d 72 e0 b9
104+
3b 16 6c 99 77 d1 85 58 b3 29 ef 33 b3 d0 7a 87
105+
6a 77 62 ca 02 5b ab a4 4f 69 f6 54 3f d3 f6 21
106+
e1 f6 71 70 b4 6d f7 9c d5 7b 23 96 e1 ce f2 1a
107+
108+
encrypt offset=1048576
109+
----
110+
c9 77 fa e6 0e 74 c9 2b 6c 01 25 50 eb 96 c1 4b
111+
51 a6 d5 90 1a 77 3d c0 9c 33 48 21 b5 78 fb 94
112+
a5 f6 16 05 41 4c 4b 11 3e b8 fc bf 20 6c a6 b8
113+
34 8e 12 46 80 d9 24 7c 35 90 5c d4 34 0c 7c eb
114+
77 3d 8b 1d 30 36 7a d3 f9 7e 04 f6 2f ab 6f 4c
115+
6c 8d 80 bc a7 c3 10 5d ac f1 bb e5 9b 1e 1a cd
116+
bd 28 2a 5b 44 63 55 85 f2 ed df cf 29 cc d2 81
117+
c6 ee 58 d4 7d 1c 53 34 e5 0f 4b 96 1f ac 29 8d
118+
119+
encrypt offset=268435456
120+
----
121+
ba f1 04 5a 3c 96 0e a4 13 aa 61 a2 81 9e aa 0b
122+
95 6b 11 dd 6c 70 4f 85 94 41 3d 68 47 9b 09 de
123+
20 b3 bf ce 45 67 05 70 d8 85 2d e4 b9 dc ba 99
124+
27 e5 c7 30 4f 4e 39 c8 3f cc 95 9b 39 b3 9b ea
125+
13 34 9b ec 08 9a 86 59 10 c9 90 42 0c 17 fc a1
126+
41 c6 5c f5 b7 c6 d4 81 a6 33 b9 25 4d 97 21 02
127+
70 0b 5a 9f fe fd 97 da 11 bf ea 5d 53 5e 54 5e
128+
dd 3b 2d 67 71 8b 10 ac 14 a9 91 29 13 4b 91 1f
129+
130+
# When the 32-bit counter wraps around we see the same data again,
131+
# but this code is not used for files that can get that large.
132+
encrypt offset=68719476736 expect_duplicate=true
133+
----
134+
70 54 03 81 d3 f0 e5 f2 ab 28 03 55 f0 53 a3 63
135+
a8 03 e1 f5 06 48 53 3a 86 81 f8 a6 da 35 80 a3
136+
4a fb 80 48 39 cf 77 32 17 1e ec 32 cc 22 e9 4a
137+
3f b7 f9 e4 93 52 89 21 0c bc bc 66 77 3a 23 18
138+
df 79 ab 4a 7a 29 c7 3d 45 41 dd fc e0 d9 10 e0
139+
76 22 e8 78 2c 6a cb 3d 62 af 84 3e 3c 9f ee f7
140+
84 7b 38 01 db fb 98 e5 1d 9b 61 e1 ed ef be 94
141+
93 3a 79 84 c3 dc 9d 26 be 2f ed 3c 83 68 d4 30

0 commit comments

Comments
 (0)