browser_crypto/lib.rs
1//! A safe Rust interface to browser-based cryptographic operations using the
2//! Web Crypto API.
3//!
4//! This crate provides a type-safe wrapper around the browser's native
5//! cryptographic functionality, making it easier to perform common
6//! cryptographic operations in WebAssembly applications.
7//!
8//! # Features
9//!
10//! - Type-safe cryptographic algorithm implementations
11//! - Secure nonce generation and handling
12//! - AES-256-GCM encryption and decryption
13//! - Proper error handling and conversion from Web API exceptions
14//!
15//! # Examples
16//!
17//! ```rust,no_run
18//! use browser_crypto::aes256gcm::Aes256Gcm;
19//! use browser_crypto::algorithm::Algorithm;
20//!
21//! async fn encrypt_data() -> Result<(), Box<dyn std::error::Error>> {
22//! // Create a new AES-256-GCM instance with a key
23//! let key_bytes = [0u8; 32]; // Replace with your secure key
24//! let cipher = Aes256Gcm::from_key(&key_bytes).await?;
25//!
26//! // Generate a random nonce
27//! let nonce = Aes256Gcm::generate_nonce()?;
28//!
29//! // Encrypt some data
30//! let data = b"Secret message";
31//! let encrypted = cipher.encrypt(&nonce, data).await?;
32//!
33//! // Decrypt the data
34//! let decrypted = cipher.decrypt(&nonce, &encrypted).await?;
35//! assert_eq!(data.to_vec(), decrypted);
36//!
37//! Ok(())
38//! }
39//! ```
40//!
41//! # Security Considerations
42//!
43//! This crate relies on the browser's implementation of the Web Crypto API,
44//! which:
45//!
46//! - Uses the platform's secure random number generator
47//! - Implements cryptographic operations in native code
48//! - Provides protection against timing attacks
49//! - Follows modern cryptographic standards
50//!
51//! However, users should be aware that:
52//!
53//! - Keys should be generated and stored securely
54//! - Nonces should never be reused with the same key
55//! - The security of the application depends on the security of the browser
56//!
57//! # Features Flags
58//!
59//! - `log-error`: Enables console logging of unknown errors (useful for
60//! debugging)
61//!
62//! # Browser Compatibility
63//!
64//! This crate requires a browser with support for:
65//!
66//! - Web Crypto API
67//! - WebAssembly
68//! - Async/await
69//!
70//! Most modern browsers (Chrome, Firefox, Safari, Edge) support these features.
71//!
72//! # Error Handling
73//!
74//! The crate provides detailed error types that map directly to Web Crypto API
75//! exceptions, making it easier to handle and debug cryptographic operations:
76//!
77//! - `Error`: General Web Crypto API errors
78//! - `EncryptionError`: Encryption-specific errors
79//! - `DecryptionError`: Decryption-specific errors
80//! - `NonceError`: Nonce generation and validation errors
81//! - `ImportKeyError`: Key import and format errors
82//!
83//! # Implementation Details
84//!
85//! This crate uses `wasm-bindgen` to interface with the Web Crypto API and
86//! provides a safe Rust interface for:
87//!
88//! - Key management
89//! - Nonce generation
90//! - Encryption/decryption operations
91//! - Error handling and conversion
92//!
93//! The implementation focuses on safety, correctness, and ergonomic use in Rust
94//! while maintaining the security properties of the underlying Web Crypto API.
95
96use js_sys::Promise;
97use wasm_bindgen::{JsCast, JsValue};
98use wasm_bindgen_futures::JsFuture;
99use web_sys::{DomException, WorkerGlobalScope};
100
101pub mod aes256gcm;
102pub mod algorithm;
103
104/// Utility functions
105/// Resolves a JavaScript Promise to a Rust Result
106///
107/// # Arguments
108/// * `promise` - JavaScript Promise to resolve
109///
110/// # Returns
111/// Result containing resolved value or error
112async fn resolve<V, E>(promise: Promise) -> Result<V, E>
113where
114 V: JsCast,
115 E: From<JsValue>,
116 E: From<Error>,
117{
118 JsFuture::from(promise)
119 .await
120 .and_then(|value| value.dyn_into::<V>())
121 .map_err(E::from)
122}
123
124/// General errors that can occur when interacting with the Web Crypto API.
125#[derive(Debug, Clone, thiserror::Error)]
126pub enum Error {
127 /// Indicates that the global scope (window or worker context) could not be
128 /// accessed. This might occur in environments where the Web API is not
129 /// available.
130 #[error("unable to read global scope")]
131 GlobalScopeNotFound,
132 /// Indicates that the Web Crypto API is not available in the current
133 /// environment. This might occur in environments that don't support the
134 /// Web Crypto API or where it's been disabled.
135 #[error("unable to access crypto interface")]
136 CryptoUnreachable,
137 /// Represents a DOM exception with a name and message.
138 /// Provides more detailed information about Web API-specific errors.
139 ///
140 /// # Fields
141 /// * `0` - The name of the DOM exception
142 /// * `1` - The error message
143 #[error("DOMException {0}: {1}")]
144 DomException(String, String),
145 /// Represents an unknown or unexpected error that couldn't be classified.
146 /// When the `log-error` feature is enabled, these errors will be logged
147 /// to the console.
148 #[error("unknown exception")]
149 Unknown,
150}
151
152impl From<JsValue> for Error {
153 /// Converts a JavaScript value into a Rust Error.
154 ///
155 /// If the JavaScript value is a DOMException, it will be converted into
156 /// a `Error::DomException` with the appropriate name and message.
157 /// Otherwise, it will be converted into `Error::Unknown`.
158 ///
159 /// When the `log-error` feature is enabled, unknown errors will be logged
160 /// to the console for debugging purposes.
161 fn from(value: JsValue) -> Self {
162 if let Some(exception) = value.dyn_ref::<DomException>() {
163 Self::DomException(exception.name(), exception.message())
164 } else {
165 #[cfg(feature = "log-error")]
166 web_sys::console::error_1(&value);
167 Self::Unknown
168 }
169 }
170}
171
172fn scope() -> Result<web_sys::WorkerGlobalScope, Error> {
173 js_sys::global()
174 .dyn_into::<WorkerGlobalScope>()
175 .map_err(|_| Error::GlobalScopeNotFound)
176}
177
178fn crypto() -> Result<web_sys::Crypto, Error> {
179 scope().and_then(|scope| scope.crypto().map_err(|_| Error::CryptoUnreachable))
180}
181
182/// Gets the Web Crypto API interface
183///
184/// # Returns
185/// Result containing SubtleCrypto interface or Error
186fn subtle() -> Result<web_sys::SubtleCrypto, Error> {
187 crypto().map(|crypto| crypto.subtle())
188}
189
190fn array_to_vec(input: &js_sys::Uint8Array) -> Vec<u8> {
191 let mut output = vec![0; input.length() as usize];
192 input.copy_to(&mut output);
193 output
194}