Update für bessere Kompatibilität

This commit is contained in:
2026-02-19 13:23:54 +01:00
parent 2043df644e
commit 553d216a42
4 changed files with 231 additions and 22 deletions

131
main.go
View File

@@ -271,16 +271,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() {
@@ -322,7 +344,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
@@ -428,16 +450,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 +477,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 +549,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 ---