Skip to content

Commit 1893850

Browse files
FiloSottileclaude
andcommitted
cmd/age-keyserver: boilerplate for centralized email-auth'd keyserver
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 53f7398 commit 1893850

12 files changed

Lines changed: 1552 additions & 0 deletions

File tree

cmd/age-keylookup/main.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/url"
10+
"os"
11+
"time"
12+
)
13+
14+
func main() {
15+
flag.Parse()
16+
17+
if flag.NArg() != 1 {
18+
fmt.Fprintf(os.Stderr, "Usage: age-keylookup <email>\n")
19+
fmt.Fprintf(os.Stderr, "\n")
20+
fmt.Fprintf(os.Stderr, "Look up an age public key by email address.\n")
21+
fmt.Fprintf(os.Stderr, "\n")
22+
fmt.Fprintf(os.Stderr, "Example:\n")
23+
fmt.Fprintf(os.Stderr, " age-keylookup filippo@example.com\n")
24+
fmt.Fprintf(os.Stderr, " age -r $(age-keylookup filippo@example.com) -o secret.txt.age secret.txt\n")
25+
fmt.Fprintf(os.Stderr, "\n")
26+
fmt.Fprintf(os.Stderr, "Environment:\n")
27+
fmt.Fprintf(os.Stderr, " AGE_KEYSERVER_URL Default keyserver URL\n")
28+
os.Exit(2)
29+
}
30+
31+
email := flag.Arg(0)
32+
33+
// Determine server URL
34+
server := os.Getenv("AGE_KEYSERVER_URL")
35+
if server == "" {
36+
server = "https://keyserver.geomys.org"
37+
}
38+
39+
pubkey, err := lookupKey(server, email)
40+
if err != nil {
41+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
42+
os.Exit(1)
43+
}
44+
45+
fmt.Println(pubkey)
46+
}
47+
48+
func lookupKey(serverURL, email string) (string, error) {
49+
// Build the lookup URL
50+
lookupURL := serverURL + "/api/lookup?email=" + url.QueryEscape(email)
51+
52+
// Create HTTP client with timeout
53+
client := &http.Client{
54+
Timeout: 10 * time.Second,
55+
}
56+
57+
// Make the request
58+
resp, err := client.Get(lookupURL)
59+
if err != nil {
60+
return "", fmt.Errorf("failed to connect to keyserver: %w", err)
61+
}
62+
defer resp.Body.Close()
63+
64+
// Check status code
65+
if resp.StatusCode == http.StatusNotFound {
66+
return "", fmt.Errorf("no key found for %s", email)
67+
}
68+
69+
if resp.StatusCode != http.StatusOK {
70+
body, _ := io.ReadAll(resp.Body)
71+
return "", fmt.Errorf("keyserver error: %s - %s", resp.Status, string(body))
72+
}
73+
74+
// Parse JSON response
75+
var result struct {
76+
Email string `json:"email"`
77+
Pubkey string `json:"pubkey"`
78+
}
79+
80+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
81+
return "", fmt.Errorf("failed to parse response: %w", err)
82+
}
83+
84+
if result.Email != email {
85+
return "", fmt.Errorf("keyserver returned unexpected email: %q", result.Email)
86+
}
87+
if result.Pubkey == "" {
88+
return "", fmt.Errorf("empty public key returned")
89+
}
90+
91+
return result.Pubkey, nil
92+
}

0 commit comments

Comments
 (0)