1#![allow(dead_code)]
2use crate::binary::BinaryAnalysis;
3use crate::enterprise::types::{CodeLocation, SeverityLevel};
4use chrono::Utc;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CryptoAnalysisResult {
11 pub analysis_id: Uuid,
12 pub file_path: String,
13 pub key_issues: Vec<KeyIssue>,
14 pub algorithm_issues: Vec<AlgorithmIssue>,
15 pub implementation_issues: Vec<CryptoImplementationIssue>,
16 pub analysis_duration_ms: u64,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct KeyIssue {
21 pub issue_type: KeyIssueType,
22 pub location: CodeLocation,
23 pub severity: SeverityLevel,
24 pub key_material: Option<String>, pub recommendation: String,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub enum KeyIssueType {
30 HardcodedKey,
31 WeakKey,
32 KeyReuse,
33 InsecureKeyGeneration,
34 KeyInPlaintext,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct AlgorithmIssue {
39 pub algorithm_name: String,
40 pub issue_type: AlgorithmIssueType,
41 pub location: CodeLocation,
42 pub severity: SeverityLevel,
43 pub replacement_suggestion: Option<String>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub enum AlgorithmIssueType {
48 Deprecated,
49 Weak,
50 Broken,
51 Misconfigured,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct CryptoImplementationIssue {
56 pub issue_description: String,
57 pub location: CodeLocation,
58 pub severity: SeverityLevel,
59 pub cwe_id: Option<String>,
60}
61
62pub fn analyze_crypto_security(analysis: &BinaryAnalysis) -> CryptoAnalysisResult {
63 let start_time = Utc::now();
64
65 let mut result = CryptoAnalysisResult {
66 analysis_id: Uuid::new_v4(),
67 file_path: analysis.file_name.clone(),
68 key_issues: Vec::new(),
69 algorithm_issues: Vec::new(),
70 implementation_issues: Vec::new(),
71 analysis_duration_ms: 0,
72 };
73
74 result.key_issues = analyze_key_issues(analysis);
76
77 result.algorithm_issues = analyze_algorithm_issues(analysis);
79
80 result.implementation_issues = analyze_implementation_issues(analysis);
82
83 let end_time = Utc::now();
84 result.analysis_duration_ms = (end_time - start_time).num_milliseconds() as u64;
85
86 result
87}
88
89fn analyze_key_issues(analysis: &BinaryAnalysis) -> Vec<KeyIssue> {
90 let mut issues = Vec::new();
91
92 for string in &analysis.embedded_strings {
94 if let Some(key_issue) = detect_hardcoded_key(string, &analysis.file_name) {
95 issues.push(key_issue);
96 }
97 }
98
99 let weak_key_gen_functions = [
101 (
102 "rand",
103 "Use cryptographically secure random number generators like /dev/urandom or CryptGenRandom",
104 ),
105 (
106 "random",
107 "Use cryptographically secure random number generators",
108 ),
109 ("srand", "Predictable seed - use secure random generators"),
110 (
111 "time",
112 "Time-based seeds are predictable - use secure entropy sources",
113 ),
114 ];
115
116 for (func, recommendation) in &weak_key_gen_functions {
117 if analysis.imports.contains(&func.to_string())
118 || analysis.detected_symbols.contains(&func.to_string())
119 {
120 issues.push(KeyIssue {
121 issue_type: KeyIssueType::InsecureKeyGeneration,
122 location: CodeLocation {
123 file_path: analysis.file_name.clone(),
124 line_number: None,
125 column_number: None,
126 function_name: Some(func.to_string()),
127 binary_offset: None,
128 },
129 severity: SeverityLevel::High,
130 key_material: None,
131 recommendation: recommendation.to_string(),
132 });
133 }
134 }
135
136 let key_storage_functions = ["fopen", "fwrite", "fprintf"];
138 let has_file_ops = key_storage_functions.iter().any(|&func| {
139 analysis.imports.contains(&func.to_string())
140 || analysis.detected_symbols.contains(&func.to_string())
141 });
142
143 if has_file_ops && has_crypto_context(analysis) {
144 issues.push(KeyIssue {
145 issue_type: KeyIssueType::KeyInPlaintext,
146 location: CodeLocation {
147 file_path: analysis.file_name.clone(),
148 line_number: None,
149 column_number: None,
150 function_name: None,
151 binary_offset: None,
152 },
153 severity: SeverityLevel::Medium,
154 key_material: None,
155 recommendation: "Ensure cryptographic keys are encrypted before storage".to_string(),
156 });
157 }
158
159 issues
160}
161
162fn detect_hardcoded_key(string: &str, file_path: &str) -> Option<KeyIssue> {
163 if string.contains("-----BEGIN RSA PRIVATE KEY-----")
167 || string.contains("-----BEGIN PRIVATE KEY-----")
168 {
169 return Some(KeyIssue {
170 issue_type: KeyIssueType::HardcodedKey,
171 location: CodeLocation {
172 file_path: file_path.to_string(),
173 line_number: None,
174 column_number: None,
175 function_name: None,
176 binary_offset: None,
177 },
178 severity: SeverityLevel::Critical,
179 key_material: Some(format!(
180 "RSA_KEY_{}...",
181 &string.chars().take(8).collect::<String>()
182 )),
183 recommendation: "Remove hardcoded private keys - use secure key management".to_string(),
184 });
185 }
186
187 if string.contains("-----BEGIN CERTIFICATE-----") {
189 return Some(KeyIssue {
190 issue_type: KeyIssueType::HardcodedKey,
191 location: CodeLocation {
192 file_path: file_path.to_string(),
193 line_number: None,
194 column_number: None,
195 function_name: None,
196 binary_offset: None,
197 },
198 severity: SeverityLevel::Medium,
199 key_material: Some("CERTIFICATE_DATA".to_string()),
200 recommendation: "Certificates should be loaded from secure storage".to_string(),
201 });
202 }
203
204 if is_likely_encoded_key(string) {
206 return Some(KeyIssue {
207 issue_type: KeyIssueType::HardcodedKey,
208 location: CodeLocation {
209 file_path: file_path.to_string(),
210 line_number: None,
211 column_number: None,
212 function_name: None,
213 binary_offset: None,
214 },
215 severity: SeverityLevel::High,
216 key_material: Some(format!(
217 "ENCODED_KEY_{}...",
218 &string.chars().take(8).collect::<String>()
219 )),
220 recommendation: "Suspected encoded key material should not be hardcoded".to_string(),
221 });
222 }
223
224 if is_hex_key_pattern(string) {
226 return Some(KeyIssue {
227 issue_type: KeyIssueType::HardcodedKey,
228 location: CodeLocation {
229 file_path: file_path.to_string(),
230 line_number: None,
231 column_number: None,
232 function_name: None,
233 binary_offset: None,
234 },
235 severity: SeverityLevel::High,
236 key_material: Some(format!(
237 "HEX_KEY_{}...",
238 &string.chars().take(8).collect::<String>()
239 )),
240 recommendation: "Suspected hexadecimal key should not be hardcoded".to_string(),
241 });
242 }
243
244 if is_api_key_pattern(string) {
246 return Some(KeyIssue {
247 issue_type: KeyIssueType::HardcodedKey,
248 location: CodeLocation {
249 file_path: file_path.to_string(),
250 line_number: None,
251 column_number: None,
252 function_name: None,
253 binary_offset: None,
254 },
255 severity: SeverityLevel::High,
256 key_material: Some("API_KEY_[REDACTED]".to_string()),
257 recommendation:
258 "API keys should be loaded from environment variables or secure configuration"
259 .to_string(),
260 });
261 }
262
263 None
264}
265
266fn analyze_algorithm_issues(analysis: &BinaryAnalysis) -> Vec<AlgorithmIssue> {
267 let mut issues = Vec::new();
268
269 let weak_algorithms: HashMap<&str, (AlgorithmIssueType, SeverityLevel, Option<&str>)> =
271 HashMap::from([
272 (
273 "md5",
274 (
275 AlgorithmIssueType::Broken,
276 SeverityLevel::High,
277 Some("SHA-256 or SHA-3"),
278 ),
279 ),
280 (
281 "sha1",
282 (
283 AlgorithmIssueType::Weak,
284 SeverityLevel::Medium,
285 Some("SHA-256 or SHA-3"),
286 ),
287 ),
288 (
289 "des",
290 (
291 AlgorithmIssueType::Broken,
292 SeverityLevel::Critical,
293 Some("AES"),
294 ),
295 ),
296 (
297 "3des",
298 (
299 AlgorithmIssueType::Deprecated,
300 SeverityLevel::Medium,
301 Some("AES"),
302 ),
303 ),
304 (
305 "rc4",
306 (
307 AlgorithmIssueType::Broken,
308 SeverityLevel::Critical,
309 Some("AES or ChaCha20"),
310 ),
311 ),
312 (
313 "rc2",
314 (AlgorithmIssueType::Broken, SeverityLevel::High, Some("AES")),
315 ),
316 (
317 "blowfish",
318 (AlgorithmIssueType::Weak, SeverityLevel::Medium, Some("AES")),
319 ),
320 (
321 "md4",
322 (
323 AlgorithmIssueType::Broken,
324 SeverityLevel::Critical,
325 Some("SHA-256 or SHA-3"),
326 ),
327 ),
328 (
329 "md2",
330 (
331 AlgorithmIssueType::Broken,
332 SeverityLevel::Critical,
333 Some("SHA-256 or SHA-3"),
334 ),
335 ),
336 ]);
337
338 for string in analysis
340 .embedded_strings
341 .iter()
342 .chain(analysis.imports.iter())
343 .chain(analysis.detected_symbols.iter())
344 {
345 let lower = string.to_lowercase();
346
347 for (algorithm, (issue_type, severity, replacement)) in &weak_algorithms {
348 if lower.contains(algorithm) || lower.contains(&algorithm.to_uppercase()) {
349 issues.push(AlgorithmIssue {
350 algorithm_name: algorithm.to_uppercase(),
351 issue_type: issue_type.clone(),
352 location: CodeLocation {
353 file_path: analysis.file_name.clone(),
354 line_number: None,
355 column_number: None,
356 function_name: if analysis.imports.contains(string)
357 || analysis.detected_symbols.contains(string)
358 {
359 Some(string.clone())
360 } else {
361 None
362 },
363 binary_offset: None,
364 },
365 severity: severity.clone(),
366 replacement_suggestion: replacement.map(|s| s.to_string()),
367 });
368 }
369 }
370 }
371
372 for lib in &analysis.linked_libraries {
374 let lower = lib.to_lowercase();
375
376 if lower.contains("openssl") || lower.contains("libssl") || lower.contains("libcrypto") {
378 if lower.contains("0.9") || lower.contains("1.0.0") {
380 issues.push(AlgorithmIssue {
381 algorithm_name: "OpenSSL".to_string(),
382 issue_type: AlgorithmIssueType::Deprecated,
383 location: CodeLocation {
384 file_path: analysis.file_name.clone(),
385 line_number: None,
386 column_number: None,
387 function_name: None,
388 binary_offset: None,
389 },
390 severity: SeverityLevel::High,
391 replacement_suggestion: Some("Update to OpenSSL 1.1.1+ or 3.x".to_string()),
392 });
393 }
394 }
395 }
396
397 if analysis
399 .embedded_strings
400 .iter()
401 .any(|s| s.contains("1024") && s.to_lowercase().contains("rsa"))
402 {
403 issues.push(AlgorithmIssue {
404 algorithm_name: "RSA-1024".to_string(),
405 issue_type: AlgorithmIssueType::Weak,
406 location: CodeLocation {
407 file_path: analysis.file_name.clone(),
408 line_number: None,
409 column_number: None,
410 function_name: None,
411 binary_offset: None,
412 },
413 severity: SeverityLevel::Medium,
414 replacement_suggestion: Some("Use RSA-2048 or higher, or consider ECDSA".to_string()),
415 });
416 }
417
418 issues
419}
420
421fn analyze_implementation_issues(analysis: &BinaryAnalysis) -> Vec<CryptoImplementationIssue> {
422 let mut issues = Vec::new();
423
424 if analysis
426 .embedded_strings
427 .iter()
428 .any(|s| s.to_lowercase().contains("ecb"))
429 {
430 issues.push(CryptoImplementationIssue {
431 issue_description:
432 "ECB (Electronic Codebook) mode detected - insecure block cipher mode".to_string(),
433 location: CodeLocation {
434 file_path: analysis.file_name.clone(),
435 line_number: None,
436 column_number: None,
437 function_name: None,
438 binary_offset: None,
439 },
440 severity: SeverityLevel::High,
441 cwe_id: Some("CWE-327".to_string()),
442 });
443 }
444
445 let iv_salt_indicators = ["iv", "salt", "nonce", "vector"];
447 for indicator in &iv_salt_indicators {
448 if analysis.embedded_strings.iter().any(|s| {
449 let lower = s.to_lowercase();
450 lower.contains(indicator) && (s.len() > 16 && s.chars().all(|c| c.is_ascii_hexdigit()))
451 }) {
452 issues.push(CryptoImplementationIssue {
453 issue_description: format!("Potential hardcoded {} detected", indicator)
454 .to_string(),
455 location: CodeLocation {
456 file_path: analysis.file_name.clone(),
457 line_number: None,
458 column_number: None,
459 function_name: None,
460 binary_offset: None,
461 },
462 severity: SeverityLevel::Medium,
463 cwe_id: Some("CWE-330".to_string()),
464 });
465 }
466 }
467
468 if has_crypto_context(analysis) {
470 let insecure_rng = ["rand", "random", "srand"];
471 for rng_func in &insecure_rng {
472 if analysis.imports.contains(&rng_func.to_string())
473 || analysis.detected_symbols.contains(&rng_func.to_string())
474 {
475 issues.push(CryptoImplementationIssue {
476 issue_description: format!(
477 "Insecure RNG ({}) used in cryptographic context",
478 rng_func
479 ),
480 location: CodeLocation {
481 file_path: analysis.file_name.clone(),
482 line_number: None,
483 column_number: None,
484 function_name: Some(rng_func.to_string()),
485 binary_offset: None,
486 },
487 severity: SeverityLevel::High,
488 cwe_id: Some("CWE-338".to_string()),
489 });
490 }
491 }
492 }
493
494 let timing_vuln_functions = ["strcmp", "memcmp"];
496 let has_crypto_functions = analysis
497 .imports
498 .iter()
499 .chain(analysis.detected_symbols.iter())
500 .any(|s| {
501 s.to_lowercase().contains("crypto")
502 || s.to_lowercase().contains("hash")
503 || s.to_lowercase().contains("cipher")
504 });
505
506 if has_crypto_functions {
507 for func in &timing_vuln_functions {
508 if analysis.imports.contains(&func.to_string())
509 || analysis.detected_symbols.contains(&func.to_string())
510 {
511 issues.push(CryptoImplementationIssue {
512 issue_description: format!(
513 "Timing attack vulnerability: {} used in crypto context",
514 func
515 ),
516 location: CodeLocation {
517 file_path: analysis.file_name.clone(),
518 line_number: None,
519 column_number: None,
520 function_name: Some(func.to_string()),
521 binary_offset: None,
522 },
523 severity: SeverityLevel::Medium,
524 cwe_id: Some("CWE-208".to_string()),
525 });
526 }
527 }
528 }
529
530 let ssl_functions = ["SSL_CTX_new", "SSL_new", "TLS_method"];
532 let has_ssl = ssl_functions.iter().any(|&func| {
533 analysis.imports.contains(&func.to_string())
534 || analysis.detected_symbols.contains(&func.to_string())
535 });
536
537 if has_ssl {
538 let dangerous_ssl_funcs = ["SSL_CTX_set_verify_mode", "SSL_set_verify"];
540 for func in &dangerous_ssl_funcs {
541 if analysis.imports.contains(&func.to_string())
542 || analysis.detected_symbols.contains(&func.to_string())
543 {
544 issues.push(CryptoImplementationIssue {
545 issue_description: "SSL/TLS certificate verification may be disabled"
546 .to_string(),
547 location: CodeLocation {
548 file_path: analysis.file_name.clone(),
549 line_number: None,
550 column_number: None,
551 function_name: Some(func.to_string()),
552 binary_offset: None,
553 },
554 severity: SeverityLevel::High,
555 cwe_id: Some("CWE-295".to_string()),
556 });
557 }
558 }
559
560 if analysis
562 .embedded_strings
563 .iter()
564 .any(|s| s.contains("SSLv3") || s.contains("TLSv1.0") || s.contains("TLSv1.1"))
565 {
566 issues.push(CryptoImplementationIssue {
567 issue_description: "Insecure SSL/TLS version detected".to_string(),
568 location: CodeLocation {
569 file_path: analysis.file_name.clone(),
570 line_number: None,
571 column_number: None,
572 function_name: None,
573 binary_offset: None,
574 },
575 severity: SeverityLevel::High,
576 cwe_id: Some("CWE-326".to_string()),
577 });
578 }
579 }
580
581 issues
582}
583
584fn has_crypto_context(analysis: &BinaryAnalysis) -> bool {
587 let crypto_indicators = [
588 "crypto", "cipher", "encrypt", "decrypt", "hash", "hmac", "aes", "rsa", "ecdsa", "ssl",
589 "tls", "key", "openssl",
590 ];
591
592 for item in analysis
593 .imports
594 .iter()
595 .chain(analysis.detected_symbols.iter())
596 .chain(analysis.embedded_strings.iter())
597 .chain(analysis.linked_libraries.iter())
598 {
599 let lower = item.to_lowercase();
600 if crypto_indicators
601 .iter()
602 .any(|&indicator| lower.contains(indicator))
603 {
604 return true;
605 }
606 }
607 false
608}
609
610fn is_likely_encoded_key(string: &str) -> bool {
611 if string.len() >= 32 && string.len() % 4 == 0 {
613 let base64_chars = string
614 .chars()
615 .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '/' || c == '=');
616 let padding_count = string.chars().filter(|&c| c == '=').count();
617
618 if base64_chars && padding_count <= 2 {
619 let entropy = calculate_entropy_simple(string);
621 return entropy > 4.5;
622 }
623 }
624 false
625}
626
627fn is_hex_key_pattern(string: &str) -> bool {
628 string.len() >= 32 &&
630 string.len() <= 128 && string.chars().all(|c| c.is_ascii_hexdigit())
632}
633
634fn is_api_key_pattern(string: &str) -> bool {
635 let lower = string.to_lowercase();
636
637 let api_prefixes = [
639 "api_key",
640 "apikey",
641 "api-key",
642 "secret_key",
643 "secretkey",
644 "secret-key",
645 "access_token",
646 "accesstoken",
647 "access-token",
648 "bearer",
649 "jwt",
650 "aws_access_key",
651 "aws_secret",
652 "gcp_key",
653 "azure_key",
654 ];
655
656 for prefix in &api_prefixes {
657 if lower.contains(prefix) && string.len() > 20 {
658 return true;
659 }
660 }
661
662 if string.len() >= 20 {
664 if string.starts_with("AKIA") || string.starts_with("ASIA") {
666 return true;
667 }
668
669 if string.starts_with("AIza") && string.len() == 39 {
671 return true;
672 }
673
674 if string.starts_with("ghp_") || string.starts_with("gho_") || string.starts_with("ghu_") {
676 return true;
677 }
678 }
679
680 false
681}
682
683fn calculate_entropy_simple(string: &str) -> f32 {
684 let mut char_counts = HashMap::new();
685 for c in string.chars() {
686 *char_counts.entry(c).or_insert(0) += 1;
687 }
688
689 let len = string.len() as f32;
690 let mut entropy = 0.0;
691
692 for count in char_counts.values() {
693 let p = *count as f32 / len;
694 entropy -= p * p.log2();
695 }
696
697 entropy
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703 use chrono::Utc;
704
705 fn create_test_analysis() -> BinaryAnalysis {
706 BinaryAnalysis {
707 id: Uuid::new_v4(),
708 file_name: "test.bin".to_string(),
709 format: "elf".to_string(),
710 architecture: "x86_64".to_string(),
711 languages: vec!["C".to_string()],
712 detected_symbols: vec!["md5".to_string(), "rand".to_string()],
713 embedded_strings: vec![
714 "-----BEGIN RSA PRIVATE KEY-----".to_string(),
715 "des_encrypt".to_string(),
716 "ecb_mode".to_string(),
717 ],
718 suspected_secrets: vec![],
719 imports: vec!["SSL_CTX_new".to_string(), "strcmp".to_string()],
720 exports: vec![],
721 hash_sha256: "test".to_string(),
722 hash_blake3: None,
723 size_bytes: 1024,
724 linked_libraries: vec!["libssl.so.1.0.0".to_string()],
725 static_linked: false,
726 version_info: None,
727 license_info: None,
728 metadata: serde_json::json!({}),
729 created_at: Utc::now(),
730 sbom: None,
731 binary_data: Some(vec![0x7f, 0x45, 0x4c, 0x46]),
732 entry_point: Some("0x401000".to_string()),
733 code_sections: vec![],
734 }
735 }
736
737 #[test]
738 fn test_analyze_crypto_security() {
739 let analysis = create_test_analysis();
740 let result = analyze_crypto_security(&analysis);
741
742 assert_eq!(result.file_path, "test.bin");
743 assert!(!result.key_issues.is_empty());
744 assert!(!result.algorithm_issues.is_empty());
745 assert!(!result.implementation_issues.is_empty());
746 }
747
748 #[test]
749 fn test_detect_hardcoded_key() {
750 let key_string = "-----BEGIN RSA PRIVATE KEY-----";
751 let issue = detect_hardcoded_key(key_string, "test.bin");
752 assert!(issue.is_some());
753 assert!(matches!(
754 issue.unwrap().issue_type,
755 KeyIssueType::HardcodedKey
756 ));
757 }
758
759 #[test]
760 fn test_is_hex_key_pattern() {
761 assert!(is_hex_key_pattern("0123456789abcdef0123456789abcdef"));
762 assert!(!is_hex_key_pattern("short"));
763 assert!(!is_hex_key_pattern("not_hex_at_all"));
764 }
765
766 #[test]
767 fn test_is_api_key_pattern() {
768 assert!(is_api_key_pattern("api_key_abcdef123456789"));
769 assert!(is_api_key_pattern("AKIA1234567890123456"));
770 assert!(!is_api_key_pattern("normal_string"));
771 }
772}