Skip to main content

winisland_plugin_api/packager/
signing.rs

1use ed25519_dalek::pkcs8::DecodePrivateKey;
2use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
3use sha2::{Digest, Sha256};
4use std::path::Path;
5
6/// Errors that can occur during signing operations.
7#[derive(Debug)]
8pub enum SigningError {
9    Io(std::io::Error),
10    Key(String),
11    Signature(String),
12}
13
14impl std::fmt::Display for SigningError {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match self {
17            Self::Io(e) => write!(f, "I/O error: {}", e),
18            Self::Key(msg) => write!(f, "Key error: {}", msg),
19            Self::Signature(msg) => write!(f, "Signature error: {}", msg),
20        }
21    }
22}
23
24impl std::error::Error for SigningError {}
25
26impl From<std::io::Error> for SigningError {
27    fn from(e: std::io::Error) -> Self {
28        Self::Io(e)
29    }
30}
31
32impl From<SigningError> for String {
33    fn from(e: SigningError) -> Self {
34        e.to_string()
35    }
36}
37
38/// Compute SHA-256 digest of a file.
39pub fn hash_file(path: &Path) -> Result<String, SigningError> {
40    let data = std::fs::read(path)?;
41    let mut hasher = Sha256::new();
42    hasher.update(&data);
43    Ok(hex::encode(hasher.finalize()))
44}
45
46/// Load an Ed25519 signing key from a PEM-encoded PKCS#8 file.
47pub fn load_signing_key(path: &Path) -> Result<SigningKey, SigningError> {
48    let pem = std::fs::read_to_string(path).map_err(|e| {
49        SigningError::Key(format!("Cannot read key file '{}': {}", path.display(), e))
50    })?;
51    let key = SigningKey::from_pkcs8_pem(&pem).map_err(|e| {
52        SigningError::Key(format!("Invalid PEM key in '{}': {}", path.display(), e))
53    })?;
54    Ok(key)
55}
56
57/// Load an Ed25519 signing key from a PEM-encoded environment variable.
58pub fn load_signing_key_from_env(var: &str) -> Result<SigningKey, SigningError> {
59    let pem = std::env::var(var)
60        .map_err(|_| SigningError::Key(format!("Environment variable '{}' not set", var)))?;
61    let key = SigningKey::from_pkcs8_pem(&pem)
62        .map_err(|e| SigningError::Key(format!("Invalid PEM key in env '{}': {}", var, e)))?;
63    Ok(key)
64}
65
66/// Sign a payload with the given signing key.
67/// Returns the signature as a lowercase hex string.
68pub fn sign_payload(key: &SigningKey, payload: &[u8]) -> String {
69    let sig: Signature = key.sign(payload);
70    hex::encode(sig.to_bytes())
71}
72
73/// Verify a signature against a payload and public key.
74pub fn verify_signature(
75    public_key: &VerifyingKey,
76    payload: &[u8],
77    signature_hex: &str,
78) -> Result<(), SigningError> {
79    let sig_bytes = hex::decode(signature_hex)
80        .map_err(|_| SigningError::Signature("Invalid hex signature".into()))?;
81    let sig = Signature::from_slice(&sig_bytes)
82        .map_err(|_| SigningError::Signature("Invalid signature bytes".into()))?;
83    public_key
84        .verify_strict(payload, &sig)
85        .map_err(|_| SigningError::Signature("Signature verification failed".into()))
86}