1use std::fs;
4use std::path::{Path, PathBuf};
5
6use andysu_core::keys::{PublicKey, SecretKey};
7use andysu_core::sig::Cert;
8
9use crate::{random, write, R};
10
11fn write_private(p: &Path, pem: &str) -> R<()> {
12 if p.exists() {
13 return Err(format!("{} exists; refusing to overwrite a private key", p.display()));
14 }
15 write(p, pem.as_bytes())?;
16 #[cfg(unix)]
17 {
18 use std::os::unix::fs::PermissionsExt;
19 fs::set_permissions(p, fs::Permissions::from_mode(0o600)).map_err(|e| e.to_string())?;
20 }
21 Ok(())
22}
23
24pub struct Generated {
25 pub private: PathBuf,
26 pub public: PathBuf,
27 pub id: String,
28}
29
30pub fn keygen(dir: &Path, name: &str) -> R<Generated> {
32 let sk = SecretKey::from_seed(&random::<32>());
33 let private = dir.join(format!("{name}-root.key"));
34 let public = dir.join(format!("{name}-public.pem"));
35 if public.exists() {
36 return Err(format!("{} exists", public.display()));
37 }
38 write_private(&private, &sk.to_pem())?;
39 write(&public, sk.public().to_pem().as_bytes())?;
40 Ok(Generated { private, public, id: sk.public().id() })
41}
42
43pub fn load_secret(p: &Path) -> R<SecretKey> {
44 let s = fs::read_to_string(p).map_err(|e| format!("read {}: {e}", p.display()))?;
45 SecretKey::from_pem(&s).map_err(|e| e.to_string())
46}
47
48pub fn load_public(p: &Path) -> R<PublicKey> {
49 let s = fs::read_to_string(p).map_err(|e| format!("read {}: {e}", p.display()))?;
50 PublicKey::parse_any(&s).map_err(|e| e.to_string())
51}
52
53pub fn release_key(dir: &Path, name: &str, root: &SecretKey, not_before: i64, days: i64) -> R<(PathBuf, PathBuf)> {
55 let sk = SecretKey::from_seed(&random::<32>());
56 let kp = dir.join(format!("{name}-release.key"));
57 let cp = dir.join(format!("{name}-release.cert.json"));
58 write_private(&kp, &sk.to_pem())?;
59 let cert = Cert::issue(root, &sk.public(), not_before, not_before + days * 86400);
60 write(&cp, serde_json::to_string_pretty(&cert).unwrap().as_bytes())?;
61 Ok((kp, cp))
62}
63
64pub fn load_cert(p: &Path) -> R<Cert> {
65 serde_json::from_slice(&crate::read(p)?).map_err(|e| format!("{}: {e}", p.display()))
66}
67
68fn file_message(path: &Path) -> R<Vec<u8>> {
71 let (sha, size) = andysu_core::hash::sha256_file(path).map_err(|e| format!("{}: {e}", path.display()))?;
72 Ok(format!("andysu-file-v1\0{sha}\0{size}").into_bytes())
73}
74
75pub fn sign_file(path: &Path, key: &SecretKey, cert: Option<Cert>) -> R<PathBuf> {
77 let env = andysu_core::sig::sign(&file_message(path)?, key, cert).map_err(|e| e.to_string())?;
78 let out = PathBuf::from(format!("{}.sig", path.display()));
79 write(&out, &env)?;
80 Ok(out)
81}
82
83pub fn verify_file(path: &Path, sig: Option<&Path>, roots: &[PublicKey], now: i64) -> R<andysu_core::sig::Verified> {
85 let sig = sig.map(Path::to_path_buf).unwrap_or_else(|| PathBuf::from(format!("{}.sig", path.display())));
86 let env = crate::read(&sig)?;
87 andysu_core::sig::verify(&file_message(path)?, &env, roots, now).map_err(|e| format!("{}: {e}", path.display()))
88}