coreason_meta_engineering_rust/
signer.rs1use std::env;
6use std::fs;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9use ed25519_dalek::{SigningKey, Signer};
10use rand::rngs::OsRng;
11
12pub fn canonicalize_source(source_code: &str) -> String {
14 let temp_name = format!("coresig_temp_{}.py", rand::random::<u64>());
16 let temp_path = env::temp_dir().join(temp_name);
17
18 if let Err(e) = fs::write(&temp_path, source_code) {
19 log::error!("Failed to write temp file for canonicalization: {}", e);
20 return fallback_canonicalization(source_code);
21 }
22
23 let python_exe = env::var("PYTHON").unwrap_or_else(|_| "python".to_string());
24
25 let workspace_root = env::var("COREASON_WORKSPACE_ROOT").unwrap_or_else(|_| ".".to_string());
27 let worker_module = "coreason_meta_engineering.validation_worker";
28
29 let mut cmd = Command::new(&python_exe);
30 cmd.arg("-m")
31 .arg(worker_module)
32 .arg("canonicalize")
33 .arg("--code-path")
34 .arg(&temp_path);
35
36 let python_path = format!("{}{}{}/src", workspace_root, if cfg!(windows) { ";" } else { ":" }, workspace_root);
38 cmd.env("PYTHONPATH", python_path);
39
40 let output = cmd.output();
41 let _ = fs::remove_file(&temp_path);
42
43 match output {
44 Ok(out) if out.status.success() => {
45 String::from_utf8_lossy(&out.stdout).into_owned()
46 }
47 _ => {
48 log::warn!("Python canonicalization failed; falling back to basic cleaning.");
49 fallback_canonicalization(source_code)
50 }
51 }
52}
53
54fn fallback_canonicalization(source_code: &str) -> String {
55 let mut clean_lines = Vec::new();
56 for line in source_code.lines() {
57 if line.trim().starts_with("# CORESIG:") {
58 continue;
59 }
60 clean_lines.push(line);
61 }
62 clean_lines.join("\n").replace("\r\n", "\n")
63}
64
65pub fn sign_source_code(source_code: &str, private_key_hex: &str) -> String {
68 let private_bytes = match hex::decode(private_key_hex.trim()) {
69 Ok(b) => b,
70 Err(e) => {
71 log::error!("Failed to decode private key hex: {}", e);
72 return source_code.to_string();
73 }
74 };
75
76 if private_bytes.len() != 32 {
77 log::error!("Ed25519 private key must be exactly 32 bytes (64 hex characters), got {}", private_bytes.len());
78 return source_code.to_string();
79 }
80
81 let mut key_arr = [0u8; 32];
82 key_arr.copy_from_slice(&private_bytes);
83 let signing_key = SigningKey::from_bytes(&key_arr);
84
85 let canonical_source = canonicalize_source(source_code);
86 let signature = signing_key.sign(canonical_source.as_bytes());
87 let signature_hex = hex::encode(signature.to_bytes());
88
89 let base_code = source_code.trim_end();
90 format!("{}\n\n# CORESIG: {}\n", base_code, signature_hex)
91}
92
93pub fn resolve_signing_key() -> Option<String> {
96 if let Ok(key_env) = env::var("COREASON_SIGNING_KEY") {
98 let key_env = key_env.trim();
99 let path = Path::new(key_env);
101 if path.is_file() {
102 if let Ok(content) = fs::read_to_string(path) {
103 return Some(content.trim().to_string());
104 }
105 }
106 return Some(key_env.to_string());
107 }
108
109 let vault_url = env::var("VAULT_ADDR");
111 let vault_token = env::var("VAULT_TOKEN");
112 if let (Ok(url), Ok(token)) = (vault_url, vault_token) {
113 log::info!("Attempting to fetch signing key from Vault...");
114 let client = reqwest::blocking::Client::new();
115 let api_url = format!("{}/v1/secret/data/coreason/identity", url.trim_end_matches('/'));
116 let response = client.get(&api_url)
117 .header("X-Vault-Token", token)
118 .send();
119
120 match response {
121 Ok(res) if res.status().is_success() => {
122 if let Ok(json) = res.json::<serde_json::Value>() {
123 if let Some(private_key) = json["data"]["data"]["private_key"].as_str() {
124 return Some(private_key.trim().to_string());
125 }
126 }
127 }
128 Ok(res) => log::warn!("Vault query returned non-success status: {}", res.status()),
129 Err(e) => log::warn!("Failed to query Vault: {}", e),
130 }
131 }
132
133 let home_dir = env::var("COREASON_HOME")
135 .map(PathBuf::from)
136 .ok()
137 .or_else(|| dirs::home_dir());
138
139 if let Some(home) = home_dir {
140 let dev_dir = home.join(".coreason");
141 let dev_key_path = dev_dir.join("dev_signing_key.hex");
142 let dev_pub_path = dev_dir.join("dev_signing_key.pub");
143
144 if dev_key_path.is_file() {
145 if let Ok(content) = fs::read_to_string(&dev_key_path) {
146 return Some(content.trim().to_string());
147 }
148 }
149
150 if let Err(e) = fs::create_dir_all(&dev_dir) {
152 log::warn!("Failed to create dev dir {:?}: {}", dev_dir, e);
153 return None;
154 }
155
156 let mut rng = OsRng;
157 let signing_key = SigningKey::generate(&mut rng);
158 let private_hex = hex::encode(signing_key.to_bytes());
159 let public_hex = hex::encode(signing_key.verifying_key().to_bytes());
160
161 if fs::write(&dev_key_path, &private_hex).is_ok() && fs::write(&dev_pub_path, &public_hex).is_ok() {
162 log::warn!(
163 "No COREASON_SIGNING_KEY found. Generated a new developer key-pair at:\n\
164 Private: {:?}\n\
165 Public: {:?}\n\
166 Please register the public key with your runtime verification engine.",
167 dev_key_path,
168 dev_pub_path
169 );
170 return Some(private_hex);
171 }
172 }
173
174 None
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_sign_source_code() {
183 let code = "print('hello')\n";
184 let private_bytes = [1u8; 32];
185 let private_hex = hex::encode(private_bytes);
186
187 let signed = sign_source_code(code, &private_hex);
188 assert!(signed.contains("print('hello')"));
189 assert!(signed.contains("# CORESIG:"));
190 }
191}