Skip to main content

lib_q_core/wasm/
providers.rs

1//! WASM-compatible provider bindings
2//!
3//! This module provides WASM-compatible bindings for cryptographic providers,
4//! integrating with the new modular architecture and security validation system.
5
6#[cfg(feature = "wasm")]
7extern crate alloc;
8#[cfg(feature = "wasm")]
9use alloc::{
10    string::{
11        String,
12        ToString,
13    },
14    vec::Vec,
15};
16
17#[cfg(feature = "wasm")]
18// use js_sys::Uint8Array;
19use serde_json;
20#[cfg(feature = "wasm")]
21use wasm_bindgen::prelude::*;
22
23use crate::api::{
24    Algorithm,
25    AlgorithmCategory,
26    CryptoProvider,
27};
28// use crate::error::Result;
29use crate::providers::LibQCryptoProvider;
30use crate::security::SecurityValidator;
31
32/// WASM-compatible provider manager
33///
34/// This manager provides JavaScript-compatible bindings for provider operations:
35/// - Integrates with the new modular architecture
36/// - Includes security validation
37/// - Provides consistent error handling
38/// - Supports all provider operations
39#[cfg_attr(feature = "wasm", wasm_bindgen)]
40pub struct WasmProviderManager {
41    provider: LibQCryptoProvider,
42    security_validator: SecurityValidator,
43}
44
45impl WasmProviderManager {
46    /// Create a new WASM provider manager
47    pub fn new() -> WasmProviderManager {
48        WasmProviderManager {
49            provider: LibQCryptoProvider::new()
50                .unwrap_or_else(|_| LibQCryptoProvider::new().unwrap()),
51            security_validator: SecurityValidator::new()
52                .unwrap_or_else(|_| SecurityValidator::new().unwrap()),
53        }
54    }
55}
56
57impl Default for WasmProviderManager {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl WasmProviderManager {
64    /// Get provider information
65    pub fn get_provider_info(&self) -> String {
66        #[cfg(feature = "wasm")]
67        {
68            serde_json::json!({
69                "name": "lib-Q Crypto Provider",
70                "version": crate::VERSION,
71                "description": "Post-Quantum Cryptography Provider",
72                "features": {
73                    "kem": true,
74                    "signature": true,
75                    "hash": true,
76                    "aead": true,
77                    "security_hardened": true,
78                    "post_quantum": true
79                },
80                "security_levels": [128, 192, 256],
81                "algorithms": {
82                    "kem": self.get_kem_algorithms(),
83                    "signature": self.get_signature_algorithms(),
84                    "hash": self.get_hash_algorithms(),
85                    "aead": self.get_aead_algorithms()
86                }
87            })
88            .to_string()
89        }
90        #[cfg(not(feature = "wasm"))]
91        {
92            "{}".to_string()
93        }
94    }
95
96    /// Check if an algorithm is supported
97    pub fn is_algorithm_supported(&self, algorithm: &str) -> bool {
98        // First check if it's a valid algorithm name
99        let algorithm = match self.parse_algorithm(algorithm) {
100            Ok(alg) => alg,
101            Err(_) => return false,
102        };
103
104        // Then check if the provider actually supports this algorithm
105        match algorithm.category() {
106            AlgorithmCategory::Kem => self.provider.kem().is_some(),
107            AlgorithmCategory::Signature => self.provider.signature().is_some(),
108            AlgorithmCategory::Hash => self.provider.hash().is_some(),
109            AlgorithmCategory::Aead => self.provider.aead().is_some(),
110            AlgorithmCategory::PrivacyProtocol => false,
111        }
112    }
113
114    /// Get algorithm information
115    pub fn get_algorithm_info(&self, algorithm: &str) -> Result<JsValue, JsValue> {
116        let algorithm = self.parse_algorithm(algorithm)?;
117
118        #[cfg(feature = "wasm")]
119        {
120            let info = Self::algorithm_info_json(algorithm);
121
122            match serde_wasm_bindgen::to_value(&info) {
123                Ok(value) => Ok(value),
124                Err(_) => Err(JsValue::from_str("Serialization error")),
125            }
126        }
127        #[cfg(not(feature = "wasm"))]
128        {
129            Err(JsValue::from_str("WASM feature not enabled"))
130        }
131    }
132
133    /// Build the algorithm-info JSON payload from real per-algorithm sources.
134    ///
135    /// Every numeric field here is sourced from [`crate::security::SecurityConstants`] — the same
136    /// table used elsewhere in this crate (e.g. key-size validation) — rather than a second,
137    /// independent hardcoded table that could drift from it (as happened with FN-DSA-1024's secret
138    /// key size). A field is `null` when the algorithm genuinely does not have that property (e.g.
139    /// a hash algorithm has no `signature` size) or when this crate has no per-algorithm source of
140    /// truth for it at all (there is no claimed-security-level table, so `security_level` is always
141    /// `null` — inventing a number here is exactly the defect being fixed).
142    fn algorithm_info_json(algorithm: Algorithm) -> serde_json::Value {
143        let constants = crate::security::SecurityConstants::new();
144
145        let public_key = constants.get_expected_key_size(algorithm, false).ok();
146        let secret_key = constants.get_expected_key_size(algorithm, true).ok();
147        let signature = constants.get_expected_signature_size(algorithm).ok();
148        let ciphertext = constants.get_expected_ciphertext_size(algorithm).ok();
149
150        // Only AEAD algorithms have a message-size ceiling sourced from this crate; other
151        // categories have no per-algorithm message-limit source, so leave it null there.
152        let max_message_size = if algorithm.category() == AlgorithmCategory::Aead {
153            Some(constants.max_aead_message_size())
154        } else {
155            None
156        };
157
158        serde_json::json!({
159            "name": algorithm.to_string(),
160            "category": algorithm.category().to_string(),
161            // No per-algorithm claimed-security-level table exists anywhere in this crate.
162            // Reporting a fixed number (the previous "256" placeholder) would fabricate the same
163            // kind of drift this fix removes, so this is honestly reported as unknown.
164            "security_level": serde_json::Value::Null,
165            "key_sizes": {
166                "public_key": public_key,
167                "secret_key": secret_key,
168                "signature": signature,
169                "ciphertext": ciphertext
170            },
171            "message_limits": {
172                "max_size": max_message_size
173            },
174            "features": {
175                "kem": algorithm.category() == AlgorithmCategory::Kem,
176                "signature": algorithm.category() == AlgorithmCategory::Signature,
177                "hash": algorithm.category() == AlgorithmCategory::Hash,
178                "aead": algorithm.category() == AlgorithmCategory::Aead,
179                "privacy_protocol": algorithm.category() == AlgorithmCategory::PrivacyProtocol
180            }
181        })
182    }
183
184    /// Get all supported algorithms
185    pub fn get_all_algorithms(&self) -> String {
186        #[cfg(feature = "wasm")]
187        {
188            let algorithms = serde_json::json!({
189                "kem": self.get_kem_algorithms(),
190                "signature": self.get_signature_algorithms(),
191                "hash": self.get_hash_algorithms(),
192                "aead": self.get_aead_algorithms()
193            });
194            algorithms.to_string()
195        }
196        #[cfg(not(feature = "wasm"))]
197        {
198            "{}".to_string()
199        }
200    }
201
202    /// Get KEM algorithms
203    pub fn get_kem_algorithms(&self) -> Vec<String> {
204        #[allow(unused_mut)] // mut needed when feature flags are enabled
205        let mut algorithms = alloc::vec![
206            "ml-kem-512".to_string(),
207            "ml-kem-768".to_string(),
208            "ml-kem-1024".to_string(),
209        ];
210
211        algorithms
212    }
213
214    /// Get signature algorithms
215    pub fn get_signature_algorithms(&self) -> Vec<String> {
216        crate::wasm::conversions::WASM_SIGNATURE_ALGORITHM_IDS
217            .iter()
218            .map(|s| (*s).to_string())
219            .collect()
220    }
221
222    /// Get hash algorithms
223    pub fn get_hash_algorithms(&self) -> Vec<String> {
224        alloc::vec![
225            "sha3-224".to_string(),
226            "sha3-256".to_string(),
227            "sha3-384".to_string(),
228            "sha3-512".to_string(),
229            "shake128".to_string(),
230            "shake256".to_string(),
231        ]
232    }
233
234    /// Get AEAD algorithms
235    pub fn get_aead_algorithms(&self) -> Vec<String> {
236        let algorithms = alloc::vec!["saturnin".to_string(), "shake256-aead".to_string(),];
237
238        algorithms
239    }
240
241    /// Validate algorithm parameters
242    pub fn validate_algorithm_params(
243        &self,
244        algorithm: &str,
245        key_size: Option<usize>,
246        message_size: Option<usize>,
247        nonce_size: Option<usize>,
248    ) -> Result<bool, JsValue> {
249        let algorithm = self.parse_algorithm(algorithm)?;
250
251        // Use security validator for comprehensive validation
252        if let Some(size) = key_size {
253            if size == 0 {
254                return Err(JsValue::from_str("Invalid algorithm key: empty key"));
255            }
256            // Validate key size against algorithm requirements
257            let test_key = (0..size).map(|_| 0u8).collect::<Vec<u8>>();
258            self.security_validator
259                .validate_key_size(algorithm, &test_key, true)
260                .map_err(crate::wasm::error::error_to_js_value)?;
261        }
262
263        if let Some(size) = message_size {
264            if size == 0 {
265                return Err(JsValue::from_str("Invalid message size: empty data"));
266            }
267            // Validate message size against the limit that applies to this algorithm family.
268            let test_message = (0..size).map(|_| 0u8).collect::<Vec<u8>>();
269            if algorithm.supports_category(AlgorithmCategory::Aead) {
270                self.security_validator
271                    .validate_aead_message(&test_message)
272                    .map_err(crate::wasm::error::error_to_js_value)?;
273            } else {
274                self.security_validator
275                    .validate_hash_input(&test_message)
276                    .map_err(crate::wasm::error::error_to_js_value)?;
277            }
278        }
279
280        if let Some(size) = nonce_size {
281            if size == 0 {
282                return Err(JsValue::from_str("Invalid nonce size: empty nonce"));
283            }
284            // Validate nonce size
285            let test_nonce = (0..size).map(|_| 0u8).collect::<Vec<u8>>();
286            self.security_validator
287                .validate_nonce(&test_nonce)
288                .map_err(crate::wasm::error::error_to_js_value)?;
289        }
290
291        Ok(true)
292    }
293
294    /// Get security recommendations
295    pub fn get_security_recommendations(&self) -> String {
296        #[cfg(feature = "wasm")]
297        {
298            serde_json::json!({
299                "general": {
300                    "use_authenticated_encryption": true,
301                    "validate_all_inputs": true,
302                    "use_secure_random": true,
303                    "protect_secret_keys": true,
304                    "rotate_keys_regularly": true
305                },
306                "kem": {
307                    "recommended_algorithms": ["ml-kem-768", "ml-kem-1024"],
308                    "key_rotation": "Every 90 days",
309                    "security_level": "Minimum 192-bit"
310                },
311                "signature": {
312                    "recommended_algorithms": ["ml-dsa-65", "ml-dsa-87"],
313                    "key_rotation": "Every 90 days",
314                    "security_level": "Minimum 192-bit"
315                },
316                "hash": {
317                    "recommended_algorithms": ["sha3-256", "sha3-384"],
318                    "security_level": "Minimum 256-bit"
319                },
320                "aead": {
321                    "recommended_algorithms": ["saturnin", "shake256-aead"],
322                    "nonce_requirements": "Unique per key",
323                    "security_level": "Minimum 256-bit"
324                }
325            })
326            .to_string()
327        }
328        #[cfg(not(feature = "wasm"))]
329        {
330            "{}".to_string()
331        }
332    }
333
334    /// Get performance benchmarks
335    pub fn get_performance_benchmarks(&self) -> String {
336        #[cfg(feature = "wasm")]
337        {
338            serde_json::json!({
339                "note": "Performance benchmarks are environment-dependent",
340                "recommendations": {
341                    "kem": {
342                        "fastest": "ml-kem-512",
343                        "most_secure": "ml-kem-1024",
344                        "balanced": "ml-kem-768"
345                    },
346                    "signature": {
347                        "fastest": "ml-dsa-44",
348                        "most_secure": "ml-dsa-87",
349                        "balanced": "ml-dsa-65"
350                    },
351                    "hash": {
352                        "fastest": "sha3-224",
353                        "most_secure": "sha3-512",
354                        "balanced": "sha3-256"
355                    }
356                }
357            })
358            .to_string()
359        }
360        #[cfg(not(feature = "wasm"))]
361        {
362            "{}".to_string()
363        }
364    }
365
366    /// Parse algorithm from string
367    fn parse_algorithm(&self, algorithm: &str) -> Result<Algorithm, crate::error::Error> {
368        crate::wasm::error::parse_algorithm_wasm(algorithm).map_err(|_| {
369            crate::error::Error::InvalidAlgorithm {
370                algorithm: "Invalid algorithm name",
371            }
372        })
373    }
374}
375
376/// WASM-compatible provider factory
377///
378/// This factory provides JavaScript-compatible bindings for creating providers:
379/// - Integrates with the new modular architecture
380/// - Provides consistent error handling
381/// - Supports all provider creation operations
382#[cfg_attr(feature = "wasm", wasm_bindgen)]
383pub struct WasmProviderFactory;
384
385#[cfg_attr(feature = "wasm", wasm_bindgen)]
386impl WasmProviderFactory {
387    /// Create a new provider manager
388    #[cfg_attr(feature = "wasm", wasm_bindgen)]
389    pub fn create_provider_manager() -> WasmProviderManager {
390        WasmProviderManager::new()
391    }
392
393    /// Create a provider manager with specific configuration
394    #[cfg_attr(feature = "wasm", wasm_bindgen)]
395    pub fn create_provider_manager_with_config(
396        config: &str,
397    ) -> Result<WasmProviderManager, JsValue> {
398        #[cfg(feature = "wasm")]
399        {
400            // Parse configuration (simplified for now)
401            let _config: serde_json::Value = match serde_json::from_str(config) {
402                Ok(config) => config,
403                Err(_) => {
404                    return Err(JsValue::from_str("Configuration parsing error"));
405                }
406            };
407
408            // For now, just create a default provider manager
409            // In a real implementation, this would configure the provider based on the config
410            Ok(WasmProviderManager::new())
411        }
412        #[cfg(not(feature = "wasm"))]
413        {
414            Err(JsValue::from_str("WASM feature not enabled"))
415        }
416    }
417
418    /// Get available provider types
419    #[cfg_attr(feature = "wasm", wasm_bindgen)]
420    pub fn get_available_providers() -> String {
421        #[cfg(feature = "wasm")]
422        {
423            serde_json::json!({
424                "providers": [
425                    {
426                        "name": "lib-q-crypto",
427                        "description": "Default lib-Q cryptographic provider",
428                        "features": ["kem", "signature", "hash", "aead"],
429                        "security_levels": [128, 192, 256]
430                    }
431                ]
432            })
433            .to_string()
434        }
435        #[cfg(not(feature = "wasm"))]
436        {
437            "{}".to_string()
438        }
439    }
440
441    /// Validate provider configuration
442    #[cfg_attr(feature = "wasm", wasm_bindgen)]
443    pub fn validate_provider_config(config: &str) -> Result<bool, JsValue> {
444        #[cfg(feature = "wasm")]
445        {
446            let _config: serde_json::Value = match serde_json::from_str(config) {
447                Ok(config) => config,
448                Err(_) => {
449                    return Err(JsValue::from_str("Configuration parsing error"));
450                }
451            };
452
453            // Basic validation - in a real implementation, this would be more comprehensive
454            Ok(true)
455        }
456        #[cfg(not(feature = "wasm"))]
457        {
458            Err(JsValue::from_str("WASM feature not enabled"))
459        }
460    }
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466
467    /// Regression for lead 16: `get_algorithm_info` used to report a hardcoded
468    /// `public_key`/`secret_key`/`signature` size of 1024 for every algorithm and a hardcoded
469    /// `security_level` of 256 for every algorithm, so ML-KEM-512 and ML-DSA-87 were reported as
470    /// having identical key sizes. This exercises the pure-Rust JSON builder directly (no JsValue
471    /// involved) so it can run natively — `get_algorithm_info` itself round-trips the same value
472    /// through `serde_wasm_bindgen::to_value`, which is wasm32-only.
473    #[test]
474    fn test_algorithm_info_reports_real_distinct_key_sizes() {
475        let kem_info = WasmProviderManager::algorithm_info_json(Algorithm::MlKem512);
476        let sig_info = WasmProviderManager::algorithm_info_json(Algorithm::MlDsa87);
477
478        let kem_pk = kem_info["key_sizes"]["public_key"].as_u64().unwrap();
479        let sig_pk = sig_info["key_sizes"]["public_key"].as_u64().unwrap();
480
481        // Different algorithms must report different key sizes.
482        assert_ne!(
483            kem_pk, sig_pk,
484            "ML-KEM-512 and ML-DSA-87 must not report the same public key size"
485        );
486
487        // Each reported size must match the crate's real source of truth exactly.
488        let constants = crate::security::SecurityConstants::new();
489        assert_eq!(
490            kem_pk as usize,
491            constants
492                .get_expected_key_size(Algorithm::MlKem512, false)
493                .unwrap()
494        );
495        assert_eq!(
496            sig_pk as usize,
497            constants
498                .get_expected_key_size(Algorithm::MlDsa87, false)
499                .unwrap()
500        );
501
502        // A hash algorithm has no keys/signature — those fields must be honestly null, not 1024.
503        let hash_info = WasmProviderManager::algorithm_info_json(Algorithm::Sha3_256);
504        assert!(hash_info["key_sizes"]["public_key"].is_null());
505        assert!(hash_info["key_sizes"]["secret_key"].is_null());
506        assert!(hash_info["key_sizes"]["signature"].is_null());
507
508        // No per-algorithm claimed-security-level table exists; it must be null, not 256.
509        assert!(kem_info["security_level"].is_null());
510        assert!(sig_info["security_level"].is_null());
511    }
512
513    #[test]
514    #[cfg(target_arch = "wasm32")]
515    fn test_wasm_provider_manager_creation() {
516        let manager = WasmProviderManager::new();
517        let info = manager.get_provider_info();
518        assert!(info.contains("lib-Q") || info == "{}");
519    }
520
521    #[test]
522    #[cfg(target_arch = "wasm32")]
523    fn test_wasm_provider_factory() {
524        let manager = WasmProviderFactory::create_provider_manager();
525        assert!(manager.is_algorithm_supported("sha3-256"));
526    }
527
528    #[test]
529    #[cfg(target_arch = "wasm32")]
530    fn test_algorithm_support() {
531        let manager = WasmProviderManager::new();
532        assert!(manager.is_algorithm_supported("sha3-256"));
533        assert!(!manager.is_algorithm_supported("invalid-algorithm"));
534    }
535
536    #[test]
537    #[cfg(target_arch = "wasm32")]
538    fn test_algorithm_info() {
539        let manager = WasmProviderManager::new();
540        let info = manager.get_algorithm_info("sha3-256");
541        assert!(info.is_ok());
542    }
543
544    #[test]
545    #[cfg(target_arch = "wasm32")]
546    fn test_security_recommendations() {
547        let manager = WasmProviderManager::new();
548        let recommendations = manager.get_security_recommendations();
549        assert!(
550            recommendations.contains("use_authenticated_encryption") || recommendations == "{}"
551        );
552    }
553}