coreason_runtime_rust/
license.rs1use ed25519_dalek::{Signature, Verifier, VerifyingKey};
5use serde::{Deserialize, Serialize};
6use std::convert::TryInto;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9#[derive(Serialize, Deserialize, Debug, Clone)]
10pub struct CommercialOverrideReceipt {
11 pub license_tier: String,
12 pub signer_did: String,
13 pub signature_algorithm: String,
14 pub credential_format: String,
15 pub distr_license_cid: String,
16 pub hardware_zk_proof: Option<String>,
17 pub issued_at_epoch: u64,
18 pub expires_at_epoch: u64,
19 pub exp: u64,
20 pub iat: u64,
21 pub entitlements: Vec<String>,
22 pub network_mode: String,
23 pub federation_enabled: bool,
24 pub signature: Option<String>,
25 #[serde(default = "default_tenant_cid")]
26 pub tenant_cid: String,
27}
28
29fn default_tenant_cid() -> String {
30 "coreason-tenant-default".to_string()
31}
32
33impl CommercialOverrideReceipt {
34 pub fn verify_signature(&self, public_key_hex: &str) -> Result<bool, &'static str> {
35 let signature_hex = match &self.signature {
36 Some(sig) => sig,
37 None => return Err("Missing signature in receipt"),
38 };
39
40 let message = format!("{}:{}", self.tenant_cid, self.expires_at_epoch);
41
42 let public_bytes = hex::decode(public_key_hex).map_err(|_| "Invalid hex in public key")?;
43 let signature_bytes = hex::decode(signature_hex).map_err(|_| "Invalid hex in signature")?;
44
45 let public_array: [u8; 32] = public_bytes
46 .as_slice()
47 .try_into()
48 .map_err(|_| "Public key must be 32 bytes")?;
49 let signature_array: [u8; 64] = signature_bytes
50 .as_slice()
51 .try_into()
52 .map_err(|_| "Signature must be 64 bytes")?;
53
54 let verifying_key =
55 VerifyingKey::from_bytes(&public_array).map_err(|_| "Invalid public key")?;
56 let signature = Signature::from_bytes(&signature_array);
57
58 Ok(verifying_key.verify(message.as_bytes(), &signature).is_ok())
59 }
60
61 pub fn is_valid_epoch(&self) -> bool {
62 let start = SystemTime::now();
63 let since_the_epoch = start
64 .duration_since(UNIX_EPOCH)
65 .expect("Time went backwards")
66 .as_secs();
67
68 self.expires_at_epoch > since_the_epoch && since_the_epoch >= self.issued_at_epoch
69 }
70}
71
72#[derive(Debug, Clone)]
76pub enum LicenseError {
77 TemporalExpiration(String),
78 CryptographicVerification(String),
79 HardwareFingerprint(String),
80 ProductionGuard(String),
81}
82
83impl std::fmt::Display for LicenseError {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 match self {
86 LicenseError::TemporalExpiration(msg) => write!(f, "TemporalExpiration: {}", msg),
87 LicenseError::CryptographicVerification(msg) => {
88 write!(f, "CryptographicVerification: {}", msg)
89 }
90 LicenseError::HardwareFingerprint(msg) => {
91 write!(f, "HardwareFingerprint: {}", msg)
92 }
93 LicenseError::ProductionGuard(msg) => write!(f, "ProductionGuard: {}", msg),
94 }
95 }
96}
97
98impl std::error::Error for LicenseError {}
99
100pub struct LicenseVerifier {
112 local_fingerprint: Option<String>,
113}
114
115impl LicenseVerifier {
116 pub fn new(local_cluster_fingerprint_hash: Option<String>) -> Self {
117 Self {
118 local_fingerprint: local_cluster_fingerprint_hash,
119 }
120 }
121
122 fn verify_zk_snark(&self, proof: &str) -> Result<bool, LicenseError> {
127 if proof.is_empty() {
128 return Ok(true); }
130
131 let local_fp = self.local_fingerprint.as_deref().ok_or_else(|| {
132 LicenseError::HardwareFingerprint(
133 "Receipt requires hardware binding, but no local fingerprint provided.".into(),
134 )
135 })?;
136
137 use sha2::{Digest, Sha256};
139 let mut hasher = Sha256::new();
140 hasher.update(local_fp.as_bytes());
141 let expected_hash = format!("{:x}", hasher.finalize());
142
143 if proof.trim() == expected_hash {
144 Ok(true)
145 } else {
146 Err(LicenseError::HardwareFingerprint(
147 "Hardware fingerprint mismatch: proof does not match local fingerprint hash."
148 .into(),
149 ))
150 }
151 }
152
153 pub fn verify_and_apply(
160 &self,
161 receipt: &CommercialOverrideReceipt,
162 public_key_hex: &str,
163 ) -> Vec<String> {
164 let coreason_env =
166 std::env::var("COREASON_ENV").unwrap_or_else(|_| "development".to_string());
167 if coreason_env == "production" {
168 let root_ca_key = std::env::var("COREASON_ROOT_CA_KEY").unwrap_or_default();
169 if root_ca_key.is_empty() {
170 eprintln!(
171 "CRITICAL: COREASON_ROOT_CA_KEY is not set in production. \
172 License verification fail-closed. Defaulting to Prosperity 3.0."
173 );
174 return Vec::new();
175 }
176
177 let dev_key = std::env::var("COREASON_DEV_KEY").unwrap_or_default();
178 if !dev_key.is_empty() && root_ca_key == dev_key {
179 eprintln!(
180 "CRITICAL: COREASON_ROOT_CA_KEY matches COREASON_DEV_KEY in production. \
181 This is a security violation. Fail-closed."
182 );
183 return Vec::new();
184 }
185 }
186
187 if !receipt.is_valid_epoch() {
189 eprintln!(
190 "License expired at {}. Falling back to Prosperity 3.0.",
191 receipt.expires_at_epoch
192 );
193 return Vec::new();
194 }
195
196 match receipt.verify_signature(public_key_hex) {
198 Ok(true) => {}
199 Ok(false) => {
200 eprintln!(
201 "License signature verification returned false. Defaulting to Prosperity 3.0."
202 );
203 return Vec::new();
204 }
205 Err(e) => {
206 eprintln!(
207 "License signature verification failed: {}. Defaulting to Prosperity 3.0.",
208 e
209 );
210 return Vec::new();
211 }
212 }
213
214 if let Some(ref proof) = receipt.hardware_zk_proof {
216 if let Err(e) = self.verify_zk_snark(proof) {
217 eprintln!(
218 "Hardware fingerprint verification failed: {}. Defaulting to Prosperity 3.0.",
219 e
220 );
221 return Vec::new();
222 }
223 }
224
225 if receipt.credential_format == "sd-jwt" {
227 }
229
230 receipt.entitlements.clone()
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 fn make_receipt(expires_at_epoch: u64) -> CommercialOverrideReceipt {
239 let now = SystemTime::now()
240 .duration_since(UNIX_EPOCH)
241 .unwrap()
242 .as_secs();
243 CommercialOverrideReceipt {
244 license_tier: "enterprise".into(),
245 signer_did: "did:coreason:test".into(),
246 signature_algorithm: "Ed25519".into(),
247 credential_format: "jwt".into(),
248 distr_license_cid: "test-cid".into(),
249 hardware_zk_proof: None,
250 issued_at_epoch: now - 3600,
251 expires_at_epoch,
252 exp: expires_at_epoch,
253 iat: now - 3600,
254 entitlements: vec!["COMMERCIAL_USE".into()],
255 network_mode: "sovereign".into(),
256 federation_enabled: false,
257 signature: None,
258 tenant_cid: "test-tenant".into(),
259 }
260 }
261
262 #[test]
263 fn test_is_valid_epoch_unexpired() {
264 let now = SystemTime::now()
265 .duration_since(UNIX_EPOCH)
266 .unwrap()
267 .as_secs();
268 let receipt = make_receipt(now + 86400);
269 assert!(receipt.is_valid_epoch());
270 }
271
272 #[test]
273 fn test_is_valid_epoch_expired() {
274 let receipt = make_receipt(1000);
275 assert!(!receipt.is_valid_epoch());
276 }
277
278 #[test]
279 fn test_verify_and_apply_expired() {
280 let receipt = make_receipt(1000); let verifier = LicenseVerifier::new(None);
282 let entitlements = verifier.verify_and_apply(&receipt, "dummy");
283 assert!(entitlements.is_empty());
284 }
285
286 #[test]
287 fn test_zk_snark_empty_proof() {
288 let verifier = LicenseVerifier::new(None);
289 assert!(verifier.verify_zk_snark("").is_ok());
290 }
291
292 #[test]
293 fn test_zk_snark_no_fingerprint() {
294 let verifier = LicenseVerifier::new(None);
295 let result = verifier.verify_zk_snark("some_proof");
296 assert!(result.is_err());
297 }
298
299 #[test]
300 fn test_zk_snark_matching_fingerprint() {
301 use sha2::{Digest, Sha256};
302 let fp = "test-fingerprint";
303 let mut hasher = Sha256::new();
304 hasher.update(fp.as_bytes());
305 let proof = format!("{:x}", hasher.finalize());
306
307 let verifier = LicenseVerifier::new(Some(fp.to_string()));
308 assert!(verifier.verify_zk_snark(&proof).is_ok());
309 }
310
311 #[test]
312 fn test_zk_snark_mismatched_fingerprint() {
313 let verifier = LicenseVerifier::new(Some("test-fp".to_string()));
314 let result = verifier.verify_zk_snark("bad_proof");
315 assert!(result.is_err());
316 }
317}