1#[cfg(all(feature = "crypto-rust", feature = "crypto-openssl"))]
6compile_error!("Features `crypto-rust` and `crypto-openssl` are mutually exclusive");
7
8#[cfg(all(feature = "crypto-rust", feature = "crypto-ring"))]
9compile_error!("Features `crypto-rust` and `crypto-ring` are mutually exclusive");
10
11#[cfg(all(feature = "crypto-rust", feature = "crypto-graviola"))]
12compile_error!("Features `crypto-rust` and `crypto-graviola` are mutually exclusive");
13
14#[cfg(all(feature = "crypto-openssl", feature = "crypto-ring"))]
15compile_error!("Features `crypto-openssl` and `crypto-ring` are mutually exclusive");
16
17#[cfg(all(feature = "crypto-openssl", feature = "crypto-graviola"))]
18compile_error!("Features `crypto-openssl` and `crypto-graviola` are mutually exclusive");
19
20#[cfg(all(feature = "crypto-ring", feature = "crypto-graviola"))]
21compile_error!("Features `crypto-ring` and `crypto-graviola` are mutually exclusive");
22
23mod crc32;
24mod hash;
25mod subtle;
26
27mod provider;
28
29use std::slice;
30
31use crate::buffer::Buffer;
32use crate::context::CtxExtension;
33use crate::encoding::{bytes_to_b64_string, bytes_to_hex_string};
34use crate::exceptions::DOMException;
35use crate::utils::{
36 bytes::{get_start_end_indexes, ObjectBytes},
37 error::ErrorExtensions,
38 error_messages::{ERROR_MSG_ARRAY_BUFFER_DETACHED, ERROR_MSG_NOT_ARRAY_BUFFER},
39 module::{export_default, ModuleInfo},
40 result::ResultExt,
41};
42use once_cell::sync::Lazy;
43use rand::RngExt;
44use rquickjs::prelude::Async;
45use rquickjs::{
46 atom::PredefinedAtom,
47 function::{Constructor, Opt},
48 module::{Declarations, Exports, ModuleDef},
49 prelude::{Func, Rest},
50 Class, Ctx, Error, Exception, Function, IntoJs, Null, Object, Result, Value,
51};
52pub use subtle::CryptoKey;
53use subtle::{
54 subtle_decapsulate_bits, subtle_decapsulate_key, subtle_decrypt, subtle_derive_bits,
55 subtle_derive_key, subtle_digest, subtle_encapsulate_bits, subtle_encapsulate_key,
56 subtle_encrypt, subtle_export_key, subtle_generate_key, subtle_import_key, subtle_sign,
57 subtle_unwrap_key, subtle_verify, subtle_wrap_key, SubtleCrypto,
58};
59
60use self::{
61 crc32::{Crc32, Crc32c},
62 hash::{Hash, HashAlgorithm, Hmac},
63};
64
65static CRYPTO_PROVIDER: Lazy<provider::DefaultProvider> =
66 Lazy::new(|| provider::DefaultProvider {});
67
68fn encoded_bytes<'js>(ctx: &Ctx<'js>, bytes: &[u8], encoding: &str) -> Result<Option<Value<'js>>> {
69 match encoding {
70 "hex" => {
71 let hex = bytes_to_hex_string(bytes);
72 let hex = rquickjs::String::from_str(ctx.clone(), &hex)?;
73 Ok(Some(Value::from_string(hex)))
74 },
75 "base64" => {
76 let b64 = bytes_to_b64_string(bytes);
77 let b64 = rquickjs::String::from_str(ctx.clone(), &b64)?;
78 Ok(Some(Value::from_string(b64)))
79 },
80 _ => Ok(None),
81 }
82}
83
84#[inline]
85pub fn random_byte_array(length: usize) -> Vec<u8> {
86 let mut vec = vec![0u8; length];
87 rand::rng().fill(&mut vec[..]);
88 vec
89}
90
91fn get_random_bytes(ctx: Ctx, length: usize) -> Result<Value> {
92 let random_bytes = random_byte_array(length);
93 Buffer(random_bytes).into_js(&ctx)
94}
95
96fn get_random_int(first: i64, second: Opt<i64>) -> Result<i64> {
97 let mut rng = rand::rng();
98 let random_number = match second.0 {
99 Some(max) => rng.random_range(first..max),
100 None => rng.random_range(0..first),
101 };
102
103 Ok(random_number)
104}
105
106fn random_fill<'js>(ctx: Ctx<'js>, obj: Object<'js>, args: Rest<Value<'js>>) -> Result<()> {
107 let args_iter = args.0.into_iter();
108 let mut args_iter = args_iter.rev();
109
110 let callback: Function = args_iter
111 .next()
112 .and_then(|v| v.into_function())
113 .or_throw_msg(&ctx, "Callback required")?;
114 let size = args_iter
115 .next()
116 .and_then(|arg| arg.as_int())
117 .map(|i| i as usize);
118 let offset = args_iter
119 .next()
120 .and_then(|arg| arg.as_int())
121 .map(|i| i as usize);
122
123 ctx.clone().spawn_exit(async move {
124 if let Err(err) = random_fill_sync(ctx.clone(), obj.clone(), Opt(offset), Opt(size)) {
125 let err = err.into_value(&ctx)?;
126 () = callback.call((err,))?;
127
128 return Ok(());
129 }
130 () = callback.call((Null.into_js(&ctx), obj))?;
131 Ok::<_, Error>(())
132 })?;
133 Ok(())
134}
135
136fn random_fill_sync<'js>(
137 ctx: Ctx<'js>,
138 obj: Object<'js>,
139 offset: Opt<usize>,
140 size: Opt<usize>,
141) -> Result<Object<'js>> {
142 let offset = offset.unwrap_or(0);
143
144 if let Some(object_bytes) = ObjectBytes::from_array_buffer(&obj)? {
145 let (array_buffer, source_length, source_offset) = object_bytes
146 .get_array_buffer()?
147 .expect(ERROR_MSG_NOT_ARRAY_BUFFER);
148 let raw = array_buffer
149 .as_raw()
150 .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED)
151 .or_throw(&ctx)?;
152
153 if offset > source_length {
154 return Err(Exception::throw_range(
155 &ctx,
156 "The value of \"offset\" is out of range",
157 ));
158 }
159 if let Some(size) = size.0 {
160 if offset + size > source_length {
161 return Err(Exception::throw_range(
162 &ctx,
163 "The value of \"size + offset\" is out of range",
164 ));
165 }
166 }
167
168 let (start, end) = get_start_end_indexes(source_length, size.0, offset);
169
170 let bytes = unsafe {
173 slice::from_raw_parts_mut(raw.cast::<u8>().as_ptr().add(source_offset), source_length)
174 };
175
176 rand::rng().fill(&mut bytes[start..end]);
177 }
178
179 Ok(obj)
180}
181
182fn get_random_values<'js>(ctx: Ctx<'js>, obj: Object<'js>) -> Result<Object<'js>> {
183 if let Some(object_bytes) = ObjectBytes::from_array_buffer(&obj)? {
184 if matches!(
185 object_bytes,
186 ObjectBytes::F64Array(_)
187 | ObjectBytes::F32Array(_)
188 | ObjectBytes::F16Array(_)
189 | ObjectBytes::DataView(_, _, _)
190 ) {
191 return Err(DOMException::type_mismatch_error(
192 &ctx,
193 "getRandomValues requires an integer TypedArray",
194 ));
195 }
196
197 let (array_buffer, source_length, source_offset) = object_bytes
198 .get_array_buffer()?
199 .expect(ERROR_MSG_NOT_ARRAY_BUFFER);
200 let raw = array_buffer
201 .as_raw()
202 .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED)
203 .or_throw(&ctx)?;
204
205 if source_length > 0x10000 {
206 return Err(DOMException::quota_exceeded_error(
207 &ctx,
208 "The requested length exceeds 65,536 bytes",
209 ));
210 }
211
212 let bytes = unsafe {
213 std::slice::from_raw_parts_mut(raw.cast::<u8>().as_ptr().add(source_offset), source_length)
214 };
215
216 rand::rng().fill(bytes)
217 }
218
219 Ok(obj)
220}
221
222fn uuidv4() -> String {
223 let uuid = rand::random::<u128>() & 0xFFFFFFFFFFFF4FFFBFFFFFFFFFFFFFFF | 0x40008000000000000000;
224
225 static HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
226 let bytes = uuid.to_be_bytes();
227
228 let mut buf = [0u8; 36];
229
230 static HEX_POS: [usize; 32] = [
232 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15, 16, 17, 19, 20, 21, 22, 24, 25, 26, 27, 28,
233 29, 30, 31, 32, 33, 34, 35,
234 ];
235
236 let mut hex_idx = 0;
238 for &byte in &bytes[..] {
239 let high = HEX_CHARS[(byte >> 4) as usize];
240 let low = HEX_CHARS[(byte & 0x0f) as usize];
241
242 buf[HEX_POS[hex_idx]] = high;
243 buf[HEX_POS[hex_idx + 1]] = low;
244 hex_idx += 2;
245 }
246
247 buf[8] = b'-';
249 buf[13] = b'-';
250 buf[18] = b'-';
251 buf[23] = b'-';
252
253 unsafe { String::from_utf8_unchecked(buf.to_vec()) }
256}
257
258#[rquickjs::class]
259#[derive(rquickjs::JsLifetime, rquickjs::class::Trace)]
260struct Crypto {}
261
262#[rquickjs::methods]
263impl Crypto {
264 #[qjs(constructor)]
265 pub fn new(ctx: Ctx<'_>) -> Result<Self> {
266 Err(Exception::throw_type(&ctx, "Illegal constructor"))
267 }
268
269 #[qjs(prop, rename = PredefinedAtom::SymbolToStringTag, configurable)]
270 pub fn to_string_tag() -> &'static str {
271 stringify!(Crypto)
272 }
273}
274
275pub fn init(ctx: &Ctx<'_>) -> Result<()> {
276 let globals = ctx.globals();
277
278 Class::<Crypto>::define(&globals)?;
279 let crypto = Class::instance(ctx.clone(), Crypto {})?;
280
281 crypto.set("createHash", Func::from(Hash::new))?;
282 crypto.set("createHmac", Func::from(Hmac::new))?;
283 crypto.set("randomBytes", Func::from(get_random_bytes))?;
284 crypto.set("randomInt", Func::from(get_random_int))?;
285 crypto.set("randomUUID", Func::from(uuidv4))?;
286 crypto.set("randomFillSync", Func::from(random_fill_sync))?;
287 crypto.set("randomFill", Func::from(random_fill))?;
288 crypto.set("getRandomValues", Func::from(get_random_values))?;
289
290 Class::<SubtleCrypto>::define(&globals)?;
291 Class::<CryptoKey>::define(&globals)?;
292
293 let subtle = Class::instance(ctx.clone(), SubtleCrypto {})?;
294 subtle.set(
295 "decapsulateBits",
296 Func::from(Async(subtle_decapsulate_bits)),
297 )?;
298 subtle.set("decapsulateKey", Func::from(Async(subtle_decapsulate_key)))?;
299 subtle.set("decrypt", Func::from(Async(subtle_decrypt)))?;
300 subtle.set("deriveKey", Func::from(Async(subtle_derive_key)))?;
301 subtle.set("deriveBits", Func::from(Async(subtle_derive_bits)))?;
302 subtle.set("digest", Func::from(Async(subtle_digest)))?;
303 subtle.set("encrypt", Func::from(Async(subtle_encrypt)))?;
304 subtle.set(
305 "encapsulateBits",
306 Func::from(Async(subtle_encapsulate_bits)),
307 )?;
308 subtle.set("encapsulateKey", Func::from(Async(subtle_encapsulate_key)))?;
309 subtle.set("exportKey", Func::from(Async(subtle_export_key)))?;
310 subtle.set("generateKey", Func::from(Async(subtle_generate_key)))?;
311 subtle.set("importKey", Func::from(Async(subtle_import_key)))?;
312 subtle.set("sign", Func::from(Async(subtle_sign)))?;
313 subtle.set("verify", Func::from(Async(subtle_verify)))?;
314 subtle.set("wrapKey", Func::from(Async(subtle_wrap_key)))?;
315 subtle.set("unwrapKey", Func::from(Async(subtle_unwrap_key)))?;
316 crypto.set("subtle", subtle)?;
317
318 globals.set("crypto", crypto)?;
319
320 Ok(())
321}
322
323pub struct CryptoModule;
324
325impl ModuleDef for CryptoModule {
326 fn declare(declare: &Declarations) -> Result<()> {
327 declare.declare("createHash")?;
328 declare.declare("createHmac")?;
329 declare.declare("Crc32")?;
330 declare.declare("Crc32c")?;
331 declare.declare("randomBytes")?;
332 declare.declare("randomUUID")?;
333 declare.declare("randomInt")?;
334 declare.declare("randomFillSync")?;
335 declare.declare("randomFill")?;
336 declare.declare("getRandomValues")?;
337
338 for algorithm in HashAlgorithm::iter() {
339 declare.declare(algorithm.class_name())?;
340 }
341
342 declare.declare("crypto")?;
343 declare.declare("webcrypto")?;
344 declare.declare("default")?;
345
346 Ok(())
347 }
348
349 fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
350 export_default(ctx, exports, |default| {
351 for algorithm in HashAlgorithm::iter() {
352 let class_name: &str = algorithm.class_name();
353 let algo_name = String::from(algorithm.as_str());
354
355 let ctor = Constructor::new_class::<Hash, _, _>(
356 ctx.clone(),
357 move |ctx: Ctx<'js>, secret: Opt<ObjectBytes<'js>>| match secret.0 {
358 Some(secret) => Hash::new_hmac(ctx, algo_name.clone(), secret),
359 None => Hash::new(ctx, algo_name.clone()),
360 },
361 )?;
362
363 default.set(class_name, ctor)?;
364 }
365
366 let crypto: Object = ctx.globals().get("crypto")?;
367
368 Class::<Crc32>::define(default)?;
369 Class::<Crc32c>::define(default)?;
370
371 default.set("createHash", Func::from(Hash::new))?;
372 default.set("createHmac", Func::from(Hmac::new))?;
373 default.set("randomBytes", Func::from(get_random_bytes))?;
374 default.set("randomInt", Func::from(get_random_int))?;
375 default.set("randomUUID", Func::from(uuidv4))?;
376 default.set("randomFillSync", Func::from(random_fill_sync))?;
377 default.set("randomFill", Func::from(random_fill))?;
378 default.set("getRandomValues", Func::from(get_random_values))?;
379 default.set("crypto", crypto.clone())?;
380 default.set("webcrypto", crypto)?;
381 Ok(())
382 })?;
383
384 Ok(())
385 }
386}
387
388impl From<CryptoModule> for ModuleInfo<CryptoModule> {
389 fn from(val: CryptoModule) -> Self {
390 ModuleInfo {
391 name: "crypto",
392 module: val,
393 }
394 }
395}