aster/codesign/
signing.rs1use sha2::{Digest, Sha256, Sha384, Sha512};
4
5use super::keys::get_key;
6use super::types::*;
7
8pub fn hash_content(content: &str, algorithm: HashAlgorithm) -> String {
10 match algorithm {
11 HashAlgorithm::Sha256 => {
12 let mut hasher = Sha256::new();
13 hasher.update(content.as_bytes());
14 hex::encode(hasher.finalize())
15 }
16 HashAlgorithm::Sha384 => {
17 let mut hasher = Sha384::new();
18 hasher.update(content.as_bytes());
19 hex::encode(hasher.finalize())
20 }
21 HashAlgorithm::Sha512 => {
22 let mut hasher = Sha512::new();
23 hasher.update(content.as_bytes());
24 hex::encode(hasher.finalize())
25 }
26 }
27}
28
29pub fn sign_content(content: &str, key: &SigningKey) -> Option<CodeSignature> {
34 let private_key = key.private_key.as_ref()?;
35
36 let hash = hash_content(content, HashAlgorithm::Sha256);
37
38 use sha2::{Digest, Sha256};
40 let mut hasher = Sha256::new();
41 hasher.update(hash.as_bytes());
42 hasher.update(private_key.as_bytes());
43 let signature = hex::encode(hasher.finalize());
44
45 Some(CodeSignature {
46 hash,
47 algorithm: HashAlgorithm::Sha256,
48 timestamp: chrono::Utc::now().timestamp_millis(),
49 signed_by: Some(key.id.clone()),
50 signature: Some(signature),
51 })
52}
53
54pub fn verify_signature(content: &str, signature: &CodeSignature) -> bool {
56 let (sig, key) = match (&signature.signature, &signature.signed_by) {
57 (Some(sig), Some(signer)) => {
58 let key = match get_key(signer) {
59 Some(k) => k,
60 None => return false,
61 };
62 (sig.clone(), key)
63 }
64 _ => return false,
65 };
66
67 let hash = hash_content(content, signature.algorithm);
69 if hash != signature.hash {
70 return false;
71 }
72
73 let private_key = match &key.private_key {
75 Some(pk) => pk,
76 None => return false,
77 };
78
79 use sha2::{Digest, Sha256};
80 let mut hasher = Sha256::new();
81 hasher.update(hash.as_bytes());
82 hasher.update(private_key.as_bytes());
83 let expected_sig = hex::encode(hasher.finalize());
84
85 sig == expected_sig
86}
87
88pub fn sign_file(file_path: &str, key_id: Option<&str>) -> Option<SignedFile> {
90 use std::path::Path;
91
92 let absolute_path = Path::new(file_path)
93 .canonicalize()
94 .map(|p| p.to_string_lossy().to_string())
95 .unwrap_or_else(|_| file_path.to_string());
96
97 let content = std::fs::read_to_string(&absolute_path).ok()?;
98
99 let key = if let Some(id) = key_id {
101 get_key(id)
102 } else {
103 super::keys::get_signing_key()
104 };
105
106 let signature = if let Some(k) = key {
107 sign_content(&content, &k)?
108 } else {
109 CodeSignature {
111 hash: hash_content(&content, HashAlgorithm::Sha256),
112 algorithm: HashAlgorithm::Sha256,
113 timestamp: chrono::Utc::now().timestamp_millis(),
114 signed_by: None,
115 signature: None,
116 }
117 };
118
119 super::storage::cache_signature(&absolute_path, signature.clone());
121 super::storage::save_signatures();
122
123 Some(SignedFile {
124 path: absolute_path,
125 content,
126 signature,
127 })
128}
129
130pub fn verify_file(file_path: &str) -> VerifyResult {
132 use std::path::Path;
133
134 let absolute_path = Path::new(file_path)
135 .canonicalize()
136 .map(|p| p.to_string_lossy().to_string())
137 .unwrap_or_else(|_| file_path.to_string());
138
139 let content = match std::fs::read_to_string(&absolute_path) {
140 Ok(c) => c,
141 Err(_) => return VerifyResult::err("File not found"),
142 };
143
144 let signature = match super::storage::get_cached_signature(&absolute_path) {
146 Some(s) => s,
147 None => return VerifyResult::err("No signature found"),
148 };
149
150 let current_hash = hash_content(&content, signature.algorithm);
152 if current_hash != signature.hash {
153 return VerifyResult::err_with_sig("File has been modified", signature);
154 }
155
156 if signature.signature.is_some() && !verify_signature(&content, &signature) {
158 return VerifyResult::err_with_sig("Cryptographic signature invalid", signature);
159 }
160
161 VerifyResult::ok(signature)
162}