Skip to main content

nodejs/stdlib/
crypto.rs

1//! Node `crypto` module.
2//!
3//! * `createHash(algo)` → a `Hash` instance (`update(...).digest(enc)`), backed
4//!   by the `md-5`/`sha1`/`sha2` crates.
5//! * `createHmac(algo, key)` → an `Hmac` instance (`update(...).digest(enc)`),
6//!   backed by the `hmac` crate over the same digests.
7//! * `randomBytes`, `randomUUID`, `randomInt` — CSPRNG output via `getrandom`.
8//!
9//! `Hash`/`Hmac` are plain objects tagged `@@native = "Hash"` / `"Hmac"`
10//! accumulating input in a hidden `@@data` byte array until `digest` finalizes.
11
12use super::{arg_str, to_base64, to_hex};
13use crate::host::{is_callable, with_host, JsObj};
14use cipher::block_padding::Pkcs7;
15use cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, StreamCipher};
16use fusevm::Value;
17use hkdf::Hkdf;
18use hmac::{Hmac, Mac};
19use indexmap::IndexMap;
20use md5::{Digest as _, Md5};
21use num_bigint::BigUint;
22use p256::elliptic_curve::sec1::ToEncodedPoint;
23use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding};
24use rsa::pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey};
25use sha1::Sha1;
26use sha2::{Sha256, Sha384, Sha512};
27use signature::{SignatureEncoding, Signer, Verifier};
28use spki::{DecodePublicKey, EncodePublicKey};
29use subtle::ConstantTimeEq;
30
31/// Cipher algorithms `createCipheriv`/`createDecipheriv` support (AES CBC/CTR).
32const CIPHERS: &[&str] = &[
33    "aes-128-cbc",
34    "aes-192-cbc",
35    "aes-256-cbc",
36    "aes-128-ctr",
37    "aes-192-ctr",
38    "aes-256-ctr",
39];
40
41/// Digest names `createHash`/`createHmac`/`pbkdf2`/`hkdf` accept.
42const HASHES: &[&str] = &["md5", "sha1", "sha256", "sha512"];
43
44/// Standard EC curve names for `getCurves()`. Key generation over these is not
45/// supported (no EC crate available); the list mirrors the common OpenSSL names
46/// so feature-detection code sees them.
47const CURVES: &[&str] = &[
48    "prime256v1",
49    "secp256k1",
50    "secp384r1",
51    "secp521r1",
52    "secp224r1",
53    "secp192k1",
54    "secp256r1",
55];
56
57pub const METHODS: &[&str] = &[
58    "createHash",
59    "createHmac",
60    "randomBytes",
61    "randomUUID",
62    "randomInt",
63    "pbkdf2Sync",
64    "pbkdf2",
65    "scryptSync",
66    "scrypt",
67    "hkdfSync",
68    "hkdf",
69    "createCipheriv",
70    "createDecipheriv",
71    "randomFillSync",
72    "randomFill",
73    "timingSafeEqual",
74    "getHashes",
75    "getCiphers",
76    "getCurves",
77    "getFips",
78    "pseudoRandomBytes",
79    "prng",
80    "rng",
81    "hash",
82    "randomUUIDv7",
83    "getCipherInfo",
84    // Asymmetric keys & signatures
85    "generateKeyPairSync",
86    "generateKeyPair",
87    "createPrivateKey",
88    "createPublicKey",
89    "createSecretKey",
90    "createSign",
91    "createVerify",
92    "sign",
93    "verify",
94    "publicEncrypt",
95    "privateDecrypt",
96    "privateEncrypt",
97    "publicDecrypt",
98    // Diffie-Hellman / ECDH
99    "createDiffieHellman",
100    "createDiffieHellmanGroup",
101    "getDiffieHellman",
102    "createECDH",
103    "diffieHellman",
104    // Primes
105    "checkPrime",
106    "checkPrimeSync",
107    "generatePrime",
108    "generatePrimeSync",
109    // Misc
110    "argon2",
111    "argon2Sync",
112    "getRandomValues",
113];
114
115pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
116    Some(match method {
117        "createHash" => {
118            let algo = arg_str(args, 0).to_ascii_lowercase();
119            if !supported(&algo) {
120                // Node names no algorithm here — the reference message is the bare
121                // `Digest method not supported`, and it carries no `code`.
122                return Some(Err("Error: Digest method not supported".into()));
123            }
124            Ok(with_host(|h| {
125                let data = h.new_array(Vec::new());
126                let mut m = IndexMap::new();
127                m.insert("@@native".into(), h.new_str("Hash"));
128                m.insert("@@algo".into(), h.new_str(algo));
129                m.insert("@@data".into(), data);
130                h.new_object(m)
131            }))
132        }
133        "createHmac" => {
134            let algo = arg_str(args, 0).to_ascii_lowercase();
135            if !supported(&algo) {
136                // Node names no algorithm here — the reference message is the bare
137                // `Digest method not supported`, and it carries no `code`.
138                return Some(Err("Error: Digest method not supported".into()));
139            }
140            // Key may be a Buffer (raw bytes) or a string (utf8 by default).
141            let key = key_bytes(args.get(1));
142            Ok(with_host(|h| {
143                let data = h.new_array(Vec::new());
144                let keyv = h.new_array(key.iter().map(|b| Value::Float(*b as f64)).collect());
145                let mut m = IndexMap::new();
146                m.insert("@@native".into(), h.new_str("Hmac"));
147                m.insert("@@algo".into(), h.new_str(algo));
148                m.insert("@@key".into(), keyv);
149                m.insert("@@data".into(), data);
150                h.new_object(m)
151            }))
152        }
153        "randomBytes" => {
154            let n = super::arg_num(args, 0).max(0.0) as usize;
155            let mut buf = vec![0u8; n];
156            if let Err(e) = getrandom::getrandom(&mut buf) {
157                return Some(Err(format!("Error: failed to generate random bytes: {e}")));
158            }
159            // Callback form: randomBytes(n, (err, buf) => ...). Build the Buffer,
160            // release the host borrow, then queue the callback with (null, buf).
161            let cb = args
162                .get(1)
163                .cloned()
164                .filter(|v| with_host(|h| is_callable(h, v)));
165            if let Some(cb) = cb {
166                let bufv = super::buffer::from_bytes(&buf);
167                with_host(|h| {
168                    let nullv = h.null();
169                    h.queue_micro(cb, vec![nullv, bufv]);
170                });
171                Ok(Value::Undef)
172            } else {
173                Ok(super::buffer::from_bytes(&buf))
174            }
175        }
176        "randomUUID" => {
177            let mut b = [0u8; 16];
178            if let Err(e) = getrandom::getrandom(&mut b) {
179                return Some(Err(format!("Error: failed to generate random bytes: {e}")));
180            }
181            // RFC 4122 v4: version nibble = 4, variant nibble ∈ [8..b].
182            b[6] = (b[6] & 0x0f) | 0x40;
183            b[8] = (b[8] & 0x3f) | 0x80;
184            let h = to_hex(&b);
185            let uuid = format!(
186                "{}-{}-{}-{}-{}",
187                &h[0..8],
188                &h[8..12],
189                &h[12..16],
190                &h[16..20],
191                &h[20..32],
192            );
193            Ok(with_host(|host| host.new_str(uuid)))
194        }
195        "randomInt" => {
196            // randomInt([min, ]max) — uniform integer in [min, max).
197            let (min, max) = if args.len() >= 2 {
198                (super::arg_num(args, 0), super::arg_num(args, 1))
199            } else {
200                (0.0, super::arg_num(args, 0))
201            };
202            let (min, max) = (min as i64, max as i64);
203            if max <= min {
204                return Some(Err(crate::host::coded_error(
205                    "RangeError",
206                    "ERR_OUT_OF_RANGE",
207                    &format!(
208                        "The value of \"max\" is out of range. It must be greater than \
209                         the value of \"min\" ({min}). Received {max}"
210                    ),
211                )));
212            }
213            let range = (max - min) as u64;
214            match random_below(range) {
215                Ok(r) => Ok(Value::Float((min + r as i64) as f64)),
216                Err(e) => Err(format!("Error: failed to generate random bytes: {e}")),
217            }
218        }
219        // ── Key derivation ──────────────────────────────────────────────
220        "pbkdf2Sync" => {
221            let digest = arg_str(args, 4).to_ascii_lowercase();
222            match pbkdf2_derive(
223                &digest,
224                &val_bytes_at(args, 0),
225                &val_bytes_at(args, 1),
226                super::arg_num(args, 2) as u32,
227                super::arg_num(args, 3).max(0.0) as usize,
228            ) {
229                Ok(out) => Ok(super::buffer::from_bytes(&out)),
230                Err(e) => Err(e),
231            }
232        }
233        "pbkdf2" => {
234            let digest = arg_str(args, 4).to_ascii_lowercase();
235            let res = pbkdf2_derive(
236                &digest,
237                &val_bytes_at(args, 0),
238                &val_bytes_at(args, 1),
239                super::arg_num(args, 2) as u32,
240                super::arg_num(args, 3).max(0.0) as usize,
241            );
242            deliver_async(args.get(5).cloned(), res)
243        }
244        "scryptSync" => {
245            let keylen = super::arg_num(args, 2).max(0.0) as usize;
246            match scrypt_derive(
247                &val_bytes_at(args, 0),
248                &val_bytes_at(args, 1),
249                keylen,
250                opts_object(args, 3),
251            ) {
252                Ok(out) => Ok(super::buffer::from_bytes(&out)),
253                Err(e) => Err(e),
254            }
255        }
256        "scrypt" => {
257            let keylen = super::arg_num(args, 2).max(0.0) as usize;
258            let res = scrypt_derive(
259                &val_bytes_at(args, 0),
260                &val_bytes_at(args, 1),
261                keylen,
262                opts_object(args, 3),
263            );
264            deliver_async(trailing_cb(args), res)
265        }
266        "hkdfSync" => {
267            let digest = arg_str(args, 0).to_ascii_lowercase();
268            match hkdf_derive(
269                &digest,
270                &val_bytes_at(args, 1),
271                &val_bytes_at(args, 2),
272                &val_bytes_at(args, 3),
273                super::arg_num(args, 4).max(0.0) as usize,
274            ) {
275                Ok(out) => Ok(super::buffer::from_bytes(&out)),
276                Err(e) => Err(e),
277            }
278        }
279        "hkdf" => {
280            let digest = arg_str(args, 0).to_ascii_lowercase();
281            let res = hkdf_derive(
282                &digest,
283                &val_bytes_at(args, 1),
284                &val_bytes_at(args, 2),
285                &val_bytes_at(args, 3),
286                super::arg_num(args, 4).max(0.0) as usize,
287            );
288            deliver_async(args.get(5).cloned(), res)
289        }
290        // ── Symmetric ciphers ───────────────────────────────────────────
291        "createCipheriv" => make_cipher("Cipheriv", args),
292        "createDecipheriv" => make_cipher("Decipheriv", args),
293        // ── Random fill ─────────────────────────────────────────────────
294        "randomFillSync" => random_fill(args),
295        "randomFill" => {
296            let cb = trailing_cb(args);
297            let res = random_fill(args);
298            match (cb, res) {
299                (Some(cb), Ok(buf)) => {
300                    with_host(|h| {
301                        let nullv = h.null();
302                        h.queue_micro(cb, vec![nullv, buf]);
303                    });
304                    Ok(Value::Undef)
305                }
306                (Some(cb), Err(e)) => {
307                    let errv = with_host(|h| h.new_str(e));
308                    with_host(|h| h.queue_micro(cb, vec![errv]));
309                    Ok(Value::Undef)
310                }
311                (None, r) => r,
312            }
313        }
314        // ── Constant-time compare ───────────────────────────────────────
315        "timingSafeEqual" => {
316            let a = val_bytes_at(args, 0);
317            let b = val_bytes_at(args, 1);
318            if a.len() != b.len() {
319                return Some(Err(crate::host::plain_coded_error(
320                    "RangeError",
321                    "ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH",
322                    "Input buffers must have the same byte length",
323                )));
324            }
325            Ok(Value::Bool(a.ct_eq(&b).into()))
326        }
327        // ── Introspection ───────────────────────────────────────────────
328        "getHashes" => Ok(with_host(|h| {
329            let items: Vec<Value> = HASHES.iter().map(|s| h.new_str(*s)).collect();
330            h.new_array(items)
331        })),
332        "getCiphers" => Ok(with_host(|h| {
333            let items: Vec<Value> = CIPHERS.iter().map(|s| h.new_str(*s)).collect();
334            h.new_array(items)
335        })),
336        "getCurves" => Ok(with_host(|h| {
337            let items: Vec<Value> = CURVES.iter().map(|s| h.new_str(*s)).collect();
338            h.new_array(items)
339        })),
340        // node v26 returns the number 0 (not the boolean false) from getFips().
341        "getFips" => Ok(Value::Float(0.0)),
342        "getCipherInfo" => cipher_info(&arg_str(args, 0).to_ascii_lowercase()),
343        // ── randomBytes aliases (all return a Buffer of CSPRNG bytes) ────
344        "pseudoRandomBytes" | "prng" | "rng" => {
345            let n = super::arg_num(args, 0).max(0.0) as usize;
346            let mut buf = vec![0u8; n];
347            if let Err(e) = getrandom::getrandom(&mut buf) {
348                return Some(Err(format!("Error: failed to generate random bytes: {e}")));
349            }
350            let cb = args
351                .get(1)
352                .cloned()
353                .filter(|v| with_host(|h| is_callable(h, v)));
354            if let Some(cb) = cb {
355                let bufv = super::buffer::from_bytes(&buf);
356                with_host(|h| {
357                    let nullv = h.null();
358                    h.queue_micro(cb, vec![nullv, bufv]);
359                });
360                Ok(Value::Undef)
361            } else {
362                Ok(super::buffer::from_bytes(&buf))
363            }
364        }
365        // ── One-shot hash (node 21+) ────────────────────────────────────
366        "hash" => {
367            let algo = arg_str(args, 0).to_ascii_lowercase();
368            if !supported(&algo) {
369                // Node names no algorithm here — the reference message is the bare
370                // `Digest method not supported`, and it carries no `code`.
371                return Some(Err("Error: Digest method not supported".into()));
372            }
373            // args[1] is the data (utf8 string or Buffer); there is no input-encoding param.
374            let data = val_bytes_at(args, 1);
375            // args[2] is the output encoding (default "hex"); "buffer" yields a Buffer.
376            let out = digest(&algo, &data);
377            let out_enc = if args.len() > 2 {
378                arg_str(args, 2)
379            } else {
380                "hex".into()
381            };
382            Ok(if out_enc == "buffer" {
383                super::buffer::from_bytes(&out)
384            } else {
385                encode_out(&out, Some(&out_enc))
386            })
387        }
388        // ── time-ordered UUID v7 ────────────────────────────────────────
389        "randomUUIDv7" => uuid_v7(),
390        // ── Asymmetric key generation ───────────────────────────────────
391        "generateKeyPairSync" => {
392            generate_key_pair(&arg_str(args, 0).to_ascii_lowercase(), args.get(1))
393        }
394        "generateKeyPair" => {
395            let cb = trailing_cb(args);
396            let res = generate_key_pair(&arg_str(args, 0).to_ascii_lowercase(), args.get(1));
397            match cb {
398                Some(cb) => {
399                    match res {
400                        Ok(pair) => with_host(|h| {
401                            // Deliver (err=null, publicKey, privateKey).
402                            let nullv = h.null();
403                            let (pubk, prvk) = match h.get(&pair) {
404                                Some(JsObj::Object(p)) => (
405                                    p.get("publicKey").cloned().unwrap_or(Value::Undef),
406                                    p.get("privateKey").cloned().unwrap_or(Value::Undef),
407                                ),
408                                _ => (Value::Undef, Value::Undef),
409                            };
410                            h.queue_micro(cb, vec![nullv, pubk, prvk]);
411                        }),
412                        Err(e) => {
413                            let errv = with_host(|h| h.new_str(e));
414                            with_host(|h| h.queue_micro(cb, vec![errv]));
415                        }
416                    }
417                    Ok(Value::Undef)
418                }
419                None => res,
420            }
421        }
422        "createPrivateKey" => create_private_key(args.first()),
423        "createPublicKey" => create_public_key(args.first()),
424        "createSecretKey" => Ok(secret_key_object(&val_bytes_at(args, 0))),
425        // ── Sign / Verify (streaming instances) ─────────────────────────
426        "createSign" => Ok(new_sign_verify("Sign", &arg_str(args, 0))),
427        "createVerify" => Ok(new_sign_verify("Verify", &arg_str(args, 0))),
428        // ── Sign / Verify (one-shot) ────────────────────────────────────
429        "sign" => {
430            let algo = arg_str(args, 0);
431            let data = val_bytes_at(args, 1);
432            let key = key_material(args.get(2).unwrap_or(&Value::Undef));
433            match sign_data(&key, &algo, &data) {
434                Ok(sig) => Ok(super::buffer::from_bytes(&sig)),
435                Err(e) => Err(e),
436            }
437        }
438        "verify" => {
439            let algo = arg_str(args, 0);
440            let data = val_bytes_at(args, 1);
441            let key = key_material(args.get(2).unwrap_or(&Value::Undef));
442            let sig = val_bytes_at(args, 3);
443            match verify_data(&key, &algo, &data, &sig) {
444                Ok(ok) => Ok(Value::Bool(ok)),
445                Err(e) => Err(e),
446            }
447        }
448        // ── RSA public/private encryption ───────────────────────────────
449        "publicEncrypt" => rsa_public_op(args, true, true),
450        "privateDecrypt" => rsa_public_op(args, false, false),
451        "privateEncrypt" => rsa_private_encrypt(args),
452        "publicDecrypt" => rsa_public_decrypt(args),
453        // ── Diffie-Hellman ──────────────────────────────────────────────
454        "createDiffieHellman" => create_diffie_hellman(args),
455        "createDiffieHellmanGroup" | "getDiffieHellman" => diffie_hellman_group(&arg_str(args, 0)),
456        "createECDH" => create_ecdh(&arg_str(args, 0)),
457        "diffieHellman" => diffie_hellman_oneshot(args.first()),
458        // ── Primes ──────────────────────────────────────────────────────
459        "checkPrimeSync" => Ok(Value::Bool(check_prime(args.first()))),
460        "checkPrime" => {
461            let ok = check_prime(args.first());
462            let cb = trailing_cb(args);
463            match cb {
464                Some(cb) => {
465                    with_host(|h| {
466                        let nullv = h.null();
467                        h.queue_micro(cb, vec![nullv, Value::Bool(ok)]);
468                    });
469                    Ok(Value::Undef)
470                }
471                None => Ok(Value::Bool(ok)),
472            }
473        }
474        "generatePrimeSync" => {
475            generate_prime(super::arg_num(args, 0) as usize, opts_object(args, 1))
476        }
477        "generatePrime" => {
478            let res = generate_prime(super::arg_num(args, 0) as usize, opts_object(args, 1));
479            let cb = trailing_cb(args);
480            match (cb, res) {
481                (Some(cb), Ok(v)) => {
482                    with_host(|h| {
483                        let nullv = h.null();
484                        h.queue_micro(cb, vec![nullv, v]);
485                    });
486                    Ok(Value::Undef)
487                }
488                (Some(cb), Err(e)) => {
489                    let errv = with_host(|h| h.new_str(e));
490                    with_host(|h| h.queue_micro(cb, vec![errv]));
491                    Ok(Value::Undef)
492                }
493                (None, r) => r,
494            }
495        }
496        // ── argon2 ──────────────────────────────────────────────────────
497        "argon2Sync" => match argon2_hash(&arg_str(args, 0), args.get(1)) {
498            Ok(out) => Ok(super::buffer::from_bytes(&out)),
499            Err(e) => Err(e),
500        },
501        "argon2" => deliver_async(
502            trailing_cb(args),
503            argon2_hash(&arg_str(args, 0), args.get(1)),
504        ),
505        // ── WebCrypto getRandomValues ───────────────────────────────────
506        "getRandomValues" => get_random_values(args.first()),
507        _ => return None,
508    })
509}
510
511/// `Hash` instance methods: `update` (chainable) and `digest`.
512pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
513    hashlike_call("Hash", recv, method, args)
514}
515
516/// `Hmac` instance methods: `update` (chainable) and `digest`.
517pub fn hmac_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
518    hashlike_call("Hmac", recv, method, args)
519}
520
521/// `Cipheriv`/`Decipheriv` instance methods: `update(data[,inEnc,outEnc])` and
522/// `final([outEnc])`. Input is accumulated in `@@data`; the transform runs at
523/// `final` (CBC needs the full block/padding stream, so `update` returns empty
524/// and `final` returns the whole result — the standard `update()+final()`
525/// concatenation is byte-identical to node).
526pub fn cipher_instance_call(
527    tag: &str,
528    recv: &Value,
529    method: &str,
530    args: &[Value],
531) -> Result<Value, String> {
532    match method {
533        "update" => {
534            let bytes = cipher_input_bytes(args);
535            with_host(|h| {
536                if let Some(JsObj::Object(p)) = h.get(recv).cloned() {
537                    if let Some(arr) = p.get("@@data").cloned() {
538                        if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
539                            items.extend(bytes.iter().map(|b| Value::Float(*b as f64)));
540                        }
541                    }
542                }
543            });
544            let out_enc = if args.len() > 2 {
545                Some(arg_str(args, 2))
546            } else {
547                None
548            };
549            Ok(encode_out(&[], out_enc.as_deref()))
550        }
551        "final" => {
552            let (algo, key, iv, data) = with_host(|h| {
553                let (mut algo, mut key, mut iv, mut data) =
554                    (String::new(), Vec::new(), Vec::new(), Vec::new());
555                if let Some(JsObj::Object(p)) = h.get(recv) {
556                    algo = p.get("@@algo").map(|v| h.str_of(v)).unwrap_or_default();
557                    if let Some(JsObj::Array(it)) = p.get("@@key").and_then(|v| h.get(v)) {
558                        key = it.iter().map(|v| h.to_number(v) as u8).collect();
559                    }
560                    if let Some(JsObj::Array(it)) = p.get("@@iv").and_then(|v| h.get(v)) {
561                        iv = it.iter().map(|v| h.to_number(v) as u8).collect();
562                    }
563                    if let Some(JsObj::Array(it)) = p.get("@@data").and_then(|v| h.get(v)) {
564                        data = it.iter().map(|v| h.to_number(v) as u8).collect();
565                    }
566                }
567                (algo, key, iv, data)
568            });
569            let out = cipher_crypt(&algo, &key, &iv, &data, tag == "Cipheriv")?;
570            let out_enc = if args.is_empty() {
571                None
572            } else {
573                Some(arg_str(args, 0))
574            };
575            Ok(encode_out(&out, out_enc.as_deref()))
576        }
577        "setAutoPadding" => Ok(recv.clone()),
578        _ => Err(crate::host::type_error(&format!(
579            "{}.{method} is not a function",
580            tag.to_ascii_lowercase()
581        ))),
582    }
583}
584
585/// Shared `update`/`digest` for `Hash` and `Hmac` (both accumulate into `@@data`;
586/// `digest` finalizes via a plain digest or an HMAC keyed by `@@key`).
587fn hashlike_call(kind: &str, recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
588    match method {
589        "update" => {
590            let enc = if args.len() > 1 {
591                arg_str(args, 1)
592            } else {
593                "utf8".into()
594            };
595            let bytes = decode(&arg_str(args, 0), &enc);
596            with_host(|h| {
597                if let Some(JsObj::Object(p)) = h.get(recv).cloned() {
598                    if let Some(arr) = p.get("@@data").cloned() {
599                        if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
600                            items.extend(bytes.iter().map(|b| Value::Float(*b as f64)));
601                        }
602                    }
603                }
604            });
605            Ok(recv.clone())
606        }
607        "digest" => {
608            let (algo, key, data) = with_host(|h| {
609                let (mut algo, mut key, mut data) = (String::new(), Vec::new(), Vec::new());
610                if let Some(JsObj::Object(p)) = h.get(recv) {
611                    algo = p.get("@@algo").map(|v| h.str_of(v)).unwrap_or_default();
612                    if let Some(JsObj::Array(items)) = p.get("@@data").and_then(|v| h.get(v)) {
613                        data = items.iter().map(|v| h.to_number(v) as u8).collect();
614                    }
615                    if let Some(JsObj::Array(items)) = p.get("@@key").and_then(|v| h.get(v)) {
616                        key = items.iter().map(|v| h.to_number(v) as u8).collect();
617                    }
618                }
619                (algo, key, data)
620            });
621            let out = if kind == "Hmac" {
622                hmac_digest(&algo, &key, &data)
623            } else {
624                digest(&algo, &data)
625            };
626            let enc = if args.is_empty() {
627                None
628            } else {
629                Some(arg_str(args, 0))
630            };
631            Ok(match enc.as_deref() {
632                Some("hex") => with_host(|h| h.new_str(to_hex(&out))),
633                Some("base64") | Some("base64url") => with_host(|h| h.new_str(to_base64(&out))),
634                Some("latin1") | Some("binary") => {
635                    with_host(|h| h.new_str(out.iter().map(|b| *b as char).collect::<String>()))
636                }
637                _ => super::buffer::from_bytes(&out),
638            })
639        }
640        _ => Err(crate::host::type_error(&format!(
641            "{}.{method} is not a function",
642            kind.to_ascii_lowercase()
643        ))),
644    }
645}
646
647fn supported(algo: &str) -> bool {
648    matches!(algo, "md5" | "sha1" | "sha256" | "sha512")
649}
650
651fn digest(algo: &str, data: &[u8]) -> Vec<u8> {
652    match algo {
653        "md5" => {
654            let mut h = Md5::new();
655            h.update(data);
656            h.finalize().to_vec()
657        }
658        "sha1" => {
659            let mut h = Sha1::new();
660            h.update(data);
661            h.finalize().to_vec()
662        }
663        "sha512" => {
664            let mut h = Sha512::new();
665            h.update(data);
666            h.finalize().to_vec()
667        }
668        _ => {
669            let mut h = Sha256::new();
670            h.update(data);
671            h.finalize().to_vec()
672        }
673    }
674}
675
676fn hmac_digest(algo: &str, key: &[u8], data: &[u8]) -> Vec<u8> {
677    // HMAC accepts any key length, so `new_from_slice` never fails here.
678    match algo {
679        "md5" => {
680            let mut m = Hmac::<Md5>::new_from_slice(key).expect("HMAC accepts any key length");
681            m.update(data);
682            m.finalize().into_bytes().to_vec()
683        }
684        "sha1" => {
685            let mut m = Hmac::<Sha1>::new_from_slice(key).expect("HMAC accepts any key length");
686            m.update(data);
687            m.finalize().into_bytes().to_vec()
688        }
689        "sha512" => {
690            let mut m = Hmac::<Sha512>::new_from_slice(key).expect("HMAC accepts any key length");
691            m.update(data);
692            m.finalize().into_bytes().to_vec()
693        }
694        _ => {
695            let mut m = Hmac::<Sha256>::new_from_slice(key).expect("HMAC accepts any key length");
696            m.update(data);
697            m.finalize().into_bytes().to_vec()
698        }
699    }
700}
701
702/// The `createHmac` key argument as raw bytes: a Buffer's bytes, else the value's
703/// utf8 string encoding.
704fn key_bytes(v: Option<&Value>) -> Vec<u8> {
705    v.map(val_bytes).unwrap_or_default()
706}
707
708/// A value's raw bytes: a Buffer/TypedArray's backing bytes, else its utf8
709/// string encoding. Used by pbkdf2/scrypt/hkdf/cipher/timingSafeEqual inputs.
710fn val_bytes(v: &Value) -> Vec<u8> {
711    if super::native_tag(v).as_deref() == Some("Buffer") {
712        return with_host(|h| match h.get(v) {
713            Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
714                Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
715                _ => Vec::new(),
716            },
717            _ => Vec::new(),
718        });
719    }
720    with_host(|h| h.str_of(v)).into_bytes()
721}
722
723/// `val_bytes` for the arg at index `i` (`Value::Undef` → empty).
724fn val_bytes_at(args: &[Value], i: usize) -> Vec<u8> {
725    args.get(i).map(val_bytes).unwrap_or_default()
726}
727
728/// The trailing argument if it is a callback (async form detection).
729fn trailing_cb(args: &[Value]) -> Option<Value> {
730    args.last()
731        .cloned()
732        .filter(|v| with_host(|h| is_callable(h, v)))
733}
734
735/// The arg at index `i` if it is a plain (non-callable) options object.
736fn opts_object(args: &[Value], i: usize) -> Option<Value> {
737    let v = args.get(i)?.clone();
738    let is_obj = with_host(|h| matches!(h.get(&v), Some(JsObj::Object(_))) && !is_callable(h, &v));
739    is_obj.then_some(v)
740}
741
742/// Queue a derived-key result to an async callback as `(null, buf)` / `(err)`;
743/// if there is no callback, return the value/error synchronously.
744fn deliver_async(cb: Option<Value>, res: Result<Vec<u8>, String>) -> Result<Value, String> {
745    match (cb.filter(|v| with_host(|h| is_callable(h, v))), res) {
746        (Some(cb), Ok(out)) => {
747            let bufv = super::buffer::from_bytes(&out);
748            with_host(|h| {
749                let nullv = h.null();
750                h.queue_micro(cb, vec![nullv, bufv]);
751            });
752            Ok(Value::Undef)
753        }
754        (Some(cb), Err(e)) => {
755            let errv = with_host(|h| h.new_str(e));
756            with_host(|h| h.queue_micro(cb, vec![errv]));
757            Ok(Value::Undef)
758        }
759        (None, Ok(out)) => Ok(super::buffer::from_bytes(&out)),
760        (None, Err(e)) => Err(e),
761    }
762}
763
764/// PBKDF2-HMAC derivation over a supported digest.
765fn pbkdf2_derive(
766    digest: &str,
767    pass: &[u8],
768    salt: &[u8],
769    iters: u32,
770    keylen: usize,
771) -> Result<Vec<u8>, String> {
772    let mut out = vec![0u8; keylen];
773    match digest {
774        "sha1" => pbkdf2::pbkdf2_hmac::<Sha1>(pass, salt, iters, &mut out),
775        "sha256" => pbkdf2::pbkdf2_hmac::<Sha256>(pass, salt, iters, &mut out),
776        "sha512" => pbkdf2::pbkdf2_hmac::<Sha512>(pass, salt, iters, &mut out),
777        "md5" => pbkdf2::pbkdf2_hmac::<Md5>(pass, salt, iters, &mut out),
778        _ => return Err(format!("Error: Invalid digest: {digest}")),
779    }
780    Ok(out)
781}
782
783/// scrypt derivation. Reads node's `N`/`cost`, `r`/`blockSize`, `p`/
784/// `parallelization` options (defaults 16384/8/1).
785fn scrypt_derive(
786    pass: &[u8],
787    salt: &[u8],
788    keylen: usize,
789    opts: Option<Value>,
790) -> Result<Vec<u8>, String> {
791    let (mut n, mut r, mut p) = (16384.0f64, 8.0f64, 1.0f64);
792    if let Some(o) = opts {
793        n = opt_num(&o, &["N", "cost"], n);
794        r = opt_num(&o, &["r", "blockSize"], r);
795        p = opt_num(&o, &["p", "parallelization"], p);
796    }
797    let n = n as u64;
798    if n < 2 || (n & (n - 1)) != 0 {
799        return Err("Error: Invalid scrypt param: N must be a power of two > 1".into());
800    }
801    let params = scrypt::Params::new(n.trailing_zeros() as u8, r as u32, p as u32, keylen)
802        .map_err(|e| format!("Error: {e}"))?;
803    let mut out = vec![0u8; keylen];
804    scrypt::scrypt(pass, salt, &params, &mut out).map_err(|e| format!("Error: {e}"))?;
805    Ok(out)
806}
807
808/// HKDF (extract + expand) over a supported digest.
809fn hkdf_derive(
810    digest: &str,
811    ikm: &[u8],
812    salt: &[u8],
813    info: &[u8],
814    keylen: usize,
815) -> Result<Vec<u8>, String> {
816    let mut out = vec![0u8; keylen];
817    let ok = match digest {
818        "sha1" => Hkdf::<Sha1>::new(Some(salt), ikm).expand(info, &mut out),
819        "sha256" => Hkdf::<Sha256>::new(Some(salt), ikm).expand(info, &mut out),
820        "sha512" => Hkdf::<Sha512>::new(Some(salt), ikm).expand(info, &mut out),
821        "md5" => Hkdf::<Md5>::new(Some(salt), ikm).expand(info, &mut out),
822        _ => return Err(format!("Error: Invalid digest: {digest}")),
823    };
824    ok.map_err(|_| "Error: Invalid key length".to_string())?;
825    Ok(out)
826}
827
828/// Read the first present, finite numeric property among `keys` from an options
829/// object, else `default`.
830fn opt_num(obj: &Value, keys: &[&str], default: f64) -> f64 {
831    with_host(|h| {
832        if let Some(JsObj::Object(p)) = h.get(obj) {
833            for k in keys {
834                if let Some(v) = p.get(*k) {
835                    let n = h.to_number(v);
836                    if !n.is_nan() {
837                        return n;
838                    }
839                }
840            }
841        }
842        default
843    })
844}
845
846/// Build a `Cipheriv`/`Decipheriv` instance object from `(algo, key, iv)`.
847fn make_cipher(tag: &str, args: &[Value]) -> Result<Value, String> {
848    let algo = arg_str(args, 0).to_ascii_lowercase();
849    if !CIPHERS.contains(&algo.as_str()) {
850        return Err(format!("Error: Unknown cipher: {algo}"));
851    }
852    let key = val_bytes_at(args, 1);
853    let iv = val_bytes_at(args, 2);
854    let want_key = key_len(&algo);
855    if key.len() != want_key {
856        return Err("Error: Invalid key length".into());
857    }
858    if iv.len() != 16 {
859        return Err("Error: Invalid initialization vector".into());
860    }
861    Ok(with_host(|h| {
862        let keyv = h.new_array(key.iter().map(|b| Value::Float(*b as f64)).collect());
863        let ivv = h.new_array(iv.iter().map(|b| Value::Float(*b as f64)).collect());
864        let data = h.new_array(Vec::new());
865        let mut m = IndexMap::new();
866        m.insert("@@native".into(), h.new_str(tag));
867        m.insert("@@algo".into(), h.new_str(algo));
868        m.insert("@@key".into(), keyv);
869        m.insert("@@iv".into(), ivv);
870        m.insert("@@data".into(), data);
871        h.new_object(m)
872    }))
873}
874
875/// Cipher `update` input: a Buffer's bytes, else the string decoded by the input
876/// encoding (arg 1, default utf8).
877fn cipher_input_bytes(args: &[Value]) -> Vec<u8> {
878    if super::native_tag(args.first().unwrap_or(&Value::Undef)).as_deref() == Some("Buffer") {
879        return val_bytes_at(args, 0);
880    }
881    let enc = if args.len() > 1 {
882        arg_str(args, 1)
883    } else {
884        "utf8".into()
885    };
886    decode(&arg_str(args, 0), &enc)
887}
888
889/// AES-CBC (Pkcs7) / AES-CTR transform. `encrypt` selects direction (CTR is
890/// symmetric so the flag is unused there).
891fn cipher_crypt(
892    algo: &str,
893    key: &[u8],
894    iv: &[u8],
895    data: &[u8],
896    encrypt: bool,
897) -> Result<Vec<u8>, String> {
898    const KEYERR: &str = "Error: Invalid key length";
899    const DECERR: &str = "Error: error:1C800064:Provider routines::bad decrypt";
900    match (algo, encrypt) {
901        ("aes-128-cbc", true) => Ok(cbc::Encryptor::<aes::Aes128>::new_from_slices(key, iv)
902            .map_err(|_| KEYERR.to_string())?
903            .encrypt_padded_vec_mut::<Pkcs7>(data)),
904        ("aes-192-cbc", true) => Ok(cbc::Encryptor::<aes::Aes192>::new_from_slices(key, iv)
905            .map_err(|_| KEYERR.to_string())?
906            .encrypt_padded_vec_mut::<Pkcs7>(data)),
907        ("aes-256-cbc", true) => Ok(cbc::Encryptor::<aes::Aes256>::new_from_slices(key, iv)
908            .map_err(|_| KEYERR.to_string())?
909            .encrypt_padded_vec_mut::<Pkcs7>(data)),
910        ("aes-128-cbc", false) => cbc::Decryptor::<aes::Aes128>::new_from_slices(key, iv)
911            .map_err(|_| KEYERR.to_string())?
912            .decrypt_padded_vec_mut::<Pkcs7>(data)
913            .map_err(|_| DECERR.to_string()),
914        ("aes-192-cbc", false) => cbc::Decryptor::<aes::Aes192>::new_from_slices(key, iv)
915            .map_err(|_| KEYERR.to_string())?
916            .decrypt_padded_vec_mut::<Pkcs7>(data)
917            .map_err(|_| DECERR.to_string()),
918        ("aes-256-cbc", false) => cbc::Decryptor::<aes::Aes256>::new_from_slices(key, iv)
919            .map_err(|_| KEYERR.to_string())?
920            .decrypt_padded_vec_mut::<Pkcs7>(data)
921            .map_err(|_| DECERR.to_string()),
922        ("aes-128-ctr", _) => {
923            let mut buf = data.to_vec();
924            ctr::Ctr128BE::<aes::Aes128>::new_from_slices(key, iv)
925                .map_err(|_| KEYERR.to_string())?
926                .apply_keystream(&mut buf);
927            Ok(buf)
928        }
929        ("aes-192-ctr", _) => {
930            let mut buf = data.to_vec();
931            ctr::Ctr128BE::<aes::Aes192>::new_from_slices(key, iv)
932                .map_err(|_| KEYERR.to_string())?
933                .apply_keystream(&mut buf);
934            Ok(buf)
935        }
936        ("aes-256-ctr", _) => {
937            let mut buf = data.to_vec();
938            ctr::Ctr128BE::<aes::Aes256>::new_from_slices(key, iv)
939                .map_err(|_| KEYERR.to_string())?
940                .apply_keystream(&mut buf);
941            Ok(buf)
942        }
943        _ => Err(format!("Error: Unsupported cipher: {algo}")),
944    }
945}
946
947/// Required key length in bytes for a supported cipher name.
948fn key_len(algo: &str) -> usize {
949    if algo.starts_with("aes-128") {
950        16
951    } else if algo.starts_with("aes-192") {
952        24
953    } else {
954        32
955    }
956}
957
958/// Encode bytes for a cipher `update`/`final` (or one-shot `hash`) output.
959fn encode_out(bytes: &[u8], enc: Option<&str>) -> Value {
960    match enc {
961        Some("hex") => with_host(|h| h.new_str(to_hex(bytes))),
962        Some("base64") | Some("base64url") => with_host(|h| h.new_str(to_base64(bytes))),
963        Some("latin1") | Some("binary") => {
964            with_host(|h| h.new_str(bytes.iter().map(|b| *b as char).collect::<String>()))
965        }
966        Some("utf8") | Some("utf-8") => {
967            with_host(|h| h.new_str(String::from_utf8_lossy(bytes).into_owned()))
968        }
969        _ => super::buffer::from_bytes(bytes),
970    }
971}
972
973/// `getCipherInfo(name)` → `{ name, nid, blockSize, ivLength, mode, keyLength }`.
974fn cipher_info(algo: &str) -> Result<Value, String> {
975    if !CIPHERS.contains(&algo) {
976        return Ok(Value::Undef);
977    }
978    let nid = match algo {
979        "aes-128-cbc" => 419,
980        "aes-192-cbc" => 423,
981        "aes-256-cbc" => 427,
982        "aes-128-ctr" => 904,
983        "aes-192-ctr" => 905,
984        _ => 906,
985    };
986    let (mode, block) = if algo.ends_with("ctr") {
987        ("ctr", 1)
988    } else {
989        ("cbc", 16)
990    };
991    Ok(with_host(|h| {
992        let mut m = IndexMap::new();
993        m.insert("mode".into(), h.new_str(mode));
994        m.insert("name".into(), h.new_str(algo));
995        m.insert("nid".into(), Value::Float(nid as f64));
996        m.insert("keyLength".into(), Value::Float(key_len(algo) as f64));
997        m.insert("blockSize".into(), Value::Float(block as f64));
998        m.insert("ivLength".into(), Value::Float(16.0));
999        h.new_object(m)
1000    }))
1001}
1002
1003/// `randomFillSync`/`randomFill` core: fill `buf[offset..offset+size]` with
1004/// CSPRNG bytes in place, returning the same Buffer.
1005fn random_fill(args: &[Value]) -> Result<Value, String> {
1006    let buf = args.first().cloned().unwrap_or(Value::Undef);
1007    if super::native_tag(&buf).as_deref() != Some("Buffer") {
1008        return Err("Error: The \"buf\" argument must be a Buffer".into());
1009    }
1010    let cur = val_bytes(&buf);
1011    let len = cur.len();
1012    let offset = if args.len() > 1 {
1013        super::arg_num(args, 1).max(0.0) as usize
1014    } else {
1015        0
1016    };
1017    let offset = offset.min(len);
1018    let size = if args.len() > 2 {
1019        super::arg_num(args, 2).max(0.0) as usize
1020    } else {
1021        len - offset
1022    };
1023    let end = (offset + size).min(len);
1024    let mut rnd = vec![0u8; end.saturating_sub(offset)];
1025    if let Err(e) = getrandom::getrandom(&mut rnd) {
1026        return Err(format!("Error: failed to generate random bytes: {e}"));
1027    }
1028    let mut out = cur;
1029    out[offset..end].copy_from_slice(&rnd);
1030    with_host(|h| {
1031        let arr = match h.get(&buf) {
1032            Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
1033            _ => None,
1034        };
1035        if let Some(a) = arr {
1036            if let Some(JsObj::Array(items)) = h.get_mut(&a) {
1037                *items = out.iter().map(|b| Value::Float(*b as f64)).collect();
1038            }
1039        }
1040    });
1041    Ok(buf)
1042}
1043
1044/// `randomUUIDv7()` — RFC 9562 v7: 48-bit unix-ms timestamp prefix, version 7,
1045/// variant, and CSPRNG tail.
1046fn uuid_v7() -> Result<Value, String> {
1047    let ms = std::time::SystemTime::now()
1048        .duration_since(std::time::UNIX_EPOCH)
1049        .map(|d| d.as_millis() as u64)
1050        .unwrap_or(0);
1051    let mut b = [0u8; 16];
1052    if let Err(e) = getrandom::getrandom(&mut b) {
1053        return Err(format!("Error: failed to generate random bytes: {e}"));
1054    }
1055    b[0..6].copy_from_slice(&ms.to_be_bytes()[2..8]);
1056    b[6] = (b[6] & 0x0f) | 0x70;
1057    b[8] = (b[8] & 0x3f) | 0x80;
1058    let h = to_hex(&b);
1059    let uuid = format!(
1060        "{}-{}-{}-{}-{}",
1061        &h[0..8],
1062        &h[8..12],
1063        &h[12..16],
1064        &h[16..20],
1065        &h[20..32]
1066    );
1067    Ok(with_host(|host| host.new_str(uuid)))
1068}
1069
1070/// A uniform random `u64` in `[0, range)` via rejection sampling over 8 CSPRNG
1071/// bytes (discards the biased tail so the distribution stays exactly uniform).
1072fn random_below(range: u64) -> Result<u64, getrandom::Error> {
1073    // Largest multiple of `range` that fits in u64; values at/above it are biased.
1074    let limit = u64::MAX - (u64::MAX % range);
1075    loop {
1076        let mut b = [0u8; 8];
1077        getrandom::getrandom(&mut b)?;
1078        let n = u64::from_le_bytes(b);
1079        if n < limit {
1080            return Ok(n % range);
1081        }
1082    }
1083}
1084
1085fn decode(s: &str, enc: &str) -> Vec<u8> {
1086    match enc.to_ascii_lowercase().as_str() {
1087        "hex" => super::from_hex(s),
1088        "base64" | "base64url" => super::from_base64(s),
1089        "ascii" | "latin1" | "binary" => s.chars().map(|c| c as u8).collect(),
1090        _ => s.as_bytes().to_vec(),
1091    }
1092}
1093
1094// ════════════════════════════════════════════════════════════════════════
1095//  Asymmetric cryptography: key generation, KeyObjects, sign/verify, RSA
1096//  encryption, Diffie-Hellman / ECDH, primes, argon2, X.509.
1097// ════════════════════════════════════════════════════════════════════════
1098
1099/// A `KeyObject` for an asymmetric key: carries `type` (`private`/`public`),
1100/// `asymmetricKeyType`, and the PEM material in the hidden `@@pem`.
1101fn key_object(kind: &str, asym: &str, pem: &str) -> Value {
1102    with_host(|h| {
1103        let mut m = IndexMap::new();
1104        m.insert("@@native".into(), h.new_str("KeyObject"));
1105        m.insert("type".into(), h.new_str(kind));
1106        m.insert("asymmetricKeyType".into(), h.new_str(asym));
1107        m.insert("@@pem".into(), h.new_str(pem));
1108        h.new_object(m)
1109    })
1110}
1111
1112/// A secret (symmetric) `KeyObject` wrapping raw bytes.
1113fn secret_key_object(bytes: &[u8]) -> Value {
1114    with_host(|h| {
1115        let arr = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
1116        let mut m = IndexMap::new();
1117        m.insert("@@native".into(), h.new_str("KeyObject"));
1118        m.insert("type".into(), h.new_str("secret"));
1119        m.insert("@@secret".into(), arr);
1120        h.new_object(m)
1121    })
1122}
1123
1124/// Wrap DER bytes as a PEM block with the given label (64-char lines, LF).
1125fn pem_wrap(label: &str, der: &[u8]) -> String {
1126    let b64 = to_base64(der);
1127    let mut s = format!("-----BEGIN {label}-----\n");
1128    for chunk in b64.as_bytes().chunks(64) {
1129        s.push_str(std::str::from_utf8(chunk).unwrap_or_default());
1130        s.push('\n');
1131    }
1132    s.push_str(&format!("-----END {label}-----\n"));
1133    s
1134}
1135
1136/// Extract the DER bytes from a single PEM block (any label).
1137fn pem_body(pem: &str) -> Vec<u8> {
1138    let body: String = pem.lines().filter(|l| !l.starts_with("-----")).collect();
1139    super::from_base64(&body)
1140}
1141
1142/// PKCS#8 PEM for a raw X25519 private key (OID 1.3.101.110).
1143fn x25519_private_pem(raw: &[u8]) -> String {
1144    let mut der = vec![
1145        0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04,
1146        0x20,
1147    ];
1148    der.extend_from_slice(raw);
1149    pem_wrap("PRIVATE KEY", &der)
1150}
1151
1152/// SPKI PEM for a raw X25519 public key.
1153fn x25519_public_pem(raw: &[u8]) -> String {
1154    let mut der = vec![
1155        0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00,
1156    ];
1157    der.extend_from_slice(raw);
1158    pem_wrap("PUBLIC KEY", &der)
1159}
1160
1161/// The trailing 32 raw bytes of an X25519 PKCS#8/SPKI PEM.
1162fn x25519_raw(pem: &str) -> Option<[u8; 32]> {
1163    let der = pem_body(pem);
1164    if der.len() < 32 {
1165        return None;
1166    }
1167    let mut out = [0u8; 32];
1168    out.copy_from_slice(&der[der.len() - 32..]);
1169    Some(out)
1170}
1171
1172/// The options object at `args[1]` for key generation.
1173fn keygen_encoding(opts: Option<&Value>, priv_side: bool) -> Option<String> {
1174    let o = opts?;
1175    let field = if priv_side {
1176        "privateKeyEncoding"
1177    } else {
1178        "publicKeyEncoding"
1179    };
1180    with_host(|h| {
1181        let JsObj::Object(p) = h.get(o)? else {
1182            return None;
1183        };
1184        let enc = p.get(field)?;
1185        let JsObj::Object(e) = h.get(enc)? else {
1186            return None;
1187        };
1188        e.get("format").map(|v| h.str_of(v))
1189    })
1190}
1191
1192/// `generateKeyPairSync(type, opts)`: RSA / EC (P-256, P-384) / Ed25519 / X25519.
1193/// Returns `{ publicKey, privateKey }` — PEM strings when the matching
1194/// `*KeyEncoding.format` is `'pem'`, else `KeyObject`s.
1195fn generate_key_pair(kind: &str, opts: Option<&Value>) -> Result<Value, String> {
1196    let mut rng = rand_core::OsRng;
1197    let (asym, priv_pem, pub_pem) = match kind {
1198        "rsa" => {
1199            let bits = opt_num(opts.unwrap_or(&Value::Undef), &["modulusLength"], 2048.0) as usize;
1200            let sk = rsa::RsaPrivateKey::new(&mut rng, bits).map_err(|e| format!("Error: {e}"))?;
1201            let pk = rsa::RsaPublicKey::from(&sk);
1202            let priv_pem = sk
1203                .to_pkcs8_pem(LineEnding::LF)
1204                .map_err(|e| format!("Error: {e}"))?
1205                .to_string();
1206            let pub_pem = pk
1207                .to_public_key_pem(LineEnding::LF)
1208                .map_err(|e| format!("Error: {e}"))?;
1209            ("rsa", priv_pem, pub_pem)
1210        }
1211        "ec" => {
1212            let curve = opt_str(opts.unwrap_or(&Value::Undef), "namedCurve");
1213            match ec_curve_id(&curve) {
1214                Some("p256") => {
1215                    let sk = p256::SecretKey::random(&mut rng);
1216                    let priv_pem = sk
1217                        .to_pkcs8_pem(LineEnding::LF)
1218                        .map_err(|e| format!("Error: {e}"))?
1219                        .to_string();
1220                    let pub_pem = sk
1221                        .public_key()
1222                        .to_public_key_pem(LineEnding::LF)
1223                        .map_err(|e| format!("Error: {e}"))?;
1224                    ("ec", priv_pem, pub_pem)
1225                }
1226                Some("p384") => {
1227                    let sk = p384::SecretKey::random(&mut rng);
1228                    let priv_pem = sk
1229                        .to_pkcs8_pem(LineEnding::LF)
1230                        .map_err(|e| format!("Error: {e}"))?
1231                        .to_string();
1232                    let pub_pem = sk
1233                        .public_key()
1234                        .to_public_key_pem(LineEnding::LF)
1235                        .map_err(|e| format!("Error: {e}"))?;
1236                    ("ec", priv_pem, pub_pem)
1237                }
1238                _ => return Err(format!("Error: Unsupported EC curve: {curve}")),
1239            }
1240        }
1241        "ed25519" => {
1242            let sk = ed25519_dalek::SigningKey::generate(&mut rng);
1243            let priv_pem = sk
1244                .to_pkcs8_pem(LineEnding::LF)
1245                .map_err(|e| format!("Error: {e}"))?
1246                .to_string();
1247            let pub_pem = sk
1248                .verifying_key()
1249                .to_public_key_pem(LineEnding::LF)
1250                .map_err(|e| format!("Error: {e}"))?;
1251            ("ed25519", priv_pem, pub_pem)
1252        }
1253        "x25519" => {
1254            let sk = x25519_dalek::StaticSecret::random_from_rng(rng);
1255            let pk = x25519_dalek::PublicKey::from(&sk);
1256            (
1257                "x25519",
1258                x25519_private_pem(&sk.to_bytes()),
1259                x25519_public_pem(pk.as_bytes()),
1260            )
1261        }
1262        _ => return Err(format!("Error: Unsupported key type: {kind}")),
1263    };
1264    let pub_is_pem = keygen_encoding(opts, false).as_deref() == Some("pem");
1265    let priv_is_pem = keygen_encoding(opts, true).as_deref() == Some("pem");
1266    let publik = if pub_is_pem {
1267        with_host(|h| h.new_str(pub_pem.clone()))
1268    } else {
1269        key_object("public", asym, &pub_pem)
1270    };
1271    let privat = if priv_is_pem {
1272        with_host(|h| h.new_str(priv_pem.clone()))
1273    } else {
1274        key_object("private", asym, &priv_pem)
1275    };
1276    Ok(with_host(|h| {
1277        let mut m = IndexMap::new();
1278        m.insert("publicKey".into(), publik);
1279        m.insert("privateKey".into(), privat);
1280        h.new_object(m)
1281    }))
1282}
1283
1284/// Map a Node EC curve name to an internal id.
1285fn ec_curve_id(name: &str) -> Option<&'static str> {
1286    match name {
1287        "P-256" | "prime256v1" | "secp256r1" => Some("p256"),
1288        "P-384" | "secp384r1" => Some("p384"),
1289        _ => None,
1290    }
1291}
1292
1293/// A string option on an options object (empty if absent).
1294fn opt_str(obj: &Value, key: &str) -> String {
1295    with_host(|h| match h.get(obj) {
1296        Some(JsObj::Object(p)) => p.get(key).map(|v| h.str_of(v)).unwrap_or_default(),
1297        _ => String::new(),
1298    })
1299}
1300
1301/// Detect the asymmetric type of a private-key PEM/DER by trial parsing.
1302fn detect_private(bytes: &[u8]) -> Option<&'static str> {
1303    let pem = std::str::from_utf8(bytes).ok();
1304    if pem
1305        .map(|p| {
1306            rsa::RsaPrivateKey::from_pkcs8_pem(p).is_ok()
1307                || rsa::RsaPrivateKey::from_pkcs1_pem(p).is_ok()
1308        })
1309        .unwrap_or(false)
1310        || rsa::RsaPrivateKey::from_pkcs8_der(bytes).is_ok()
1311    {
1312        return Some("rsa");
1313    }
1314    if pem
1315        .map(|p| p256::SecretKey::from_pkcs8_pem(p).is_ok())
1316        .unwrap_or(false)
1317    {
1318        return Some("ec");
1319    }
1320    if pem
1321        .map(|p| p384::SecretKey::from_pkcs8_pem(p).is_ok())
1322        .unwrap_or(false)
1323    {
1324        return Some("ec");
1325    }
1326    if pem
1327        .map(|p| ed25519_dalek::SigningKey::from_pkcs8_pem(p).is_ok())
1328        .unwrap_or(false)
1329    {
1330        return Some("ed25519");
1331    }
1332    if pem.map(|p| p.contains("PRIVATE KEY")).unwrap_or(false)
1333        && x25519_raw(pem.unwrap_or("")).is_some()
1334    {
1335        // X25519 PKCS#8 is a fixed 48-byte structure; distinguish by OID byte.
1336        let der = pem_body(pem.unwrap_or(""));
1337        if der.len() == 48 && der[9..12] == [0x2b, 0x65, 0x6e] {
1338            return Some("x25519");
1339        }
1340    }
1341    None
1342}
1343
1344/// Detect the asymmetric type of a public-key PEM/DER by trial parsing.
1345fn detect_public(bytes: &[u8]) -> Option<&'static str> {
1346    let pem = std::str::from_utf8(bytes).ok();
1347    if pem
1348        .map(|p| rsa::RsaPublicKey::from_public_key_pem(p).is_ok())
1349        .unwrap_or(false)
1350    {
1351        return Some("rsa");
1352    }
1353    if pem
1354        .map(|p| p256::PublicKey::from_public_key_pem(p).is_ok())
1355        .unwrap_or(false)
1356    {
1357        return Some("ec");
1358    }
1359    if pem
1360        .map(|p| p384::PublicKey::from_public_key_pem(p).is_ok())
1361        .unwrap_or(false)
1362    {
1363        return Some("ec");
1364    }
1365    if pem
1366        .map(|p| ed25519_dalek::VerifyingKey::from_public_key_pem(p).is_ok())
1367        .unwrap_or(false)
1368    {
1369        return Some("ed25519");
1370    }
1371    if let Some(p) = pem {
1372        let der = pem_body(p);
1373        if der.len() == 44 && der[6..9] == [0x2b, 0x65, 0x6e] {
1374            return Some("x25519");
1375        }
1376    }
1377    None
1378}
1379
1380/// `createPrivateKey(input)` → a private `KeyObject`.
1381fn create_private_key(input: Option<&Value>) -> Result<Value, String> {
1382    let bytes = input.map(key_material).unwrap_or_default();
1383    let asym = detect_private(&bytes).ok_or("Error: Failed to read private key")?;
1384    let pem = String::from_utf8_lossy(&bytes).into_owned();
1385    Ok(key_object("private", asym, &pem))
1386}
1387
1388/// `createPublicKey(input)` → a public `KeyObject`. Accepts a public key, a
1389/// private key/`KeyObject` (derives the public half), or a PEM/DER buffer.
1390fn create_public_key(input: Option<&Value>) -> Result<Value, String> {
1391    let bytes = input.map(key_material).unwrap_or_default();
1392    if let Some(asym) = detect_public(&bytes) {
1393        let pem = String::from_utf8_lossy(&bytes).into_owned();
1394        return Ok(key_object("public", asym, &pem));
1395    }
1396    // Derive the public key from a private key.
1397    if let Some(asym) = detect_private(&bytes) {
1398        let pem = public_pem_from_private(&bytes, asym)?;
1399        return Ok(key_object("public", asym, &pem));
1400    }
1401    Err("Error: Failed to read public key".into())
1402}
1403
1404/// The SPKI public PEM derived from a private-key PEM/DER of a known type.
1405fn public_pem_from_private(bytes: &[u8], asym: &str) -> Result<String, String> {
1406    let pem = std::str::from_utf8(bytes).ok();
1407    let err = || "Error: Failed to derive public key".to_string();
1408    match asym {
1409        "rsa" => {
1410            let sk = pem
1411                .and_then(|p| rsa::RsaPrivateKey::from_pkcs8_pem(p).ok())
1412                .or_else(|| rsa::RsaPrivateKey::from_pkcs8_der(bytes).ok())
1413                .ok_or_else(err)?;
1414            rsa::RsaPublicKey::from(&sk)
1415                .to_public_key_pem(LineEnding::LF)
1416                .map_err(|e| format!("Error: {e}"))
1417        }
1418        "ec" => {
1419            if let Some(sk) = pem.and_then(|p| p256::SecretKey::from_pkcs8_pem(p).ok()) {
1420                return sk
1421                    .public_key()
1422                    .to_public_key_pem(LineEnding::LF)
1423                    .map_err(|e| format!("Error: {e}"));
1424            }
1425            let sk = pem
1426                .and_then(|p| p384::SecretKey::from_pkcs8_pem(p).ok())
1427                .ok_or_else(err)?;
1428            sk.public_key()
1429                .to_public_key_pem(LineEnding::LF)
1430                .map_err(|e| format!("Error: {e}"))
1431        }
1432        "ed25519" => {
1433            let sk = pem
1434                .and_then(|p| ed25519_dalek::SigningKey::from_pkcs8_pem(p).ok())
1435                .ok_or_else(err)?;
1436            sk.verifying_key()
1437                .to_public_key_pem(LineEnding::LF)
1438                .map_err(|e| format!("Error: {e}"))
1439        }
1440        "x25519" => {
1441            let raw = pem.and_then(x25519_raw).ok_or_else(err)?;
1442            let sk = x25519_dalek::StaticSecret::from(raw);
1443            Ok(x25519_public_pem(
1444                x25519_dalek::PublicKey::from(&sk).as_bytes(),
1445            ))
1446        }
1447        _ => Err(err()),
1448    }
1449}
1450
1451/// Raw key material of a key argument: a `KeyObject`'s stored PEM, a `{ key }`
1452/// wrapper's inner key, a Buffer's bytes, or a PEM/DER string's bytes.
1453fn key_material(v: &Value) -> Vec<u8> {
1454    if super::native_tag(v).as_deref() == Some("KeyObject") {
1455        return with_host(|h| match h.get(v) {
1456            Some(JsObj::Object(p)) => p.get("@@pem").map(|s| h.str_of(s)).unwrap_or_default(),
1457            _ => String::new(),
1458        })
1459        .into_bytes();
1460    }
1461    if super::native_tag(v).as_deref() != Some("Buffer") {
1462        let inner = with_host(|h| match h.get(v) {
1463            Some(JsObj::Object(p)) => p.get("key").cloned(),
1464            _ => None,
1465        });
1466        if let Some(k) = inner {
1467            return key_material(&k);
1468        }
1469    }
1470    val_bytes(v)
1471}
1472
1473/// A `Sign`/`Verify` streaming instance (`update(...).sign(key)` /
1474/// `update(...).verify(key, sig)`).
1475fn new_sign_verify(tag: &str, algo: &str) -> Value {
1476    with_host(|h| {
1477        let data = h.new_array(Vec::new());
1478        let mut m = IndexMap::new();
1479        m.insert("@@native".into(), h.new_str(tag));
1480        m.insert("@@algo".into(), h.new_str(algo.to_ascii_lowercase()));
1481        m.insert("@@data".into(), data);
1482        h.new_object(m)
1483    })
1484}
1485
1486/// Normalize a Node signature-algorithm name to a bare digest (`sha256`).
1487fn digest_of(algo: &str) -> String {
1488    let a = algo.to_ascii_lowercase();
1489    let a = a.strip_prefix("rsa-").unwrap_or(&a);
1490    a.replace('-', "")
1491}
1492
1493/// One-shot asymmetric sign over `data` with a private key (auto key-type).
1494fn sign_data(key: &[u8], algo: &str, data: &[u8]) -> Result<Vec<u8>, String> {
1495    let pem = std::str::from_utf8(key).ok();
1496    let d = digest_of(algo);
1497    if let Some(sk) = pem
1498        .and_then(|p| rsa::RsaPrivateKey::from_pkcs8_pem(p).ok())
1499        .or_else(|| pem.and_then(|p| rsa::RsaPrivateKey::from_pkcs1_pem(p).ok()))
1500        .or_else(|| rsa::RsaPrivateKey::from_pkcs8_der(key).ok())
1501    {
1502        return rsa_sign(&sk, &d, data);
1503    }
1504    if let Some(sk) = pem.and_then(|p| p256::ecdsa::SigningKey::from_pkcs8_pem(p).ok()) {
1505        let sig: p256::ecdsa::Signature = sk.try_sign(data).map_err(|e| format!("Error: {e}"))?;
1506        return Ok(sig.to_der().as_bytes().to_vec());
1507    }
1508    if let Some(sk) = pem.and_then(|p| p384::ecdsa::SigningKey::from_pkcs8_pem(p).ok()) {
1509        let sig: p384::ecdsa::Signature = sk.try_sign(data).map_err(|e| format!("Error: {e}"))?;
1510        return Ok(sig.to_der().as_bytes().to_vec());
1511    }
1512    if let Some(sk) = pem.and_then(|p| ed25519_dalek::SigningKey::from_pkcs8_pem(p).ok()) {
1513        return Ok(sk.sign(data).to_bytes().to_vec());
1514    }
1515    Err("Error: Invalid or unsupported private key for signing".into())
1516}
1517
1518/// RSA PKCS#1 v1.5 signature over `data` for the selected digest.
1519fn rsa_sign(sk: &rsa::RsaPrivateKey, digest: &str, data: &[u8]) -> Result<Vec<u8>, String> {
1520    let sig = match digest {
1521        "sha256" => rsa::pkcs1v15::SigningKey::<Sha256>::new(sk.clone())
1522            .sign(data)
1523            .to_vec(),
1524        "sha384" => rsa::pkcs1v15::SigningKey::<Sha384>::new(sk.clone())
1525            .sign(data)
1526            .to_vec(),
1527        "sha512" => rsa::pkcs1v15::SigningKey::<Sha512>::new(sk.clone())
1528            .sign(data)
1529            .to_vec(),
1530        _ => {
1531            return Err(format!(
1532                "Error: Unsupported digest for RSA signing: {digest}"
1533            ))
1534        }
1535    };
1536    Ok(sig)
1537}
1538
1539/// One-shot asymmetric verify (auto key-type). ECDSA signatures are DER.
1540fn verify_data(key: &[u8], algo: &str, data: &[u8], sig: &[u8]) -> Result<bool, String> {
1541    let pem = std::str::from_utf8(key).ok();
1542    let d = digest_of(algo);
1543    if let Some(pk) = pem
1544        .and_then(|p| rsa::RsaPublicKey::from_public_key_pem(p).ok())
1545        .or_else(|| pem.and_then(|p| rsa::RsaPublicKey::from_pkcs1_pem(p).ok()))
1546        .or_else(|| {
1547            pem.and_then(|p| rsa::RsaPrivateKey::from_pkcs8_pem(p).ok())
1548                .map(|s| rsa::RsaPublicKey::from(&s))
1549        })
1550    {
1551        return rsa_verify(&pk, &d, data, sig);
1552    }
1553    if let Some(vk) = pem
1554        .and_then(|p| p256::ecdsa::VerifyingKey::from_public_key_pem(p).ok())
1555        .or_else(|| {
1556            pem.and_then(|p| p256::ecdsa::SigningKey::from_pkcs8_pem(p).ok())
1557                .map(|s| *s.verifying_key())
1558        })
1559    {
1560        let s = p256::ecdsa::Signature::from_der(sig).map_err(|e| format!("Error: {e}"))?;
1561        return Ok(vk.verify(data, &s).is_ok());
1562    }
1563    if let Some(vk) = pem
1564        .and_then(|p| p384::ecdsa::VerifyingKey::from_public_key_pem(p).ok())
1565        .or_else(|| {
1566            pem.and_then(|p| p384::ecdsa::SigningKey::from_pkcs8_pem(p).ok())
1567                .map(|s| *s.verifying_key())
1568        })
1569    {
1570        let s = p384::ecdsa::Signature::from_der(sig).map_err(|e| format!("Error: {e}"))?;
1571        return Ok(vk.verify(data, &s).is_ok());
1572    }
1573    if let Some(vk) = pem
1574        .and_then(|p| ed25519_dalek::VerifyingKey::from_public_key_pem(p).ok())
1575        .or_else(|| {
1576            pem.and_then(|p| ed25519_dalek::SigningKey::from_pkcs8_pem(p).ok())
1577                .map(|s| s.verifying_key())
1578        })
1579    {
1580        let s = ed25519_dalek::Signature::from_slice(sig).map_err(|e| format!("Error: {e}"))?;
1581        return Ok(vk.verify(data, &s).is_ok());
1582    }
1583    Err("Error: Invalid or unsupported public key for verifying".into())
1584}
1585
1586/// RSA PKCS#1 v1.5 verify for the selected digest.
1587fn rsa_verify(
1588    pk: &rsa::RsaPublicKey,
1589    digest: &str,
1590    data: &[u8],
1591    sig: &[u8],
1592) -> Result<bool, String> {
1593    let signature = match rsa::pkcs1v15::Signature::try_from(sig) {
1594        Ok(s) => s,
1595        Err(_) => return Ok(false),
1596    };
1597    let ok = match digest {
1598        "sha256" => rsa::pkcs1v15::VerifyingKey::<Sha256>::new(pk.clone())
1599            .verify(data, &signature)
1600            .is_ok(),
1601        "sha384" => rsa::pkcs1v15::VerifyingKey::<Sha384>::new(pk.clone())
1602            .verify(data, &signature)
1603            .is_ok(),
1604        "sha512" => rsa::pkcs1v15::VerifyingKey::<Sha512>::new(pk.clone())
1605            .verify(data, &signature)
1606            .is_ok(),
1607        _ => {
1608            return Err(format!(
1609                "Error: Unsupported digest for RSA verifying: {digest}"
1610            ))
1611        }
1612    };
1613    Ok(ok)
1614}
1615
1616/// `Sign`/`Verify` instance dispatch.
1617pub fn sign_verify_instance_call(
1618    tag: &str,
1619    recv: &Value,
1620    method: &str,
1621    args: &[Value],
1622) -> Result<Value, String> {
1623    match method {
1624        "update" => {
1625            let enc = if args.len() > 1 {
1626                arg_str(args, 1)
1627            } else {
1628                "utf8".into()
1629            };
1630            let bytes = if super::native_tag(args.first().unwrap_or(&Value::Undef)).as_deref()
1631                == Some("Buffer")
1632            {
1633                val_bytes_at(args, 0)
1634            } else {
1635                decode(&arg_str(args, 0), &enc)
1636            };
1637            with_host(|h| {
1638                if let Some(JsObj::Object(p)) = h.get(recv).cloned() {
1639                    if let Some(arr) = p.get("@@data").cloned() {
1640                        if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
1641                            items.extend(bytes.iter().map(|b| Value::Float(*b as f64)));
1642                        }
1643                    }
1644                }
1645            });
1646            Ok(recv.clone())
1647        }
1648        "sign" => {
1649            let (algo, data) = sign_verify_state(recv);
1650            let key = key_material(args.first().unwrap_or(&Value::Undef));
1651            let sig = sign_data(&key, &algo, &data)?;
1652            let out_enc = if args.len() > 1 {
1653                Some(arg_str(args, 1))
1654            } else {
1655                None
1656            };
1657            Ok(encode_out(&sig, out_enc.as_deref()))
1658        }
1659        "verify" => {
1660            let (algo, data) = sign_verify_state(recv);
1661            let key = key_material(args.first().unwrap_or(&Value::Undef));
1662            let sig = if args.len() > 2 {
1663                decode(&arg_str(args, 1), &arg_str(args, 2))
1664            } else {
1665                val_bytes_at(args, 1)
1666            };
1667            Ok(Value::Bool(verify_data(&key, &algo, &data, &sig)?))
1668        }
1669        _ => Err(crate::host::type_error(&format!(
1670            "{}.{method} is not a function",
1671            tag.to_ascii_lowercase()
1672        ))),
1673    }
1674}
1675
1676/// Read the `@@algo`/`@@data` of a `Sign`/`Verify` instance.
1677fn sign_verify_state(recv: &Value) -> (String, Vec<u8>) {
1678    with_host(|h| {
1679        let (mut algo, mut data) = (String::new(), Vec::new());
1680        if let Some(JsObj::Object(p)) = h.get(recv) {
1681            algo = p.get("@@algo").map(|v| h.str_of(v)).unwrap_or_default();
1682            if let Some(JsObj::Array(items)) = p.get("@@data").and_then(|v| h.get(v)) {
1683                data = items.iter().map(|v| h.to_number(v) as u8).collect();
1684            }
1685        }
1686        (algo, data)
1687    })
1688}
1689
1690/// Read the numeric `padding`/oaepHash options from a key argument object.
1691fn rsa_padding(v: &Value) -> (i64, String) {
1692    let padding = opt_num(v, &["padding"], 4.0) as i64; // RSA_PKCS1_OAEP_PADDING
1693    let oaep = {
1694        let h = opt_str(v, "oaepHash");
1695        if h.is_empty() {
1696            "sha1".to_string()
1697        } else {
1698            h.to_ascii_lowercase()
1699        }
1700    };
1701    (padding, oaep)
1702}
1703
1704/// Parse an RSA public key from PEM/DER (or derive from a private key).
1705fn parse_rsa_public(key: &[u8]) -> Result<rsa::RsaPublicKey, String> {
1706    let pem = std::str::from_utf8(key).ok();
1707    pem.and_then(|p| rsa::RsaPublicKey::from_public_key_pem(p).ok())
1708        .or_else(|| pem.and_then(|p| rsa::RsaPublicKey::from_pkcs1_pem(p).ok()))
1709        .or_else(|| rsa::RsaPublicKey::from_public_key_der(key).ok())
1710        .or_else(|| {
1711            pem.and_then(|p| rsa::RsaPrivateKey::from_pkcs8_pem(p).ok())
1712                .map(|s| rsa::RsaPublicKey::from(&s))
1713        })
1714        .ok_or_else(|| "Error: Failed to parse RSA public key".into())
1715}
1716
1717/// Parse an RSA private key from PEM/DER.
1718fn parse_rsa_private(key: &[u8]) -> Result<rsa::RsaPrivateKey, String> {
1719    let pem = std::str::from_utf8(key).ok();
1720    pem.and_then(|p| rsa::RsaPrivateKey::from_pkcs8_pem(p).ok())
1721        .or_else(|| pem.and_then(|p| rsa::RsaPrivateKey::from_pkcs1_pem(p).ok()))
1722        .or_else(|| rsa::RsaPrivateKey::from_pkcs8_der(key).ok())
1723        .ok_or_else(|| "Error: Failed to parse RSA private key".into())
1724}
1725
1726/// `publicEncrypt` / `privateDecrypt`: OAEP by default, PKCS#1 v1.5 when
1727/// `padding == RSA_PKCS1_PADDING (1)`.
1728fn rsa_public_op(args: &[Value], _public: bool, encrypt: bool) -> Result<Value, String> {
1729    let key_arg = args.first().cloned().unwrap_or(Value::Undef);
1730    let key = key_material(&key_arg);
1731    let (padding, oaep) = rsa_padding(&key_arg);
1732    let data = val_bytes_at(args, 1);
1733    let out = if encrypt {
1734        let pk = parse_rsa_public(&key)?;
1735        let mut rng = rand_core::OsRng;
1736        if padding == 1 {
1737            pk.encrypt(&mut rng, rsa::Pkcs1v15Encrypt, &data)
1738        } else if oaep == "sha256" {
1739            pk.encrypt(&mut rng, rsa::Oaep::new::<Sha256>(), &data)
1740        } else {
1741            pk.encrypt(&mut rng, rsa::Oaep::new::<Sha1>(), &data)
1742        }
1743        .map_err(|e| format!("Error: {e}"))?
1744    } else {
1745        let sk = parse_rsa_private(&key)?;
1746        if padding == 1 {
1747            sk.decrypt(rsa::Pkcs1v15Encrypt, &data)
1748        } else if oaep == "sha256" {
1749            sk.decrypt(rsa::Oaep::new::<Sha256>(), &data)
1750        } else {
1751            sk.decrypt(rsa::Oaep::new::<Sha1>(), &data)
1752        }
1753        .map_err(|e| format!("Error: {e}"))?
1754    };
1755    Ok(super::buffer::from_bytes(&out))
1756}
1757
1758/// `privateEncrypt`: raw RSA PKCS#1 v1.5 (type 1) block signed with the
1759/// private key.
1760fn rsa_private_encrypt(args: &[Value]) -> Result<Value, String> {
1761    let key = key_material(args.first().unwrap_or(&Value::Undef));
1762    let data = val_bytes_at(args, 1);
1763    let sk = parse_rsa_private(&key)?;
1764    let out = sk
1765        .sign(rsa::Pkcs1v15Sign::new_unprefixed(), &data)
1766        .map_err(|e| format!("Error: {e}"))?;
1767    Ok(super::buffer::from_bytes(&out))
1768}
1769
1770/// `publicDecrypt`: recover a `privateEncrypt` block via raw modular
1771/// exponentiation `s^e mod n`, then strip PKCS#1 type-1 padding.
1772fn rsa_public_decrypt(args: &[Value]) -> Result<Value, String> {
1773    use rsa::traits::PublicKeyParts;
1774    let key = key_material(args.first().unwrap_or(&Value::Undef));
1775    let ct = val_bytes_at(args, 1);
1776    let pk = parse_rsa_public(&key)?;
1777    let n = BigUint::from_bytes_be(&pk.n().to_bytes_be());
1778    let e = BigUint::from_bytes_be(&pk.e().to_bytes_be());
1779    let k = pk.n().to_bytes_be().len();
1780    let s = BigUint::from_bytes_be(&ct);
1781    let m = s.modpow(&e, &n);
1782    let mut em = m.to_bytes_be();
1783    while em.len() < k {
1784        em.insert(0, 0);
1785    }
1786    // EM = 0x00 0x01 0xFF..0xFF 0x00 || message
1787    if em.len() < 11 || em[0] != 0x00 || em[1] != 0x01 {
1788        return Err("Error: error:0200006E:rsa routines::padding check failed".into());
1789    }
1790    let sep = em[2..].iter().position(|&b| b == 0x00).map(|i| i + 2);
1791    match sep {
1792        Some(i) => Ok(super::buffer::from_bytes(&em[i + 1..])),
1793        None => Err("Error: error:0200006E:rsa routines::padding check failed".into()),
1794    }
1795}
1796
1797// ── Diffie-Hellman (finite-field) ───────────────────────────────────────
1798
1799/// RFC 2409/3526 MODP group primes (hex), generator 2.
1800const MODP_GROUPS: &[(&str, &str)] = &[
1801    ("modp1", MODP1),
1802    ("modp2", MODP2),
1803    ("modp5", MODP5),
1804    ("modp14", MODP14),
1805    ("modp15", MODP15),
1806    ("modp16", MODP16),
1807    ("modp17", MODP17),
1808    ("modp18", MODP18),
1809];
1810
1811/// Byte-array property of an object as raw bytes.
1812fn obj_bytes(recv: &Value, key: &str) -> Vec<u8> {
1813    with_host(|h| {
1814        if let Some(JsObj::Object(p)) = h.get(recv) {
1815            if let Some(JsObj::Array(it)) = p.get(key).and_then(|v| h.get(v)) {
1816                return it.iter().map(|v| h.to_number(v) as u8).collect();
1817            }
1818        }
1819        Vec::new()
1820    })
1821}
1822
1823/// Store raw bytes as a hidden byte-array property.
1824fn set_obj_bytes(recv: &Value, key: &str, bytes: &[u8]) {
1825    with_host(|h| {
1826        let arr = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
1827        if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1828            p.insert(key.to_string(), arr);
1829        }
1830    });
1831}
1832
1833/// Build a `DiffieHellman` instance from a prime + generator.
1834fn dh_object(prime: &[u8], gen: &[u8]) -> Value {
1835    with_host(|h| {
1836        let pv = h.new_array(prime.iter().map(|b| Value::Float(*b as f64)).collect());
1837        let gv = h.new_array(gen.iter().map(|b| Value::Float(*b as f64)).collect());
1838        let mut m = IndexMap::new();
1839        m.insert("@@native".into(), h.new_str("DiffieHellman"));
1840        m.insert("@@prime".into(), pv);
1841        m.insert("@@gen".into(), gv);
1842        h.new_object(m)
1843    })
1844}
1845
1846/// `createDiffieHellman(primeLength)` or `createDiffieHellman(prime[, generator])`.
1847fn create_diffie_hellman(args: &[Value]) -> Result<Value, String> {
1848    // Numeric first arg → generate a prime of that bit length; generator 2.
1849    let first = args.first().cloned().unwrap_or(Value::Undef);
1850    if matches!(first, Value::Int(_) | Value::Float(_)) {
1851        let bits = super::arg_num(args, 0) as usize;
1852        let prime = gen_prime(bits)?;
1853        return Ok(dh_object(&prime.to_bytes_be(), &[2]));
1854    }
1855    let prime = val_bytes_at(args, 0);
1856    let gen = if args.len() > 1 {
1857        match args.get(1) {
1858            Some(Value::Int(_)) | Some(Value::Float(_)) => {
1859                let g = super::arg_num(args, 1) as u64;
1860                BigUint::from(g).to_bytes_be()
1861            }
1862            _ => val_bytes_at(args, 1),
1863        }
1864    } else {
1865        vec![2]
1866    };
1867    Ok(dh_object(&prime, &gen))
1868}
1869
1870/// `getDiffieHellman(group)` / `createDiffieHellmanGroup(group)`.
1871fn diffie_hellman_group(name: &str) -> Result<Value, String> {
1872    let hex = MODP_GROUPS
1873        .iter()
1874        .find(|(n, _)| *n == name)
1875        .map(|(_, h)| *h)
1876        .ok_or_else(|| format!("Error: Unknown group: {name}"))?;
1877    Ok(dh_object(&super::from_hex(hex), &[2]))
1878}
1879
1880/// `DiffieHellman` instance dispatch.
1881pub fn dh_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
1882    let enc = |args: &[Value], i: usize| -> Option<String> {
1883        if args.len() > i {
1884            let s = arg_str(args, i);
1885            if s.is_empty() {
1886                None
1887            } else {
1888                Some(s)
1889            }
1890        } else {
1891            None
1892        }
1893    };
1894    match method {
1895        "generateKeys" => {
1896            let p = BigUint::from_bytes_be(&obj_bytes(recv, "@@prime"));
1897            let g = BigUint::from_bytes_be(&obj_bytes(recv, "@@gen"));
1898            let priv_key = dh_random_priv(&p)?;
1899            let pub_key = g.modpow(&priv_key, &p);
1900            set_obj_bytes(recv, "@@priv", &priv_key.to_bytes_be());
1901            set_obj_bytes(recv, "@@pub", &pub_key.to_bytes_be());
1902            Ok(encode_out(&pub_key.to_bytes_be(), enc(args, 0).as_deref()))
1903        }
1904        "computeSecret" => {
1905            let other = if args.len() > 1
1906                && !arg_str(args, 1).is_empty()
1907                && !matches!(args.first(), Some(v) if super::native_tag(v).as_deref() == Some("Buffer"))
1908            {
1909                decode(&arg_str(args, 0), &arg_str(args, 1))
1910            } else {
1911                val_bytes_at(args, 0)
1912            };
1913            let p = BigUint::from_bytes_be(&obj_bytes(recv, "@@prime"));
1914            let priv_key = BigUint::from_bytes_be(&obj_bytes(recv, "@@priv"));
1915            let their_pub = BigUint::from_bytes_be(&other);
1916            let secret = their_pub.modpow(&priv_key, &p);
1917            let out_enc = if args.len() > 2 { enc(args, 2) } else { None };
1918            Ok(encode_out(&secret.to_bytes_be(), out_enc.as_deref()))
1919        }
1920        "getPrime" => Ok(encode_out(
1921            &obj_bytes(recv, "@@prime"),
1922            enc(args, 0).as_deref(),
1923        )),
1924        "getGenerator" => Ok(encode_out(
1925            &obj_bytes(recv, "@@gen"),
1926            enc(args, 0).as_deref(),
1927        )),
1928        "getPublicKey" => Ok(encode_out(
1929            &obj_bytes(recv, "@@pub"),
1930            enc(args, 0).as_deref(),
1931        )),
1932        "getPrivateKey" => Ok(encode_out(
1933            &obj_bytes(recv, "@@priv"),
1934            enc(args, 0).as_deref(),
1935        )),
1936        "setPublicKey" => {
1937            set_obj_bytes(recv, "@@pub", &val_bytes_at(args, 0));
1938            Ok(recv.clone())
1939        }
1940        "setPrivateKey" => {
1941            set_obj_bytes(recv, "@@priv", &val_bytes_at(args, 0));
1942            Ok(recv.clone())
1943        }
1944        _ => Err(crate::host::type_error(&format!(
1945            "dh.{method} is not a function"
1946        ))),
1947    }
1948}
1949
1950/// A random DH private exponent in `[2, p-2]`.
1951fn dh_random_priv(p: &BigUint) -> Result<BigUint, String> {
1952    let nbytes = p.to_bytes_be().len();
1953    let mut buf = vec![0u8; nbytes];
1954    getrandom::getrandom(&mut buf).map_err(|e| format!("Error: {e}"))?;
1955    let two = BigUint::from(2u32);
1956    let modulus = p - &two; // p-2 range size
1957    let x = BigUint::from_bytes_be(&buf) % &modulus;
1958    Ok(x + &two)
1959}
1960
1961// ── ECDH ────────────────────────────────────────────────────────────────
1962
1963/// `createECDH(curve)` — P-256 / P-384.
1964fn create_ecdh(curve: &str) -> Result<Value, String> {
1965    let id = ec_curve_id(curve).ok_or_else(|| format!("Error: Unsupported curve: {curve}"))?;
1966    Ok(with_host(|h| {
1967        let mut m = IndexMap::new();
1968        m.insert("@@native".into(), h.new_str("ECDH"));
1969        m.insert("@@curve".into(), h.new_str(id));
1970        h.new_object(m)
1971    }))
1972}
1973
1974/// `ECDH` instance dispatch.
1975pub fn ecdh_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
1976    let curve = obj_str(recv, "@@curve");
1977    match method {
1978        "generateKeys" => {
1979            let (priv_b, pub_b) = ecdh_generate(&curve)?;
1980            set_obj_bytes(recv, "@@priv", &priv_b);
1981            set_obj_bytes(recv, "@@pub", &pub_b);
1982            let out_enc = if args.len() > 1 {
1983                Some(arg_str(args, 1))
1984            } else {
1985                None
1986            };
1987            Ok(encode_out(&pub_b, out_enc.as_deref()))
1988        }
1989        "computeSecret" => {
1990            let other = if args.len() > 1
1991                && !arg_str(args, 1).is_empty()
1992                && super::native_tag(args.first().unwrap_or(&Value::Undef)).as_deref()
1993                    != Some("Buffer")
1994            {
1995                decode(&arg_str(args, 0), &arg_str(args, 1))
1996            } else {
1997                val_bytes_at(args, 0)
1998            };
1999            let secret = ecdh_compute(&curve, &obj_bytes(recv, "@@priv"), &other)?;
2000            let out_enc = if args.len() > 2 {
2001                Some(arg_str(args, 2))
2002            } else {
2003                None
2004            };
2005            Ok(encode_out(&secret, out_enc.as_deref()))
2006        }
2007        "getPublicKey" => {
2008            let out_enc = if args.len() > 1 {
2009                Some(arg_str(args, 1))
2010            } else {
2011                None
2012            };
2013            Ok(encode_out(&obj_bytes(recv, "@@pub"), out_enc.as_deref()))
2014        }
2015        "getPrivateKey" => {
2016            let out_enc = if !args.is_empty() {
2017                Some(arg_str(args, 0))
2018            } else {
2019                None
2020            };
2021            Ok(encode_out(&obj_bytes(recv, "@@priv"), out_enc.as_deref()))
2022        }
2023        "setPrivateKey" => {
2024            let priv_b = val_bytes_at(args, 0);
2025            let pub_b = ecdh_public_from_private(&curve, &priv_b)?;
2026            set_obj_bytes(recv, "@@priv", &priv_b);
2027            set_obj_bytes(recv, "@@pub", &pub_b);
2028            Ok(recv.clone())
2029        }
2030        _ => Err(crate::host::type_error(&format!(
2031            "ecdh.{method} is not a function"
2032        ))),
2033    }
2034}
2035
2036/// Generate an ECDH keypair; public key is the uncompressed SEC1 point.
2037fn ecdh_generate(curve: &str) -> Result<(Vec<u8>, Vec<u8>), String> {
2038    let mut rng = rand_core::OsRng;
2039    match curve {
2040        "p256" => {
2041            let sk = p256::SecretKey::random(&mut rng);
2042            let pubk = sk.public_key().to_encoded_point(false).as_bytes().to_vec();
2043            Ok((sk.to_bytes().to_vec(), pubk))
2044        }
2045        "p384" => {
2046            let sk = p384::SecretKey::random(&mut rng);
2047            let pubk = sk.public_key().to_encoded_point(false).as_bytes().to_vec();
2048            Ok((sk.to_bytes().to_vec(), pubk))
2049        }
2050        _ => Err(format!("Error: Unsupported curve: {curve}")),
2051    }
2052}
2053
2054/// The uncompressed SEC1 public point for a raw ECDH private scalar.
2055fn ecdh_public_from_private(curve: &str, priv_b: &[u8]) -> Result<Vec<u8>, String> {
2056    match curve {
2057        "p256" => {
2058            let sk = p256::SecretKey::from_slice(priv_b).map_err(|e| format!("Error: {e}"))?;
2059            Ok(sk.public_key().to_encoded_point(false).as_bytes().to_vec())
2060        }
2061        "p384" => {
2062            let sk = p384::SecretKey::from_slice(priv_b).map_err(|e| format!("Error: {e}"))?;
2063            Ok(sk.public_key().to_encoded_point(false).as_bytes().to_vec())
2064        }
2065        _ => Err(format!("Error: Unsupported curve: {curve}")),
2066    }
2067}
2068
2069/// ECDH shared secret (raw X coordinate) from a private scalar + peer point.
2070fn ecdh_compute(curve: &str, priv_b: &[u8], pub_b: &[u8]) -> Result<Vec<u8>, String> {
2071    match curve {
2072        "p256" => {
2073            let sk = p256::SecretKey::from_slice(priv_b).map_err(|e| format!("Error: {e}"))?;
2074            let pk = p256::PublicKey::from_sec1_bytes(pub_b).map_err(|e| format!("Error: {e}"))?;
2075            let shared = p256::ecdh::diffie_hellman(sk.to_nonzero_scalar(), pk.as_affine());
2076            Ok(shared.raw_secret_bytes().to_vec())
2077        }
2078        "p384" => {
2079            let sk = p384::SecretKey::from_slice(priv_b).map_err(|e| format!("Error: {e}"))?;
2080            let pk = p384::PublicKey::from_sec1_bytes(pub_b).map_err(|e| format!("Error: {e}"))?;
2081            let shared = p384::ecdh::diffie_hellman(sk.to_nonzero_scalar(), pk.as_affine());
2082            Ok(shared.raw_secret_bytes().to_vec())
2083        }
2084        _ => Err(format!("Error: Unsupported curve: {curve}")),
2085    }
2086}
2087
2088/// String property helper.
2089fn obj_str(recv: &Value, key: &str) -> String {
2090    with_host(|h| match h.get(recv) {
2091        Some(JsObj::Object(p)) => p.get(key).map(|v| h.str_of(v)).unwrap_or_default(),
2092        _ => String::new(),
2093    })
2094}
2095
2096/// `crypto.diffieHellman({ privateKey, publicKey })` one-shot (EC / X25519).
2097fn diffie_hellman_oneshot(opts: Option<&Value>) -> Result<Value, String> {
2098    let o = opts.ok_or("Error: options object required")?;
2099    let priv_v = with_host(|h| match h.get(o) {
2100        Some(JsObj::Object(p)) => p.get("privateKey").cloned(),
2101        _ => None,
2102    })
2103    .ok_or("Error: privateKey required")?;
2104    let pub_v = with_host(|h| match h.get(o) {
2105        Some(JsObj::Object(p)) => p.get("publicKey").cloned(),
2106        _ => None,
2107    })
2108    .ok_or("Error: publicKey required")?;
2109    let priv_bytes = key_material(&priv_v);
2110    let pub_bytes = key_material(&pub_v);
2111    let asym = detect_private(&priv_bytes).ok_or("Error: unsupported private key")?;
2112    let priv_pem = std::str::from_utf8(&priv_bytes).ok();
2113    let pub_pem = std::str::from_utf8(&pub_bytes).ok();
2114    let secret = match asym {
2115        "ec" => {
2116            if let Some(sk) = priv_pem.and_then(|p| p256::SecretKey::from_pkcs8_pem(p).ok()) {
2117                let pk = pub_pem
2118                    .and_then(|p| p256::PublicKey::from_public_key_pem(p).ok())
2119                    .ok_or("Error: bad public key")?;
2120                p256::ecdh::diffie_hellman(sk.to_nonzero_scalar(), pk.as_affine())
2121                    .raw_secret_bytes()
2122                    .to_vec()
2123            } else {
2124                let sk = priv_pem
2125                    .and_then(|p| p384::SecretKey::from_pkcs8_pem(p).ok())
2126                    .ok_or("Error: bad private key")?;
2127                let pk = pub_pem
2128                    .and_then(|p| p384::PublicKey::from_public_key_pem(p).ok())
2129                    .ok_or("Error: bad public key")?;
2130                p384::ecdh::diffie_hellman(sk.to_nonzero_scalar(), pk.as_affine())
2131                    .raw_secret_bytes()
2132                    .to_vec()
2133            }
2134        }
2135        "x25519" => {
2136            let sraw = priv_pem
2137                .and_then(x25519_raw)
2138                .ok_or("Error: bad private key")?;
2139            let praw = pub_pem
2140                .and_then(x25519_raw)
2141                .ok_or("Error: bad public key")?;
2142            let sk = x25519_dalek::StaticSecret::from(sraw);
2143            let pk = x25519_dalek::PublicKey::from(praw);
2144            sk.diffie_hellman(&pk).as_bytes().to_vec()
2145        }
2146        _ => return Err("Error: diffieHellman requires EC or X25519 keys".into()),
2147    };
2148    Ok(super::buffer::from_bytes(&secret))
2149}
2150
2151// ── Primes ──────────────────────────────────────────────────────────────
2152
2153/// A key/number argument as a `BigUint` (BigInt, Buffer big-endian, or number).
2154fn arg_biguint(v: Option<&Value>) -> BigUint {
2155    let Some(v) = v else {
2156        return BigUint::from(0u32);
2157    };
2158    if let Some(b) = with_host(|h| match h.get(v) {
2159        Some(JsObj::BigInt(b)) => b.to_biguint(),
2160        _ => None,
2161    }) {
2162        return b;
2163    }
2164    if super::native_tag(v).as_deref() == Some("Buffer") {
2165        return BigUint::from_bytes_be(&val_bytes(v));
2166    }
2167    let n = with_host(|h| h.to_number(v));
2168    BigUint::from(n.max(0.0) as u64)
2169}
2170
2171/// `checkPrimeSync(candidate)` — probabilistic primality.
2172fn check_prime(v: Option<&Value>) -> bool {
2173    let n = arg_biguint(v);
2174    num_prime::nt_funcs::is_prime(&n, None).probably()
2175}
2176
2177/// `generatePrimeSync(size[, {bigint}])` — a random prime of `size` bits.
2178fn generate_prime(bits: usize, opts: Option<Value>) -> Result<Value, String> {
2179    let p = gen_prime(bits)?;
2180    let want_bigint = opts
2181        .map(|o| with_host(|h| matches!(h.get(&o), Some(JsObj::Object(m)) if m.get("bigint").map(|v| h.truthy(v)).unwrap_or(false))))
2182        .unwrap_or(false);
2183    if want_bigint {
2184        Ok(with_host(|h| {
2185            h.alloc(JsObj::BigInt(num_bigint::BigInt::from(p)))
2186        }))
2187    } else {
2188        // Node returns an ArrayBuffer; this runtime's ArrayBuffer carries no
2189        // backing bytes, so a Buffer (also a byte view) is returned instead.
2190        Ok(super::buffer::from_bytes(&p.to_bytes_be()))
2191    }
2192}
2193
2194/// A random probable prime of `bits` bits (top bit set, odd, then next_prime).
2195fn gen_prime(bits: usize) -> Result<BigUint, String> {
2196    if bits < 2 {
2197        return Err("Error: size must be >= 2".into());
2198    }
2199    let nbytes = bits.div_ceil(8);
2200    let mut buf = vec![0u8; nbytes];
2201    getrandom::getrandom(&mut buf).map_err(|e| format!("Error: {e}"))?;
2202    let excess = nbytes * 8 - bits;
2203    buf[0] &= 0xffu8 >> excess;
2204    buf[0] |= 0x80u8 >> excess;
2205    let last = nbytes - 1;
2206    buf[last] |= 1;
2207    let start = BigUint::from_bytes_be(&buf);
2208    num_prime::nt_funcs::next_prime(&start, None)
2209        .ok_or_else(|| "Error: prime generation failed".into())
2210}
2211
2212// ── argon2 ──────────────────────────────────────────────────────────────
2213
2214/// `argon2Sync(algorithm, options)` → raw tag bytes.
2215fn argon2_hash(algo: &str, opts: Option<&Value>) -> Result<Vec<u8>, String> {
2216    let o = opts.cloned().ok_or("Error: argon2 options required")?;
2217    let msg = prop_bytes(&o, "message");
2218    let salt = prop_bytes(&o, "nonce");
2219    let secret = prop_bytes(&o, "secret");
2220    let taglen = opt_num(&o, &["tagLength"], 32.0).max(4.0) as usize;
2221    let mem = opt_num(&o, &["memory"], 65536.0) as u32;
2222    let passes = opt_num(&o, &["passes"], 3.0) as u32;
2223    let par = opt_num(&o, &["parallelism"], 4.0) as u32;
2224    let algorithm = match algo.to_ascii_lowercase().as_str() {
2225        "argon2d" => argon2::Algorithm::Argon2d,
2226        "argon2i" => argon2::Algorithm::Argon2i,
2227        _ => argon2::Algorithm::Argon2id,
2228    };
2229    let params =
2230        argon2::Params::new(mem, passes, par, Some(taglen)).map_err(|e| format!("Error: {e}"))?;
2231    let ctx = if secret.is_empty() {
2232        argon2::Argon2::new(algorithm, argon2::Version::V0x13, params)
2233    } else {
2234        argon2::Argon2::new_with_secret(&secret, algorithm, argon2::Version::V0x13, params)
2235            .map_err(|e| format!("Error: {e}"))?
2236    };
2237    let mut out = vec![0u8; taglen];
2238    ctx.hash_password_into(&msg, &salt, &mut out)
2239        .map_err(|e| format!("Error: {e}"))?;
2240    Ok(out)
2241}
2242
2243/// The bytes of a Buffer/typed-array/string property of an object.
2244fn prop_bytes(obj: &Value, key: &str) -> Vec<u8> {
2245    let v = with_host(|h| match h.get(obj) {
2246        Some(JsObj::Object(p)) => p.get(key).cloned(),
2247        _ => None,
2248    });
2249    v.map(|x| val_bytes(&x)).unwrap_or_default()
2250}
2251
2252// ── WebCrypto getRandomValues ───────────────────────────────────────────
2253
2254/// `getRandomValues(typedArray)` — fill in place with CSPRNG bytes, return it.
2255fn get_random_values(v: Option<&Value>) -> Result<Value, String> {
2256    let ta = v
2257        .cloned()
2258        .ok_or("Error: argument must be an integer-type TypedArray")?;
2259    let tag = super::native_tag(&ta);
2260    if tag.as_deref() == Some("Buffer") {
2261        let len = val_bytes(&ta).len();
2262        let mut rnd = vec![0u8; len];
2263        getrandom::getrandom(&mut rnd).map_err(|e| format!("Error: {e}"))?;
2264        set_obj_bytes_named(&ta, "@@bytes", &rnd);
2265        return Ok(ta);
2266    }
2267    if tag.as_deref() != Some("TypedArray") {
2268        return Err("Error: argument must be an integer-type TypedArray".into());
2269    }
2270    let kind = obj_str(&ta, "@@kind");
2271    if kind.starts_with("Float") {
2272        return Err("Error: The provided ArrayBufferView is of type 'Float', which is not an integer array type".into());
2273    }
2274    let len = with_host(|h| {
2275        if let Some(JsObj::Object(p)) = h.get(&ta) {
2276            if let Some(JsObj::Array(it)) = p.get("@@elems").and_then(|v| h.get(v)) {
2277                return it.len();
2278            }
2279        }
2280        0
2281    });
2282    let bpe = match kind.as_str() {
2283        "Int16Array" | "Uint16Array" => 2,
2284        "Int32Array" | "Uint32Array" => 4,
2285        _ => 1,
2286    };
2287    let mut raw = vec![0u8; len * bpe];
2288    getrandom::getrandom(&mut raw).map_err(|e| format!("Error: {e}"))?;
2289    let elems: Vec<Value> = (0..len)
2290        .map(|i| {
2291            let mut acc: u64 = 0;
2292            for j in 0..bpe {
2293                acc |= (raw[i * bpe + j] as u64) << (8 * j);
2294            }
2295            Value::Float(ta_coerce(&kind, acc))
2296        })
2297        .collect();
2298    with_host(|h| {
2299        let arr = match h.get(&ta) {
2300            Some(JsObj::Object(p)) => p.get("@@elems").cloned(),
2301            _ => None,
2302        };
2303        if let Some(a) = arr {
2304            if let Some(JsObj::Array(items)) = h.get_mut(&a) {
2305                *items = elems;
2306            }
2307        }
2308    });
2309    Ok(ta)
2310}
2311
2312/// Coerce a raw little-endian integer into a typed-array element value.
2313fn ta_coerce(kind: &str, raw: u64) -> f64 {
2314    match kind {
2315        "Int8Array" => (raw as i8) as f64,
2316        "Int16Array" => (raw as i16) as f64,
2317        "Int32Array" => (raw as i32) as f64,
2318        "Uint16Array" => (raw as u16) as f64,
2319        "Uint32Array" => (raw as u32) as f64,
2320        _ => (raw as u8) as f64, // Uint8Array / Uint8ClampedArray
2321    }
2322}
2323
2324/// Store raw bytes into a named byte-array property (for Buffer `@@bytes`).
2325fn set_obj_bytes_named(recv: &Value, key: &str, bytes: &[u8]) {
2326    with_host(|h| {
2327        let arr = match h.get(recv) {
2328            Some(JsObj::Object(p)) => p.get(key).cloned(),
2329            _ => None,
2330        };
2331        if let Some(a) = arr {
2332            if let Some(JsObj::Array(items)) = h.get_mut(&a) {
2333                *items = bytes.iter().map(|b| Value::Float(*b as f64)).collect();
2334            }
2335        }
2336    });
2337}
2338
2339// ── KeyObject instance methods ──────────────────────────────────────────
2340
2341/// `KeyObject` instance dispatch (`export({type,format})`, `equals`).
2342pub fn key_object_instance_call(
2343    recv: &Value,
2344    method: &str,
2345    args: &[Value],
2346) -> Result<Value, String> {
2347    match method {
2348        "export" => {
2349            // Secret key: raw bytes (Buffer) unless format 'jwk' (unsupported).
2350            let secret = obj_bytes(recv, "@@secret");
2351            if !secret.is_empty() {
2352                return Ok(super::buffer::from_bytes(&secret));
2353            }
2354            let pem = obj_str(recv, "@@pem");
2355            let format = args
2356                .first()
2357                .map(|o| opt_str(o, "format"))
2358                .filter(|s| !s.is_empty())
2359                .unwrap_or_else(|| "pem".into());
2360            if format == "der" {
2361                Ok(super::buffer::from_bytes(&pem_body(&pem)))
2362            } else {
2363                Ok(with_host(|h| h.new_str(pem)))
2364            }
2365        }
2366        "equals" => {
2367            let other = args.first().map(key_material).unwrap_or_default();
2368            let mine = obj_str(recv, "@@pem").into_bytes();
2369            Ok(Value::Bool(mine == other))
2370        }
2371        _ => Err(crate::host::type_error(&format!(
2372            "keyObject.{method} is not a function"
2373        ))),
2374    }
2375}
2376
2377// ── X.509 certificates ──────────────────────────────────────────────────
2378
2379/// `new X509Certificate(pemOrDer)` → an `X509Certificate` instance.
2380pub fn construct_x509(args: &[Value]) -> Result<Value, String> {
2381    use x509_cert::der::{Decode, DecodePem, Encode};
2382    let bytes = val_bytes_at(args, 0);
2383    let is_pem = bytes.starts_with(b"-----BEGIN");
2384    let cert = if is_pem {
2385        x509_cert::Certificate::from_pem(&bytes).map_err(|e| format!("Error: {e}"))?
2386    } else {
2387        x509_cert::Certificate::from_der(&bytes).map_err(|e| format!("Error: {e}"))?
2388    };
2389    // The canonical DER (for fingerprint + raw).
2390    let der = cert.to_der().map_err(|e| format!("Error: {e}"))?;
2391    let fp = {
2392        let d = digest("sha1", &der);
2393        d.iter()
2394            .map(|b| format!("{b:02X}"))
2395            .collect::<Vec<_>>()
2396            .join(":")
2397    };
2398    let subject = x509_name_node(&cert.tbs_certificate.subject.to_string());
2399    let issuer = x509_name_node(&cert.tbs_certificate.issuer.to_string());
2400    let not_before = x509_time(&cert.tbs_certificate.validity.not_before);
2401    let not_after = x509_time(&cert.tbs_certificate.validity.not_after);
2402    let serial = cert
2403        .tbs_certificate
2404        .serial_number
2405        .as_bytes()
2406        .iter()
2407        .map(|b| format!("{b:02X}"))
2408        .collect::<String>();
2409    let spki_der = cert
2410        .tbs_certificate
2411        .subject_public_key_info
2412        .to_der()
2413        .map_err(|e| format!("Error: {e}"))?;
2414    let pub_pem = pem_wrap("PUBLIC KEY", &spki_der);
2415    let pub_key =
2416        create_public_key(Some(&with_host(|h| h.new_str(pub_pem)))).unwrap_or(Value::Undef);
2417    let cert_pem = pem_wrap("CERTIFICATE", &der);
2418    let raw = super::buffer::from_bytes(&der);
2419    Ok(with_host(|h| {
2420        let mut m = IndexMap::new();
2421        m.insert("@@native".into(), h.new_str("X509Certificate"));
2422        m.insert("subject".into(), h.new_str(subject));
2423        m.insert("issuer".into(), h.new_str(issuer));
2424        m.insert("validFrom".into(), h.new_str(not_before));
2425        m.insert("validTo".into(), h.new_str(not_after));
2426        m.insert("serialNumber".into(), h.new_str(serial));
2427        m.insert("fingerprint".into(), h.new_str(fp));
2428        m.insert("publicKey".into(), pub_key);
2429        m.insert("raw".into(), raw);
2430        m.insert("@@pem".into(), h.new_str(cert_pem));
2431        h.new_object(m)
2432    }))
2433}
2434
2435/// `X509Certificate` instance dispatch (`toString`, `toLegacyObject`).
2436pub fn x509_instance_call(recv: &Value, method: &str, _args: &[Value]) -> Result<Value, String> {
2437    match method {
2438        // Same nested-borrow hazard as `events::getEventListeners`: `obj_str`
2439        // takes the host, so reading it inside `with_host` aborts the process.
2440        "toString" => {
2441            let pem = obj_str(recv, "@@pem");
2442            Ok(with_host(|h| h.new_str(pem)))
2443        }
2444        _ => Err(crate::host::type_error(&format!(
2445            "x509Certificate.{method} is not a function"
2446        ))),
2447    }
2448}
2449
2450/// Convert an RFC 4514 name string ("O=Org,CN=Name") to Node's newline,
2451/// most-significant-last form ("CN=Name\nO=Org").
2452fn x509_name_node(rfc4514: &str) -> String {
2453    rfc4514
2454        .split(',')
2455        .map(|s| s.trim())
2456        .rev()
2457        .collect::<Vec<_>>()
2458        .join("\n")
2459}
2460
2461/// Render an X.509 time in OpenSSL's `%b %e %H:%M:%S %Y GMT` form.
2462fn x509_time(t: &x509_cert::time::Time) -> String {
2463    const MON: [&str; 12] = [
2464        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
2465    ];
2466    let dt = t.to_date_time();
2467    let mon = MON
2468        .get(dt.month().saturating_sub(1) as usize)
2469        .copied()
2470        .unwrap_or("Jan");
2471    format!(
2472        "{} {:2} {:02}:{:02}:{:02} {} GMT",
2473        mon,
2474        dt.day(),
2475        dt.hour(),
2476        dt.minutes(),
2477        dt.seconds(),
2478        dt.year()
2479    )
2480}
2481
2482// RFC 2409/3526 MODP group primes (generator 2).
2483const MODP1: &str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF";
2484const MODP2: &str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF";
2485const MODP5: &str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF";
2486const MODP14: &str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF";
2487const MODP15: &str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
2488const MODP16: &str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF";
2489const MODP17: &str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF";
2490const MODP18: &str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF";