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