Skip to content

Commit e5b50fa

Browse files
committed
cmd/age-keyserver: add witness cosigning to prevent split-views
1 parent 194a603 commit e5b50fa

10 files changed

Lines changed: 142 additions & 14 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
log keyserver.geomys.org+16b31509+ARLJ+pmTj78HzTeBj04V+LVfB+GFAQyrg54CRIju7Nn8
2+
witness TrustFabric transparency.dev/DEV:witness-little-garden+d8042a87+BCtusOxINQNUTN5Oj8HObRkh2yHf/MwYaGX4CPdiVEPM https://api.transparency.dev/dev/witness/little-garden/
3+
witness Mullvad witness.stagemole.eu+67f7aea0+BEqSG3yu9YrmcM3BHvQYTxwFj3uSWakQepafafpUqklv https://witness.stagemole.eu/
4+
witness Geomys witness.navigli.sunlight.geomys.org+a3e00fe2+BNy/co4C1Hn1p+INwJrfUlgz7W55dSZReusH/GhUhJ/G https://witness.navigli.sunlight.geomys.org/
5+
group public 2 TrustFabric Mullvad Geomys
6+
quorum public

cmd/age-keylookup/main.go

Lines changed: 15 additions & 9 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"
@@ -17,16 +18,17 @@ import (
1718

1819
"filippo.io/mostly-harmless/vrf-r255"
1920
"filippo.io/torchwood"
20-
"golang.org/x/mod/sumdb/note"
2121
"golang.org/x/mod/sumdb/tlog"
2222
)
2323

2424
const (
2525
defaultKeyserverURL = "https://keyserver.geomys.org"
26-
defaultKeyserverPubkey = "keyserver.geomys.org+16b31509+ARLJ+pmTj78HzTeBj04V+LVfB+GFAQyrg54CRIju7Nn8"
2726
defaultKeyserverVRFKey = "mKPsDHDcVB95iPXW4Yc7+HPfi3xOw/bHFvfWw6CAMBs="
2827
)
2928

