feat(security): SSHFingerprintSHA256 helper (T02.1, REQ-058)
---ci--- project: orca phase: 2 milestone: v0.8 status: execute ---/ci---
This commit is contained in:
@@ -26,6 +26,17 @@ const (
|
||||
sshPubFile = "orca_ssh_key.pub"
|
||||
)
|
||||
|
||||
// SSHFingerprintSHA256 returns the canonical SSH public-key fingerprint
|
||||
// in the form `SHA256:base64` (no trailing padding), as produced by
|
||||
// `ssh-keygen -lf` and OpenSSH's host-key verification prompts. This is
|
||||
// a thin wrapper over ssh.FingerprintSHA256 (AD-027) for use by the
|
||||
// proxmox bootstrap pinned-host-key callback (REQ-058) and any other
|
||||
// SSH-domain identity checks. Do NOT reuse security.Fingerprint — that
|
||||
// returns an X.509 DER hex digest (different domain; RESEARCH §2.2).
|
||||
func SSHFingerprintSHA256(pubKey ssh.PublicKey) string {
|
||||
return ssh.FingerprintSHA256(pubKey)
|
||||
}
|
||||
|
||||
// GenerateOrLoadSSHKey returns the orca SSH keypair, generating it
|
||||
// lazily on first call (D-037). The key is Ed25519 (smaller, faster,
|
||||
// more secure than RSA for SSH auth), persisted as PKCS8 PEM to
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -91,3 +93,43 @@ func TestGenerateOrLoadSSHKey_CreatesDir(t *testing.T) {
|
||||
t.Errorf("nested dir not created: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHFingerprintSHA256_Ed25519(t *testing.T) {
|
||||
pub, _, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("ed25519 gen: %v", err)
|
||||
}
|
||||
sshPub, err := ssh.NewPublicKey(pub)
|
||||
if err != nil {
|
||||
t.Fatalf("new pubkey: %v", err)
|
||||
}
|
||||
|
||||
got := SSHFingerprintSHA256(sshPub)
|
||||
|
||||
// Canonical form: SHA256: followed by unpadded base64.
|
||||
if !strings.HasPrefix(got, "SHA256:") {
|
||||
t.Fatalf("fingerprint = %q, want SHA256: prefix", got)
|
||||
}
|
||||
// Must match the reference implementation exactly.
|
||||
want := ssh.FingerprintSHA256(sshPub)
|
||||
if got != want {
|
||||
t.Errorf("SSHFingerprintSHA256 = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHFingerprintSHA256_StableAcrossCalls(t *testing.T) {
|
||||
pub, _, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("ed25519 gen: %v", err)
|
||||
}
|
||||
sshPub, err := ssh.NewPublicKey(pub)
|
||||
if err != nil {
|
||||
t.Fatalf("new pubkey: %v", err)
|
||||
}
|
||||
|
||||
a := SSHFingerprintSHA256(sshPub)
|
||||
b := SSHFingerprintSHA256(sshPub)
|
||||
if a != b {
|
||||
t.Errorf("fingerprint not stable: %q vs %q", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user