Skip to content

Commit 62395e1

Browse files
author
sebastian
committed
chore: add fuzzy test for encrypting/decrypting random trees
1 parent 1405c51 commit 62395e1

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

pkg/sss/policy_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package sss
2+
3+
import (
4+
cryptoRand "crypto/rand"
5+
"math/rand"
6+
"reflect"
7+
8+
"filippo.io/age"
9+
10+
"testing"
11+
)
12+
13+
type TestPolicy struct {
14+
Threshold int
15+
Shares []*TestPolicy
16+
Identity *age.X25519Identity
17+
}
18+
19+
func testPolicyToRealPolicy(testPolicy *TestPolicy) *SSS {
20+
sss := &SSS{}
21+
22+
if testPolicy.Identity != nil {
23+
sss.Recipient = testPolicy.Identity.Recipient().String()
24+
return sss
25+
}
26+
27+
sss.Threshold = testPolicy.Threshold
28+
for _, t := range testPolicy.Shares {
29+
sss.Shares = append(sss.Shares, testPolicyToRealPolicy(t))
30+
}
31+
32+
return sss
33+
}
34+
35+
const (
36+
SELECT_ALL = iota
37+
SELECT_MIN
38+
SELECT_SUFFICIENT
39+
SELECT_INSUFFICIENT
40+
)
41+
42+
func selectIdentities(node *TestPolicy, rng *rand.Rand, mode int) []*age.X25519Identity {
43+
if node.Threshold == 0 {
44+
return []*age.X25519Identity{node.Identity}
45+
}
46+
47+
result := []*age.X25519Identity{}
48+
var maxCount int
49+
switch mode {
50+
case SELECT_ALL:
51+
maxCount = len(node.Shares)
52+
case SELECT_MIN:
53+
maxCount = node.Threshold
54+
case SELECT_SUFFICIENT:
55+
maxCount = max(rng.Intn(len(node.Shares)+1), node.Threshold)
56+
case SELECT_INSUFFICIENT:
57+
maxCount = rng.Intn(node.Threshold)
58+
default:
59+
panic("unknown mode")
60+
}
61+
62+
selectedCount := 0
63+
64+
for _, i := range rand.Perm(len(node.Shares))[0:maxCount] {
65+
result = append(result, selectIdentities(node.Shares[i], rng, mode)...)
66+
selectedCount += 1
67+
}
68+
69+
return result
70+
}
71+
72+
func testPolicyToIdentity(rng *rand.Rand, testPolicy *TestPolicy, mode int) *SSSIdentity {
73+
identities := []*age.X25519Identity{}
74+
75+
var pickIdentities func(node *TestPolicy)
76+
pickIdentities = func(node *TestPolicy) {
77+
if node.Threshold == 0 {
78+
identities = append(identities, node.Identity)
79+
return
80+
}
81+
82+
identities = append(identities, selectIdentities(node, rng, mode)...)
83+
}
84+
85+
pickIdentities(testPolicy)
86+
87+
sssIdentity := &SSSIdentity{
88+
Identities: []*SSSIdentityItem{},
89+
}
90+
91+
for _, id := range identities {
92+
sssIdentity.Identities = append(sssIdentity.Identities, &SSSIdentityItem{
93+
IdentityStr: id.String(),
94+
Identity: id,
95+
})
96+
}
97+
98+
return sssIdentity
99+
}
100+
101+
const MAX_DEPTH = 5
102+
103+
func generateRandomTestPolicy(rng *rand.Rand, maxBranching, currentDepth int) *TestPolicy {
104+
node := &TestPolicy{}
105+
106+
var numChildren int
107+
if currentDepth == 0 {
108+
// root must have at least one child
109+
numChildren = max(1, rng.Intn(maxBranching+1))
110+
} else if currentDepth >= MAX_DEPTH {
111+
numChildren = 0
112+
} else {
113+
numChildren = rng.Intn(maxBranching + 1)
114+
}
115+
116+
for i := 0; i < numChildren; i++ {
117+
child := generateRandomTestPolicy(rng, maxBranching, currentDepth+1)
118+
if child != nil {
119+
node.Shares = append(node.Shares, child)
120+
}
121+
}
122+
123+
if len(node.Shares) == 0 {
124+
node.Identity, _ = age.GenerateX25519Identity()
125+
return node
126+
}
127+
128+
node.Threshold = rng.Intn(len(node.Shares)) + 1
129+
return node
130+
}
131+
132+
func FuzzRandomPolicies(f *testing.F) {
133+
f.Fuzz(func(t *testing.T, seed int64) {
134+
rng := rand.New(rand.NewSource(seed))
135+
testPolicy := generateRandomTestPolicy(rng, 5, 0)
136+
policy := testPolicyToRealPolicy(testPolicy)
137+
138+
validIdentities := []*SSSIdentity{
139+
testPolicyToIdentity(rng, testPolicy, SELECT_ALL),
140+
testPolicyToIdentity(rng, testPolicy, SELECT_MIN),
141+
testPolicyToIdentity(rng, testPolicy, SELECT_SUFFICIENT),
142+
}
143+
invalidIdentity := testPolicyToIdentity(rng, testPolicy, SELECT_INSUFFICIENT)
144+
145+
fileKey := make([]byte, 16)
146+
if _, err := cryptoRand.Read(fileKey); err != nil {
147+
t.Error(err)
148+
}
149+
stanzas, err := policy.Wrap(fileKey)
150+
if err != nil {
151+
t.Error(err)
152+
}
153+
154+
for _, id := range validIdentities {
155+
unwrapped, err := id.Unwrap(stanzas)
156+
if err != nil {
157+
t.Error(err)
158+
}
159+
if !reflect.DeepEqual(fileKey, unwrapped) {
160+
t.Logf("fileKey: %v, unwrapped: %v\n", fileKey, unwrapped)
161+
t.Error("File keys do not match")
162+
}
163+
}
164+
165+
unwrapped, err := invalidIdentity.Unwrap(stanzas)
166+
if err.Error() != "incorrect identity for recipient block" {
167+
t.Error(err)
168+
}
169+
if reflect.DeepEqual(fileKey, unwrapped) {
170+
t.Logf("fileKey: %v, unwrapped: %v\n", fileKey, unwrapped)
171+
t.Error("File keys should not match")
172+
}
173+
})
174+
}

0 commit comments

Comments
 (0)