bugfix + upload-security hinzugefügt
All checks were successful
release-tag / release-image (push) Successful in 1m35s

This commit is contained in:
2025-09-22 22:46:34 +02:00
parent 1c2a5e0ceb
commit f90f4215c5
3 changed files with 77 additions and 54 deletions

3
go.mod
View File

@@ -2,8 +2,9 @@ module git.send.nrw/sendnrw/go-pgp-server
go 1.24.4 go 1.24.4
require github.com/ProtonMail/go-crypto v1.3.0
require ( require (
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/cloudflare/circl v1.6.0 // indirect github.com/cloudflare/circl v1.6.0 // indirect
golang.org/x/crypto v0.33.0 // indirect golang.org/x/crypto v0.33.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sys v0.30.0 // indirect

126
main.go
View File

@@ -35,6 +35,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -210,6 +211,21 @@ func sanitizeFilename(name string) string {
return name return name
} }
func getenv(k, d string) string {
if v := os.Getenv(k); v != "" {
return v
}
return d
}
func enabled(k string, def bool) bool {
b, err := strconv.ParseBool(strings.ToLower(os.Getenv(k)))
if err != nil {
return def
}
return b
}
func genID(email, fingerprint string) string { func genID(email, fingerprint string) string {
base := strings.ToLower(strings.TrimSpace(email)) base := strings.ToLower(strings.TrimSpace(email))
fp := strings.ToUpper(strings.TrimSpace(fingerprint)) fp := strings.ToUpper(strings.TrimSpace(fingerprint))
@@ -306,62 +322,68 @@ func main() {
}) })
// Upload with automatic fingerprint parsing // Upload with automatic fingerprint parsing
mux.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if enabled("WRITEACCESS", false) {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed) if r.Method != http.MethodPost {
return http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
} return
if err := r.ParseMultipartForm(maxUploadSize); err != nil { }
http.Error(w, "invalid form", http.StatusBadRequest) if err := r.ParseMultipartForm(maxUploadSize); err != nil {
return http.Error(w, "invalid form", http.StatusBadRequest)
} return
name := strings.TrimSpace(r.FormValue("name")) }
email := strings.TrimSpace(r.FormValue("email")) name := strings.TrimSpace(r.FormValue("name"))
userFPR := strings.TrimSpace(r.FormValue("fingerprint")) // optional override email := strings.TrimSpace(r.FormValue("email"))
file, hdr, err := r.FormFile("file") userFPR := strings.TrimSpace(r.FormValue("fingerprint")) // optional override
if err != nil { file, hdr, err := r.FormFile("file")
http.Error(w, "missing file", http.StatusBadRequest) if err != nil {
return http.Error(w, "missing file", http.StatusBadRequest)
} return
defer file.Close() }
var buf bytes.Buffer defer file.Close()
lr := io.LimitedReader{R: file, N: maxUploadSize} var buf bytes.Buffer
if _, err := io.Copy(&buf, &lr); err != nil { lr := io.LimitedReader{R: file, N: maxUploadSize}
http.Error(w, "read error", http.StatusBadRequest) if _, err := io.Copy(&buf, &lr); err != nil {
return http.Error(w, "read error", http.StatusBadRequest)
} return
b := buf.Bytes() }
if !isASCII(string(b)) || !bytes.Contains(b, []byte("-----BEGIN PGP PUBLIC KEY BLOCK-----")) { b := buf.Bytes()
http.Error(w, "file must be ASCII-armored PGP public key (.asc)", http.StatusBadRequest) if !isASCII(string(b)) || !bytes.Contains(b, []byte("-----BEGIN PGP PUBLIC KEY BLOCK-----")) {
return http.Error(w, "file must be ASCII-armored PGP public key (.asc)", http.StatusBadRequest)
} return
// Parse fingerprint }
autoFPR, parseErr := parseFingerprintFromASCII(b) // Parse fingerprint
fpr := userFPR autoFPR, parseErr := parseFingerprintFromASCII(b)
if fpr == "" && parseErr == nil { fpr := userFPR
fpr = autoFPR if fpr == "" && parseErr == nil {
} fpr = autoFPR
if fpr == "" { }
http.Error(w, "could not parse fingerprint; please provide it manually", http.StatusBadRequest) if fpr == "" {
return http.Error(w, "could not parse fingerprint; please provide it manually", http.StatusBadRequest)
} return
fpr = strings.ToUpper(strings.ReplaceAll(fpr, " ", "")) }
fpr = strings.ToUpper(strings.ReplaceAll(fpr, " ", ""))
base := sanitizeFilename(hdr.Filename) base := sanitizeFilename(hdr.Filename)
if base == ".asc" || base == "" { if base == ".asc" || base == "" {
base = sanitizeFilename(email) base = sanitizeFilename(email)
} }
path := filepath.Join(keysDir, base) path := filepath.Join(keysDir, base)
if err := os.WriteFile(path, b, 0o644); err != nil { if err := os.WriteFile(path, b, 0o644); err != nil {
http.Error(w, "save error", 500) http.Error(w, "save error", 500)
return
}
rec := KeyRecord{ID: genID(email, fpr), Name: name, Email: email, Fingerprint: fpr, Filename: base, CreatedAt: time.Now()}
if err := st.upsert(rec); err != nil {
http.Error(w, "index error", 500)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
} else {
http.Error(w, "method not allowed - WRITEACCESS", http.StatusMethodNotAllowed)
return return
} }
rec := KeyRecord{ID: genID(email, fpr), Name: name, Email: email, Fingerprint: fpr, Filename: base, CreatedAt: time.Now()}
if err := st.upsert(rec); err != nil {
http.Error(w, "index error", 500)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}) })
// Serve/download by ID // Serve/download by ID

View File

@@ -20,7 +20,7 @@
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<div> <div>
<h1 class="brand h3 mb-0">🔐 PGP Key Server</h1> <h1 class="brand h3 mb-0">🔐 PGP Key Server</h1>
<div class="muted">Durchsuche öffentliche OpenPGP-Schlüssel</div> <div class="muted">Unsere öffentlichen OpenPGP-Schlüssel</div>
</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">+ Upload</button>
</div> </div>