bugfix + upload-security hinzugefügt
All checks were successful
release-tag / release-image (push) Successful in 1m35s
All checks were successful
release-tag / release-image (push) Successful in 1m35s
This commit is contained in:
3
go.mod
3
go.mod
@@ -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
126
main.go
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user