fix(host): verify fingerprint and use distinct error for key parsing

- Adds ErrPublicKeyParse (510010) so TrustHostKey returns a semantically
  correct error instead of reusing ErrHostKeyMismatch's expected/got template.
- The TrustHostKey HTTP handler now recomputes the SHA256 fingerprint of
  the submitted public key and rejects requests where the client-confirmed
  fingerprint does not match. Closes a security gap where a tampered
  request body could install an unverified key in known_hosts.
This commit is contained in:
Hintay
2026-05-21 08:42:31 +09:00
parent 4af2a371c9
commit 8eb7fb141f
3 changed files with 24 additions and 1 deletions
+22
View File
@@ -11,6 +11,7 @@ import (
"github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
gossh "golang.org/x/crypto/ssh"
)
// Preview renders all snippets from the posted SetupParams (or current
@@ -103,11 +104,32 @@ type knownHostRequest struct {
}
// TrustHostKey appends a known_hosts entry after the user confirms a fingerprint.
// It recomputes the SHA256 fingerprint of the submitted public key and rejects
// requests where the client-provided fingerprint does not match.
func TrustHostKey(c *gin.Context) {
var req knownHostRequest
if !cosy.BindAndValid(c, &req) {
return
}
// Parse the public key so we can recompute its fingerprint and reject
// requests where the client-provided fingerprint disagrees with the key.
parsed, _, _, _, err := gossh.ParseAuthorizedKey([]byte(req.PublicKey))
if err != nil {
cosy.ErrHandler(c, cosy.WrapErrorWithParams(hostssh.ErrPublicKeyParse, err.Error()))
return
}
actual := gossh.FingerprintSHA256(parsed)
// Require an exact match of the SHA256: form we computed.
if req.Fingerprint != actual {
c.JSON(http.StatusBadRequest, gin.H{
"message": "fingerprint mismatch",
"expected": actual,
"got": req.Fingerprint,
})
return
}
path := settings.NginxSettings.HostKnownHostsPath
if path == "" {
path = "/etc/nginx-ui/known_hosts"
+1
View File
@@ -14,4 +14,5 @@ var (
ErrSessionFailed = e.New(510007, "failed to open ssh session: {0}")
ErrKnownHostsRead = e.New(510008, "failed to read known_hosts: {0}")
ErrKnownHostsWrite = e.New(510009, "failed to write known_hosts: {0}")
ErrPublicKeyParse = e.New(510010, "failed to parse public key: {0}")
)
+1 -1
View File
@@ -14,7 +14,7 @@ func TrustHostKey(path, hostPort, publicKeyOpenSSH string) error {
}
parsed, _, _, _, err := gossh.ParseAuthorizedKey([]byte(publicKeyOpenSSH))
if err != nil {
return cosy.WrapErrorWithParams(ErrHostKeyMismatch, "parse public key", err.Error())
return cosy.WrapErrorWithParams(ErrPublicKeyParse, err.Error())
}
return kh.Trust(hostPort, parsed)
}