29+
//go:embed default_policy.txt
30+
var defaultPolicy []byte
31+
3032
func main() {
3133
allFlag := flag.Bool("all", false, "list all public keys in the transparency log")
3234
flag.Parse()
@@ -44,8 +46,8 @@ func main() {
4446
fmt.Fprintf(os.Stderr, "\n")
4547
fmt.Fprintf(os.Stderr, "Environment:\n")
4648
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_URL Default keyserver URL\n")
47-
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_PUBKEY Default keyserver transparency log vkey\n")
4849
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_VRFKEY Default keyserver transparency log VRF public key\n")
50+
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_POLICY Default keyserver transparency log policy\n")
4951
os.Exit(2)
5052
}
5153

@@ -57,16 +59,20 @@ func main() {
5759
server = defaultKeyserverURL
5860
}
5961

60-
vkey := os.Getenv("AGE_KEYSERVER_PUBKEY")
61-
if vkey == "" {
62-
vkey = defaultKeyserverPubkey
62+
policyBytes := defaultPolicy
63+
if policyPath := os.Getenv("AGE_KEYSERVER_POLICY"); policyPath != "" {
64+
p, err := os.ReadFile(policyPath)
65+
if err != nil {
66+
fmt.Fprintf(os.Stderr, "Error: failed to read policy file: %v\n", err)
67+
os.Exit(1)
68+
}
69+
policyBytes = p
6370
}
64-
v, err := note.NewVerifier(vkey)
71+
policy, err := torchwood.ParsePolicy(policyBytes)
6572
if err != nil {
66-
fmt.Fprintf(os.Stderr, "Error: invalid keyserver public key: %v\n", err)
73+
fmt.Fprintf(os.Stderr, "Error: invalid policy: %v\n", err)
6774
os.Exit(1)
6875
}
69-
policy := torchwood.ThresholdPolicy(2, torchwood.OriginPolicy(v.Name()), torchwood.SingleVerifierPolicy(v))
7076

7177
vrfKeyB64 := os.Getenv("AGE_KEYSERVER_VRFKEY")
7278
if vrfKeyB64 == "" {

cmd/age-keyserver-keygen/keygen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ func main() {
2727

2828
fmt.Printf("Private key (for LOG_KEY in age-keyserver): %s\n", skey)
2929
fmt.Printf("Private VRF key (for VRF_KEY in age-keyserver): %s\n", base64.StdEncoding.EncodeToString(vrfKey.Bytes()))
30-
fmt.Printf("Public key (for AGE_KEYSERVER_PUBKEY in age-keylookup): %s\n", vkey)
30+
fmt.Printf("Public key (for AGE_KEYSERVER_POLICY in age-keylookup): %s\n", vkey)
3131
fmt.Printf("Public VRF key (for AGE_KEYSERVER_VRFKEY in age-keylookup): %s\n", base64.StdEncoding.EncodeToString(vrfKey.PublicKey().Bytes()))
3232
}

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")
@@ -112,6 +114,17 @@ func main() {
112114
log.Fatalln("failed to create log storage driver:", err)
113115
}
114116

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

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"
@@ -27,6 +28,10 @@ func TestMain(m *testing.M) {
2728
}
2829

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

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
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=policy.txt
820
env VRF_KEY=vni5C6++aVMFR5tg3bwvLamWlhJEmVrtNT7uNeyo6gQ=
9-
env AGE_KEYSERVER_PUBKEY=example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
1021
env AGE_KEYSERVER_VRFKEY=cmJCh5QTwp9VqN+QVV+BRxKLKmCFRuVAx+dahotxqw0=
22+
env AGE_KEYSERVER_POLICY=policy.txt
1123
exec age-keyserver -db=$WORK/test.sqlite3 -listen=localhost:13893 &srv&
1224
waitfor http://localhost:13893/
1325

@@ -41,3 +53,19 @@ stdout 'age1v9mqpk5wx65vxqz429s93uamfu2z0rm8y9az4kfkt4dp6tua8dhqvh3lff'
4153
killall
4254
wait srv
4355
stderr 'shutting down'
56+
57+
58+
-- witness_key.pem --
59+
-----BEGIN OPENSSH PRIVATE KEY-----
60+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
61+
c2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67SwL3A6yjsecbvWqOUAAA
62+
AIgN5+09DeftPQAAAAtzc2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67
63+
SwL3A6yjsecbvWqOUAAAAEAx/8IRbsvgA6yqgAq3B1e9fVMgbj/r72ptB5bZVTCz
64+
T2SEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5QAAAAAAECAwQF
65+
-----END OPENSSH PRIVATE KEY-----
66+
67+
68+
-- policy.txt --
69+
log example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
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: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
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=policy.txt
820
env VRF_KEY=vni5C6++aVMFR5tg3bwvLamWlhJEmVrtNT7uNeyo6gQ=
9-
env AGE_KEYSERVER_PUBKEY=example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
1021
env AGE_KEYSERVER_VRFKEY=cmJCh5QTwp9VqN+QVV+BRxKLKmCFRuVAx+dahotxqw0=
22+
env AGE_KEYSERVER_POLICY=policy.txt
1123
exec age-keyserver -db=$WORK/test.sqlite3 -listen=localhost:13894 &srv&
1224
waitfor http://localhost:13894/
1325

@@ -29,3 +41,19 @@ stdout 'age1h24cj29vfe5t0wq9sp88fawatczsalq33qnh53wzpa03y8eeka7suwjcfs'
2941
killall
3042
wait srv
3143
stderr 'shutting down'
44+
45+
46+
-- witness_key.pem --
47+
-----BEGIN OPENSSH PRIVATE KEY-----
48+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
49+
c2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67SwL3A6yjsecbvWqOUAAA
50+
AIgN5+09DeftPQAAAAtzc2gtZWQyNTUxOQAAACBkhIrYq+1uhZgbOzh1slK4dn67
51+
SwL3A6yjsecbvWqOUAAAAEAx/8IRbsvgA6yqgAq3B1e9fVMgbj/r72ptB5bZVTCz
52+
T2SEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5QAAAAAAECAwQF
53+
-----END OPENSSH PRIVATE KEY-----
54+
55+
56+
-- policy.txt --
57+
log example.com+5800330c+ARPRGiaIwfx6xka5nXhdD/rqojPMjrjhm7OCuy+03Ymz
58+
witness W example.com/witness+10a1c019+BGSEitir7W6FmBs7OHWyUrh2frtLAvcDrKOx5xu9ao5Q http://localhost:7392
59+
quorum W
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
log keyserver.geomys.org+16b31509+ARLJ+pmTj78HzTeBj04V+LVfB+GFAQyrg54CRIju7Nn8
2+
witness TrustFabric transparency.dev/DEV:witness-little-garden+d8042a87+BCtusOxINQNUTN5Oj8HObRkh2yHf/MwYaGX4CPdiVEPM https://api.transparency.dev/dev/witness/little-garden/
3+
witness Mullvad witness.stagemole.eu+67f7aea0+BEqSG3yu9YrmcM3BHvQYTxwFj3uSWakQepafafpUqklv https://witness.stagemole.eu/
4+
witness Geomys witness.navigli.sunlight.geomys.org+a3e00fe2+BNy/co4C1Hn1p+INwJrfUlgz7W55dSZReusH/GhUhJ/G https://witness.navigli.sunlight.geomys.org/
5+
group public 2 TrustFabric Mullvad Geomys
6+
quorum public

cmd/litewitness/litewitness_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"os/exec"
99
"path/filepath"
10+
"runtime"
1011
"slices"
1112
"strconv"
1213
"strings"
@@ -26,6 +27,10 @@ func TestMain(m *testing.M) {
2627
}
2728

2829
func TestScript(t *testing.T) {
30+
// On macOS, the default TMPDIR is too long for ssh-agent socket paths.
31+
if runtime.GOOS == "darwin" {
32+
t.Setenv("TMPDIR", "/tmp")
33+
}
2934
p := testscript.Params{
3035
Dir: "testdata",
3136
Setup: func(e *testscript.Env) error {

0 commit comments

Comments
 (0)