Skip to content

Commit fbb6f19

Browse files
committed
cmd/age-keyserver: add witness co-signing to prevent split-views
1 parent e60c428 commit fbb6f19

8 files changed

Lines changed: 143 additions & 9 deletions

File tree

cmd/age-keylookup/main.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"crypto/sha256"
7+
_ "embed"
78
"encoding/base64"
89
"encoding/json"
910
"flag"
@@ -27,6 +28,9 @@ const (
2728
defaultKeyserverVRFKey = "vKHX1vKXl7yF0qBiDxCUXWgOHlapMvqFeIBXt7c29iQ="
2829
)
2930

31+
//go:embed witness_policy.txt
32+
var defaultWitnessPolicy []byte
33+
3034
func main() {
3135
allFlag := flag.Bool("all", false, "list all public keys in the transparency log")
3236
flag.Parse()
@@ -46,6 +50,7 @@ func main() {
4650
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_URL Default keyserver URL\n")
4751
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_PUBKEY Default keyserver transparency log vkey\n")
4852
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_VRFKEY Default keyserver transparency log VRF public key\n")
53+
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_POLICY Default keyserver transparency log witness policy\n")
4954
os.Exit(2)
5055
}
5156

@@ -67,6 +72,21 @@ func main() {
6772
os.Exit(1)
6873
}
6974

75+
witnessPolicy := defaultWitnessPolicy
76+
if policyPath := os.Getenv("AGE_KEYSERVER_POLICY"); policyPath != "" {
77+
witnessPolicy, err = os.ReadFile(policyPath)
78+
if err != nil {
79+
fmt.Fprintf(os.Stderr, "Error: failed to read witness policy file: %v\n", err)
80+
os.Exit(1)
81+
}
82+
}
83+
policy, err := torchwood.ParsePolicy(witnessPolicy)
84+
if err != nil {
85+
fmt.Fprintf(os.Stderr, "Error: invalid witness policy: %v\n", err)
86+
os.Exit(1)
87+
}
88+
policy = torchwood.ThresholdPolicy(2, torchwood.SingleVerifierPolicy(v), policy)
89+
7090
vrfKeyB64 := os.Getenv("AGE_KEYSERVER_VRFKEY")
7191
if vrfKeyB64 == "" {
7292
vrfKeyB64 = defaultKeyserverVRFKey
@@ -86,7 +106,7 @@ func main() {
86106
email = strings.TrimSpace(strings.ToLower(email))
87107

88108
if *allFlag {
89-
pubkeys, err := monitorLog(server, v, vrfKey, email)
109+
pubkeys, err := monitorLog(server, v.Name(), policy, vrfKey, email)
90110
if err != nil {
91111
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
92112
os.Exit(1)
@@ -97,7 +117,7 @@ func main() {
97117
return
98118
}
99119

100-
pubkey, err := lookupKey(server, v, vrfKey, email)
120+
pubkey, err := lookupKey(server, v.Name(), policy, vrfKey, email)
101121
if err != nil {
102122
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
103123
os.Exit(1)
@@ -106,7 +126,7 @@ func main() {
106126
fmt.Println(pubkey)
107127
}
108128

109-
func lookupKey(serverURL string, v note.Verifier, vrfKey *vrf.PublicKey, email string) (string, error) {
129+
func lookupKey(serverURL string, logOrigin string, policy torchwood.Policy, vrfKey *vrf.PublicKey, email string) (string, error) {
110130
// Build the lookup URL
111131
lookupURL := serverURL + "/api/lookup?email=" + url.QueryEscape(email)
112132

@@ -168,15 +188,14 @@ func lookupKey(serverURL string, v note.Verifier, vrfKey *vrf.PublicKey, email s
168188
h := sha256.New()
169189
h.Write([]byte(result.Pubkey))
170190
entry := h.Sum(vrfHash) // vrf-r255(email) || SHA-256(pubkey)
171-
if err := torchwood.VerifyProof(v.Name(), torchwood.SingleVerifierPolicy(v),
172-
tlog.RecordHash(entry), []byte(result.Proof)); err != nil {
191+
if err := torchwood.VerifyProof(logOrigin, policy, tlog.RecordHash(entry), []byte(result.Proof)); err != nil {
173192
return "", fmt.Errorf("failed to verify key proof: %w", err)
174193
}
175194

176195
return result.Pubkey, nil
177196
}
178197

179-
func monitorLog(serverURL string, v note.Verifier, vrfKey *vrf.PublicKey, email string) ([]string, error) {
198+
func monitorLog(serverURL string, logOrigin string, policy torchwood.Policy, vrfKey *vrf.PublicKey, email string) ([]string, error) {
180199
// Request the VRF proof and history from the monitor endpoint
181200
monitorURL := serverURL + "/api/monitor?email=" + url.QueryEscape(email)
182201
client := &http.Client{
@@ -237,7 +256,7 @@ func monitorLog(serverURL string, v note.Verifier, vrfKey *vrf.PublicKey, email
237256
if err != nil {
238257
return nil, fmt.Errorf("failed to read checkpoint: %w", err)
239258
}
240-
checkpoint, _, err := torchwood.VerifyCheckpoint(v.Name(), torchwood.SingleVerifierPolicy(v), signedCheckpoint)
259+
checkpoint, _, err := torchwood.VerifyCheckpoint(logOrigin, policy, signedCheckpoint)
241260
if err != nil {
242261
return nil, fmt.Errorf("failed to parse checkpoint: %w", err)
243262
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
witness TrustFabric transparency.dev/DEV:witness-little-garden+d8042a87+BCtusOxINQNUTN5Oj8HObRkh2yHf/MwYaGX4CPdiVEPM https://api.transparency.dev/dev/witness/little-garden/
2+
witness Mullvad witness.stagemole.eu+67f7aea0+BEqSG3yu9YrmcM3BHvQYTxwFj3uSWakQepafafpUqklv https://witness.stagemole.eu/
3+
witness Geomys witness.navigli.sunlight.geomys.org+a3e00fe2+BNy/co4C1Hn1p+INwJrfUlgz7W55dSZReusH/GhUhJ/G https://witness.navigli.sunlight.geomys.org/
4+
group public 2 TrustFabric Mullvad Geomys
5+
quorum public

cmd/age-keyserver/main.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import (
4141
var (
4242
//go:embed templates static
4343
embeddedFS embed.FS
44+
//go:embed witness_policy.txt
45+
defaultWitnessPolicy []byte
4446

4547
dbPath = flag.String("db", "keyserver.sqlite3", "path to SQLite database")
4648
logPath = flag.String("logdir", "keyserver-tlog", "directory for transparency log")
@@ -110,6 +112,17 @@ func main() {
110112
log.Fatalln("failed to create log storage driver:", err)
111113
}
112114

115+
witnessPolicy := defaultWitnessPolicy
116+
if path := os.Getenv("LOG_WITNESS_POLICY"); path != "" {
117+
witnessPolicy, err = os.ReadFile(path)
118+
if err != nil {
119+
log.Fatalln("failed to read witness policy file:", err)
120+
}
121+
}
122+
witnesses, err := tessera.NewWitnessGroupFromPolicy(witnessPolicy)
123+
if err != nil {
124+
log.Fatalln("failed to create witness group from policy:", err)
125+
}
113126
// Since this is a low-traffic but interactive server, disable batching to
114127
// remove integration latency for the first request. Keep a 1s checkpoint
115128
// interval not to hit the witnesses too often; this will be observed only
@@ -125,7 +138,8 @@ func main() {
125138
WithCheckpointSigner(s).
126139
WithBatching(1, tessera.DefaultBatchMaxAge).
127140
WithCheckpointInterval(checkpointInterval).
128-
WithCheckpointRepublishInterval(24*time.Hour))
141+
WithCheckpointRepublishInterval(24*time.Hour).
142+
WithWitnesses(witnesses, nil))
129143
if err != nil {
130144
log.Fatalln("failed to create log appender:", err)
131145
}

cmd/age-keyserver/main_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"os/exec"
1111
"path/filepath"
12+
"runtime"
1213
"strconv"
1314
"strings"
1415
"testing"
@@ -28,6 +29,10 @@ func TestMain(m *testing.M) {
2829
}
2930

3031
func TestScript(t *testing.T) {
32+
// On macOS, the default TMPDIR is too long for ssh-agent socket paths.
33+
if runtime.GOOS == "darwin" {
34+
t.Setenv("TMPDIR", "/tmp")
35+
}
3136
p := testscript.Params{
3237
Dir: "testdata",
3338
Setup: func(e *testscript.Env) error {
@@ -38,6 +43,8 @@ func TestScript(t *testing.T) {
3843
cmd.Args = append(cmd.Args, "-cover")
3944
}
4045
cmd.Args = append(cmd.Args, "filippo.io/torchwood/cmd/age-keylookup")
46+
cmd.Args = append(cmd.Args, "filippo.io/torchwood/cmd/litewitness")
47+
cmd.Args = append(cmd.Args, "filippo.io/torchwood/cmd/witnessctl")
4148
cmd.Stdout = os.Stdout
4249
cmd.Stderr = os.Stderr
4350
return cmd.Run()

cmd/age-keyserver/testdata/age-keylookup.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
# Test age-keylookup against the actual age-keyserver
22

3+
# Start witness
4+
exec witnessctl add-log -origin example.com
5+
exec witnessctl add-key -origin example.com -key example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
6+
env SSH_AUTH_SOCK=$WORK/sock
7+
! exec ssh-agent -a $SSH_AUTH_SOCK -D & # ssh-agent always exits 2
8+
waitfor $SSH_AUTH_SOCK
9+
chmod 600 witness_key.pem
10+
exec ssh-add witness_key.pem
11+
exec litewitness -ssh-agent=$SSH_AUTH_SOCK -listen localhost:7391 -name=example.com/witness -key=e933707e0e36c30f01d94b5d81e742da373679d88eb0f85f959ccd80b83b992a &
12+
waitfor localhost:7391
13+
314
# Start age-keyserver
415
env HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000
516
env AGE_KEYSERVER_URL=http://localhost:13893
617
env AGE_KEYSERVER_HMAC_FILE=$WORK/hmac.txt
718
env LOG_KEY=PRIVATE+KEY+example.com+5800330c+AaAoObvamoDOmN6c30Xh9pH1e/xqKcsU+fNmthQ8qmvM
19+
env LOG_WITNESS_POLICY=witness_policy.txt
820
env VRF_KEY=vni5C6++aVMFR5tg3bwvLamWlhJEmVrtNT7uNeyo6gQ=
921
env AGE_KEYSERVER_PUBKEY=example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
1022
env AGE_KEYSERVER_VRFKEY=cmJCh5QTwp9VqN+QVV+BRxKLKmCFRuVAx+dahotxqw0=
23+
env AGE_KEYSERVER_POLICY=witness_policy.txt
1124
exec age-keyserver -db=$WORK/test.sqlite3 -listen=localhost:13893 &srv&
1225
waitfor http://localhost:13893/
1326

@@ -41,3 +54,18 @@ stdout 'age1v9mqpk5wx65vxqz429s93uamfu2z0rm8y9az4kfkt4dp6tua8dhqvh3lff'
4154
killall
4255
wait srv
4356
stderr 'shutting down'
57+
58+
59+
-- witness_key.pem --
60+
-----BEGIN OPENSSH PRIVATE KEY-----
61+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
62+
c2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67SwL3A6yjsecbvWqOUAAA
63+
AIgN5+09DeftPQAAAAtzc2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67
64+
SwL3A6yjsecbvWqOUAAAAEAx/8IRbsvgA6yqgAq3B1e9fVMgbj/r72ptB5bZVTCz
65+
T2SEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5QAAAAAAECAwQF
66+
-----END OPENSSH PRIVATE KEY-----
67+
68+
69+
-- witness_policy.txt --
70+
witness W example.com/witness+10a1c019+BGSEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5Q http://localhost:7391
71+
quorum W

cmd/age-keyserver/testdata/age-keyserver.txt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1+
# start witness
2+
exec witnessctl add-log -origin example.com
3+
exec witnessctl add-key -origin example.com -key example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
4+
env SSH_AUTH_SOCK=$WORK/sock
5+
! exec ssh-agent -a $SSH_AUTH_SOCK -D & # ssh-agent always exits 2
6+
waitfor $SSH_AUTH_SOCK
7+
chmod 600 witness_key.pem
8+
exec ssh-add witness_key.pem
9+
exec litewitness -ssh-agent=$SSH_AUTH_SOCK -listen localhost:7390 -name=example.com/witness -key=e933707e0e36c30f01d94b5d81e742da373679d88eb0f85f959ccd80b83b992a &
10+
waitfor localhost:7390
11+
112
# start age-keyserver with test hCaptcha secret
213
env HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000
314
env LOG_KEY=PRIVATE+KEY+example.com+5800330c+AaAoObvamoDOmN6c30Xh9pH1e/xqKcsU+fNmthQ8qmvM
15+
env LOG_WITNESS_POLICY=witness_policy.txt
416
env VRF_KEY=vni5C6++aVMFR5tg3bwvLamWlhJEmVrtNT7uNeyo6gQ=
517
exec age-keyserver -db=$WORK/test.sqlite3 -listen=localhost:13892 &srv&
618
waitfor http://localhost:13892/
@@ -26,6 +38,21 @@ wait srv
2638
stderr 'shutting down'
2739

2840

41+
-- witness_key.pem --
42+
-----BEGIN OPENSSH PRIVATE KEY-----
43+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
44+
c2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67SwL3A6yjsecbvWqOUAAA
45+
AIgN5+09DeftPQAAAAtzc2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67
46+
SwL3A6yjsecbvWqOUAAAAEAx/8IRbsvgA6yqgAq3B1e9fVMgbj/r72ptB5bZVTCz
47+
T2SEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5QAAAAAAECAwQF
48+
-----END OPENSSH PRIVATE KEY-----
49+
50+
51+
-- witness_policy.txt --
52+
witness W example.com/witness+10a1c019+BGSEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5Q http://localhost:7390
53+
quorum W
54+
55+
2956
-- pages.hurl --
3057
# Test home page
3158
GET http://localhost:13892/
@@ -44,7 +71,8 @@ HTTP 200
4471
GET http://localhost:13892/tlog/checkpoint
4572
HTTP 200
4673
[Asserts]
47-
body contains "example.com"
74+
body contains "— example.com "
75+
body contains "— example.com/witness "
4876

4977

5078
-- lookup.hurl --

cmd/age-keyserver/testdata/monitor.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
# Test monitoring mode
22

3+
# Start witness
4+
exec witnessctl add-log -origin example.com
5+
exec witnessctl add-key -origin example.com -key example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
6+
env SSH_AUTH_SOCK=$WORK/sock
7+
! exec ssh-agent -a $SSH_AUTH_SOCK -D & # ssh-agent always exits 2
8+
waitfor $SSH_AUTH_SOCK
9+
chmod 600 witness_key.pem
10+
exec ssh-add witness_key.pem
11+
exec litewitness -ssh-agent=$SSH_AUTH_SOCK -listen localhost:7392 -name=example.com/witness -key=e933707e0e36c30f01d94b5d81e742da373679d88eb0f85f959ccd80b83b992a &
12+
waitfor localhost:7392
13+
314
# Start age-keyserver
415
env HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000
516
env AGE_KEYSERVER_URL=http://localhost:13894
617
env AGE_KEYSERVER_HMAC_FILE=$WORK/hmac.txt
718
env LOG_KEY=PRIVATE+KEY+example.com+5800330c+AaAoObvamoDOmN6c30Xh9pH1e/xqKcsU+fNmthQ8qmvM
19+
env LOG_WITNESS_POLICY=witness_policy.txt
820
env VRF_KEY=vni5C6++aVMFR5tg3bwvLamWlhJEmVrtNT7uNeyo6gQ=
921
env AGE_KEYSERVER_PUBKEY=example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
1022
env AGE_KEYSERVER_VRFKEY=cmJCh5QTwp9VqN+QVV+BRxKLKmCFRuVAx+dahotxqw0=
23+
env AGE_KEYSERVER_POLICY=witness_policy.txt
1124
exec age-keyserver -db=$WORK/test.sqlite3 -listen=localhost:13894 &srv&
1225
waitfor http://localhost:13894/
1326

@@ -29,3 +42,18 @@ stdout 'age1h24cj29vfe5t0wq9sp88fawatczsalq33qnh53wzpa03y8eeka7suwjcfs'
2942
killall
3043
wait srv
3144
stderr 'shutting down'
45+
46+
47+
-- witness_key.pem --
48+
-----BEGIN OPENSSH PRIVATE KEY-----
49+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
50+
c2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67SwL3A6yjsecbvWqOUAAA
51+
AIgN5+09DeftPQAAAAtzc2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67
52+
SwL3A6yjsecbvWqOUAAAAEAx/8IRbsvgA6yqgAq3B1e9fVMgbj/r72ptB5bZVTCz
53+
T2SEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5QAAAAAAECAwQF
54+
-----END OPENSSH PRIVATE KEY-----
55+
56+
57+
-- witness_policy.txt --
58+
witness W example.com/witness+10a1c019+BGSEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5Q http://localhost:7392
59+
quorum W
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
witness TrustFabric transparency.dev/DEV:witness-little-garden+d8042a87+BCtusOxINQNUTN5Oj8HObRkh2yHf/MwYaGX4CPdiVEPM https://api.transparency.dev/dev/witness/little-garden/
2+
witness Mullvad witness.stagemole.eu+67f7aea0+BEqSG3yu9YrmcM3BHvQYTxwFj3uSWakQepafafpUqklv https://witness.stagemole.eu/
3+
witness Geomys witness.navigli.sunlight.geomys.org+a3e00fe2+BNy/co4C1Hn1p+INwJrfUlgz7W55dSZReusH/GhUhJ/G https://witness.navigli.sunlight.geomys.org/
4+
group public 2 TrustFabric Mullvad Geomys
5+
quorum public

0 commit comments

Comments
 (0)