browser_crypto/
aes256gcm.rs

1//! AES-256-GCM encryption implementation
2
3use js_sys::SyntaxError;
4use wasm_bindgen::{JsCast, JsValue};
5use web_sys::DomException;
6
7use crate::algorithm::{Algorithm, DecryptionError, EncryptionError, Nonce};
8
9const NAME: &str = "AES-GCM";
10
11/// Errors that can occur when importing cryptographic keys.
12///
13/// These errors map to the exceptions defined in the Web Crypto API
14/// specification for key import operations.
15///
16/// See [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#exceptions)
17#[derive(Debug, Clone, thiserror::Error)]
18pub enum ImportKeyError {
19    /// Indicates that the key usage array is empty for a secret or private key.
20    ///
21    /// This error occurs when:
22    /// - No key usages are specified during import
23    /// - The key type requires at least one usage to be specified
24    ///
25    /// Key usages typically include operations like "encrypt", "decrypt",
26    /// "sign", or "verify".
27    #[error("keyUsages is empty but the unwrapped key is of type secret or private")]
28    Syntax,
29    /// Indicates that the key data is not suitable for the specified format.
30    ///
31    /// This error occurs when:
32    /// - The key data is malformed
33    /// - The key data doesn't match the expected format
34    /// - The key data is invalid for the specified algorithm
35    ///
36    /// For example, trying to import non-AES data as an AES key would trigger
37    /// this error.
38    #[error("invalid format or keyData not suited for that format")]
39    Type,
40    /// Indicates that an invalid key format was specified during import.
41    ///
42    /// This error occurs when:
43    /// - The specified format (e.g., "raw", "pkcs8", "spki", "jwk") is not
44    ///   supported
45    /// - The specified format is not appropriate for the key type
46    ///
47    /// For example, trying to import a symmetric key using "spki" format would
48    /// trigger this error.
49    #[error("invalid key format provided")]
50    InvalidKeyFormat,
51    /// A wrapper for other types of errors that may occur during key import.
52    ///
53    /// This includes general Web Crypto API errors and other unexpected
54    /// failures.
55    #[error(transparent)]
56    Generic(#[from] crate::Error),
57}
58
59impl From<JsValue> for ImportKeyError {
60    /// Converts a JavaScript value into an ImportKeyError.
61    ///
62    /// Maps specific DOM exceptions to their corresponding ImportKeyError
63    /// variants:
64    /// - `SyntaxError` → `ImportKeyError::Syntax`
65    /// - `DataError` → `ImportKeyError::InvalidKeyFormat`
66    /// - JavaScript `SyntaxError` → `ImportKeyError::Type`
67    /// - Other errors → `ImportKeyError::Generic`
68    ///
69    /// # Arguments
70    /// * `value` - The JavaScript value to convert
71    ///
72    /// # Returns
73    /// The corresponding ImportKeyError variant
74    fn from(value: JsValue) -> Self {
75        if let Some(exception) = value.dyn_ref::<DomException>() {
76            if exception.name() == "SyntaxError" {
77                return Self::Syntax;
78            }
79            if exception.name() == "DataError" {
80                return Self::InvalidKeyFormat;
81            }
82        }
83        if value.dyn_ref::<SyntaxError>().is_some() {
84            return Self::Type;
85        }
86        Self::Generic(crate::Error::from(value))
87    }
88}
89
90/// AES-256-GCM encryption implementation
91#[derive(Debug, Clone)]
92pub struct Aes256Gcm {
93    key: web_sys::CryptoKey,
94}
95
96impl Aes256Gcm {
97    /// Creates a new AES-256-GCM instance from a raw key.
98    ///
99    /// # Arguments
100    /// * `data` - Raw key bytes (should be 32 bytes for AES-256)
101    ///
102    /// # Returns
103    /// Result containing the Aes256Gcm instance or an ImportKeyError
104    ///
105    /// # Errors
106    /// - `ImportKeyError::Syntax` if key usage array is empty
107    /// - `ImportKeyError::Type` if key format/data is invalid
108    /// - `ImportKeyError::InvalidKeyFormat` if provided key format is invalid
109    pub async fn from_key(data: &[u8]) -> Result<Self, ImportKeyError> {
110        let subtle = crate::subtle()?;
111
112        // Convert Rust array to Uint8Array
113        let js_key_data = js_sys::Uint8Array::from(data);
114
115        // Define AES-GCM import parameters
116        let algorithm = js_sys::Object::new();
117        js_sys::Reflect::set(&algorithm, &"name".into(), &NAME.into())?;
118
119        // Import the key as a CryptoKey
120        let usages = js_sys::Array::new();
121        usages.push(&"encrypt".into());
122        usages.push(&"decrypt".into());
123        let promise: js_sys::Promise = subtle.import_key_with_object(
124            "raw",               // Import format
125            &js_key_data.into(), // Key material (converted to JsValue)
126            &algorithm,          // Algorithm details
127            true,                // Extractable (true allows exporting later)
128            &usages,             // Allowed usages
129        )?;
130
131        let key: web_sys::CryptoKey =
132            crate::resolve::<web_sys::CryptoKey, ImportKeyError>(promise).await?;
133        Ok(Self { key })
134    }
135}
136
137impl Algorithm for Aes256Gcm {
138    const NONCE_SIZE: u32 = 12;
139
140    async fn encrypt(
141        &self,
142        nonce: &Nonce<Self>,
143        payload: &[u8],
144    ) -> Result<Vec<u8>, EncryptionError> {
145        let subtle = crate::subtle()?;
146        // Convert plaintext to Uint8Array
147        let plaintext = js_sys::Uint8Array::from(payload);
148
149        let params = web_sys::AesGcmParams::new(NAME, nonce.as_ref());
150        let promise: js_sys::Promise =
151            subtle.encrypt_with_object_and_js_u8_array(&params, &self.key, &plaintext)?;
152        let ciphertext = crate::resolve::<js_sys::ArrayBuffer, EncryptionError>(promise).await?;
153
154        Ok(crate::array_to_vec(&js_sys::Uint8Array::new(&ciphertext)))
155    }
156
157    async fn decrypt(
158        &self,
159        nonce: &Nonce<Self>,
160        payload: &[u8],
161    ) -> Result<Vec<u8>, DecryptionError> {
162        let subtle = crate::subtle()?;
163        // Convert plaintext to Uint8Array
164        let payload = js_sys::Uint8Array::from(payload);
165        let params = web_sys::AesGcmParams::new(NAME, nonce.as_ref());
166        let promise: js_sys::Promise =
167            subtle.decrypt_with_object_and_js_u8_array(&params, &self.key, &payload)?;
168        let clear = crate::resolve::<js_sys::ArrayBuffer, DecryptionError>(promise).await?;
169
170        Ok(crate::array_to_vec(&js_sys::Uint8Array::new(&clear)))
171    }
172}