mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-02 13:01:29 +02:00
Harden VNC server, IPC, and management plumbing
This commit is contained in:
@@ -28,6 +28,37 @@ const (
|
||||
MetaExpiresAt = "expires_at"
|
||||
)
|
||||
|
||||
// ShortKeyFingerprint formats a hex-encoded Noise_IK static pubkey as a
|
||||
// short, eyeball-able fingerprint to display in the approval dialog.
|
||||
// The dashboard-supplied display name attached to a SessionPubKey isn't
|
||||
// cryptographically asserted by the connecting client, so the prompt
|
||||
// must also show something that IS: the key fingerprint, a hash of
|
||||
// the static public key the client just proved possession of during the
|
||||
// Noise handshake. Returns the empty string when the input is too short
|
||||
// to plausibly be a hex pubkey, so the row is omitted rather than
|
||||
// rendered as a misleading partial.
|
||||
//
|
||||
// Output format: 16 hex chars grouped as XXXX-XXXX-XXXX-XXXX (64 bits of
|
||||
// fingerprint, resistant to random-prefix collisions and easy for a human
|
||||
// to compare with an out-of-band reference).
|
||||
func ShortKeyFingerprint(hexKey string) string {
|
||||
if len(hexKey) < 8 {
|
||||
return ""
|
||||
}
|
||||
src := hexKey
|
||||
if len(src) > 16 {
|
||||
src = src[:16]
|
||||
}
|
||||
var out []byte
|
||||
for i, c := range src {
|
||||
if i > 0 && i%4 == 0 {
|
||||
out = append(out, '-')
|
||||
}
|
||||
out = append(out, byte(c))
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// Kind values for the well-known prompt subjects. New subsystems should
|
||||
// add a constant here so the UI can dispatch on a known string.
|
||||
const (
|
||||
|
||||
62
client/internal/approval/fingerprint_test.go
Normal file
62
client/internal/approval/fingerprint_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package approval
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestShortKeyFingerprint locks in the format the VNC approval prompt
|
||||
// shows to the user. The fingerprint is the user's only cryptographic
|
||||
// anchor against a malicious management server that pushes a spoofed
|
||||
// display name, so accidental changes to its format would silently
|
||||
// undermine that defence.
|
||||
func TestShortKeyFingerprint(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "full_32_byte_pubkey",
|
||||
in: "0123456789abcdeffedcba9876543210ffeeddccbbaa99887766554433221100",
|
||||
want: "0123-4567-89ab-cdef",
|
||||
},
|
||||
{
|
||||
name: "exactly_16_chars",
|
||||
in: "0123456789abcdef",
|
||||
want: "0123-4567-89ab-cdef",
|
||||
},
|
||||
{
|
||||
name: "borderline_8_chars",
|
||||
in: "01234567",
|
||||
want: "0123-4567",
|
||||
},
|
||||
{
|
||||
name: "too_short_returns_empty",
|
||||
in: "0123",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty_returns_empty",
|
||||
in: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := ShortKeyFingerprint(tc.in)
|
||||
if got != tc.want {
|
||||
t.Fatalf("ShortKeyFingerprint(%q) = %q, want %q", tc.in, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestShortKeyFingerprint_DistinctKeysDistinctOutputs guards against a
|
||||
// formatting bug that would collapse different prefixes onto the same
|
||||
// displayed fingerprint and let an attacker substitute their pubkey for
|
||||
// a victim's while keeping the prompt visually identical.
|
||||
func TestShortKeyFingerprint_DistinctKeysDistinctOutputs(t *testing.T) {
|
||||
a := ShortKeyFingerprint("0123456789abcdef" + "rest_of_pubkey_ignored")
|
||||
b := ShortKeyFingerprint("0123456789abcde0" + "rest_of_pubkey_ignored")
|
||||
if a == b {
|
||||
t.Fatalf("expected distinct outputs for distinct prefixes, both = %q", a)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user