-
Notifications
You must be signed in to change notification settings - Fork 7
/
ssh_signer.rs
98 lines (90 loc) · 3.28 KB
/
ssh_signer.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use super::signer_error::SignerError;
use crate::ps::private::utils;
use std::{
fs::File,
io::{self, Write},
path::PathBuf,
};
use tempfile::tempdir;
pub fn ssh_signer(
signing_key: String,
program: Option<String>,
) -> impl Fn(String) -> Result<String, SignerError> {
move |commit_string: String| {
ssh_sign_string(commit_string, signing_key.clone(), program.clone())
.map_err(|e| SignerError::Signing(e.into()))
}
}
#[derive(Debug)]
enum SshSignStringError {
CreateTempDirFailed(io::Error),
CreateTempFileFailed(io::Error),
WriteTempFileFailed(io::Error),
TempPathToStrFailed,
Unhandled(Box<dyn std::error::Error>),
}
impl std::fmt::Display for SshSignStringError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SshSignStringError::CreateTempDirFailed(e) => write!(f, "{}", e),
SshSignStringError::CreateTempFileFailed(e) => write!(f, "{}", e),
SshSignStringError::WriteTempFileFailed(e) => write!(f, "{}", e),
SshSignStringError::TempPathToStrFailed => {
write!(f, "Failed to convert temp path to string")
}
SshSignStringError::Unhandled(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for SshSignStringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::CreateTempDirFailed(e) => Some(e),
Self::CreateTempFileFailed(e) => Some(e),
Self::WriteTempFileFailed(e) => Some(e),
Self::TempPathToStrFailed => None,
Self::Unhandled(e) => Some(e.as_ref()),
}
}
}
fn ssh_sign_string(
commit: String,
signing_key: String,
program: Option<String>,
) -> Result<String, SshSignStringError> {
let prog = program.unwrap_or("ssh-keygen".to_string());
let dir = tempdir().map_err(SshSignStringError::CreateTempDirFailed)?;
// keep the binding alive so the path doesn't get dropped
let dir_binding = dir.path().join(".tmp_signing_key");
let path = signing_key_path(&dir_binding, &signing_key)?;
let output = utils::execute_with_input_and_output(
&commit,
&prog,
&["-Y", "sign", "-n", "git", "-q", "-f", path],
)
.map_err(|e| SshSignStringError::Unhandled(e.into()))?;
String::from_utf8(output.stdout).map_err(|e| SshSignStringError::Unhandled(e.into()))
}
fn literal_ssh_key(signing_key_config: &str) -> Option<&str> {
if signing_key_config.starts_with("ssh-") {
Some(signing_key_config)
} else if let Some(stripped) = signing_key_config.strip_prefix("key::") {
Some(stripped)
} else {
None
}
}
// If the signing key is a literal SSH key, write it to a temporary file and return the path.
fn signing_key_path<'a>(
path: &'a PathBuf,
signing_key_config: &'a str,
) -> Result<&'a str, SshSignStringError> {
match literal_ssh_key(signing_key_config) {
Some(literal) => {
let mut file = File::create(path).map_err(SshSignStringError::CreateTempFileFailed)?;
writeln!(file, "{}", literal).map_err(SshSignStringError::WriteTempFileFailed)?;
path.to_str().ok_or(SshSignStringError::TempPathToStrFailed)
}
None => Ok(signing_key_config),
}
}