@@ -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"
@@ -20,6 +22,7 @@ import (
2022const (
2123 defaultKeyserverURL = "https://keyserver.geomys.org"
2224 defaultKeyserverPubkey = "keyserver.geomys.org+16b31509+ARLJ+pmTj78HzTeBj04V+LVfB+GFAQyrg54CRIju7Nn8"
25+ defaultKeyserverVRFKey = "mKPsDHDcVB95iPXW4Yc7+HPfi3xOw/bHFvfWw6CAMBs="
2326)
2427
2528func main () {
@@ -40,6 +43,7 @@ func main() {
4043 fmt .Fprintf (os .Stderr , "Environment:\n " )
4144 fmt .Fprintf (os .Stderr , " AGE_KEYSERVER_URL Default keyserver URL\n " )
4245 fmt .Fprintf (os .Stderr , " AGE_KEYSERVER_PUBKEY Default keyserver transparency log vkey\n " )
46+ fmt .Fprintf (os .Stderr , " AGE_KEYSERVER_VRFKEY Default keyserver transparency log VRF public key\n " )
4347 os .Exit (2 )
4448 }
4549
@@ -62,11 +66,26 @@ func main() {
6266 }
6367 policy := torchwood .ThresholdPolicy (2 , torchwood .OriginPolicy (v .Name ()), torchwood .SingleVerifierPolicy (v ))
6468
69+ vrfKeyB64 := os .Getenv ("AGE_KEYSERVER_VRFKEY" )
70+ if vrfKeyB64 == "" {
71+ vrfKeyB64 = defaultKeyserverVRFKey
72+ }
73+ vrfKeyBytes , err := base64 .StdEncoding .DecodeString (vrfKeyB64 )
74+ if err != nil {
75+ fmt .Fprintf (os .Stderr , "Error: invalid base64 keyserver VRF public key: %v\n " , err )
76+ os .Exit (1 )
77+ }
78+ vrfKey , err := vrf .NewPublicKey (vrfKeyBytes )
79+ if err != nil {
80+ fmt .Fprintf (os .Stderr , "Error: invalid keyserver VRF public key: %v\n " , err )
81+ os .Exit (1 )
82+ }
83+
6584 // Normalize email
6685 email = strings .TrimSpace (strings .ToLower (email ))
6786
6887 if * allFlag {
69- pubkeys , err := monitorLog (server , policy , email )
88+ pubkeys , err := monitorLog (server , policy , vrfKey , email )
7089 if err != nil {
7190 fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
7291 os .Exit (1 )
@@ -77,7 +96,7 @@ func main() {
7796 return
7897 }
7998
80- pubkey , err := lookupKey (server , policy , email )
99+ pubkey , err := lookupKey (server , policy , vrfKey , email )
81100 if err != nil {
82101 fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
83102 os .Exit (1 )
@@ -86,7 +105,7 @@ func main() {
86105 fmt .Println (pubkey )
87106}
88107
89- func lookupKey (serverURL string , policy torchwood.Policy , email string ) (string , error ) {
108+ func lookupKey (serverURL string , policy torchwood.Policy , vrfKey * vrf. PublicKey , email string ) (string , error ) {
90109 // Build the lookup URL
91110 lookupURL := serverURL + "/api/lookup?email=" + url .QueryEscape (email )
92111
@@ -130,16 +149,69 @@ func lookupKey(serverURL string, policy torchwood.Policy, email string) (string,
130149 return "" , fmt .Errorf ("empty public key returned" )
131150 }
132151
152+ // Compute and verify VRF hash
153+ vrfProofBytes , err := torchwood .ProofExtraData ([]byte (result .Proof ))
154+ if err != nil {
155+ return "" , fmt .Errorf ("failed to extract VRF proof: %w" , err )
156+ }
157+ vrfProof , err := vrf .NewProof (vrfProofBytes )
158+ if err != nil {
159+ return "" , fmt .Errorf ("failed to parse VRF proof: %w" , err )
160+ }
161+ vrfHash , err := vrfKey .Verify (vrfProof , []byte (email ))
162+ if err != nil {
163+ return "" , fmt .Errorf ("failed to verify VRF proof: %w" , err )
164+ }
165+
133166 // Verify spicy signature
134- entry := fmt .Appendf (nil , "%s\n %s\n " , result .Email , result .Pubkey )
167+ vrfHashB64 := base64 .StdEncoding .EncodeToString (vrfHash )
168+ entry := fmt .Appendf (nil , "%s\n %s\n " , vrfHashB64 , result .Pubkey )
135169 if err := torchwood .VerifyProof (policy , tlog .RecordHash (entry ), []byte (result .Proof )); err != nil {
136170 return "" , fmt .Errorf ("failed to verify key proof: %w" , err )
137171 }
138172
139173 return result .Pubkey , nil
140174}
141175
142- func monitorLog (serverURL string , policy torchwood.Policy , email string ) ([]string , error ) {
176+ func monitorLog (serverURL string , policy torchwood.Policy , vrfKey * vrf.PublicKey , email string ) ([]string , error ) {
177+ // Request the VRF proof from the monitor endpoint
178+ monitorURL := serverURL + "/api/monitor?email=" + url .QueryEscape (email )
179+ client := & http.Client {
180+ Timeout : 10 * time .Second ,
181+ }
182+ resp , err := client .Get (monitorURL )
183+ if err != nil {
184+ return nil , fmt .Errorf ("failed to connect to keyserver: %w" , err )
185+ }
186+ defer resp .Body .Close ()
187+ if resp .StatusCode == http .StatusNotFound {
188+ return nil , fmt .Errorf ("no key found for %s" , email )
189+ }
190+ if resp .StatusCode != http .StatusOK {
191+ body , _ := io .ReadAll (resp .Body )
192+ return nil , fmt .Errorf ("keyserver error: %s - %s" , resp .Status , string (body ))
193+ }
194+ var result struct {
195+ Email string `json:"email"`
196+ VRFProof []byte `json:"vrf_proof"`
197+ }
198+ if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
199+ return nil , fmt .Errorf ("failed to parse response: %w" , err )
200+ }
201+ if result .Email != email {
202+ return nil , fmt .Errorf ("keyserver returned unexpected email: %q" , result .Email )
203+ }
204+
205+ // Compute and verify VRF hash
206+ vrfProof , err := vrf .NewProof (result .VRFProof )
207+ if err != nil {
208+ return nil , fmt .Errorf ("failed to parse VRF proof: %w" , err )
209+ }
210+ vrfHash , err := vrfKey .Verify (vrfProof , []byte (email ))
211+ if err != nil {
212+ return nil , fmt .Errorf ("failed to verify VRF proof: %w" , err )
213+ }
214+
143215 f , err := torchwood .NewTileFetcher (serverURL + "/tlog" , torchwood .WithUserAgent ("age-keylookup/1.0" ))
144216 if err != nil {
145217 return nil , fmt .Errorf ("failed to create tile fetcher: %w" , err )
@@ -170,7 +242,7 @@ func monitorLog(serverURL string, policy torchwood.Policy, email string) ([]stri
170242 if ! ok || rest != "" {
171243 return nil , fmt .Errorf ("malformed log entry %d: %q" , i , string (entry ))
172244 }
173- if e == email {
245+ if e == base64 . StdEncoding . EncodeToString ( vrfHash ) {
174246 pubkeys = append (pubkeys , k )
175247 }
176248 }
0 commit comments