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(¶ms, &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(¶ms, &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}