Verify stored keychain SSH signatures
This commit is contained in:
parent
788522404d
commit
f7f14f6b27
10 changed files with 160 additions and 15 deletions
|
|
@ -2890,7 +2890,7 @@ pub fn handle_request(
|
|||
}
|
||||
|
||||
let signatures = if let Some(signing_key_path) = signing_key_path {
|
||||
let signer =
|
||||
let (signer, signer_public_key) =
|
||||
keychain_signer_from_paths(&signing_key_path, admin_key_path.as_deref())?;
|
||||
let mut signatures = Vec::new();
|
||||
for op in &ops {
|
||||
|
|
@ -2900,6 +2900,7 @@ pub fn handle_request(
|
|||
op,
|
||||
&signing_key_path,
|
||||
&signer,
|
||||
&signer_public_key,
|
||||
)?);
|
||||
}
|
||||
signatures
|
||||
|
|
@ -2911,10 +2912,13 @@ pub fn handle_request(
|
|||
}
|
||||
ControlRequest::KeychainStatus => {
|
||||
let view = geth_keychain::reduce_keychain_ops(&load_keychain_ops(&store)?);
|
||||
let signature_status = verify_keychain_signatures(&store, node)?;
|
||||
Ok(ControlResponse::KeychainStatus(KeychainStatusResponse {
|
||||
initialized: view.initialized,
|
||||
admin_keys: view.admin_keys.len(),
|
||||
signatures: store.list_keychain_signatures()?.len(),
|
||||
signatures: signature_status.total,
|
||||
verified_signatures: signature_status.verified,
|
||||
failed_signatures: signature_status.failed,
|
||||
users: view.users.len(),
|
||||
devices: view.devices.len(),
|
||||
nodes: view.nodes.len(),
|
||||
|
|
@ -4021,12 +4025,15 @@ fn store_keychain_op(store: &Store, op: &KeychainOp) -> Result<(), NodeError> {
|
|||
fn keychain_signer_from_paths(
|
||||
signing_key_path: &Path,
|
||||
admin_key_path: Option<&Path>,
|
||||
) -> Result<KeyId, NodeError> {
|
||||
) -> Result<(KeyId, String), NodeError> {
|
||||
let public_key_path = admin_key_path
|
||||
.map(Path::to_path_buf)
|
||||
.unwrap_or_else(|| Path::new(&format!("{}.pub", signing_key_path.display())).to_path_buf());
|
||||
let public_key = std::fs::read_to_string(public_key_path)?;
|
||||
Ok(KeyId::new(ssh_public_key_fingerprint(&public_key)))
|
||||
Ok((
|
||||
KeyId::new(ssh_public_key_fingerprint(&public_key)),
|
||||
public_key,
|
||||
))
|
||||
}
|
||||
|
||||
fn sign_keychain_op_with_ssh(
|
||||
|
|
@ -4035,6 +4042,7 @@ fn sign_keychain_op_with_ssh(
|
|||
op: &KeychainOp,
|
||||
signing_key_path: &Path,
|
||||
signer: &KeyId,
|
||||
signer_public_key: &str,
|
||||
) -> Result<KeychainOpSignature, NodeError> {
|
||||
geth_ssh_identity::ensure_ssh_keygen_available()?;
|
||||
let signature_dir = node.paths.home().join("keychain-signatures");
|
||||
|
|
@ -4062,6 +4070,7 @@ fn sign_keychain_op_with_ssh(
|
|||
let signature = KeychainOpSignature {
|
||||
op_id: op.id.clone(),
|
||||
signer: signer.clone(),
|
||||
signer_public_key: signer_public_key.to_owned(),
|
||||
namespace: geth_keychain::KEYCHAIN_SIGNATURE_NAMESPACE.to_owned(),
|
||||
signature: signature_bytes,
|
||||
created_at,
|
||||
|
|
@ -4069,6 +4078,7 @@ fn sign_keychain_op_with_ssh(
|
|||
store.insert_keychain_signature(&StoredKeychainSignature {
|
||||
op_id: signature.op_id.to_string(),
|
||||
signer: signature.signer.to_string(),
|
||||
signer_public_key: signature.signer_public_key.clone(),
|
||||
namespace: signature.namespace.clone(),
|
||||
signature: signature.signature.clone(),
|
||||
created_at_ms: signature.created_at.0,
|
||||
|
|
@ -4076,6 +4086,96 @@ fn sign_keychain_op_with_ssh(
|
|||
Ok(signature)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
struct KeychainSignatureStatus {
|
||||
total: usize,
|
||||
verified: usize,
|
||||
failed: usize,
|
||||
}
|
||||
|
||||
fn verify_keychain_signatures(
|
||||
store: &Store,
|
||||
node: &LocalNode,
|
||||
) -> Result<KeychainSignatureStatus, NodeError> {
|
||||
let ops = load_keychain_ops(store)?;
|
||||
let ops_by_id = ops
|
||||
.into_iter()
|
||||
.map(|op| (op.id.to_string(), op))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let signatures = store.list_keychain_signatures()?;
|
||||
let total = signatures.len();
|
||||
let mut verified = 0;
|
||||
let mut failed = 0;
|
||||
for signature in signatures {
|
||||
let Some(op) = ops_by_id.get(&signature.op_id) else {
|
||||
failed += 1;
|
||||
continue;
|
||||
};
|
||||
if verify_keychain_signature_with_ssh(node, op, &signature)? {
|
||||
verified += 1;
|
||||
} else {
|
||||
failed += 1;
|
||||
}
|
||||
}
|
||||
Ok(KeychainSignatureStatus {
|
||||
total,
|
||||
verified,
|
||||
failed,
|
||||
})
|
||||
}
|
||||
|
||||
fn verify_keychain_signature_with_ssh(
|
||||
node: &LocalNode,
|
||||
op: &KeychainOp,
|
||||
signature: &StoredKeychainSignature,
|
||||
) -> Result<bool, NodeError> {
|
||||
if signature.signer_public_key.trim().is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
if geth_ssh_identity::ensure_ssh_keygen_available().is_err() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let verify_dir = node.paths.home().join("keychain-signatures").join("verify");
|
||||
std::fs::create_dir_all(&verify_dir)?;
|
||||
let stable_id = geth_crypto::blake3_hex(
|
||||
format!(
|
||||
"{}\0{}\0{}\0{}",
|
||||
op.id, signature.signer, signature.namespace, signature.created_at_ms
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
let payload_path = verify_dir.join(format!("{stable_id}.payload"));
|
||||
let signature_path = verify_dir.join(format!("{stable_id}.sig"));
|
||||
let allowed_signers_path = verify_dir.join(format!("{stable_id}.allowed-signers"));
|
||||
std::fs::write(&payload_path, geth_keychain::keychain_signing_payload(op)?)?;
|
||||
std::fs::write(&signature_path, &signature.signature)?;
|
||||
std::fs::write(
|
||||
&allowed_signers_path,
|
||||
format!(
|
||||
"{} {}\n",
|
||||
signature.signer,
|
||||
signature.signer_public_key.trim()
|
||||
),
|
||||
)?;
|
||||
|
||||
let payload = std::fs::File::open(&payload_path)?;
|
||||
let output = std::process::Command::new("ssh-keygen")
|
||||
.arg("-Y")
|
||||
.arg("verify")
|
||||
.arg("-f")
|
||||
.arg(&allowed_signers_path)
|
||||
.arg("-I")
|
||||
.arg(&signature.signer)
|
||||
.arg("-n")
|
||||
.arg(&signature.namespace)
|
||||
.arg("-s")
|
||||
.arg(&signature_path)
|
||||
.stdin(std::process::Stdio::from(payload))
|
||||
.output()?;
|
||||
Ok(output.status.success())
|
||||
}
|
||||
|
||||
fn load_keychain_ops(store: &Store) -> Result<Vec<KeychainOp>, NodeError> {
|
||||
store
|
||||
.list_keychain_ops()?
|
||||
|
|
|
|||
Loading…
Reference in a new issue