rqjs_ext/modules/crypto/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3mod crc32;
4mod md5_hash;
5mod sha_hash;
6use std::slice;
7
8use once_cell::sync::Lazy;
9use rand::prelude::ThreadRng;
10use rand::Rng;
11use ring::rand::{SecureRandom, SystemRandom};
12use rquickjs::{
13    function::{Constructor, Opt},
14    module::{Declarations, Exports, ModuleDef},
15    prelude::{Func, Rest},
16    Class, Ctx, Exception, Function, IntoJs, Object, Result, Value,
17};
18
19use crate::{
20    // module_builder::ModuleInfo,
21    modules::{
22        buffer::Buffer,
23        encoding::encoder::{bytes_to_b64_string, bytes_to_hex_string},
24        module::export_default,
25        uuid::uuidv4,
26    },
27    utils::{
28        class::get_class_name,
29        object::{bytes_to_typed_array, get_start_end_indexes, obj_to_array_buffer},
30        result::ResultExt,
31    },
32    // vm::{CtxExtension, ErrorExtensions},
33};
34
35use self::{
36    crc32::{Crc32, Crc32c},
37    md5_hash::Md5,
38    sha_hash::{Hash, Hmac, ShaAlgorithm, ShaHash},
39};
40
41pub static SYSTEM_RANDOM: Lazy<SystemRandom> = Lazy::new(SystemRandom::new);
42
43fn encoded_bytes<'js>(ctx: Ctx<'js>, bytes: &[u8], encoding: &str) -> Result<Value<'js>> {
44    match encoding {
45        "hex" => {
46            let hex = bytes_to_hex_string(bytes);
47            let hex = rquickjs::String::from_str(ctx, &hex)?;
48            Ok(Value::from_string(hex))
49        }
50        "base64" => {
51            let b64 = bytes_to_b64_string(bytes);
52            let b64 = rquickjs::String::from_str(ctx, &b64)?;
53            Ok(Value::from_string(b64))
54        }
55        _ => bytes_to_typed_array(ctx, bytes),
56    }
57}
58
59#[inline]
60pub fn random_byte_array(length: usize) -> Vec<u8> {
61    let mut vec = vec![0; length];
62    SYSTEM_RANDOM.fill(&mut vec).unwrap();
63    vec
64}
65
66fn get_random_bytes(ctx: Ctx, length: usize) -> Result<Value> {
67    let random_bytes = random_byte_array(length);
68    Buffer(random_bytes).into_js(&ctx)
69}
70
71fn get_random_int(_ctx: Ctx, first: i64, second: Opt<i64>) -> Result<i64> {
72    let mut rng = ThreadRng::default();
73    let random_number = match second.0 {
74        Some(max) => rng.gen_range(first..max),
75        None => rng.gen_range(0..first),
76    };
77
78    Ok(random_number)
79}
80
81fn random_fill<'js>(ctx: Ctx<'js>, obj: Object<'js>, args: Rest<Value<'js>>) -> Result<()> {
82    let args_iter = args.0.into_iter();
83    let mut args_iter = args_iter.rev();
84
85    let callback: Function = args_iter
86        .next()
87        .and_then(|v| v.into_function())
88        .or_throw_msg(&ctx, "Callback required")?;
89    let size = args_iter
90        .next()
91        .and_then(|arg| arg.as_int())
92        .map(|i| i as usize);
93    let offset = args_iter
94        .next()
95        .and_then(|arg| arg.as_int())
96        .map(|i| i as usize);
97
98    todo!();
99    // ctx.clone().spawn_exit(async move {
100    //     if let Err(err) = random_fill_sync(ctx.clone(), obj.clone(), Opt(offset), Opt(size)) {
101    //         let err = err.into_value(&ctx)?;
102    //         () = callback.call((err,))?;
103
104    //         return Ok(());
105    //     }
106    //     () = callback.call((Null.into_js(&ctx), obj))?;
107    //     Ok::<_, Error>(())
108    // })?;
109    // Ok(())
110}
111
112fn random_fill_sync<'js>(
113    ctx: Ctx<'js>,
114    obj: Object<'js>,
115    offset: Opt<usize>,
116    size: Opt<usize>,
117) -> Result<Object<'js>> {
118    let offset = offset.unwrap_or(0);
119
120    if let Some((array_buffer, source_length, source_offset)) = obj_to_array_buffer(&obj)? {
121        let (start, end) = get_start_end_indexes(source_length, size.0, offset);
122
123        let raw = array_buffer
124            .as_raw()
125            .ok_or("ArrayBuffer is detached")
126            .or_throw(&ctx)?;
127        let bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), source_length) };
128
129        SYSTEM_RANDOM
130            .fill(&mut bytes[start + source_offset..end - source_offset])
131            .unwrap();
132    }
133
134    Ok(obj)
135}
136
137fn get_random_values<'js>(ctx: Ctx<'js>, obj: Object<'js>) -> Result<Object<'js>> {
138    if let Some((array_buffer, source_length, source_offset)) = obj_to_array_buffer(&obj)? {
139        let raw = array_buffer
140            .as_raw()
141            .ok_or("ArrayBuffer is detached")
142            .or_throw(&ctx)?;
143
144        if source_length > 0x10000 {
145            return Err(Exception::throw_message(
146                &ctx,
147                "QuotaExceededError: The requested length exceeds 65,536 bytes",
148            ));
149        }
150
151        let bytes = unsafe {
152            std::slice::from_raw_parts_mut(raw.ptr.as_ptr().add(source_offset), source_length)
153        };
154
155        match get_class_name(&obj)?.unwrap().as_str() {
156            "Int8Array" | "Uint8Array" | "Uint8ClampedArray" | "Int16Array" | "Uint16Array"
157            | "Int32Array" | "Uint32Array" | "BigInt64Array" | "BigUint64Array" => {
158                SYSTEM_RANDOM.fill(bytes).unwrap()
159            }
160            _ => return Err(Exception::throw_message(&ctx, "Unsupported TypedArray")),
161        }
162    }
163
164    Ok(obj)
165}
166
167pub fn init(ctx: &Ctx<'_>) -> Result<()> {
168    let globals = ctx.globals();
169
170    let crypto = Object::new(ctx.clone())?;
171
172    crypto.set("createHash", Func::from(Hash::new))?;
173    crypto.set("createHmac", Func::from(Hmac::new))?;
174    crypto.set("randomBytes", Func::from(get_random_bytes))?;
175    crypto.set("randomInt", Func::from(get_random_int))?;
176    crypto.set("randomUUID", Func::from(uuidv4))?;
177    crypto.set("randomFillSync", Func::from(random_fill_sync))?;
178    crypto.set("randomFill", Func::from(random_fill))?;
179    crypto.set("getRandomValues", Func::from(get_random_values))?;
180
181    globals.set("crypto", crypto)?;
182
183    Ok(())
184}
185
186pub struct CryptoModule;
187
188impl ModuleDef for CryptoModule {
189    fn declare(declare: &Declarations<'_>) -> Result<()> {
190        declare.declare("createHash")?;
191        declare.declare("createHmac")?;
192        declare.declare("Crc32")?;
193        declare.declare("Crc32c")?;
194        declare.declare("Md5")?;
195        declare.declare("randomBytes")?;
196        declare.declare("randomUUID")?;
197        declare.declare("randomInt")?;
198        declare.declare("randomFillSync")?;
199        declare.declare("randomFill")?;
200        declare.declare("getRandomValues")?;
201
202        for sha_algorithm in ShaAlgorithm::iterate() {
203            let class_name = sha_algorithm.class_name();
204            declare.declare(class_name)?;
205        }
206
207        declare.declare("default")?;
208
209        Ok(())
210    }
211
212    fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
213        Class::<Hash>::register(ctx)?;
214        Class::<Hmac>::register(ctx)?;
215        Class::<ShaHash>::register(ctx)?;
216
217        export_default(ctx, exports, |default| {
218            for sha_algorithm in ShaAlgorithm::iterate() {
219                let class_name: &str = sha_algorithm.class_name();
220                let algo = sha_algorithm;
221
222                let ctor =
223                    Constructor::new_class::<ShaHash, _, _>(ctx.clone(), move |ctx, secret| {
224                        ShaHash::new(ctx, algo, secret)
225                    })?;
226
227                default.set(class_name, ctor)?;
228            }
229
230            Class::<Md5>::define(default)?;
231            Class::<Crc32>::define(default)?;
232            Class::<Crc32c>::define(default)?;
233
234            default.set("createHash", Func::from(Hash::new))?;
235            default.set("createHmac", Func::from(Hmac::new))?;
236            default.set("randomBytes", Func::from(get_random_bytes))?;
237            default.set("randomInt", Func::from(get_random_int))?;
238            default.set("randomUUID", Func::from(uuidv4))?;
239            default.set("randomFillSync", Func::from(random_fill_sync))?;
240            default.set("randomFill", Func::from(random_fill))?;
241            default.set("getRandomValues", Func::from(get_random_values))?;
242            Ok(())
243        })?;
244
245        Ok(())
246    }
247}
248
249// impl From<CryptoModule> for ModuleInfo<CryptoModule> {
250//     fn from(val: CryptoModule) -> Self {
251//         ModuleInfo {
252//             name: "crypto",
253//             module: val,
254//         }
255//     }
256// }