Compare commits
4 Commits
2043df644e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c0d16c2c1 | |||
| ce9086d314 | |||
| 7c5d288ca6 | |||
| 553d216a42 |
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.25-alpine AS build
|
||||
FROM golang:1.26-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
# Optional: git + certs for private modules; cache modules and build cache
|
||||
@@ -22,4 +22,5 @@ WORKDIR /app
|
||||
COPY --from=build /out/pgpdashboard /app/pgpdashboard
|
||||
EXPOSE 8080
|
||||
USER nonroot:nonroot
|
||||
#KEYSERVER_API_TOKEN='supersecret'
|
||||
ENTRYPOINT ["/app/pgpdashboard"]
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "max_at_send.nrw--D88C7FA9A544ECF8BCCEC6EB8F0B3E5851F2C8CC",
|
||||
"name": "max",
|
||||
"email": "max@send.nrw",
|
||||
"fingerprint": "D88C7FA9A544ECF8BCCEC6EB8F0B3E5851F2C8CC",
|
||||
"filename": "public.asc",
|
||||
"created_at": "2025-09-22T20:48:08.8860389+02:00"
|
||||
"id": "j.bergner.hilden_at_gmail.com--EA8F1C0B6A456CD4C1474AEC8162092BE1C1B291",
|
||||
"name": "Jan Bergner",
|
||||
"email": "j.bergner.hilden@gmail.com",
|
||||
"fingerprint": "EA8F1C0B6A456CD4C1474AEC8162092BE1C1B291",
|
||||
"filename": "EA8F1C0B6A456CD4C1474AEC8162092BE1C1B291.asc",
|
||||
"created_at": "2026-02-19T20:53:37.1668347+01:00"
|
||||
},
|
||||
{
|
||||
"id": "jan.bergner_at_gmail.com--77588003EACB3CFF76B3C1B1A1E557B03E42CC77",
|
||||
"name": "Jan Bergner",
|
||||
"email": "jan.bergner@gmail.com",
|
||||
"fingerprint": "77588003EACB3CFF76B3C1B1A1E557B03E42CC77",
|
||||
"filename": "0x3E42CC77-pub.asc",
|
||||
"created_at": "2025-09-22T20:02:50.1116136+02:00"
|
||||
"id": "jan.bergner_at_hilden.de--660D6A7E3F56BF0BADABA140EFE349B5719CEFC4",
|
||||
"name": "Jan Bergner (Stadt Hilden)",
|
||||
"email": "jan.bergner@hilden.de",
|
||||
"fingerprint": "660D6A7E3F56BF0BADABA140EFE349B5719CEFC4",
|
||||
"filename": "660D6A7E3F56BF0BADABA140EFE349B5719CEFC4.asc",
|
||||
"created_at": "2026-02-19T20:52:54.5792736+01:00"
|
||||
}
|
||||
]
|
||||
@@ -1,98 +0,0 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: Keybase OpenPGP v1.0.0
|
||||
Comment: https://keybase.io/crypto
|
||||
|
||||
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
|
||||
S6vU
|
||||
=hcF1
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
56
data/keys/660D6A7E3F56BF0BADABA140EFE349B5719CEFC4.asc
Normal file
56
data/keys/660D6A7E3F56BF0BADABA140EFE349B5719CEFC4.asc
Normal file
@@ -0,0 +1,56 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: https://gopenpgp.org
|
||||
Version: GopenPGP 2.9.0
|
||||
|
||||
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
|
||||
xx/h8PnQbTFYuJSENfy42Dg=
|
||||
=VnbW
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
56
data/keys/EA8F1C0B6A456CD4C1474AEC8162092BE1C1B291.asc
Normal file
56
data/keys/EA8F1C0B6A456CD4C1474AEC8162092BE1C1B291.asc
Normal file
@@ -0,0 +1,56 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: https://gopenpgp.org
|
||||
Version: GopenPGP 2.9.0
|
||||
|
||||
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
|
||||
JKIVC+HVIU6b
|
||||
=drfJ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -1,55 +0,0 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: https://gopenpgp.org
|
||||
Version: GopenPGP 2.9.0
|
||||
|
||||
xsFNBGjRma8BEACuJeyPs7T+VP3LDbJe/FyvojsRTQlGWpBthEKhDpU5VYyKg38P
|
||||
Q/V+402jTqJkqBLJa+S1q7hN/PQ9It/+88MZFmh5o6rSA1lOfXV3lG/RjwNMOkl6
|
||||
cgFE90wQmQHIGG+gvZcXShLItTtl8j9Y0/ag+6WIeBS5sVvs8EiYxJVWXMy/BYoR
|
||||
SMVU1nrvvGM1RISqeXCX7RTxdsRgBmFjpxFtclIEqIwxLgUOgxr+HvB8TcjvB3gP
|
||||
oCPLhp9k4riurISrKGxMy7USB+5+EFHSBP5ICP0CNODsWL+Yd5npeVfs8oA0d5HQ
|
||||
diS9qh84pcWAbzoZaqZCj8+9B07Bt8hNqtx6PCbUGymm4Ef2rtV84jhkKDV5aqJx
|
||||
M2hnrS+FzhWvaoBpDTes3GHWo3BO0InKbrqwFCp5Ds4Ekq7Tiw5vx3LmHXjgA/up
|
||||
ad+km1JQ1/aMMKAqDUVIUgpdGWu8mKeB+7nbfWM9QMLABllrSH0H0f0j7Fba5N7W
|
||||
LmaA5cmc46l0jsXtgYMnTJOrMPbRklk6pO9T64P85E60fX+mz8D09Zjrhu3u2tY3
|
||||
FwEfyN6ZhHmKiIZXuFf++pdSu+nVa+BXpnqMWej802QOVcQgEDZEHQtWasm9EtEo
|
||||
IHyLfDsC81RZYpqhcrtbFAl8/MJzLJu+U29FcjsCydca0jsyaB3NrXF3EwARAQAB
|
||||
zRJtYXggPG1heEBzZW5kLm5ydz7Cwb0EEwEIAHEFgmjRma8DCwkHCRCPCz5YUfLI
|
||||
zDUUAAAAAAAcABBzYWx0QG5vdGF0aW9ucy5vcGVucGdwanMub3JnhXnoDRWvzhAp
|
||||
QPRcXRETYgIVCAMWAAICGQECmwMCHgEWIQTYjH+ppUTs+LzOxuuPCz5YUfLIzAAA
|
||||
JUoP/0GRyu4kdLqAxCHrWaOpBzHFpZrbjrVWuhpNElEsDH3Jthhwd9vsOEQBhoTG
|
||||
Fj3R/l+BuWWI6VeaPboEtMCqSaoXnYWKyzpJvfkS0EEc+yiN4HXPcai4NBLBnSJI
|
||||
9JnSVaTg6+xEJhQ1kCbKviGzxcOKMn55ygBz5cECblaOHdZFzA4XB0bK6tiVwMi/
|
||||
M2rhq5Nk+X4LW8El3L1QKrt3Mo1iiiqmZ4XJTevZh9w3cUiKKaIwXxIO7T9KkgLe
|
||||
BFv+1jg7IemnNMfnHzeSX8JPX3odfRXsRWLfYteuO7u4awTadZjF2okH3FkhDoXF
|
||||
K2Orl1q69kbHUYRYRHzoPFURZR9wbCbdZAAn9qT6Ihaiyg8taAz58Z25ldgWDIP8
|
||||
ql8+vlCnxF102f1ZY8XTxLQfNsTYPfFZ4Kspq0g31GuY03IIpdS9RcyjrHXJ7NDx
|
||||
JXFfjQDcKRBDbzVknpEPOLYG86BMFlgxPntOoR55KHttNzzjdGN2nW/TOcwkArwF
|
||||
1xvUImIuSgeV+p6LAUDRdMXVBLVZ+1HDOcz+L/9lamZfgMq1wfma0XcxeaHJNsL2
|
||||
f6BUGr/52MdOEuEQ53VRNX3g3fEZ5qHtDu8Au6avQisJdwTQ7jJdxAvXNfRWe5Cx
|
||||
3n0uap//c/IkDHhRONdiY/Ph1aVCOJk6y/tGh0VKki4Bl/KZzsFNBGjRma8BEADX
|
||||
iVUBaa3BxGrv6tvXRfDSl6q3JQojDH6pPKmJVT3dnyPb4XIcJ3xh0lsroeme4p01
|
||||
oMNn//TNqnH1mQMf53bZtMsJ3DGfaZ6l+zRKACCnNZVuiAHzUUHua8W3bfMTkm32
|
||||
iRvpI0/6Ch19znUPANdjxrANtuDqVtZB6NLr9nvQmtZDcLfSPw0xhrE/JYaLhpWM
|
||||
ULMl99rJgpGivBLMwjtUaN42pd3yt/HiY0xh9WqHQUGCKSFxmbiuriDaR/65XNul
|
||||
OzX0LypAqP8etvfSD90dMqOFIJ9jsjGAt9qGIvasnvS8ONvcVu/hvx3C8JUxQNfx
|
||||
mi0nTR5EAUO3NXTp2wOi0tWLzRNW15NgWlEMuG6zAJPhnFPUnd3dEivbSqKEyRtO
|
||||
b9kdTruIsGMFxn+QN/IGIabTIwekCpaPg0RJQqbzk22tBRS/yJOGtX7DKbC0cU4K
|
||||
cVGyW2vOj/yHFQ84pEWZ/myk8+zdCWamxkFnmCBl7TdTGg5/VmQxsw2j44R2U6Ax
|
||||
VkHl9SRJolGCMJYJlatvKybsuAyH0kCn6dbyxtpc/A4GFkIsMToVnsDr6hA1mPEe
|
||||
lJaPRQ0mUUc0RA2BDXvmv5g62bNzNnNYlUGKvFAZotj88C9zYHKhmn4nkM9JfQIG
|
||||
f6G8xlIKhMw+XZizZbi1r2MazbUZJ7TSvoFN25G/wwARAQABwsGsBBgBCABgBYJo
|
||||
0ZmvCRCPCz5YUfLIzDUUAAAAAAAcABBzYWx0QG5vdGF0aW9ucy5vcGVucGdwanMu
|
||||
b3JnUEPsuZ872vRsropTvyLVZQKbDBYhBNiMf6mlROz4vM7G648LPlhR8sjMAAD0
|
||||
Kw/6ApWnJyDH6885a9XuP5BvwDXhLAhYtid+AgKqJI0BgC/LoseUIxed8F/W/qXs
|
||||
BO0xSge3w4XDCf/eMQrJonQvGR3CJWeD7kdxnDizMRn4xYNqnpZVJpKF+Esvym+M
|
||||
PxQoqG7uOTdAkpDwvb1d8nmFPf3+9LattSw6vTSTTE8yHL4RTG8xDBTp9d71LyCQ
|
||||
vw0C0kHCkDVAH1AOLrUkN4qCERk9i1LCWqFY8dz+i7/yKCYcZgFH5VX5nOOEoht2
|
||||
tt24XbQ8GOU7s6zhekY71UqCEr7Z5HuOmtjBtle4odT8fnogWsH3H1fIB6qNzKgz
|
||||
dlOjn09ZyFV/xJv9lqMgTURFuRO28U8BOuC8LERgX3zWjoN8b/A5ni1Pq68JHrWY
|
||||
xe0cErwnXINCMcAzp7vboAztZxnKfQ9WDJzVHFkgVAHqVNUw4qkrka0Cwp+m1y/H
|
||||
UsTVJnNNwqwzrWfgMgDxuqXc5dYRW3CPjh7691Y1Gn+J2e7m8cRAdz2hFddI0+cl
|
||||
1BblQytXfSseQ3fr/ahXjo6qDV3Y9MCjPCu+2J7zQdiDLgZzShZIOSUBISbw1dOK
|
||||
FoZCrDqG3kUis06VHKlQcQsDJvdfhAgbFkymksQKE7EPDmyIuV1EqWxI/oC5ROOT
|
||||
gaBFe8PcFGd8vUtDlyvUk3vDlQMcHEKAPIrzULgZud0Wl5Y=
|
||||
=0v61
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
273
main.go
273
main.go
@@ -13,7 +13,7 @@
|
||||
//
|
||||
// Notes
|
||||
// - This server publishes PUBLIC keys only.
|
||||
// - WKD uses z-base-32(SHA1(strings.ToLower(addr-spec))) per spec.
|
||||
// - WKD uses z-base-32(SHA1(strings.ToLower(local-part))) per spec.
|
||||
// - HKP here is minimal: /pks/lookup?op=get|index&search=<term> (email/fpr/substring).
|
||||
// - Protect /upload behind auth in production.
|
||||
package main
|
||||
@@ -69,6 +69,35 @@ type store struct {
|
||||
byID map[string]KeyRecord
|
||||
}
|
||||
|
||||
type apiUploadReq struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Fingerprint string `json:"fingerprint"` // optional, wird sonst aus Key geparsed
|
||||
PublicArmored string `json:"public_armored"` // ASCII armored public key
|
||||
Filename string `json:"filename"` // optional
|
||||
}
|
||||
|
||||
type apiUploadResp struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
WKDHash string `json:"wkd_hash"`
|
||||
Domain string `json:"domain"`
|
||||
Local string `json:"local"`
|
||||
}
|
||||
|
||||
func bearerOK(r *http.Request, token string) bool {
|
||||
if token == "" {
|
||||
return false
|
||||
}
|
||||
h := r.Header.Get("Authorization")
|
||||
const p = "Bearer "
|
||||
if !strings.HasPrefix(h, p) {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(strings.TrimPrefix(h, p)) == token
|
||||
}
|
||||
|
||||
func newStore() *store { return &store{byID: make(map[string]KeyRecord)} }
|
||||
|
||||
// persistSnapshot schreibt eine bereits kopierte Liste auf Disk.
|
||||
@@ -271,16 +300,38 @@ func zbase32Encode(b []byte) string {
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// wkdHash: z-base-32(SHA1(strings.ToLower(addr-spec))) + domain
|
||||
func wkdHash(email string) (hash string, domain string) {
|
||||
email = strings.ToLower(strings.TrimSpace(email))
|
||||
// wkdHash: z-base-32(SHA1(strings.ToLower(local-part))) + domain
|
||||
func wkdHash(email string) (hash string, domain string, local string) {
|
||||
email = strings.TrimSpace(email)
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) != 2 {
|
||||
return "", ""
|
||||
return "", "", ""
|
||||
}
|
||||
domain = parts[1]
|
||||
s := sha1.Sum([]byte(email))
|
||||
return zbase32Encode(s[:]), domain
|
||||
local = parts[0] // keep original for ?l=
|
||||
domain = strings.ToLower(parts[1])
|
||||
lp := strings.ToLower(local)
|
||||
|
||||
s := sha1.Sum([]byte(lp))
|
||||
return zbase32Encode(s[:]), domain, local
|
||||
}
|
||||
|
||||
func armoredToBinary(arm []byte) ([]byte, error) {
|
||||
ents, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(arm))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ents) == 0 {
|
||||
return nil, fmt.Errorf("no keys in armored data")
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
for _, e := range ents {
|
||||
// Serialize schreibt binary OpenPGP packets (kein Armor)
|
||||
if err := e.Serialize(&out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -311,6 +362,109 @@ func main() {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
})
|
||||
apiToken := getenv("KEYSERVER_API_TOKEN", "12345678")
|
||||
mux.HandleFunc("/api/v1/keys", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if !bearerOK(r, apiToken) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Limit body size
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
|
||||
defer r.Body.Close()
|
||||
|
||||
var req apiUploadReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid json", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req.Name = strings.TrimSpace(req.Name)
|
||||
req.Email = strings.TrimSpace(req.Email)
|
||||
req.Fingerprint = strings.TrimSpace(req.Fingerprint)
|
||||
req.PublicArmored = strings.TrimSpace(req.PublicArmored)
|
||||
|
||||
if req.Email == "" || !strings.Contains(req.Email, "@") {
|
||||
http.Error(w, "missing/invalid email", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if req.PublicArmored == "" || !strings.Contains(req.PublicArmored, "-----BEGIN PGP PUBLIC KEY BLOCK-----") {
|
||||
http.Error(w, "public_armored must be ASCII-armored PGP public key", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
b := []byte(req.PublicArmored)
|
||||
|
||||
// Parse fingerprint (authoritative)
|
||||
autoFPR, err := parseFingerprintFromASCII(b)
|
||||
if err != nil || autoFPR == "" {
|
||||
http.Error(w, "could not parse fingerprint from key", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
fpr := strings.ToUpper(strings.ReplaceAll(autoFPR, " ", ""))
|
||||
|
||||
// Optional: wenn req.Fingerprint gesetzt ist, validieren wir nur (kein override)
|
||||
if req.Fingerprint != "" && normalizeFPR(req.Fingerprint) != normalizeFPR(fpr) {
|
||||
http.Error(w, "fingerprint mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// filename: prefer fingerprint (unique) -> <FPR>.asc
|
||||
fn := normalizeFPR(fpr)
|
||||
if fn == "" {
|
||||
fn = sanitizeFilename(req.Email) // fallback (shouldn't happen)
|
||||
} else {
|
||||
fn = fn + ".asc"
|
||||
}
|
||||
fn = sanitizeFilename(fn) // keeps it safe, but doesn't mangle hex
|
||||
|
||||
path := filepath.Join(keysDir, fn)
|
||||
|
||||
oldID := genID(req.Email, fpr)
|
||||
if old, ok := st.get(oldID); ok {
|
||||
if old.Filename != "" && old.Filename != fn {
|
||||
_ = os.Remove(filepath.Join(keysDir, old.Filename))
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, b, 0o644); err != nil {
|
||||
http.Error(w, "save error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rec := KeyRecord{
|
||||
ID: oldID,
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
Fingerprint: fpr,
|
||||
Filename: fn,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if err := st.upsert(rec); err != nil {
|
||||
http.Error(w, "index error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// WKD meta (wenn du die bereits korrigierte wkdHash(local-part) nutzt)
|
||||
h, d, l := wkdHash(rec.Email) // falls deine Signatur noch (hash,domain) ist
|
||||
// Wenn du die neue wkdHash(email) -> (hash,domain,local) nutzt, dann: h,d,_ := wkdHash(rec.Email)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_ = json.NewEncoder(w).Encode(apiUploadResp{
|
||||
ID: rec.ID,
|
||||
Email: rec.Email,
|
||||
Fingerprint: rec.Fingerprint,
|
||||
WKDHash: h,
|
||||
Domain: d,
|
||||
Local: l,
|
||||
})
|
||||
})
|
||||
|
||||
// Live search (HTMX)
|
||||
mux.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query().Get("q")
|
||||
@@ -322,7 +476,7 @@ func main() {
|
||||
})
|
||||
// Upload with automatic fingerprint parsing
|
||||
mux.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
|
||||
if enabled("WRITEACCESS", false) {
|
||||
if enabled("WRITEACCESS", true) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
@@ -334,7 +488,7 @@ func main() {
|
||||
name := strings.TrimSpace(r.FormValue("name"))
|
||||
email := strings.TrimSpace(r.FormValue("email"))
|
||||
userFPR := strings.TrimSpace(r.FormValue("fingerprint")) // optional override
|
||||
file, hdr, err := r.FormFile("file")
|
||||
file, _, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
http.Error(w, "missing file", http.StatusBadRequest)
|
||||
return
|
||||
@@ -363,10 +517,8 @@ func main() {
|
||||
}
|
||||
fpr = strings.ToUpper(strings.ReplaceAll(fpr, " ", ""))
|
||||
|
||||
base := sanitizeFilename(hdr.Filename)
|
||||
if base == ".asc" || base == "" {
|
||||
base = sanitizeFilename(email)
|
||||
}
|
||||
base := normalizeFPR(fpr) + ".asc"
|
||||
base = sanitizeFilename(base)
|
||||
path := filepath.Join(keysDir, base)
|
||||
if err := os.WriteFile(path, b, 0o644); err != nil {
|
||||
http.Error(w, "save error", 500)
|
||||
@@ -428,16 +580,26 @@ func main() {
|
||||
mux.HandleFunc("/.well-known/openpgpkey/policy", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("# WKD policy"))
|
||||
_, _ = w.Write([]byte("# WKD policy\n"))
|
||||
})
|
||||
// direct method: /.well-known/openpgpkey/hu/<hash>
|
||||
mux.HandleFunc("/.well-known/openpgpkey/hu/", func(w http.ResponseWriter, r *http.Request) {
|
||||
pathHash := strings.TrimPrefix(r.URL.Path, "/.well-known/openpgpkey/hu/")
|
||||
hash := strings.TrimPrefix(r.URL.Path, "/.well-known/openpgpkey/hu/")
|
||||
hash = strings.Trim(hash, "/")
|
||||
|
||||
// Optional: ?l=... (unverändert, percent-encoded) — nur als Hint/Check
|
||||
lParam := r.URL.Query().Get("l")
|
||||
|
||||
var match *KeyRecord
|
||||
var matchLocal string
|
||||
for _, it := range st.all() {
|
||||
h, _ := wkdHash(it.Email)
|
||||
if h == pathHash {
|
||||
h, _, local := wkdHash(it.Email)
|
||||
if h == hash {
|
||||
if lParam != "" && lParam != local {
|
||||
continue
|
||||
}
|
||||
match = &it
|
||||
matchLocal = local
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -445,27 +607,70 @@ func main() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(keysDir, match.Filename))
|
||||
|
||||
arm, err := os.ReadFile(filepath.Join(keysDir, match.Filename))
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/pgp-keys")
|
||||
bin, err := armoredToBinary(arm)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid stored key data", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// WKD responses are typically application/octet-stream
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*") // optional, hilft manchen Tools
|
||||
if r.Method == http.MethodHead {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
_ = matchLocal // falls du später Logging möchtest
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(data)
|
||||
_, _ = w.Write(bin)
|
||||
})
|
||||
// advanced method: /openpgpkey/<domain>/hu/<hash>
|
||||
mux.HandleFunc("/openpgpkey/", func(w http.ResponseWriter, r *http.Request) {
|
||||
parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/openpgpkey/"), "/")
|
||||
mux.HandleFunc("/.well-known/openpgpkey/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Nur advanced bedienen, wenn Host = openpgpkey.<domain>
|
||||
host := r.Host
|
||||
if i := strings.IndexByte(host, ':'); i >= 0 {
|
||||
host = host[:i]
|
||||
}
|
||||
if !strings.HasPrefix(strings.ToLower(host), "openpgpkey.") {
|
||||
// Für die Hauptdomain sind policy + /hu/ bereits separat gemappt
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Erwartet:
|
||||
// /.well-known/openpgpkey/<domain>/hu/<hash>
|
||||
// oder: /.well-known/openpgpkey/<domain>/policy
|
||||
rest := strings.TrimPrefix(r.URL.Path, "/.well-known/openpgpkey/")
|
||||
parts := strings.Split(strings.Trim(rest, "/"), "/")
|
||||
if len(parts) == 2 && parts[1] == "policy" {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("# WKD policy\n"))
|
||||
return
|
||||
}
|
||||
if len(parts) != 3 || parts[1] != "hu" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
domain, hash := parts[0], parts[2]
|
||||
|
||||
domain := strings.ToLower(parts[0])
|
||||
hash := parts[2]
|
||||
lParam := r.URL.Query().Get("l")
|
||||
|
||||
var match *KeyRecord
|
||||
for _, it := range st.all() {
|
||||
h, d := wkdHash(it.Email)
|
||||
h, d, local := wkdHash(it.Email)
|
||||
if h == hash && strings.EqualFold(d, domain) {
|
||||
if lParam != "" && lParam != local {
|
||||
continue
|
||||
}
|
||||
match = &it
|
||||
break
|
||||
}
|
||||
@@ -474,14 +679,26 @@ func main() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(keysDir, match.Filename))
|
||||
|
||||
arm, err := os.ReadFile(filepath.Join(keysDir, match.Filename))
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/pgp-keys")
|
||||
bin, err := armoredToBinary(arm)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid stored key data", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if r.Method == http.MethodHead {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(data)
|
||||
_, _ = w.Write(bin)
|
||||
})
|
||||
|
||||
// --- Minimal HKP-compatible lookup ---
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<h1 class="brand h3 mb-0">🔐 PGP Key Server</h1>
|
||||
<div class="muted">Unsere öffentlichen OpenPGP-Schlüssel</div>
|
||||
</div>
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#uploadModal">+ Upload</button>
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#uploadModal">+ Hochladen</button>
|
||||
</div>
|
||||
|
||||
{{template "content" .}}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<td><code>{{.Fingerprint}}</code></td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-sm btn-outline-primary" href="/view/{{.ID}}">View</a>
|
||||
<a class="btn btn-sm btn-outline-primary" href="/view/{{.ID}}">Ansehen</a>
|
||||
<a class="btn btn-sm btn-primary" href="/keys/{{.ID}}">Download</a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user