browser_crypto/
algorithm.rs

1use std::marker::PhantomData;
2
3use wasm_bindgen::{JsCast, JsValue};
4use web_sys::DomException;
5
6/// Errors that can occur during nonce (number used once) operations.
7///
8/// These errors handle both Web Crypto API random generation errors and
9/// nonce validation errors.
10///
11/// See [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#exceptions)
12#[derive(Debug, Clone, thiserror::Error)]
13pub enum NonceError {
14    /// Indicates that the requested nonce length exceeds the maximum allowed
15    /// size.
16    ///
17    /// This error occurs when trying to generate a nonce larger than 65536
18    /// bytes, which is the maximum size allowed by the Web Crypto API's
19    /// getRandomValues(). This limit exists as a security measure to
20    /// prevent excessive entropy extraction.
21    ///
22    /// Note: Most cryptographic algorithms use much smaller nonces
23    /// (typically 12 or 16 bytes), so this error should rarely occur in
24    /// practice.
25    #[error("the requested nonce length exceeds 65536")]
26    QuotaExceeded,
27    /// Indicates that the provided nonce size doesn't match the algorithm's
28    /// requirements.
29    ///
30    /// This error occurs when:
31    /// - Creating a nonce from existing data
32    /// - The provided data length doesn't match the algorithm's specified nonce
33    ///   size
34    ///
35    /// # Fields
36    /// * `expected` - The nonce size required by the algorithm
37    /// * `received` - The actual size of the provided nonce data
38    ///
39    /// For example, if AES-GCM requires a 12-byte nonce but 16 bytes were
40    /// provided, this error would be returned with expected=12,
41    /// received=16.
42    #[error("invalid nonce size provided, expected {expected}, received {received}")]
43    InvalidSize { expected: u32, received: u32 },
44    /// A wrapper for other types of errors that may occur during nonce
45    /// operations.
46    ///
47    /// This includes general Web Crypto API errors and other unexpected
48    /// failures that might occur during nonce generation or handling.
49    #[error(transparent)]
50    Generic(#[from] crate::Error),
51}
52
53impl From<wasm_bindgen::JsValue> for NonceError {
54    fn from(value: wasm_bindgen::JsValue) -> Self {
55        if let Some(exception) = value.dyn_ref::<web_sys::DomException>() {
56            if exception.name().as_str() == "QuotaExceededError" {
57                return Self::QuotaExceeded;
58            }
59        }
60        Self::Generic(crate::Error::from(value))
61    }
62}
63
64/// Errors that can occur during encryption operations.
65///
66/// These errors map to the exceptions defined in the Web Crypto API
67/// specification for encryption operations.
68///
69/// See [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#exceptions)
70#[derive(Debug, Clone, thiserror::Error)]
71pub enum EncryptionError {
72    /// Indicates that the requested operation is not valid for the provided
73    /// key. This typically occurs when:
74    /// - The key doesn't support the encryption operation
75    /// - The key's algorithm doesn't match the specified algorithm
76    /// - The key's usages don't include "encrypt"
77    #[error("requested operation is not valid for the provided key")]
78    InvalidAccess,
79    /// Indicates that the operation failed for an algorithm-specific reason.
80    /// This can occur when:
81    /// - The input data is too large
82    /// - The algorithm parameters are invalid
83    /// - There's an internal error in the cryptographic implementation
84    #[error("operation failed for an operation-specific reason")]
85    Operation,
86    /// A wrapper for other types of errors that may occur during encryption
87    #[error(transparent)]
88    Generic(#[from] crate::Error),
89}
90
91impl From<JsValue> for EncryptionError {
92    fn from(value: JsValue) -> Self {
93        if let Some(exception) = value.dyn_ref::<DomException>() {
94            match exception.name().as_str() {
95                "InvalidAccessError" => {
96                    return Self::InvalidAccess;
97                }
98                "OperationError" => {
99                    return Self::Operation;
100                }
101                _ => {}
102            }
103        }
104        Self::Generic(crate::Error::from(value))
105    }
106}
107
108/// Errors that can occur during decryption operations.
109///
110/// These errors map to the exceptions defined in the Web Crypto API
111/// specification for decryption operations.
112///
113/// See [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt#exceptions)
114#[derive(Debug, Clone, thiserror::Error)]
115pub enum DecryptionError {
116    /// Indicates that the requested operation is not valid for the provided
117    /// key. This typically occurs when:
118    /// - The key doesn't support the decryption operation
119    /// - The key's algorithm doesn't match the specified algorithm
120    /// - The key's usages don't include "decrypt"
121    #[error("requested operation is not valid for the provided key")]
122    InvalidAccess,
123    /// Indicates that the operation failed for an algorithm-specific reason.
124    /// This can occur when:
125    /// - The ciphertext is corrupted or malformed
126    /// - The authentication tag is invalid (for authenticated encryption)
127    /// - The algorithm parameters (like nonce) don't match those used for
128    ///   encryption
129    #[error("operation failed for an operation-specific reason")]
130    Operation,
131    /// A wrapper for other types of errors that may occur during decryption
132    #[error(transparent)]
133    Generic(#[from] crate::Error),
134}
135
136impl From<JsValue> for DecryptionError {
137    fn from(value: JsValue) -> Self {
138        if let Some(exception) = value.dyn_ref::<DomException>() {
139            match exception.name().as_str() {
140                "InvalidAccessError" => {
141                    return Self::InvalidAccess;
142                }
143                "OperationError" => {
144                    return Self::Operation;
145                }
146                _ => {}
147            }
148        }
149        Self::Generic(crate::Error::from(value))
150    }
151}
152
153/// Nonce handling for cryptographic operations
154#[derive(Debug, Clone)]
155pub struct Nonce<A> {
156    algo: PhantomData<A>,
157    inner: js_sys::Uint8Array,
158}
159
160impl<A> AsRef<js_sys::Uint8Array> for Nonce<A> {
161    fn as_ref(&self) -> &js_sys::Uint8Array {
162        &self.inner
163    }
164}
165
166impl<A> Nonce<A>
167where
168    A: Algorithm,
169    A: Sized,
170{
171    /// Generates a new random nonce
172    ///
173    /// # Returns
174    /// Result containing generated Nonce or NonceError
175    pub fn generate() -> Result<Nonce<A>, NonceError> {
176        let crypto = crate::crypto()?;
177        let inner = js_sys::Uint8Array::new_with_length(A::NONCE_SIZE);
178        crypto.get_random_values_with_js_u8_array(&inner)?;
179        Ok(Nonce {
180            algo: PhantomData,
181            inner,
182        })
183    }
184
185    /// Creates a nonce from existing bytes
186    ///
187    /// # Arguments
188    /// * `data` - Byte slice containing nonce data
189    ///
190    /// # Returns
191    /// Result containing Nonce or NonceError
192    ///
193    /// # Errors
194    /// Returns `NonceError::InvalidSize` if data length doesn't match algorithm
195    /// requirements
196    pub fn from_slice(data: &[u8]) -> Result<Self, NonceError> {
197        let size = data.len() as u32;
198        if size != A::NONCE_SIZE {
199            return Err(NonceError::InvalidSize {
200                expected: A::NONCE_SIZE,
201                received: size,
202            });
203        }
204        Ok(Self {
205            algo: PhantomData,
206            inner: js_sys::Uint8Array::from(data),
207        })
208    }
209
210    pub fn iter(&self) -> impl Iterator<Item = u8> + '_ {
211        (0..self.inner.length()).map(|idx| self.inner.get_index(idx))
212    }
213
214    /// Returns nonce bytes as a vector
215    pub fn to_vec(&self) -> Vec<u8> {
216        crate::array_to_vec(&self.inner)
217    }
218}
219
220/// Core cryptographic algorithm trait
221pub trait Algorithm: Sized {
222    /// Required nonce size in bytes for this algorithm
223    const NONCE_SIZE: u32;
224
225    /// Generates a new random nonce suitable for this algorithm
226    ///
227    /// # Returns
228    /// Result containing the generated Nonce or a NonceError
229    ///
230    /// # Errors
231    /// - `NonceError::QuotaExceeded` if requested length > 65536 bytes
232    /// - `NonceError::InvalidSize` if nonce size doesn't match algorithm
233    ///   requirements
234    fn generate_nonce() -> Result<Nonce<Self>, NonceError> {
235        Nonce::<Self>::generate()
236    }
237
238    /// Encrypts data using this algorithm
239    ///
240    /// # Arguments
241    /// * `nonce` - Nonce to use for encryption
242    /// * `payload` - Data to encrypt
243    ///
244    /// # Returns
245    /// Result containing encrypted bytes or an EncryptionError
246    ///
247    /// # Errors
248    /// - `EncryptionError::InvalidAccess` if operation invalid for provided key
249    /// - `EncryptionError::Operation` if encryption fails for
250    ///   algorithm-specific reasons
251    fn encrypt(
252        &self,
253        nonce: &Nonce<Self>,
254        payload: &[u8],
255    ) -> impl std::future::Future<Output = Result<Vec<u8>, EncryptionError>>;
256
257    /// Decrypts data using this algorithm
258    ///
259    /// # Arguments
260    /// * `nonce` - Nonce used for encryption
261    /// * `payload` - Encrypted data to decrypt
262    ///
263    /// # Returns
264    /// Result containing decrypted bytes or a DecryptionError
265    ///
266    /// # Errors
267    /// - `DecryptionError::InvalidAccess` if operation invalid for provided key
268    /// - `DecryptionError::Operation` if decryption fails for
269    ///   algorithm-specific reasons
270    fn decrypt(
271        &self,
272        nonce: &Nonce<Self>,
273        payload: &[u8],
274    ) -> impl std::future::Future<Output = Result<Vec<u8>, DecryptionError>>;
275}