feat(license): Lizenz-System mit Ed25519-Verify gegen license.netcell-it.com
Portiert mail-gateway/internal/license (Verify, Cache, Trial, Signature) + DB-Mirror (internal/services/license) + REST-Handler (status/verify/key/clear) + UI-Page /license (Activate, Status, Limits, Features, Re-verify) + <LicenseBanner /> neben UpdateBanner (trial-expiring, expired, verify-failed) + Scheduler: täglich Re-verify (24h-Tick) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
52
internal/license/signature.go
Normal file
52
internal/license/signature.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package license
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// SignatureKey is an Ed25519 public key used to verify license-server
|
||||
// responses. Shipped as hardcoded base64 constants; multiple keys allow
|
||||
// rotation.
|
||||
type SignatureKey = ed25519.PublicKey
|
||||
|
||||
// Default signing keys, base64 of the raw 32-byte Ed25519 public key.
|
||||
// These must match the keys used by license.netcell-it.com to sign the
|
||||
// verify response's X-Signature header — they are intentionally identical
|
||||
// to the enconf/netcell-webpanel keys (same licensing backend).
|
||||
const (
|
||||
signingKeyPrimaryB64 = "uyXQLl8hFgI4rvvr5pfyF0SmFw1j2R849OL3HUZov5I="
|
||||
signingKeyNextB64 = "zSdKn799Fmu1KaZPYfkB5gDVqeU2doIUFWvmvXigN6M="
|
||||
)
|
||||
|
||||
// DefaultSigningKeys decodes the embedded base64 keys into Ed25519 public
|
||||
// keys. Invalid entries are skipped with a warning.
|
||||
func DefaultSigningKeys() []SignatureKey {
|
||||
var out []SignatureKey
|
||||
for _, b64 := range []string{signingKeyPrimaryB64, signingKeyNextB64} {
|
||||
raw, err := base64.StdEncoding.DecodeString(b64)
|
||||
if err != nil || len(raw) != ed25519.PublicKeySize {
|
||||
slog.Error("license: invalid embedded signing key", "base64", b64)
|
||||
continue
|
||||
}
|
||||
out = append(out, ed25519.PublicKey(raw))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// VerifySignature accepts base64-encoded Ed25519 signatures in the
|
||||
// X-Signature header of a license response and checks them against all
|
||||
// provided keys — success on any match.
|
||||
func VerifySignature(keys []SignatureKey, body []byte, signatureB64 string) bool {
|
||||
sig, err := base64.StdEncoding.DecodeString(signatureB64)
|
||||
if err != nil || len(sig) != ed25519.SignatureSize {
|
||||
return false
|
||||
}
|
||||
for _, pk := range keys {
|
||||
if ed25519.Verify(pk, body, sig) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user