Add binary OpenSSH KRL export
This commit is contained in:
parent
5751748458
commit
b9e7c61e67
11 changed files with 209 additions and 20 deletions
|
|
@ -13,3 +13,4 @@ geth-types = { path = "../geth-types" }
|
|||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ pub const SSH_CERT_ISSUANCE_NAMESPACE: &str = "geth.ssh-cert-issuance.v1@geth.lo
|
|||
pub const SSH_REVOCATION_LIST_NAMESPACE: &str = "geth.ssh-revocation-list.v1@geth.local";
|
||||
|
||||
pub fn ensure_ssh_keygen_available() -> Result<(), SshIdentityError> {
|
||||
let status = Command::new("ssh-keygen").arg("-?").status();
|
||||
match status {
|
||||
let output = Command::new("ssh-keygen").arg("-?").output();
|
||||
match output {
|
||||
Ok(_) => Ok(()),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
Err(SshIdentityError::SshKeygenUnavailable)
|
||||
|
|
@ -210,6 +210,7 @@ pub struct SshRevocationEntry {
|
|||
pub enum SshRevocationExportFormat {
|
||||
Jsonl,
|
||||
OpenSshKrlSpec,
|
||||
OpenSshKrl,
|
||||
}
|
||||
|
||||
impl SshRevocationExportFormat {
|
||||
|
|
@ -218,6 +219,7 @@ impl SshRevocationExportFormat {
|
|||
match self {
|
||||
Self::Jsonl => "jsonl",
|
||||
Self::OpenSshKrlSpec => "openssh-krl-spec",
|
||||
Self::OpenSshKrl => "openssh-krl",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -235,6 +237,7 @@ impl FromStr for SshRevocationExportFormat {
|
|||
match value {
|
||||
"jsonl" => Ok(Self::Jsonl),
|
||||
"openssh-krl-spec" | "krl-spec" => Ok(Self::OpenSshKrlSpec),
|
||||
"openssh-krl" | "krl" => Ok(Self::OpenSshKrl),
|
||||
_ => Err(SshIdentityError::InvalidRevocationExportFormat(
|
||||
value.to_owned(),
|
||||
)),
|
||||
|
|
@ -331,6 +334,34 @@ pub fn openssh_krl_spec(entries: &[SshRevocationEntry]) -> Result<String, SshIde
|
|||
Ok(spec)
|
||||
}
|
||||
|
||||
pub fn write_openssh_krl(
|
||||
entries: &[SshRevocationEntry],
|
||||
out: &Path,
|
||||
ca_public: Option<&Path>,
|
||||
) -> Result<(), SshIdentityError> {
|
||||
ensure_ssh_keygen_available()?;
|
||||
if let Some(parent) = out.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let spec = openssh_krl_spec(entries)?;
|
||||
let spec_path = out.with_extension("geth-krl-spec.tmp");
|
||||
std::fs::write(&spec_path, spec)?;
|
||||
let mut command = Command::new("ssh-keygen");
|
||||
command.arg("-k").arg("-f").arg(out);
|
||||
if let Some(ca_public) = ca_public {
|
||||
command.arg("-s").arg(ca_public);
|
||||
}
|
||||
command.arg(&spec_path);
|
||||
let output = command.output()?;
|
||||
let _ = std::fs::remove_file(&spec_path);
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_owned();
|
||||
return Err(SshIdentityError::SshKeygenFailed(stderr));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn openssh_krl_spec_line(entry: &SshRevocationEntry) -> Result<String, SshIdentityError> {
|
||||
let target = entry.target.trim();
|
||||
if target.is_empty() || target.bytes().any(|byte| byte == b'\n' || byte == b'\r') {
|
||||
|
|
@ -351,6 +382,8 @@ pub fn openssh_krl_spec_line(entry: &SshRevocationEntry) -> Result<String, SshId
|
|||
pub enum SshIdentityError {
|
||||
#[error("ssh-keygen failed or is unavailable")]
|
||||
SshKeygenUnavailable,
|
||||
#[error("ssh-keygen failed: {0}")]
|
||||
SshKeygenFailed(String),
|
||||
#[error("invalid ssh certificate kind: {0}")]
|
||||
InvalidCertKind(String),
|
||||
#[error("invalid ssh certificate request status: {0}")]
|
||||
|
|
@ -465,4 +498,53 @@ mod tests {
|
|||
|
||||
assert!(openssh_krl_spec_line(&entry).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openssh_krl_binary_export_revokes_public_key_when_ssh_keygen_available() {
|
||||
if ensure_ssh_keygen_available().is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let key_path = dir.path().join("revoked");
|
||||
let status = Command::new("ssh-keygen")
|
||||
.arg("-q")
|
||||
.arg("-t")
|
||||
.arg("ed25519")
|
||||
.arg("-N")
|
||||
.arg("")
|
||||
.arg("-f")
|
||||
.arg(&key_path)
|
||||
.status()
|
||||
.expect("run ssh-keygen");
|
||||
assert!(status.success());
|
||||
|
||||
let public_key_path = key_path.with_extension("pub");
|
||||
let public_key = std::fs::read_to_string(&public_key_path).expect("read public key");
|
||||
let entry = SshRevocationEntry {
|
||||
id: "ssh-revocation:key".into(),
|
||||
kind: SshRevocationKind::PublicKey,
|
||||
target: public_key,
|
||||
reason: Some("test".to_owned()),
|
||||
created_at: UnixMillis(1),
|
||||
published: true,
|
||||
};
|
||||
let krl_path = dir.path().join("revoked.krl");
|
||||
|
||||
write_openssh_krl(&[entry], &krl_path, None).expect("write krl");
|
||||
|
||||
let output = Command::new("ssh-keygen")
|
||||
.arg("-Q")
|
||||
.arg("-f")
|
||||
.arg(&krl_path)
|
||||
.arg(&public_key_path)
|
||||
.output()
|
||||
.expect("query krl");
|
||||
assert!(!output.status.success());
|
||||
assert!(
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
.to_ascii_lowercase()
|
||||
.contains("revoked")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue