1use anyhow::{Context, Result, bail};
9
10pub fn normalize_sshsig_armor(pem: &str) -> String {
18 let body: String = pem
19 .lines()
20 .filter(|line| !line.starts_with("-----"))
21 .map(str::trim)
22 .collect();
23 let mut normalized = String::from("-----BEGIN SSH SIGNATURE-----\n");
24 for chunk in body.as_bytes().chunks(70) {
25 normalized.push_str(&String::from_utf8_lossy(chunk));
27 normalized.push('\n');
28 }
29 normalized.push_str("-----END SSH SIGNATURE-----\n");
30 normalized
31}
32
33pub fn split_signed_commit(raw: &[u8]) -> Result<Option<(Vec<u8>, String)>> {
39 let text = std::str::from_utf8(raw).context("commit object is not UTF-8")?;
40 let Some((headers, body)) = text.split_once("\n\n") else {
41 bail!("malformed commit object: no header/body separator");
42 };
43
44 let mut kept_headers: Vec<&str> = Vec::new();
45 let mut signature_lines: Vec<&str> = Vec::new();
46 let mut in_gpgsig = false;
47 for line in headers.split('\n') {
48 if let Some(first) = line.strip_prefix("gpgsig ") {
49 in_gpgsig = true;
50 signature_lines.push(first);
51 } else if in_gpgsig && let Some(continuation) = line.strip_prefix(' ') {
52 signature_lines.push(continuation);
53 } else {
54 in_gpgsig = false;
55 kept_headers.push(line);
56 }
57 }
58
59 if signature_lines.is_empty() {
60 return Ok(None);
61 }
62
63 let mut payload = kept_headers.join("\n").into_bytes();
64 payload.extend_from_slice(b"\n\n");
65 payload.extend_from_slice(body.as_bytes());
66
67 let mut pem = signature_lines.join("\n");
68 pem.push('\n');
69 Ok(Some((payload, pem)))
70}