@@ -2,6 +2,7 @@ package main
22
33import (
44 "context"
5+ "encoding/base64"
56 "encoding/json"
67 "flag"
78 "fmt"
@@ -12,6 +13,7 @@ import (
1213 "strings"
1314 "time"
1415
16+ "filippo.io/mostly-harmless/vrf-r255"
1517 "filippo.io/torchwood"
1618 "golang.org/x/mod/sumdb/note"
1719 "golang.org/x/mod/sumdb/tlog"
@@ -35,6 +37,7 @@ func main() {
3537 fmt .Fprintf (os .Stderr , "Environment:\n " )
3638 fmt .Fprintf (os .Stderr , " AGE_KEYSERVER_URL Default keyserver URL\n " )
3739 fmt .Fprintf (os .Stderr , " AGE_KEYSERVER_PUBKEY Default keyserver transparency log vkey\n " )
40+ fmt .Fprintf (os .Stderr , " AGE_KEYSERVER_VRFKEY Default keyserver transparency log VRF public key\n " )
3841 os .Exit (2 )
3942 }
4043
@@ -56,11 +59,26 @@ func main() {
5659 os .Exit (1 )
5760 }
5861
62+ vrfKeyB64 := os .Getenv ("AGE_KEYSERVER_VRFKEY" )
63+ if vrfKeyB64 == "" {
64+ vrfKeyB64 = "vKHX1vKXl7yF0qBiDxCUXWgOHlapMvqFeIBXt7c29iQ="
65+ }
66+ vrfKeyBytes , err := base64 .StdEncoding .DecodeString (vrfKeyB64 )
67+ if err != nil {
68+ fmt .Fprintf (os .Stderr , "Error: invalid base64 keyserver VRF public key: %v\n " , err )
69+ os .Exit (1 )
70+ }
71+ vrfKey , err := vrf .NewPublicKey (vrfKeyBytes )
72+ if err != nil {
73+ fmt .Fprintf (os .Stderr , "Error: invalid keyserver VRF public key: %v\n " , err )
74+ os .Exit (1 )
75+ }
76+
5977 // Normalize email
6078 email = strings .TrimSpace (strings .ToLower (email ))
6179
6280 if * allFlag {
63- pubkeys , err := monitorLog (server , v , email )
81+ pubkeys , err := monitorLog (server , v , vrfKey , email )
6482 if err != nil {
6583 fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
6684 os .Exit (1 )
@@ -71,7 +89,7 @@ func main() {
7189 return
7290 }
7391
74- pubkey , err := lookupKey (server , v , email )
92+ pubkey , err := lookupKey (server , v , vrfKey , email )
7593 if err != nil {
7694 fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
7795 os .Exit (1 )
@@ -80,7 +98,7 @@ func main() {
8098 fmt .Println (pubkey )
8199}
82100
83- func lookupKey (serverURL string , v note.Verifier , email string ) (string , error ) {
101+ func lookupKey (serverURL string , v note.Verifier , vrfKey * vrf. PublicKey , email string ) (string , error ) {
84102 // Build the lookup URL
85103 lookupURL := serverURL + "/api/lookup?email=" + url .QueryEscape (email )
86104
@@ -124,8 +142,23 @@ func lookupKey(serverURL string, v note.Verifier, email string) (string, error)
124142 return "" , fmt .Errorf ("empty public key returned" )
125143 }
126144
145+ // Compute and verify VRF hash
146+ vrfProofBytes , err := torchwood .HintFromProof ([]byte (result .Proof ))
147+ if err != nil {
148+ return "" , fmt .Errorf ("failed to extract VRF proof: %w" , err )
149+ }
150+ vrfProof , err := vrf .NewProof (vrfProofBytes )
151+ if err != nil {
152+ return "" , fmt .Errorf ("failed to parse VRF proof: %w" , err )
153+ }
154+ vrfHash , err := vrfKey .Verify (vrfProof , []byte (email ))
155+ if err != nil {
156+ return "" , fmt .Errorf ("failed to verify VRF proof: %w" , err )
157+ }
158+
127159 // Verify spicy signature
128- entry := fmt .Appendf (nil , "%s\n %s\n " , result .Email , result .Pubkey )
160+ vrfHashB64 := base64 .StdEncoding .EncodeToString (vrfHash )
161+ entry := fmt .Appendf (nil , "%s\n %s\n " , vrfHashB64 , result .Pubkey )
129162 if err := torchwood .VerifyProof (v .Name (), func (b []byte ) (* note.Note , error ) {
130163 return note .Open (b , note .VerifierList (v ))
131164 }, tlog .RecordHash (entry ), []byte (result .Proof )); err != nil {
@@ -135,7 +168,45 @@ func lookupKey(serverURL string, v note.Verifier, email string) (string, error)
135168 return result .Pubkey , nil
136169}
137170
138- func monitorLog (serverURL string , v note.Verifier , email string ) ([]string , error ) {
171+ func monitorLog (serverURL string , v note.Verifier , vrfKey * vrf.PublicKey , email string ) ([]string , error ) {
172+ // Request the VRF proof from the monitor endpoint
173+ monitorURL := serverURL + "/api/monitor?email=" + url .QueryEscape (email )
174+ client := & http.Client {
175+ Timeout : 10 * time .Second ,
176+ }
177+ resp , err := client .Get (monitorURL )
178+ if err != nil {
179+ return nil , fmt .Errorf ("failed to connect to keyserver: %w" , err )
180+ }
181+ defer resp .Body .Close ()
182+ if resp .StatusCode == http .StatusNotFound {
183+ return nil , fmt .Errorf ("no key found for %s" , email )
184+ }
185+ if resp .StatusCode != http .StatusOK {
186+ body , _ := io .ReadAll (resp .Body )
187+ return nil , fmt .Errorf ("keyserver error: %s - %s" , resp .Status , string (body ))
188+ }
189+ var result struct {
190+ Email string `json:"email"`
191+ VRFProof []byte `json:"vrf_proof"`
192+ }
193+ if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
194+ return nil , fmt .Errorf ("failed to parse response: %w" , err )
195+ }
196+ if result .Email != email {
197+ return nil , fmt .Errorf ("keyserver returned unexpected email: %q" , result .Email )
198+ }
199+
200+ // Compute and verify VRF hash
201+ vrfProof , err := vrf .NewProof (result .VRFProof )
202+ if err != nil {
203+ return nil , fmt .Errorf ("failed to parse VRF proof: %w" , err )
204+ }
205+ vrfHash , err := vrfKey .Verify (vrfProof , []byte (email ))
206+ if err != nil {
207+ return nil , fmt .Errorf ("failed to verify VRF proof: %w" , err )
208+ }
209+
139210 f , err := torchwood .NewTileFetcher (serverURL + "/tlog" , torchwood .WithUserAgent ("age-keylookup/1.0" ))
140211 if err != nil {
141212 return nil , fmt .Errorf ("failed to create tile fetcher: %w" , err )
@@ -170,7 +241,7 @@ func monitorLog(serverURL string, v note.Verifier, email string) ([]string, erro
170241 if ! ok || rest != "" {
171242 return nil , fmt .Errorf ("malformed log entry %d: %q" , i , string (entry ))
172243 }
173- if e == email {
244+ if e == base64 . StdEncoding . EncodeToString ( vrfHash ) {
174245 pubkeys = append (pubkeys , k )
175246 }
176247 }
0 commit comments