1use base64::{
51 Engine,
52 engine::general_purpose::STANDARD
53};
54use ed25519_dalek::{
55 Signature,
56 Signer,
57 Verifier,
58 SigningKey,
59 VerifyingKey,
60 PUBLIC_KEY_LENGTH,
61 SECRET_KEY_LENGTH
62};
63use rand::rngs::OsRng;
64
65use crate::IronShieldChallenge;
66
67use std::env;
68
69macro_rules! debug_log {
71 ($($arg:tt)*) => {
72 #[cfg(all(target_arch = "wasm32", feature = "wasm-logging"))]
73 {
74 let msg = format!($($arg)*);
75 web_sys::console::log_1(&wasm_bindgen::JsValue::from_str(&msg));
76 }
77 #[cfg(not(target_arch = "wasm32"))]
78 eprintln!($($arg)*);
79 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-logging")))]
80 {
81 let _ = format!($($arg)*);
83 }
84 };
85}
86
87#[derive(Debug, Clone)]
88pub enum CryptoError {
89 MissingEnvironmentVariable(String),
90 InvalidKeyFormat(String),
91 SigningFailed(String),
92 VerificationFailed(String),
93 Base64DecodingFailed(String),
94 PgpParsingFailed(String),
95}
96
97impl std::fmt::Display for CryptoError {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 match self {
100 CryptoError::MissingEnvironmentVariable(var) => write!(f, "Missing environment variable: {}", var),
101 CryptoError::InvalidKeyFormat(msg) => write!(f, "Invalid key format: {}", msg),
102 CryptoError::SigningFailed(msg) => write!(f, "Signing failed: {}", msg),
103 CryptoError::VerificationFailed(msg) => write!(f, "Verification failed: {}", msg),
104 CryptoError::Base64DecodingFailed(msg) => write!(f, "Base64 decoding failed: {}", msg),
105 CryptoError::PgpParsingFailed(msg) => write!(f, "PGP parsing failed: {}", msg),
106 }
107 }
108}
109
110impl std::error::Error for CryptoError {}
111
112fn parse_key(key_data: &str, is_private: bool) -> Result<[u8; 32], CryptoError> {
126 use sequoia_openpgp::{Cert, parse::Parse};
127
128 debug_log!("Parsing key data: {} chars", key_data.len());
129
130 match Cert::from_bytes(key_data.as_bytes()) {
132 Ok(cert) => {
133 debug_log!("Successfully parsed as PGP certificate");
134 return extract_ed25519_key_from_cert(&cert, is_private);
135 }
136 Err(_) => {
137 debug_log!("Not a PGP certificate, trying base64 decode");
138 }
139 }
140
141 if let Ok(decoded) = STANDARD.decode(key_data.trim()) {
143 debug_log!("Decoded base64 to {} bytes", decoded.len());
144
145 match Cert::from_bytes(&decoded) {
147 Ok(cert) => {
148 debug_log!("Successfully parsed binary PGP certificate");
149 return extract_ed25519_key_from_cert(&cert, is_private);
150 }
151 Err(_) => {
152 debug_log!("Not binary PGP, checking for raw Ed25519 key");
153
154 if decoded.len() == 32 {
156 debug_log!("Detected raw 32-byte Ed25519 key");
157 let mut key_array = [0u8; 32];
158 key_array.copy_from_slice(&decoded);
159
160 if is_private {
164 let _signing_key = SigningKey::from_bytes(&key_array);
165 debug_log!("Raw Ed25519 private key validated");
166 } else {
167 VerifyingKey::from_bytes(&key_array)
168 .map_err(|e| CryptoError::InvalidKeyFormat(
169 format!("Invalid raw Ed25519 public key: {}", e)
170 ))?;
171 debug_log!("Raw Ed25519 public key validated");
172 }
173
174 return Ok(key_array);
175 }
176 }
177 }
178 }
179
180 Err(CryptoError::PgpParsingFailed(
181 "Could not parse as PGP certificate or raw Ed25519 key".to_string()
182 ))
183}
184
185fn extract_ed25519_key_from_cert(cert: &sequoia_openpgp::Cert, is_private: bool) -> Result<[u8; 32], CryptoError> {
187 use sequoia_openpgp::serialize::Marshal;
188
189 let primary_key = cert.primary_key().key();
191
192 let mut mpi_bytes = Vec::new();
194 primary_key.mpis().serialize(&mut mpi_bytes)
195 .map_err(|e| CryptoError::PgpParsingFailed(
196 format!("Failed to serialize key MPIs: {}", e)
197 ))?;
198
199 debug_log!("Key MPI bytes: {} bytes", mpi_bytes.len());
200
201 if mpi_bytes.len() >= 34 {
213 if (mpi_bytes[0] == 0x00 && mpi_bytes[1] == 0x20) ||
215 (mpi_bytes[0] == 0x01 && mpi_bytes[1] == 0x00) ||
216 (mpi_bytes[0] == 0x40 && mpi_bytes[1] == 0x20) {
217
218 let mut key_array = [0u8; 32];
219 key_array.copy_from_slice(&mpi_bytes[2..34]);
220
221 if is_private {
223 let _signing_key = SigningKey::from_bytes(&key_array);
224 debug_log!("Ed25519 private key validated from PGP certificate");
225 } else {
226 VerifyingKey::from_bytes(&key_array)
227 .map_err(|e| CryptoError::InvalidKeyFormat(
228 format!("Invalid Ed25519 public key from PGP: {}", e)
229 ))?;
230 debug_log!("Ed25519 public key validated from PGP certificate");
231 }
232
233 debug_log!("Successfully extracted Ed25519 key from PGP certificate");
234 return Ok(key_array);
235 }
236 }
237
238 if mpi_bytes.len() == 32 {
240 let mut key_array = [0u8; 32];
241 key_array.copy_from_slice(&mpi_bytes);
242
243 if is_private {
245 let _signing_key = SigningKey::from_bytes(&key_array);
246 debug_log!("Raw 32-byte private key validated from PGP");
247 } else {
248 VerifyingKey::from_bytes(&key_array)
249 .map_err(|e| CryptoError::InvalidKeyFormat(
250 format!("Invalid Ed25519 public key from PGP: {}", e)
251 ))?;
252 debug_log!("Raw 32-byte public key validated from PGP");
253 }
254
255 debug_log!("Extracted raw 32-byte Ed25519 key from PGP");
256 return Ok(key_array);
257 }
258
259 Err(CryptoError::PgpParsingFailed(
260 format!("Unexpected key format in PGP certificate: {} bytes, expected Ed25519", mpi_bytes.len())
261 ))
262}
263
264pub fn load_private_key(key_data: Option<&str>) -> Result<SigningKey, CryptoError> {
283 if let Some(data) = key_data {
285 debug_log!("Attempting to load private key from provided data");
286
287 match parse_key(data, true) {
289 Ok(key_array) => {
290 let signing_key = SigningKey::from_bytes(&key_array);
291 debug_log!("Successfully loaded private key from provided data");
292 return Ok(signing_key);
293 }
294 Err(CryptoError::PgpParsingFailed(_)) | Err(CryptoError::Base64DecodingFailed(_)) => {
295 }
297 Err(e) => {
298 debug_log!("Error parsing provided key data: {}, trying env var fallback", e);
300 }
301 }
302
303 match STANDARD.decode(data.trim()) {
305 Ok(key_bytes) if key_bytes.len() == SECRET_KEY_LENGTH => {
306 let mut key_array = [0u8; SECRET_KEY_LENGTH];
307 key_array.copy_from_slice(&key_bytes);
308
309 let signing_key = SigningKey::from_bytes(&key_array);
310 debug_log!("Successfully loaded private key from provided data (raw format)");
311 return Ok(signing_key);
312 }
313 Ok(key_bytes) => {
314 debug_log!("Invalid key length in provided data: {} bytes, trying env var fallback", key_bytes.len());
315 }
316 Err(e) => {
317 debug_log!("Base64 decode failed for provided data: {}, trying env var fallback", e);
318 }
319 }
320 }
321
322 debug_log!("Loading private key from IRONSHIELD_PRIVATE_KEY environment variable");
324
325 let key_str = env::var("IRONSHIELD_PRIVATE_KEY")
326 .map_err(|_| CryptoError::MissingEnvironmentVariable("IRONSHIELD_PRIVATE_KEY".to_string()))?;
327
328 match parse_key(&key_str, true) {
330 Ok(key_array) => {
331 let signing_key = SigningKey::from_bytes(&key_array);
332 debug_log!("Successfully loaded private key from environment variable");
333 return Ok(signing_key);
334 }
335 Err(CryptoError::PgpParsingFailed(_)) | Err(CryptoError::Base64DecodingFailed(_)) => {
336 }
338 Err(e) => return Err(e), }
340
341 let key_bytes = STANDARD.decode(key_str.trim())
343 .map_err(|e| CryptoError::Base64DecodingFailed(format!("Private key (legacy fallback): {}", e)))?;
344
345 if key_bytes.len() != SECRET_KEY_LENGTH {
347 return Err(CryptoError::InvalidKeyFormat(
348 format!("Private key must be {} bytes (raw Ed25519) or valid PGP format, got {} bytes",
349 SECRET_KEY_LENGTH, key_bytes.len())
350 ));
351 }
352
353 let key_array: [u8; SECRET_KEY_LENGTH] = key_bytes.try_into()
355 .map_err(|_| CryptoError::InvalidKeyFormat("Failed to convert private key bytes".to_string()))?;
356
357 let signing_key = SigningKey::from_bytes(&key_array);
358 Ok(signing_key)
359}
360
361pub fn load_public_key(key_data: Option<&str>) -> Result<VerifyingKey, CryptoError> {
380 if let Some(data) = key_data {
382 debug_log!("Attempting to load public key from provided data");
383
384 match parse_key(data, false) {
386 Ok(key_array) => {
387 let verifying_key = VerifyingKey::from_bytes(&key_array)
388 .map_err(|e| CryptoError::InvalidKeyFormat(format!("Invalid public key from PGP: {}", e)))?;
389 debug_log!("Successfully loaded public key from provided data");
390 return Ok(verifying_key);
391 }
392 Err(CryptoError::PgpParsingFailed(_)) | Err(CryptoError::Base64DecodingFailed(_)) => {
393 }
395 Err(e) => {
396 debug_log!("Error parsing provided key data: {}, trying env var fallback", e);
398 }
399 }
400
401 match STANDARD.decode(data.trim()) {
403 Ok(key_bytes) if key_bytes.len() == PUBLIC_KEY_LENGTH => {
404 let mut key_array = [0u8; PUBLIC_KEY_LENGTH];
405 key_array.copy_from_slice(&key_bytes);
406
407 match VerifyingKey::from_bytes(&key_array) {
408 Ok(verifying_key) => {
409 debug_log!("Successfully loaded public key from provided data (raw format)");
410 return Ok(verifying_key);
411 }
412 Err(e) => {
413 debug_log!("Invalid Ed25519 public key in provided data: {}, trying env var fallback", e);
414 }
415 }
416 }
417 Ok(key_bytes) => {
418 debug_log!("Invalid key length in provided data: {} bytes, trying env var fallback", key_bytes.len());
419 }
420 Err(e) => {
421 debug_log!("Base64 decode failed for provided data: {}, trying env var fallback", e);
422 }
423 }
424 }
425
426 debug_log!("Loading public key from IRONSHIELD_PUBLIC_KEY environment variable");
428
429 let key_str = env::var("IRONSHIELD_PUBLIC_KEY")
430 .map_err(|_| CryptoError::MissingEnvironmentVariable("IRONSHIELD_PUBLIC_KEY".to_string()))?;
431
432 match parse_key(&key_str, false) {
434 Ok(key_array) => {
435 let verifying_key = VerifyingKey::from_bytes(&key_array)
436 .map_err(|e| CryptoError::InvalidKeyFormat(format!("Invalid public key: {}", e)))?;
437 debug_log!("Successfully loaded public key from environment variable");
438 return Ok(verifying_key);
439 }
440 Err(CryptoError::PgpParsingFailed(_)) | Err(CryptoError::Base64DecodingFailed(_)) => {
441 }
443 Err(e) => return Err(e), }
445
446 let key_bytes = STANDARD.decode(key_str.trim())
448 .map_err(|e| CryptoError::Base64DecodingFailed(format!("Public key (legacy fallback): {}", e)))?;
449
450 if key_bytes.len() != PUBLIC_KEY_LENGTH {
452 return Err(CryptoError::InvalidKeyFormat(
453 format!("Public key must be {} bytes (raw Ed25519) or valid PGP format, got {} bytes",
454 PUBLIC_KEY_LENGTH, key_bytes.len())
455 ));
456 }
457
458 let key_array: [u8; PUBLIC_KEY_LENGTH] = key_bytes.try_into()
460 .map_err(|_| CryptoError::InvalidKeyFormat("Failed to convert public key bytes".to_string()))?;
461
462 let verifying_key = VerifyingKey::from_bytes(&key_array)
463 .map_err(|e| CryptoError::InvalidKeyFormat(format!("Invalid public key: {}", e)))?;
464
465 Ok(verifying_key)
466}
467
468pub fn create_signing_message(
485 random_nonce: &str,
486 created_time: i64,
487 expiration_time: i64,
488 website_id: &str,
489 challenge_param: &[u8; 32],
490 public_key: &[u8; 32]
491) -> String {
492 format!(
493 "{}|{}|{}|{}|{}|{}",
494 random_nonce,
495 created_time,
496 expiration_time,
497 website_id,
498 hex::encode(challenge_param),
499 hex::encode(public_key)
500 )
501}
502
503pub fn generate_signature(signing_key: &SigningKey, message: &str) -> Result<[u8; 64], CryptoError> {
515 let signature: Signature = signing_key.sign(message.as_bytes());
516 Ok(signature.to_bytes())
517}
518
519pub fn sign_challenge(challenge: &IronShieldChallenge) -> Result<[u8; 64], CryptoError> {
530 let signing_key: SigningKey = load_private_key(None)?;
531 let message: String = create_signing_message(
532 &challenge.random_nonce,
533 challenge.created_time,
534 challenge.expiration_time,
535 &challenge.website_id,
536 &challenge.challenge_param,
537 &challenge.public_key
538 );
539 generate_signature(&signing_key, &message)
540}
541
542pub fn verify_challenge_signature(challenge: &IronShieldChallenge) -> Result<(), CryptoError> {
554 let verifying_key: VerifyingKey = load_public_key(None)?;
555
556 let message: String = create_signing_message(
557 &challenge.random_nonce,
558 challenge.created_time,
559 challenge.expiration_time,
560 &challenge.website_id,
561 &challenge.challenge_param,
562 &challenge.public_key
563 );
564 let signature: Signature = Signature::from_slice(&challenge.challenge_signature)
565 .map_err(|e| CryptoError::InvalidKeyFormat(format!("Invalid signature format: {}", e)))?;
566
567 verifying_key.verify(message.as_bytes(), &signature)
568 .map_err(|e| CryptoError::VerificationFailed(format!("Signature verification failed: {}", e)))?;
569
570 Ok(())
571}
572
573pub fn verify_challenge_signature_with_key(
586 challenge: &IronShieldChallenge,
587 public_key_bytes: &[u8; 32]
588) -> Result<(), CryptoError> {
589 let verifying_key: VerifyingKey = VerifyingKey::from_bytes(public_key_bytes)
590 .map_err(|e| CryptoError::InvalidKeyFormat(format!("Invalid public key: {}", e)))?;
591
592 let message: String = create_signing_message(
593 &challenge.random_nonce,
594 challenge.created_time,
595 challenge.expiration_time,
596 &challenge.website_id,
597 &challenge.challenge_param,
598 &challenge.public_key
599 );
600 let signature: Signature = Signature::from_slice(&challenge.challenge_signature)
601 .map_err(|e| CryptoError::InvalidKeyFormat(format!("Invalid signature format: {}", e)))?;
602
603 verifying_key.verify(message.as_bytes(), &signature)
604 .map_err(|e| CryptoError::VerificationFailed(format!("Signature verification failed: {}", e)))?;
605
606 Ok(())
607}
608
609pub fn generate_test_keypair() -> (String, String) {
617 let signing_key: SigningKey = SigningKey::generate(&mut OsRng);
618 let verifying_key: VerifyingKey = signing_key.verifying_key();
619
620 let private_key_b64: String = STANDARD.encode(signing_key.to_bytes());
621 let public_key_b64: String = STANDARD.encode(verifying_key.to_bytes());
622
623 (private_key_b64, public_key_b64)
624}
625
626pub fn validate_challenge(challenge: &IronShieldChallenge) -> Result<(), CryptoError> {
639 verify_challenge_signature(challenge)?;
641
642 if challenge.is_expired() {
644 return Err(CryptoError::VerificationFailed("Challenge has expired".to_string()));
645 }
646
647 if challenge.website_id.is_empty() {
648 return Err(CryptoError::VerificationFailed("Empty website_id".to_string()));
649 }
650
651 Ok(())
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 #[test]
660 fn test_parse_raw_ed25519_private_key() {
661 let (private_b64, _) = generate_test_keypair();
663
664 let result = parse_key(&private_b64, true);
666 assert!(result.is_ok(), "Failed to parse raw Ed25519 private key");
667
668 let key_bytes = result.unwrap();
669 assert_eq!(key_bytes.len(), 32, "Key should be 32 bytes");
670
671 let signing_key = SigningKey::from_bytes(&key_bytes);
673 let _ = signing_key.verifying_key(); println!("Successfully parsed raw Ed25519 private key");
676 }
677
678 #[test]
680 fn test_parse_raw_ed25519_public_key() {
681 let (_, public_b64) = generate_test_keypair();
683
684 let result = parse_key(&public_b64, false);
686 assert!(result.is_ok(), "Failed to parse raw Ed25519 public key");
687
688 let key_bytes = result.unwrap();
689 assert_eq!(key_bytes.len(), 32, "Key should be 32 bytes");
690
691 let verifying_key = VerifyingKey::from_bytes(&key_bytes);
693 assert!(verifying_key.is_ok(), "Should be a valid Ed25519 public key");
694
695 println!("Successfully parsed raw Ed25519 public key");
696 }
697
698 #[test]
700 fn test_parse_key_with_whitespace() {
701 let (private_b64, _) = generate_test_keypair();
702
703 let with_spaces = format!(" {} ", private_b64);
705 let with_newlines = format!("{}\n\n", private_b64);
706 let with_tabs = format!("\t{}\t", private_b64);
707 let with_mixed = format!("\n {}\t\n ", private_b64);
708
709 for key_str in [with_spaces, with_newlines, with_tabs, with_mixed] {
710 let result = parse_key(&key_str, true);
711 assert!(
712 result.is_ok(),
713 "Should handle whitespace, got error: {:?}",
714 result.err()
715 );
716 }
717
718 println!("Successfully handled various whitespace formats");
719 }
720
721 #[test]
723 fn test_parse_invalid_base64() {
724 let invalid_base64 = "this is not valid base64!!!@#$%";
725
726 let result = parse_key(invalid_base64, true);
727 assert!(result.is_err(), "Should fail on invalid base64");
728
729 match result.err().unwrap() {
732 CryptoError::Base64DecodingFailed(_) | CryptoError::PgpParsingFailed(_) => {
733 println!("Correctly rejected invalid input");
734 }
735 other => panic!("Expected Base64DecodingFailed or PgpParsingFailed, got: {:?}", other),
736 }
737 }
738
739 #[test]
741 fn test_parse_wrong_size_key() {
742 let wrong_size = STANDARD.encode(&[0u8; 16]); let result = parse_key(&wrong_size, true);
746 assert!(result.is_err(), "Should fail on wrong-sized key");
747
748 println!("Correctly rejected wrong-sized key");
749 }
750
751 #[test]
753 fn test_private_vs_public_key_validation() {
754 let (private_b64, public_b64) = generate_test_keypair();
755
756 let result = parse_key(&private_b64, true);
758 assert!(result.is_ok(), "Private key should parse as private");
759
760 let result = parse_key(&public_b64, false);
762 assert!(result.is_ok(), "Public key should parse as public");
763
764 println!("Correctly validated private vs public keys");
765 }
766
767 #[test]
769 fn test_parse_key_end_to_end() {
770 let (private_b64, public_b64) = generate_test_keypair();
772
773 let private_bytes = parse_key(&private_b64, true)
775 .expect("Failed to parse private key");
776 let public_bytes = parse_key(&public_b64, false)
777 .expect("Failed to parse public key");
778
779 let signing_key = SigningKey::from_bytes(&private_bytes);
781 let verifying_key = VerifyingKey::from_bytes(&public_bytes)
782 .expect("Invalid public key");
783
784 let message = b"Test message for IronShield";
786 let signature = signing_key.sign(message);
787
788 verifying_key
790 .verify(message, &signature)
791 .expect("Signature verification failed");
792
793 let derived_public = signing_key.verifying_key();
795 assert_eq!(
796 derived_public.to_bytes(),
797 public_bytes,
798 "Derived public key should match parsed public key"
799 );
800
801 println!("Successfully completed end-to-end test");
802 }
803
804 #[test]
806 fn test_parse_empty_string() {
807 let result = parse_key("", true);
808 assert!(result.is_err(), "Should fail on empty string");
809
810 println!("Correctly rejected empty string");
811 }
812}
813