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}