1use base64::{engine::general_purpose::STANDARD, Engine};
2use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
3use std::path::Path;
4
5#[derive(Debug, thiserror::Error)]
6pub enum UpdateSignError {
7 #[error("invalid private key: {0}")]
8 InvalidPrivateKey(String),
9 #[error("invalid public key: {0}")]
10 InvalidPublicKey(String),
11 #[error("invalid signature: {0}")]
12 InvalidSignature(String),
13 #[error("signature verification failed")]
14 VerificationFailed,
15 #[error("io error: {0}")]
16 Io(#[from] std::io::Error),
17}
18
19pub fn sign_bytes(data: &[u8], private_key_b64: &str) -> Result<String, UpdateSignError> {
20 let key_bytes = STANDARD
21 .decode(private_key_b64)
22 .map_err(|e| UpdateSignError::InvalidPrivateKey(e.to_string()))?;
23 let key_array: [u8; 32] = key_bytes
24 .try_into()
25 .map_err(|_| UpdateSignError::InvalidPrivateKey("expected 32 bytes".into()))?;
26 let signing_key = SigningKey::from_bytes(&key_array);
27 let signature = signing_key.sign(data);
28 Ok(STANDARD.encode(signature.to_bytes()))
29}
30
31pub fn verify_bytes(
32 data: &[u8],
33 signature_b64: &str,
34 public_key_b64: &str,
35) -> Result<(), UpdateSignError> {
36 let pub_bytes = STANDARD
37 .decode(public_key_b64)
38 .map_err(|e| UpdateSignError::InvalidPublicKey(e.to_string()))?;
39 let pub_array: [u8; 32] = pub_bytes
40 .try_into()
41 .map_err(|_| UpdateSignError::InvalidPublicKey("expected 32 bytes".into()))?;
42 let verifying_key = VerifyingKey::from_bytes(&pub_array)
43 .map_err(|e| UpdateSignError::InvalidPublicKey(e.to_string()))?;
44
45 let sig_bytes = STANDARD
46 .decode(signature_b64)
47 .map_err(|e| UpdateSignError::InvalidSignature(e.to_string()))?;
48 let sig_array: [u8; 64] = sig_bytes
49 .try_into()
50 .map_err(|_| UpdateSignError::InvalidSignature("expected 64 bytes".into()))?;
51 let signature = Signature::from_bytes(&sig_array);
52
53 verifying_key
54 .verify(data, &signature)
55 .map_err(|_| UpdateSignError::VerificationFailed)
56}
57
58pub fn sign_file(
59 archive_path: &Path,
60 sig_path: &Path,
61 private_key_b64: &str,
62) -> Result<(), UpdateSignError> {
63 let data = std::fs::read(archive_path)?;
64 let signature_b64 = sign_bytes(&data, private_key_b64)?;
65 std::fs::write(sig_path, format!("{signature_b64}\n"))?;
66 Ok(())
67}
68
69pub fn verify_file(
70 archive_path: &Path,
71 sig_path: &Path,
72 public_key_b64: &str,
73) -> Result<(), UpdateSignError> {
74 let data = std::fs::read(archive_path)?;
75 let signature_b64 = std::fs::read_to_string(sig_path)?;
76 verify_bytes(&data, signature_b64.trim(), public_key_b64)
77}