feat(core): add device::fingerprint helper for SSH SHA256 fingerprints
Wraps ssh-key's PublicKey::fingerprint(HashAlg::Sha256). Output format matches ssh-keygen -lf and git verify-commit --raw stderr (SHA256:<43-char base64>). Used by the upcoming relicario-server verify-commit rewrite (audit S1). Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,16 @@ pub fn verify(public_key_openssh: &str, data: &[u8], signature_b64: &str) -> Res
|
||||
Ok(verifying_key.verify(data, &signature).is_ok())
|
||||
}
|
||||
|
||||
/// Compute the OpenSSH SHA-256 fingerprint of a public key.
|
||||
/// Output format matches `ssh-keygen -lf` and `git verify-commit --raw`:
|
||||
/// `SHA256:<43-char base64 without padding>`.
|
||||
pub fn fingerprint(public_key_openssh: &str) -> Result<String> {
|
||||
use ssh_key::HashAlg;
|
||||
let public = PublicKey::from_openssh(public_key_openssh)
|
||||
.map_err(|e| RelicarioError::DeviceKey(format!("parse public key: {e}")))?;
|
||||
Ok(public.fingerprint(HashAlg::Sha256).to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -132,4 +142,27 @@ mod tests {
|
||||
let sig = sign(&private, b"hello").unwrap();
|
||||
assert!(!verify(&other_public, b"hello", &sig).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fingerprint_matches_ssh_keygen_format() {
|
||||
let (_, public) = generate_keypair().unwrap();
|
||||
let fp = fingerprint(&public).unwrap();
|
||||
assert!(fp.starts_with("SHA256:"), "fingerprint should start with SHA256: prefix, got {fp}");
|
||||
let body = fp.strip_prefix("SHA256:").unwrap();
|
||||
assert_eq!(body.len(), 43, "SHA-256 fingerprint body is 43 base64 chars (no padding)");
|
||||
assert!(body.chars().all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '/'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fingerprint_is_deterministic() {
|
||||
let (_, public) = generate_keypair().unwrap();
|
||||
assert_eq!(fingerprint(&public).unwrap(), fingerprint(&public).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fingerprint_differs_per_key() {
|
||||
let (_, p1) = generate_keypair().unwrap();
|
||||
let (_, p2) = generate_keypair().unwrap();
|
||||
assert_ne!(fingerprint(&p1).unwrap(), fingerprint(&p2).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,4 +85,4 @@ pub mod import_lastpass;
|
||||
pub use import_lastpass::{parse_lastpass_csv, ImportWarning};
|
||||
|
||||
pub mod device;
|
||||
pub use device::{DeviceEntry, RevokedEntry, generate_keypair, sign, verify};
|
||||
pub use device::{fingerprint, DeviceEntry, RevokedEntry, generate_keypair, sign, verify};
|
||||
|
||||
Reference in New Issue
Block a user