char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport webcrypto extension to typescriptdbb5c40

main
78.2 KiB2269 linesraw
1mod asymmetric;
2
3use base64::Engine;
4use base64::engine::general_purpose::URL_SAFE_NO_PAD;
5use std::collections::HashMap;
6use std::io;
7use std::panic::{AssertUnwindSafe, catch_unwind};
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::sync::{Arc, Mutex, OnceLock};
10
11use aes::cipher::{BlockCipherEncrypt, KeyInit};
12use aes::{Aes128, Aes192, Aes256};
13use aes_kw::{KwAes128, KwAes192, KwAes256};
14use avril_core::serde_v8::{JsBuffer, ToJsBuffer};
15use avril_core::{Extension, Op, message};
16use cbc::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::Pkcs7};
17use ctr::cipher::StreamCipher;
18use ghash::{GHash, universal_hash::UniversalHash};
19use hmac::{Hmac, Mac};
20use sha1::Digest as _;
21use sha2::{Sha256, Sha384, Sha512};
22use subtle::ConstantTimeEq;
23use zeroize::{Zeroize, Zeroizing};
24
25type DeriveKeyArgs = (
26    u64,
27    String,
28    JsBuffer,
29    u32,
30    JsBuffer,
31    usize,
32    String,
33    String,
34    Vec<String>,
35    bool,
36    u64,
37);
38
39struct Key {
40    bytes: Vec<u8>,
41    algorithm: String,
42    hash: String,
43    usages: Vec<String>,
44    extractable: bool,
45    key_type: String,
46    length: usize,
47    curve: String,
48    public_exponent: Vec<u8>,
49}
50
51impl Drop for Key {
52    fn drop(&mut self) {
53        self.bytes.zeroize();
54    }
55}
56
57#[derive(serde::Serialize)]
58struct KeyMetadata {
59    id: u64,
60    length: usize,
61    exponent: Option<ToJsBuffer>,
62    #[serde(rename = "type")]
63    key_type: String,
64}
65
66struct KeyStore {
67    keys: Mutex<HashMap<u64, Arc<Key>>>,
68    next_id: AtomicU64,
69}
70
71impl KeyStore {
72    fn new() -> Self {
73        Self {
74            keys: Mutex::new(HashMap::new()),
75            next_id: AtomicU64::new(0),
76        }
77    }
78
79    fn next_id(&self) -> io::Result<u64> {
80        self.next_id
81            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| {
82                (id < 9_007_199_254_740_991).then_some(id + 1)
83            })
84            .map(|id| id + 1)
85            .map_err(|_| io::Error::other("Crypto key handles exhausted"))
86    }
87
88    fn metadata(&self, id: u64) -> io::Result<KeyMetadata> {
89        let key = self.get(id)?;
90        Ok(KeyMetadata {
91            id,
92            length: key.length,
93            exponent: (!key.public_exponent.is_empty())
94                .then(|| ToJsBuffer::from(key.public_exponent.clone())),
95            key_type: key.key_type.clone(),
96        })
97    }
98
99    fn insert(&self, key: Key) -> io::Result<u64> {
100        self.insert_shared(Arc::new(key))
101    }
102
103    fn insert_shared(&self, key: Arc<Key>) -> io::Result<u64> {
104        let id = self.next_id()?;
105        self.keys
106            .lock()
107            .map_err(|_| io::Error::other("Crypto key registry poisoned"))?
108            .insert(id, key);
109        Ok(id)
110    }
111
112    fn get(&self, id: u64) -> io::Result<Arc<Key>> {
113        self.keys
114            .lock()
115            .map_err(|_| io::Error::other("Crypto key registry poisoned"))?
116            .get(&id)
117            .cloned()
118            .ok_or_else(|| io::Error::other("CryptoKey is no longer available"))
119    }
120
121    fn close(&self, id: u64) {
122        if let Ok(mut keys) = self.keys.lock() {
123            keys.remove(&id);
124        }
125    }
126
127    fn clone_key(&self, id: u64) -> io::Result<u64> {
128        self.insert_shared(self.get(id)?)
129    }
130}
131
132fn allowed_usages(algorithm: &str, key_type: &str) -> io::Result<&'static [&'static str]> {
133    Ok(match (algorithm, key_type) {
134        ("AES-CBC" | "AES-CTR" | "AES-GCM", "secret") => {
135            &["encrypt", "decrypt", "wrapKey", "unwrapKey"]
136        }
137        ("AES-KW", "secret") => &["wrapKey", "unwrapKey"],
138        ("HMAC", "secret") => &["sign", "verify"],
139        ("PBKDF2" | "HKDF", "secret") => &["deriveBits", "deriveKey"],
140        ("RSA-OAEP", "public") => &["encrypt", "wrapKey"],
141        ("RSA-OAEP", "private") => &["decrypt", "unwrapKey"],
142        ("RSA-PSS" | "RSASSA-PKCS1-V1_5", "public") => &["verify"],
143        ("RSA-PSS" | "RSASSA-PKCS1-V1_5", "private") => &["sign"],
144        ("ECDSA" | "ED25519", "public") => &["verify"],
145        ("ECDSA" | "ED25519", "private") => &["sign"],
146        ("ECDH" | "X25519", "public") => &[],
147        ("ECDH" | "X25519", "private") => &["deriveBits", "deriveKey"],
148        _ => return Err(io::Error::other("Unsupported CryptoKey algorithm or type")),
149    })
150}
151
152fn apply_bit_length(bytes: &mut [u8], bits: usize) {
153    if !bits.is_multiple_of(8) && !bytes.is_empty() {
154        let last = bytes.len() - 1;
155        bytes[last] &= 0xff << (8 - bits % 8);
156    }
157}
158
159fn truncate_bits(bytes: &mut Vec<u8>, requested: usize) -> io::Result<usize> {
160    let bits = if requested == 0 {
161        bytes.len() * 8
162    } else {
163        requested
164    };
165    if bits == 0 || bits > bytes.len() * 8 || (requested != 0 && bits <= bytes.len() * 8 - 8) {
166        return Err(io::Error::other("Invalid HMAC key length"));
167    }
168    bytes.truncate(bits.div_ceil(8));
169    apply_bit_length(bytes, bits);
170    Ok(bits)
171}
172
173fn insert_key(store: &KeyStore, key: Key) -> io::Result<u64> {
174    let asymmetric = matches!(
175        key.algorithm.as_str(),
176        "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" | "ECDSA" | "ECDH" | "ED25519" | "X25519"
177    );
178    let allowed = allowed_usages(&key.algorithm, &key.key_type)?;
179    if key
180        .usages
181        .iter()
182        .any(|usage| !allowed.contains(&usage.as_str()))
183    {
184        return Err(io::Error::other("Invalid CryptoKey usage"));
185    }
186    if key.key_type == "secret" && key.usages.is_empty() {
187        return Err(io::Error::other(
188            "Secret CryptoKeys must have at least one usage",
189        ));
190    }
191    if matches!(key.algorithm.as_str(), "PBKDF2" | "HKDF") && key.extractable {
192        return Err(io::Error::other(
193            "Key derivation keys must be non-extractable",
194        ));
195    }
196    if asymmetric {
197        if key.bytes.is_empty() || (key.key_type == "private" && key.usages.is_empty()) {
198            return Err(io::Error::other("Invalid asymmetric key"));
199        }
200        if matches!(
201            key.algorithm.as_str(),
202            "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5"
203        ) && hash_len(&key.hash).is_err()
204        {
205            return Err(io::Error::other("Invalid CryptoKey hash"));
206        }
207    } else {
208        let valid_length = match key.algorithm.as_str() {
209            "AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-KW" => matches!(key.bytes.len(), 16 | 24 | 32),
210            "HMAC" => {
211                key.length > 0
212                    && key.length <= key.bytes.len() * 8
213                    && key.bytes.len() == key.length.div_ceil(8)
214            }
215            "PBKDF2" | "HKDF" => true,
216            _ => false,
217        };
218        if !valid_length {
219            return Err(io::Error::other("Invalid CryptoKey length"));
220        }
221        if key.algorithm == "HMAC" && hash_len(&key.hash).is_err() {
222            return Err(io::Error::other("Invalid CryptoKey hash"));
223        }
224    }
225    store.insert(key)
226}
227
228fn insert_key_pair(
229    store: &KeyStore,
230    algorithm: String,
231    hash: String,
232    curve: String,
233    usages: Vec<String>,
234    extractable: bool,
235    mut pair: asymmetric::KeyPairDer,
236) -> io::Result<(u64, u64, usize, Option<ToJsBuffer>)> {
237    let (public_usages, private_usages): (Vec<String>, Vec<String>) = match algorithm.as_str() {
238        "RSA-OAEP" => (
239            usages
240                .iter()
241                .filter(|u| matches!(u.as_str(), "encrypt" | "wrapKey"))
242                .cloned()
243                .collect(),
244            usages
245                .iter()
246                .filter(|u| matches!(u.as_str(), "decrypt" | "unwrapKey"))
247                .cloned()
248                .collect(),
249        ),
250        "RSA-PSS" | "RSASSA-PKCS1-V1_5" | "ECDSA" | "ED25519" => (
251            usages
252                .iter()
253                .filter(|u| u.as_str() == "verify")
254                .cloned()
255                .collect(),
256            usages
257                .iter()
258                .filter(|u| u.as_str() == "sign")
259                .cloned()
260                .collect(),
261        ),
262        "ECDH" | "X25519" => (
263            Vec::new(),
264            usages
265                .iter()
266                .filter(|u| matches!(u.as_str(), "deriveBits" | "deriveKey"))
267                .cloned()
268                .collect(),
269        ),
270        _ => return Err(io::Error::other("Unsupported asymmetric key algorithm")),
271    };
272    let public = insert_key(
273        store,
274        Key {
275            bytes: pair.public,
276            algorithm: algorithm.clone(),
277            hash: hash.clone(),
278            usages: public_usages,
279            extractable: true,
280            key_type: "public".into(),
281            length: pair.length,
282            curve: curve.clone(),
283            public_exponent: pair.exponent.clone(),
284        },
285    )?;
286    let private = match insert_key(
287        store,
288        Key {
289            bytes: std::mem::take(&mut *pair.private),
290            algorithm,
291            hash,
292            usages: private_usages,
293            extractable,
294            key_type: "private".into(),
295            length: pair.length,
296            curve,
297            public_exponent: pair.exponent.clone(),
298        },
299    ) {
300        Ok(id) => id,
301        Err(error) => {
302            store.close(public);
303            return Err(error);
304        }
305    };
306    let exponent = (!pair.exponent.is_empty()).then(|| ToJsBuffer::from(pair.exponent));
307    Ok((public, private, pair.length, exponent))
308}
309
310fn require(key: &Key, usage: &str, algorithm: &str) -> io::Result<()> {
311    if !key.usages.iter().any(|item| item == usage) {
312        return Err(io::Error::other("CryptoKey does not permit this usage"));
313    }
314    if key.algorithm != algorithm {
315        return Err(io::Error::other("CryptoKey algorithm mismatch"));
316    }
317    Ok(())
318}
319
320fn hash_len(hash: &str) -> io::Result<usize> {
321    match hash.to_ascii_uppercase().as_str() {
322        "SHA-1" => Ok(20),
323        "SHA-256" => Ok(32),
324        "SHA-384" => Ok(48),
325        "SHA-512" => Ok(64),
326        _ => Err(io::Error::other("Unsupported hash algorithm")),
327    }
328}
329
330fn digest(hash: &str, data: &[u8]) -> io::Result<Vec<u8>> {
331    match hash.to_ascii_uppercase().as_str() {
332        "SHA-1" => Ok(sha1::Sha1::digest(data).to_vec()),
333        "SHA-256" => Ok(Sha256::digest(data).to_vec()),
334        "SHA-384" => Ok(Sha384::digest(data).to_vec()),
335        "SHA-512" => Ok(Sha512::digest(data).to_vec()),
336        _ => Err(io::Error::other("Unsupported hash algorithm")),
337    }
338}
339
340fn hmac_sign(hash: &str, key: &[u8], input: &[u8]) -> io::Result<Vec<u8>> {
341    macro_rules! sign {
342        ($digest:ty) => {{
343            let mut mac = <Hmac<$digest> as Mac>::new_from_slice(key).map_err(io::Error::other)?;
344            mac.update(input);
345            Ok(mac.finalize().into_bytes().to_vec())
346        }};
347    }
348    match hash.to_ascii_uppercase().as_str() {
349        "SHA-1" => sign!(sha1::Sha1),
350        "SHA-256" => sign!(Sha256),
351        "SHA-384" => sign!(Sha384),
352        "SHA-512" => sign!(Sha512),
353        _ => Err(io::Error::other("Unsupported HMAC hash")),
354    }
355}
356
357const AES_GCM_TAG_LENGTHS: [usize; 7] = [32, 64, 96, 104, 112, 120, 128];
358
359fn validate_gcm_tag_length(bits: usize) -> io::Result<()> {
360    if AES_GCM_TAG_LENGTHS.contains(&bits) {
361        Ok(())
362    } else {
363        Err(io::Error::other("Invalid AES-GCM tag length"))
364    }
365}
366
367macro_rules! with_aes_block_cipher {
368    ($key:expr, $encrypt_block:ident => $body:block) => {{
369        match $key.len() {
370            16 => {
371                let cipher = Aes128::new_from_slice($key).map_err(io::Error::other)?;
372                let mut $encrypt_block = |block: &mut ghash::Block| cipher.encrypt_block(block);
373                $body
374            }
375            24 => {
376                let cipher = Aes192::new_from_slice($key).map_err(io::Error::other)?;
377                let mut $encrypt_block = |block: &mut ghash::Block| cipher.encrypt_block(block);
378                $body
379            }
380            32 => {
381                let cipher = Aes256::new_from_slice($key).map_err(io::Error::other)?;
382                let mut $encrypt_block = |block: &mut ghash::Block| cipher.encrypt_block(block);
383                $body
384            }
385            _ => Err(io::Error::other("Invalid AES-GCM key length")),
386        }
387    }};
388}
389
390fn gcm_counter_block(counter: &mut ghash::Block) {
391    let bytes = counter.as_mut_slice();
392    let value = u32::from_be_bytes(
393        bytes[12..16]
394            .try_into()
395            .expect("GCM counter block is 16 bytes"),
396    )
397    .wrapping_add(1);
398    bytes[12..16].copy_from_slice(&value.to_be_bytes());
399}
400
401fn gcm_j0(authenticator: &GHash, iv: &[u8]) -> ghash::Block {
402    if iv.len() == 12 {
403        let mut block = ghash::Block::default();
404        let bytes = block.as_mut_slice();
405        bytes[..12].copy_from_slice(iv);
406        bytes[15] = 1;
407        block
408    } else {
409        let mut authenticator = authenticator.clone();
410        authenticator.update_padded(iv);
411        let mut lengths = ghash::Block::default();
412        lengths.as_mut_slice()[8..]
413            .copy_from_slice(&((iv.len() as u64).wrapping_mul(8)).to_be_bytes());
414        authenticator.update(&[lengths]);
415        authenticator.finalize()
416    }
417}
418
419fn gcm_tag(
420    authenticator: &GHash,
421    mask: &ghash::Block,
422    aad: &[u8],
423    ciphertext: &[u8],
424) -> ghash::Block {
425    let mut authenticator = authenticator.clone();
426    authenticator.update_padded(aad);
427    authenticator.update_padded(ciphertext);
428    let mut lengths = ghash::Block::default();
429    let bytes = lengths.as_mut_slice();
430    bytes[..8].copy_from_slice(&((aad.len() as u64).wrapping_mul(8)).to_be_bytes());
431    bytes[8..].copy_from_slice(&((ciphertext.len() as u64).wrapping_mul(8)).to_be_bytes());
432    authenticator.update(&[lengths]);
433    let mut tag = authenticator.finalize();
434    for (tag_byte, mask_byte) in tag.as_mut_slice().iter_mut().zip(mask.as_slice()) {
435        *tag_byte ^= *mask_byte;
436    }
437    tag
438}
439
440fn gcm_keystream(
441    encrypt_block: &mut dyn FnMut(&mut ghash::Block),
442    j0: &ghash::Block,
443    output: &mut [u8],
444) {
445    let mut counter = *j0;
446    gcm_counter_block(&mut counter);
447    for chunk in output.chunks_mut(16) {
448        let mut keystream = counter;
449        encrypt_block(&mut keystream);
450        for (byte, key) in chunk.iter_mut().zip(keystream.iter()) {
451            *byte ^= *key;
452        }
453        gcm_counter_block(&mut counter);
454    }
455}
456
457fn aes_gcm_encrypt(
458    key: &[u8],
459    iv: &[u8],
460    input: &[u8],
461    aad: &[u8],
462    bits: usize,
463) -> io::Result<Vec<u8>> {
464    validate_gcm_tag_length(bits)?;
465    if iv.is_empty() {
466        return Err(io::Error::other("Invalid AES-GCM IV"));
467    }
468    let tag_length = bits / 8;
469    with_aes_block_cipher!(key, encrypt_block => {
470        let mut hash_key = ghash::Block::default();
471        encrypt_block(&mut hash_key);
472        let authenticator = GHash::new(&hash_key);
473        hash_key.as_mut_slice().zeroize();
474        let j0 = gcm_j0(&authenticator, iv);
475        let mut mask = j0;
476        encrypt_block(&mut mask);
477        let mut output = input.to_vec();
478        gcm_keystream(&mut encrypt_block, &j0, &mut output);
479        let tag = gcm_tag(&authenticator, &mask, aad, &output);
480        mask.as_mut_slice().zeroize();
481        output.extend_from_slice(&tag.as_slice()[..tag_length]);
482        Ok(output)
483    })
484}
485
486fn aes_gcm_decrypt(
487    key: &[u8],
488    iv: &[u8],
489    input: &[u8],
490    aad: &[u8],
491    bits: usize,
492) -> io::Result<Vec<u8>> {
493    validate_gcm_tag_length(bits)?;
494    if iv.is_empty() {
495        return Err(io::Error::other("Invalid AES-GCM IV"));
496    }
497    let tag_length = bits / 8;
498    if input.len() < tag_length {
499        return Err(io::Error::other("Invalid AES-GCM ciphertext"));
500    }
501    let (ciphertext, tag) = input.split_at(input.len() - tag_length);
502    with_aes_block_cipher!(key, encrypt_block => {
503        let mut hash_key = ghash::Block::default();
504        encrypt_block(&mut hash_key);
505        let authenticator = GHash::new(&hash_key);
506        hash_key.as_mut_slice().zeroize();
507        let j0 = gcm_j0(&authenticator, iv);
508        let mut mask = j0;
509        encrypt_block(&mut mask);
510        let expected = gcm_tag(&authenticator, &mask, aad, ciphertext);
511        mask.as_mut_slice().zeroize();
512        if bool::from(expected.as_slice()[..tag_length].ct_eq(tag)) {
513            let mut output = Zeroizing::new(ciphertext.to_vec());
514            gcm_keystream(&mut encrypt_block, &j0, &mut output);
515            Ok(std::mem::take(&mut *output))
516        } else {
517            Err(io::Error::other("Invalid AES-GCM ciphertext"))
518        }
519    })
520}
521
522fn aes_ctr(key: &[u8], iv: &[u8], input: &[u8], bits: usize) -> io::Result<Vec<u8>> {
523    if !(1..=128).contains(&bits) || iv.len() != 16 {
524        return Err(io::Error::other("Invalid AES-CTR counter"));
525    }
526    let blocks = input.len().div_ceil(16) as u128;
527    if bits < 128 && blocks > (1u128 << bits) {
528        return Err(io::Error::other("AES-CTR input exceeds the counter space"));
529    }
530    let counter = u128::from_be_bytes(iv.try_into().map_err(io::Error::other)?);
531    let mask = if bits == 128 {
532        u128::MAX
533    } else {
534        (1u128 << bits) - 1
535    };
536    let prefix = if bits < 128 && mask - (counter & mask) + 1 < blocks {
537        (mask - (counter & mask) + 1) as usize * 16
538    } else {
539        input.len()
540    };
541    let mut output = input.to_vec();
542    macro_rules! crypt {
543        ($aes:ty) => {{
544            let mut cipher =
545                ctr::Ctr128BE::<$aes>::new_from_slices(key, iv).map_err(io::Error::other)?;
546            cipher.apply_keystream(&mut output[..prefix]);
547            if prefix < output.len() {
548                // Wrap only the counter field; the nonce bits must not carry.
549                let nonce = (counter & !mask).to_be_bytes();
550                let mut cipher = ctr::Ctr128BE::<$aes>::new_from_slices(key, &nonce)
551                    .map_err(io::Error::other)?;
552                cipher.apply_keystream(&mut output[prefix..]);
553            }
554        }};
555    }
556    match key.len() {
557        16 => crypt!(Aes128),
558        24 => crypt!(Aes192),
559        32 => crypt!(Aes256),
560        _ => return Err(io::Error::other("Invalid AES key length")),
561    }
562    Ok(output)
563}
564
565fn aes_encrypt(
566    key: &[u8],
567    mode: &str,
568    iv: &[u8],
569    input: &[u8],
570    aad: &[u8],
571    tag_length: usize,
572) -> io::Result<Vec<u8>> {
573    macro_rules! cbc_encrypt {
574        ($aes:ty) => {{
575            let cipher =
576                cbc::Encryptor::<$aes>::new_from_slices(key, iv).map_err(io::Error::other)?;
577            Ok(cipher.encrypt_padded_vec::<Pkcs7>(input))
578        }};
579    }
580    match mode {
581        "AES-CBC" => match key.len() {
582            16 => cbc_encrypt!(Aes128),
583            24 => cbc_encrypt!(Aes192),
584            32 => cbc_encrypt!(Aes256),
585            _ => Err(io::Error::other("Invalid AES key length")),
586        },
587        "AES-CTR" => aes_ctr(key, iv, input, tag_length),
588        "AES-GCM" => aes_gcm_encrypt(key, iv, input, aad, tag_length),
589        _ => Err(io::Error::other("Unsupported AES mode")),
590    }
591}
592
593fn aes_decrypt(
594    key: &[u8],
595    mode: &str,
596    iv: &[u8],
597    input: &[u8],
598    aad: &[u8],
599    tag_length: usize,
600) -> io::Result<Vec<u8>> {
601    macro_rules! cbc_decrypt {
602        ($aes:ty) => {{
603            let cipher =
604                cbc::Decryptor::<$aes>::new_from_slices(key, iv).map_err(io::Error::other)?;
605            cipher
606                .decrypt_padded_vec::<Pkcs7>(input)
607                .map_err(io::Error::other)
608        }};
609    }
610    match mode {
611        "AES-CBC" => match key.len() {
612            16 => cbc_decrypt!(Aes128),
613            24 => cbc_decrypt!(Aes192),
614            32 => cbc_decrypt!(Aes256),
615            _ => Err(io::Error::other("Invalid AES key length")),
616        },
617        "AES-CTR" => aes_ctr(key, iv, input, tag_length),
618        "AES-GCM" => aes_gcm_decrypt(key, iv, input, aad, tag_length),
619        _ => Err(io::Error::other("Unsupported AES mode")),
620    }
621}
622
623fn wrap_kw(key: &[u8], input: &[u8]) -> io::Result<Vec<u8>> {
624    macro_rules! wrap {
625        ($kind:ty) => {{
626            let cipher = <$kind>::new_from_slice(key).map_err(io::Error::other)?;
627            let mut output = vec![0; input.len() + 8];
628            let output_len = cipher
629                .wrap_key(input, &mut output)
630                .map_err(io::Error::other)?
631                .len();
632            output.truncate(output_len);
633            Ok(output)
634        }};
635    }
636    match key.len() {
637        16 => wrap!(KwAes128),
638        24 => wrap!(KwAes192),
639        32 => wrap!(KwAes256),
640        _ => Err(io::Error::other("Invalid AES-KW key length")),
641    }
642}
643
644fn unwrap_kw(key: &[u8], input: &[u8]) -> io::Result<Vec<u8>> {
645    macro_rules! unwrap {
646        ($kind:ty) => {{
647            let cipher = <$kind>::new_from_slice(key).map_err(io::Error::other)?;
648            let mut output = vec![0; input.len()];
649            let output_len = cipher
650                .unwrap_key(input, &mut output)
651                .map_err(io::Error::other)?
652                .len();
653            output.truncate(output_len);
654            Ok(output)
655        }};
656    }
657    match key.len() {
658        16 => unwrap!(KwAes128),
659        24 => unwrap!(KwAes192),
660        32 => unwrap!(KwAes256),
661        _ => Err(io::Error::other("Invalid AES-KW key length")),
662    }
663}
664
665fn jwk_algorithm(key: &Key) -> io::Result<String> {
666    if key.algorithm == "HMAC" {
667        Ok(format!("HS{}", &key.hash[4..]))
668    } else {
669        let mode = match key.algorithm.as_str() {
670            "AES-CBC" => "CBC",
671            "AES-CTR" => "CTR",
672            "AES-GCM" => "GCM",
673            "AES-KW" => "KW",
674            _ => return Err(io::Error::other("Unsupported JWK algorithm")),
675        };
676        Ok(format!("A{}{}", key.bytes.len() * 8, mode))
677    }
678}
679
680fn key_jwk(key: &Key) -> io::Result<serde_json::Value> {
681    if key.key_type != "secret" {
682        return asymmetric::export_jwk(
683            &key.bytes,
684            &key.algorithm,
685            &key.hash,
686            &key.key_type,
687            &key.curve,
688            &key.usages,
689            key.extractable,
690        );
691    }
692    Ok(serde_json::json!({
693        "kty": "oct",
694        "k": URL_SAFE_NO_PAD.encode(&key.bytes),
695        "alg": jwk_algorithm(key)?,
696        "key_ops": key.usages,
697        "ext": key.extractable,
698    }))
699}
700
701fn key_from_jwk(
702    mut jwk: SensitiveJwk,
703    algorithm: String,
704    hash: String,
705    usages: Vec<String>,
706    extractable: bool,
707    requested_length: usize,
708) -> io::Result<Key> {
709    let value = &mut jwk.0;
710    if value.get("kty").and_then(|item| item.as_str()) != Some("oct") {
711        return Err(io::Error::other("JWK kty does not match a symmetric key"));
712    }
713    if let Some(ext) = value.get("ext")
714        && (!ext.is_boolean() || (ext.as_bool() == Some(false) && extractable))
715    {
716        return Err(io::Error::other("JWK extractability does not match"));
717    }
718    if let Some(ops) = value.get("key_ops") {
719        let ops = ops
720            .as_array()
721            .ok_or_else(|| io::Error::other("Invalid JWK key_ops"))?;
722        let mut seen = std::collections::HashSet::new();
723        let valid_ops = [
724            "encrypt",
725            "decrypt",
726            "sign",
727            "verify",
728            "deriveKey",
729            "deriveBits",
730            "wrapKey",
731            "unwrapKey",
732        ];
733        let allowed = allowed_usages(&algorithm, "secret")?;
734        if ops.iter().any(|item| {
735            item.as_str().is_none_or(|usage| {
736                !valid_ops.contains(&usage) || !allowed.contains(&usage) || !seen.insert(usage)
737            })
738        }) || usages
739            .iter()
740            .any(|usage| !ops.iter().any(|item| item.as_str() == Some(usage)))
741        {
742            return Err(io::Error::other(
743                "JWK key_ops do not permit the requested usages",
744            ));
745        }
746    }
747    let encoded = value
748        .get("k")
749        .and_then(|item| item.as_str())
750        .ok_or_else(|| io::Error::other("JWK is missing key data"))?;
751    let mut bytes = URL_SAFE_NO_PAD.decode(encoded).map_err(io::Error::other)?;
752    if let Some(serde_json::Value::String(encoded)) = value.get_mut("k") {
753        encoded.zeroize();
754    }
755    let length = if algorithm == "HMAC" {
756        truncate_bits(&mut bytes, requested_length)?
757    } else {
758        bytes.len() * 8
759    };
760    let key = Key {
761        bytes,
762        algorithm,
763        hash,
764        usages,
765        extractable,
766        key_type: "secret".into(),
767        length,
768        curve: String::new(),
769        public_exponent: Vec::new(),
770    };
771    if let Some(alg) = value.get("alg") {
772        let alg = alg
773            .as_str()
774            .ok_or_else(|| io::Error::other("Invalid JWK alg"))?;
775        if jwk_algorithm(&key)? != alg {
776            return Err(io::Error::other("JWK alg does not match the key algorithm"));
777        }
778    }
779    Ok(key)
780}
781
782#[derive(serde::Serialize, serde::Deserialize)]
783#[serde(transparent)]
784struct SensitiveJwk(serde_json::Value);
785
786impl Drop for SensitiveJwk {
787    fn drop(&mut self) {
788        for field in ["d", "p", "q", "dp", "dq", "qi", "k"] {
789            if let Some(serde_json::Value::String(encoded)) = self.0.get_mut(field) {
790                encoded.zeroize();
791            }
792        }
793    }
794}
795
796fn import_asymmetric_jwk(
797    mut sensitive: SensitiveJwk,
798    algorithm: String,
799    hash: String,
800    curve: String,
801    usages: Vec<String>,
802    extractable: bool,
803) -> io::Result<Key> {
804    let value = &mut sensitive.0;
805    if let Some(ext) = value.get("ext")
806        && (!ext.is_boolean() || (ext.as_bool() == Some(false) && extractable))
807    {
808        return Err(io::Error::other("JWK extractability does not match"));
809    }
810    if let Some(ops) = value.get("key_ops") {
811        let ops = ops
812            .as_array()
813            .ok_or_else(|| io::Error::other("Invalid JWK key_ops"))?;
814        let valid = [
815            "encrypt",
816            "decrypt",
817            "sign",
818            "verify",
819            "deriveKey",
820            "deriveBits",
821            "wrapKey",
822            "unwrapKey",
823        ];
824        let mut seen = std::collections::HashSet::new();
825        if ops.iter().any(|item| {
826            item.as_str()
827                .is_none_or(|op| !valid.contains(&op) || !seen.insert(op))
828        }) || usages
829            .iter()
830            .any(|usage| !ops.iter().any(|item| item.as_str() == Some(usage)))
831        {
832            return Err(io::Error::other(
833                "JWK key_ops do not permit the requested usages",
834            ));
835        }
836    }
837    let (imported, key_type) = asymmetric::import_jwk(value, &algorithm, &hash, &curve)?;
838    if let Some(ops) = value.get("key_ops").and_then(serde_json::Value::as_array) {
839        let allowed = allowed_usages(&algorithm, &key_type)?;
840        if ops
841            .iter()
842            .any(|item| item.as_str().is_none_or(|usage| !allowed.contains(&usage)))
843        {
844            return Err(io::Error::other(
845                "JWK key_ops are incompatible with the key type",
846            ));
847        }
848    }
849    let key = Key {
850        bytes: imported.bytes,
851        algorithm,
852        hash,
853        usages,
854        extractable,
855        key_type,
856        length: imported.length,
857        curve: imported.curve,
858        public_exponent: imported.exponent,
859    };
860    if let Some(alg) = value.get("alg") {
861        let alg = alg
862            .as_str()
863            .ok_or_else(|| io::Error::other("Invalid JWK alg"))?;
864        let expected = SensitiveJwk(key_jwk(&key)?);
865        if expected.0.get("alg").and_then(|v| v.as_str()) != Some(alg) {
866            return Err(io::Error::other("JWK alg does not match the key algorithm"));
867        }
868    }
869    Ok(key)
870}
871
872fn key_material(key: &Key, format: &str) -> io::Result<Vec<u8>> {
873    match format {
874        "raw" if key.key_type == "secret" => Ok(key.bytes.clone()),
875        "raw" | "spki" | "pkcs8" if key.key_type != "secret" => asymmetric::export_key(
876            format,
877            &key.bytes,
878            &key.algorithm,
879            &key.key_type,
880            &key.curve,
881        ),
882        "jwk" => {
883            let jwk = SensitiveJwk(key_jwk(key)?);
884            serde_json::to_vec(&jwk).map_err(io::Error::other)
885        }
886        _ => Err(io::Error::other("Unsupported key format")),
887    }
888}
889
890struct WrapKeyRequest {
891    wrapping: Arc<Key>,
892    target: Arc<Key>,
893    format: String,
894    algorithm: String,
895    hash: String,
896    iv: Vec<u8>,
897    counter: Vec<u8>,
898    additional: Vec<u8>,
899    tag_length: usize,
900}
901
902struct WrappingParams<'a> {
903    algorithm: &'a str,
904    hash: &'a str,
905    iv: &'a [u8],
906    counter: &'a [u8],
907    additional: &'a [u8],
908    tag_length: usize,
909    encrypt: bool,
910}
911
912fn wrapping_transform(key: &Key, params: WrappingParams<'_>, input: &[u8]) -> io::Result<Vec<u8>> {
913    let WrappingParams {
914        algorithm,
915        hash,
916        iv,
917        counter,
918        additional,
919        tag_length,
920        encrypt,
921    } = params;
922    match algorithm {
923        "AES-KW" => {
924            if encrypt {
925                wrap_kw(&key.bytes, input)
926            } else {
927                unwrap_kw(&key.bytes, input)
928            }
929        }
930        "AES-CBC" | "AES-CTR" | "AES-GCM" => {
931            let iv = if algorithm == "AES-CTR" { counter } else { iv };
932            if encrypt {
933                aes_encrypt(&key.bytes, algorithm, iv, input, additional, tag_length)
934            } else {
935                aes_decrypt(&key.bytes, algorithm, iv, input, additional, tag_length)
936            }
937        }
938        "RSA-OAEP" => {
939            if encrypt {
940                asymmetric::rsa_encrypt(algorithm, &key.bytes, hash, iv, input)
941            } else {
942                asymmetric::rsa_decrypt(algorithm, &key.bytes, hash, iv, input)
943            }
944        }
945        _ => Err(io::Error::other("Unsupported wrapping algorithm")),
946    }
947}
948
949fn wrapping_material(key: &Key, format: &str, algorithm: &str) -> io::Result<Zeroizing<Vec<u8>>> {
950    let mut material = Zeroizing::new(key_material(key, format)?);
951    if format == "jwk" && algorithm == "AES-KW" {
952        let padded_length = material.len().div_ceil(8) * 8;
953        material.resize(padded_length, b' ');
954    }
955    Ok(material)
956}
957
958fn wrap_key(request: WrapKeyRequest) -> io::Result<Vec<u8>> {
959    let WrapKeyRequest {
960        wrapping,
961        target,
962        format,
963        algorithm,
964        hash,
965        iv,
966        counter,
967        additional,
968        tag_length,
969    } = request;
970    require(&wrapping, "wrapKey", &wrapping.algorithm)?;
971    if wrapping.algorithm != algorithm {
972        return Err(io::Error::other(
973            "Wrapping algorithm does not match the key",
974        ));
975    }
976    if !target.extractable {
977        return Err(io::Error::other("CryptoKey is not extractable"));
978    }
979    let material = wrapping_material(&target, &format, &algorithm)?;
980    wrapping_transform(
981        &wrapping,
982        WrappingParams {
983            algorithm: &algorithm,
984            hash: &hash,
985            iv: &iv,
986            counter: &counter,
987            additional: &additional,
988            tag_length,
989            encrypt: true,
990        },
991        &material,
992    )
993}
994
995type UnwrapKeyOpArgs = (
996    u64,
997    String,
998    JsBuffer,
999    String,
1000    String,
1001    String,
1002    Vec<String>,
1003    bool,
1004    JsBuffer,
1005    JsBuffer,
1006    JsBuffer,
1007    usize,
1008    usize,
1009);
1010
1011struct UnwrapKeyRequest {
1012    wrapping: Arc<Key>,
1013    format: String,
1014    wrapped: Vec<u8>,
1015    algorithm: String,
1016    hash: String,
1017    curve: String,
1018    usages: Vec<String>,
1019    extractable: bool,
1020    iv: Vec<u8>,
1021    counter: Vec<u8>,
1022    additional: Vec<u8>,
1023    tag_length: usize,
1024    length: usize,
1025}
1026
1027fn import_unwrapped_raw(
1028    store: &KeyStore,
1029    bytes: &[u8],
1030    algorithm: String,
1031    hash: String,
1032    usages: Vec<String>,
1033    extractable: bool,
1034    requested_bits: usize,
1035) -> io::Result<u64> {
1036    let mut raw = Zeroizing::new(bytes.to_vec());
1037    let length = if algorithm == "HMAC" {
1038        truncate_bits(&mut raw, requested_bits)?
1039    } else {
1040        raw.len() * 8
1041    };
1042    insert_key(
1043        store,
1044        Key {
1045            bytes: std::mem::take(&mut *raw),
1046            algorithm,
1047            hash,
1048            usages,
1049            extractable,
1050            key_type: "secret".into(),
1051            length,
1052            curve: String::new(),
1053            public_exponent: Vec::new(),
1054        },
1055    )
1056}
1057
1058fn unwrap_key(store: &KeyStore, request: UnwrapKeyRequest) -> io::Result<u64> {
1059    let UnwrapKeyRequest {
1060        wrapping,
1061        format,
1062        wrapped,
1063        algorithm,
1064        hash,
1065        curve,
1066        usages,
1067        extractable,
1068        iv,
1069        counter,
1070        additional,
1071        tag_length,
1072        length,
1073    } = request;
1074    require(&wrapping, "unwrapKey", &wrapping.algorithm)?;
1075    let bytes = Zeroizing::new(wrapping_transform(
1076        &wrapping,
1077        WrappingParams {
1078            algorithm: &wrapping.algorithm,
1079            hash: &wrapping.hash,
1080            iv: &iv,
1081            counter: &counter,
1082            additional: &additional,
1083            tag_length,
1084            encrypt: false,
1085        },
1086        &wrapped,
1087    )?);
1088    if format == "jwk" {
1089        let jwk: SensitiveJwk =
1090            SensitiveJwk(serde_json::from_slice(&bytes).map_err(io::Error::other)?);
1091        let key = if matches!(
1092            algorithm.as_str(),
1093            "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" | "ECDSA" | "ECDH" | "ED25519" | "X25519"
1094        ) {
1095            import_asymmetric_jwk(jwk, algorithm, hash, curve, usages, extractable)?
1096        } else {
1097            if jwk.0.get("kty").and_then(|value| value.as_str()) != Some("oct") {
1098                return Err(io::Error::other("Invalid wrapped JWK"));
1099            }
1100            key_from_jwk(jwk, algorithm, hash, usages, extractable, length)?
1101        };
1102        return insert_key(store, key);
1103    }
1104    if matches!(format.as_str(), "spki" | "pkcs8" | "raw")
1105        && matches!(
1106            algorithm.as_str(),
1107            "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" | "ECDSA" | "ECDH" | "ED25519" | "X25519"
1108        )
1109    {
1110        let imported = asymmetric::import_key(&format, &bytes, &algorithm, &curve)?;
1111        let key_type = if format == "pkcs8" {
1112            "private"
1113        } else {
1114            "public"
1115        }
1116        .to_owned();
1117        return insert_key(
1118            store,
1119            Key {
1120                bytes: imported.bytes,
1121                algorithm,
1122                hash,
1123                usages,
1124                extractable,
1125                key_type,
1126                length: imported.length,
1127                curve: imported.curve,
1128                public_exponent: imported.exponent,
1129            },
1130        );
1131    }
1132    if format != "raw" {
1133        return Err(io::Error::other("Unsupported key format"));
1134    }
1135    import_unwrapped_raw(store, &bytes, algorithm, hash, usages, extractable, length)
1136}
1137
1138struct CryptoOperation {
1139    operation: String,
1140    key: Arc<Key>,
1141    peer: Option<Arc<Key>>,
1142    hash: String,
1143    iv: Vec<u8>,
1144    counter: Vec<u8>,
1145    additional: Vec<u8>,
1146    tag_length: usize,
1147    iterations: u32,
1148    salt: Vec<u8>,
1149    info: Vec<u8>,
1150    input: Vec<u8>,
1151}
1152
1153fn crypt(
1154    CryptoOperation {
1155        operation,
1156        key: base_key,
1157        peer: peer_key,
1158        hash,
1159        iv,
1160        counter: _counter,
1161        additional,
1162        tag_length,
1163        iterations,
1164        salt,
1165        info,
1166        input,
1167    }: CryptoOperation,
1168) -> io::Result<Vec<u8>> {
1169    match operation.as_str() {
1170        "encrypt" | "decrypt" => {
1171            let usage = &operation;
1172            require(&base_key, usage, &base_key.algorithm)?;
1173            if base_key.algorithm == "RSA-OAEP" {
1174                return if operation == "encrypt" {
1175                    asymmetric::rsa_encrypt(
1176                        &base_key.algorithm,
1177                        &base_key.bytes,
1178                        &base_key.hash,
1179                        &iv,
1180                        &input,
1181                    )
1182                } else {
1183                    asymmetric::rsa_decrypt(
1184                        &base_key.algorithm,
1185                        &base_key.bytes,
1186                        &base_key.hash,
1187                        &iv,
1188                        &input,
1189                    )
1190                };
1191            }
1192            if !base_key.algorithm.starts_with("AES-") {
1193                return Err(io::Error::other("Unsupported encryption key algorithm"));
1194            }
1195            let nonce = if base_key.algorithm == "AES-CTR" {
1196                &_counter
1197            } else {
1198                &iv
1199            };
1200            if operation == "encrypt" {
1201                aes_encrypt(
1202                    &base_key.bytes,
1203                    &base_key.algorithm,
1204                    nonce,
1205                    &input,
1206                    &additional,
1207                    tag_length,
1208                )
1209            } else {
1210                aes_decrypt(
1211                    &base_key.bytes,
1212                    &base_key.algorithm,
1213                    nonce,
1214                    &input,
1215                    &additional,
1216                    tag_length,
1217                )
1218            }
1219        }
1220        "wrapKey" | "unwrapKey" => {
1221            require(&base_key, &operation, &base_key.algorithm)?;
1222            if base_key.algorithm == "RSA-OAEP" {
1223                return if operation == "wrapKey" {
1224                    asymmetric::rsa_encrypt(
1225                        &base_key.algorithm,
1226                        &base_key.bytes,
1227                        &base_key.hash,
1228                        &iv,
1229                        &input,
1230                    )
1231                } else {
1232                    asymmetric::rsa_decrypt(
1233                        &base_key.algorithm,
1234                        &base_key.bytes,
1235                        &base_key.hash,
1236                        &iv,
1237                        &input,
1238                    )
1239                };
1240            }
1241            if base_key.algorithm != "AES-KW" {
1242                return Err(io::Error::other("Unsupported wrapping algorithm"));
1243            }
1244            if operation == "wrapKey" {
1245                wrap_kw(&base_key.bytes, &input)
1246            } else {
1247                unwrap_kw(&base_key.bytes, &input)
1248            }
1249        }
1250        "sign" | "verify" => {
1251            require(&base_key, &operation, &base_key.algorithm)?;
1252            if base_key.algorithm == "HMAC" {
1253                let signature = hmac_sign(&base_key.hash, &base_key.bytes, &input)?;
1254                return if operation == "sign" {
1255                    Ok(signature)
1256                } else if signature.len() == additional.len()
1257                    && bool::from(signature.ct_eq(&additional))
1258                {
1259                    Ok(vec![1])
1260                } else {
1261                    Ok(vec![0])
1262                };
1263            }
1264            let hash = if hash.is_empty() {
1265                base_key.hash.as_str()
1266            } else {
1267                hash.as_str()
1268            };
1269            if operation == "sign" {
1270                if matches!(base_key.algorithm.as_str(), "RSA-PSS" | "RSASSA-PKCS1-V1_5") {
1271                    asymmetric::rsa_sign(
1272                        &base_key.algorithm,
1273                        &base_key.bytes,
1274                        hash,
1275                        tag_length,
1276                        &input,
1277                    )
1278                } else {
1279                    asymmetric::sign(
1280                        &base_key.algorithm,
1281                        &base_key.bytes,
1282                        hash,
1283                        &base_key.curve,
1284                        &input,
1285                    )
1286                }
1287            } else {
1288                let valid =
1289                    if matches!(base_key.algorithm.as_str(), "RSA-PSS" | "RSASSA-PKCS1-V1_5") {
1290                        asymmetric::rsa_verify(
1291                            &base_key.algorithm,
1292                            &base_key.bytes,
1293                            hash,
1294                            tag_length,
1295                            &input,
1296                            &additional,
1297                        )?
1298                    } else {
1299                        asymmetric::verify(
1300                            &base_key.algorithm,
1301                            &base_key.bytes,
1302                            hash,
1303                            &base_key.curve,
1304                            &input,
1305                            &additional,
1306                        )?
1307                    };
1308                Ok(vec![u8::from(valid)])
1309            }
1310        }
1311        "deriveBits" | "deriveKey" => {
1312            require(&base_key, &operation, &base_key.algorithm)?;
1313            if matches!(base_key.algorithm.as_str(), "HKDF" | "PBKDF2")
1314                && !tag_length.is_multiple_of(8)
1315            {
1316                return Err(io::Error::other("Invalid derived bit length"));
1317            }
1318            if base_key.algorithm == "PBKDF2" && iterations == 0 {
1319                return Err(io::Error::other("Invalid PBKDF2 iteration count"));
1320            }
1321            let length = tag_length.div_ceil(8);
1322            let derive_hash = if hash.is_empty() {
1323                base_key.hash.as_str()
1324            } else {
1325                hash.as_str()
1326            };
1327            match base_key.algorithm.as_str() {
1328                "PBKDF2" => {
1329                    let mut output = vec![0; length];
1330                    match derive_hash.to_ascii_uppercase().as_str() {
1331                        "SHA-1" => pbkdf2::pbkdf2_hmac::<sha1::Sha1>(
1332                            &base_key.bytes,
1333                            &salt,
1334                            iterations,
1335                            &mut output,
1336                        ),
1337                        "SHA-256" => pbkdf2::pbkdf2_hmac::<Sha256>(
1338                            &base_key.bytes,
1339                            &salt,
1340                            iterations,
1341                            &mut output,
1342                        ),
1343                        "SHA-384" => pbkdf2::pbkdf2_hmac::<Sha384>(
1344                            &base_key.bytes,
1345                            &salt,
1346                            iterations,
1347                            &mut output,
1348                        ),
1349                        "SHA-512" => pbkdf2::pbkdf2_hmac::<Sha512>(
1350                            &base_key.bytes,
1351                            &salt,
1352                            iterations,
1353                            &mut output,
1354                        ),
1355                        _ => return Err(io::Error::other("Unsupported PBKDF2 hash")),
1356                    }
1357                    if operation == "deriveKey" {
1358                        apply_bit_length(&mut output, tag_length);
1359                    }
1360                    Ok(output)
1361                }
1362                "HKDF" => {
1363                    macro_rules! expand {
1364                        ($digest:ty) => {{
1365                            let hk = hkdf::Hkdf::<$digest>::new(Some(&salt), &base_key.bytes);
1366                            let mut output = vec![0; length];
1367                            hk.expand(&info, &mut output)
1368                                .map_err(|_| io::Error::other("HKDF output length is invalid"))?;
1369                            if operation == "deriveKey" {
1370                                apply_bit_length(&mut output, tag_length);
1371                            }
1372                            Ok(output)
1373                        }};
1374                    }
1375                    match derive_hash.to_ascii_uppercase().as_str() {
1376                        "SHA-1" => expand!(sha1::Sha1),
1377                        "SHA-256" => expand!(Sha256),
1378                        "SHA-384" => expand!(Sha384),
1379                        "SHA-512" => expand!(Sha512),
1380                        _ => Err(io::Error::other("Unsupported HKDF hash")),
1381                    }
1382                }
1383                "ECDH" | "X25519" => {
1384                    let peer = peer_key.ok_or_else(|| io::Error::other("ECDH peer key missing"))?;
1385                    if peer.algorithm != base_key.algorithm
1386                        || peer.key_type != "public"
1387                        || peer.curve != base_key.curve
1388                    {
1389                        return Err(io::Error::other("ECDH peer key does not match"));
1390                    }
1391                    let mut secret = if base_key.algorithm == "ECDH" {
1392                        asymmetric::ecdh(
1393                            &base_key.algorithm,
1394                            &base_key.curve,
1395                            &base_key.bytes,
1396                            &peer.bytes,
1397                        )?
1398                    } else {
1399                        asymmetric::x25519(&base_key.bytes, &peer.bytes)?
1400                    };
1401                    let output_len = tag_length.div_ceil(8);
1402                    if output_len > secret.len() {
1403                        secret.zeroize();
1404                        return Err(io::Error::other(
1405                            "Requested secret length exceeds the shared secret",
1406                        ));
1407                    }
1408                    secret.truncate(output_len);
1409                    apply_bit_length(&mut secret, tag_length);
1410                    Ok(secret)
1411                }
1412                _ => Err(io::Error::other("CryptoKey cannot derive bits")),
1413            }
1414        }
1415        _ => Err(io::Error::other("Crypto operation not yet implemented")),
1416    }
1417}
1418
1419macro_rules! with_store {
1420    ($store:ident, $operation:expr) => {{
1421        let $store = $store.clone();
1422        $operation
1423    }};
1424}
1425
1426fn send_key_result<T>(
1427    sender: futures::channel::oneshot::Sender<io::Result<T>>,
1428    result: io::Result<T>,
1429    store: &KeyStore,
1430    ids: impl FnOnce(&T) -> Vec<u64>,
1431) {
1432    if let Err(Ok(value)) = sender.send(result) {
1433        for id in ids(&value) {
1434            store.close(id);
1435        }
1436    }
1437}
1438
1439fn spawn_crypto(job: impl FnOnce() + Send + 'static) {
1440    crypto_pool().spawn(move || {
1441        let _ = catch_unwind(AssertUnwindSafe(job));
1442    });
1443}
1444
1445fn crypto_pool() -> &'static rayon::ThreadPool {
1446    static POOL: OnceLock<rayon::ThreadPool> = OnceLock::new();
1447    POOL.get_or_init(|| {
1448        rayon::ThreadPoolBuilder::new()
1449            .num_threads(4)
1450            .thread_name(|index| format!("avril:crypto:{index}"))
1451            .build()
1452            .expect("failed to initialize crypto worker pool")
1453    })
1454}
1455
1456pub fn init() -> Extension {
1457    let store = Arc::new(KeyStore::new());
1458    Extension {
1459        name: "ext:crypto",
1460        dependencies: &["ext:web"],
1461        ops: vec![
1462            Op::new("op_crypto_random", |mut bytes: JsBuffer| {
1463                if bytes.len() > 65536 {
1464                    return Err(io::Error::other("Random destination exceeds 65536 bytes"));
1465                }
1466                getrandom::fill(&mut bytes).map_err(io::Error::other)
1467            }),
1468            Op::new("op_crypto_uuid", |(): ()| {
1469                let mut bytes = [0; 16];
1470                getrandom::fill(&mut bytes).map_err(io::Error::other)?;
1471                bytes[6] = (bytes[6] & 0x0f) | 0x40;
1472                bytes[8] = (bytes[8] & 0x3f) | 0x80;
1473                Ok::<_, io::Error>(format!(
1474                    "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
1475                    bytes[0],
1476                    bytes[1],
1477                    bytes[2],
1478                    bytes[3],
1479                    bytes[4],
1480                    bytes[5],
1481                    bytes[6],
1482                    bytes[7],
1483                    bytes[8],
1484                    bytes[9],
1485                    bytes[10],
1486                    bytes[11],
1487                    bytes[12],
1488                    bytes[13],
1489                    bytes[14],
1490                    bytes[15]
1491                ))
1492            }),
1493            with_store!(
1494                store,
1495                Op::new("op_crypto_key_dispose", move |id: u64| {
1496                    store.close(id);
1497                    Ok::<_, io::Error>(())
1498                })
1499            ),
1500            with_store!(
1501                store,
1502                Op::new("op_crypto_key_clone", move |id| store.clone_key(id))
1503            ),
1504            with_store!(
1505                store,
1506                Op::with_scope("op_crypto_key_serialize", move |scope, id| {
1507                    message::serialize_host(scope, store.get(id)?)
1508                })
1509            ),
1510            with_store!(
1511                store,
1512                Op::with_scope("op_crypto_key_deserialize", move |scope, id: u32| {
1513                    store.insert_shared(message::deserialize_host::<Key>(scope, id)?)
1514                })
1515            ),
1516            with_store!(
1517                store,
1518                Op::asynchronous(
1519                    "op_crypto_key_pair_generate",
1520                    move |(algorithm, hash, length, exponent, curve, usages, extractable): (
1521                        String,
1522                        String,
1523                        usize,
1524                        JsBuffer,
1525                        String,
1526                        Vec<String>,
1527                        bool,
1528                    )| {
1529                        let store = store.clone();
1530                        let exponent = exponent.as_ref().to_vec();
1531                        async move {
1532                            let (sender, receiver) = futures::channel::oneshot::channel();
1533                            spawn_crypto(move || {
1534                                let result =
1535                                    asymmetric::generate(&algorithm, length, &exponent, &curve)
1536                                        .and_then(|pair| {
1537                                            insert_key_pair(
1538                                                &store,
1539                                                algorithm,
1540                                                hash,
1541                                                curve,
1542                                                usages,
1543                                                extractable,
1544                                                pair,
1545                                            )
1546                                        });
1547                                send_key_result(sender, result, &store, |pair| {
1548                                    vec![pair.0, pair.1]
1549                                });
1550                            });
1551                            receiver.await.map_err(io::Error::other)?
1552                        }
1553                    },
1554                )
1555            ),
1556            with_store!(
1557                store,
1558                Op::asynchronous(
1559                    "op_crypto_key_generate",
1560                    move |(algorithm, length, hash, usages, extractable): (
1561                        String,
1562                        usize,
1563                        String,
1564                        Vec<String>,
1565                        bool,
1566                    )| {
1567                        let store = store.clone();
1568                        async move {
1569                            let (sender, receiver) = futures::channel::oneshot::channel();
1570                            spawn_crypto(move || {
1571                                let mut bytes = vec![0; length.div_ceil(8)];
1572                                let result = getrandom::fill(&mut bytes)
1573                                    .map_err(io::Error::other)
1574                                    .and_then(|_| {
1575                                        if algorithm == "HMAC" {
1576                                            apply_bit_length(&mut bytes, length);
1577                                        }
1578                                        insert_key(
1579                                            &store,
1580                                            Key {
1581                                                length,
1582                                                bytes,
1583                                                algorithm,
1584                                                hash,
1585                                                usages,
1586                                                extractable,
1587                                                key_type: "secret".into(),
1588                                                curve: String::new(),
1589                                                public_exponent: Vec::new(),
1590                                            },
1591                                        )
1592                                    });
1593                                send_key_result(sender, result, &store, |id| vec![*id]);
1594                            });
1595                            receiver.await.map_err(io::Error::other)?
1596                        }
1597                    },
1598                )
1599            ),
1600            with_store!(
1601                store,
1602                Op::asynchronous(
1603                    "op_crypto_key_import",
1604                    move |(algorithm, bytes, length, hash, usages, extractable): (
1605                        String,
1606                        JsBuffer,
1607                        usize,
1608                        String,
1609                        Vec<String>,
1610                        bool,
1611                    )| {
1612                        let store = store.clone();
1613                        let mut bytes = bytes.as_ref().to_vec();
1614                        async move {
1615                            let length = if algorithm == "HMAC" {
1616                                truncate_bits(&mut bytes, length)?
1617                            } else {
1618                                bytes.len() * 8
1619                            };
1620                            insert_key(
1621                                &store,
1622                                Key {
1623                                    bytes,
1624                                    algorithm,
1625                                    hash,
1626                                    usages,
1627                                    extractable,
1628                                    key_type: "secret".into(),
1629                                    length,
1630                                    curve: String::new(),
1631                                    public_exponent: Vec::new(),
1632                                },
1633                            )
1634                        }
1635                    },
1636                )
1637            ),
1638            with_store!(
1639                store,
1640                Op::asynchronous(
1641                    "op_crypto_key_import_asymmetric",
1642                    move |(format, data, algorithm, hash, curve, usages, extractable): (
1643                        String,
1644                        JsBuffer,
1645                        String,
1646                        String,
1647                        String,
1648                        Vec<String>,
1649                        bool,
1650                    )| {
1651                        let store = store.clone();
1652                        let data = Zeroizing::new(data.as_ref().to_vec());
1653                        let key_type = if format == "pkcs8" {
1654                            "private"
1655                        } else {
1656                            "public"
1657                        }
1658                        .to_owned();
1659                        let public_extractable = extractable;
1660                        async move {
1661                            let (sender, receiver) = futures::channel::oneshot::channel();
1662                            spawn_crypto(move || {
1663                                let result =
1664                                    asymmetric::import_key(&format, &data, &algorithm, &curve)
1665                                        .and_then(|imported| {
1666                                            insert_key(
1667                                                &store,
1668                                                Key {
1669                                                    bytes: imported.bytes,
1670                                                    algorithm,
1671                                                    hash,
1672                                                    usages,
1673                                                    extractable: public_extractable,
1674                                                    key_type,
1675                                                    length: imported.length,
1676                                                    curve: imported.curve,
1677                                                    public_exponent: imported.exponent,
1678                                                },
1679                                            )
1680                                        })
1681                                        .and_then(|id| store.metadata(id));
1682                                send_key_result(sender, result, &store, |value| vec![value.id]);
1683                            });
1684                            receiver.await.map_err(io::Error::other)?
1685                        }
1686                    },
1687                )
1688            ),
1689            with_store!(
1690                store,
1691                Op::asynchronous(
1692                    "op_crypto_key_import_jwk",
1693                    move |(jwk, algorithm, hash, curve, usages, extractable, length): (
1694                        SensitiveJwk,
1695                        String,
1696                        String,
1697                        String,
1698                        Vec<String>,
1699                        bool,
1700                        usize,
1701                    )| {
1702                        let store = store.clone();
1703                        async move {
1704                            let (sender, receiver) = futures::channel::oneshot::channel();
1705                            spawn_crypto(move || {
1706                                let result = if matches!(
1707                                    algorithm.as_str(),
1708                                    "RSA-OAEP"
1709                                        | "RSA-PSS"
1710                                        | "RSASSA-PKCS1-V1_5"
1711                                        | "ECDSA"
1712                                        | "ECDH"
1713                                        | "ED25519"
1714                                        | "X25519"
1715                                ) {
1716                                    import_asymmetric_jwk(
1717                                        jwk,
1718                                        algorithm,
1719                                        hash,
1720                                        curve,
1721                                        usages,
1722                                        extractable,
1723                                    )
1724                                } else {
1725                                    key_from_jwk(jwk, algorithm, hash, usages, extractable, length)
1726                                }
1727                                .and_then(|key| insert_key(&store, key));
1728                                let result = result.and_then(|id| store.metadata(id));
1729                                send_key_result(sender, result, &store, |value| vec![value.id]);
1730                            });
1731                            receiver.await.map_err(io::Error::other)?
1732                        }
1733                    },
1734                )
1735            ),
1736            with_store!(
1737                store,
1738                Op::asynchronous(
1739                    "op_crypto_key_export",
1740                    move |(id, format): (u64, String)| {
1741                        let key = store.get(id);
1742                        async move {
1743                            let key = key?;
1744                            if !key.extractable {
1745                                return Err(io::Error::other("CryptoKey is not extractable"));
1746                            }
1747                            let (sender, receiver) = futures::channel::oneshot::channel();
1748                            spawn_crypto(move || {
1749                                let bytes = if key.key_type == "secret" {
1750                                    if format != "raw" {
1751                                        Err(io::Error::other("Unsupported symmetric key format"))
1752                                    } else {
1753                                        Ok(key.bytes.clone())
1754                                    }
1755                                } else {
1756                                    asymmetric::export_key(
1757                                        &format,
1758                                        &key.bytes,
1759                                        &key.algorithm,
1760                                        &key.key_type,
1761                                        &key.curve,
1762                                    )
1763                                };
1764                                let _ = sender.send(bytes);
1765                            });
1766                            let bytes = receiver.await.map_err(io::Error::other)??;
1767                            Ok::<_, io::Error>(ToJsBuffer::from(bytes))
1768                        }
1769                    },
1770                )
1771            ),
1772            with_store!(
1773                store,
1774                Op::asynchronous("op_crypto_key_export_jwk", move |id: u64| {
1775                    let key = store.get(id);
1776                    async move {
1777                        let key = key?;
1778                        if !key.extractable {
1779                            return Err(io::Error::other("CryptoKey is not extractable"));
1780                        }
1781                        let (sender, receiver) = futures::channel::oneshot::channel();
1782                        spawn_crypto(move || {
1783                            let _ = sender.send(key_jwk(&key).map(SensitiveJwk));
1784                        });
1785                        receiver.await.map_err(io::Error::other)?
1786                    }
1787                })
1788            ),
1789            with_store!(
1790                store,
1791                Op::asynchronous(
1792                    "op_crypto_derive_key",
1793                    move |(
1794                        id,
1795                        hash,
1796                        salt,
1797                        iterations,
1798                        info,
1799                        length,
1800                        algorithm,
1801                        key_hash,
1802                        usages,
1803                        extractable,
1804                        peer_id,
1805                    ): DeriveKeyArgs| {
1806                        let store = store.clone();
1807                        let salt = salt.as_ref().to_vec();
1808                        let info = info.as_ref().to_vec();
1809                        let base_key = store.get(id);
1810                        let peer_key = if peer_id == 0 {
1811                            Ok(None)
1812                        } else {
1813                            store.get(peer_id).map(Some)
1814                        };
1815                        async move {
1816                            let base_key = base_key?;
1817                            let peer_key = peer_key?;
1818                            let (sender, receiver) = futures::channel::oneshot::channel();
1819                            spawn_crypto(move || {
1820                                let result = crypt(CryptoOperation {
1821                                    operation: "deriveKey".into(),
1822                                    key: base_key,
1823                                    peer: peer_key,
1824                                    hash,
1825                                    iv: Vec::new(),
1826                                    counter: Vec::new(),
1827                                    additional: Vec::new(),
1828                                    tag_length: length,
1829                                    iterations,
1830                                    salt,
1831                                    info,
1832                                    input: Vec::new(),
1833                                })
1834                                .and_then(|bytes| {
1835                                    insert_key(
1836                                        &store,
1837                                        Key {
1838                                            bytes,
1839                                            algorithm,
1840                                            hash: key_hash,
1841                                            usages,
1842                                            extractable,
1843                                            key_type: "secret".into(),
1844                                            length,
1845                                            curve: String::new(),
1846                                            public_exponent: Vec::new(),
1847                                        },
1848                                    )
1849                                });
1850                                send_key_result(sender, result, &store, |id| vec![*id]);
1851                            });
1852                            receiver.await.map_err(io::Error::other)?
1853                        }
1854                    },
1855                )
1856            ),
1857            with_store!(
1858                store,
1859                Op::asynchronous(
1860                    "op_crypto_wrap_key",
1861                    move |(
1862                        wrapping_id,
1863                        target_id,
1864                        format,
1865                        algorithm,
1866                        hash,
1867                        iv,
1868                        counter,
1869                        additional,
1870                        tag_length,
1871                    ): (
1872                        u64,
1873                        u64,
1874                        String,
1875                        String,
1876                        String,
1877                        JsBuffer,
1878                        JsBuffer,
1879                        JsBuffer,
1880                        usize,
1881                    )| {
1882                        let iv = iv.as_ref().to_vec();
1883                        let counter = counter.as_ref().to_vec();
1884                        let additional = additional.as_ref().to_vec();
1885                        let wrapping = store.get(wrapping_id);
1886                        let target = store.get(target_id);
1887                        async move {
1888                            let wrapping = wrapping?;
1889                            let target = target?;
1890                            let (sender, receiver) = futures::channel::oneshot::channel();
1891                            spawn_crypto(move || {
1892                                let _ = sender.send(wrap_key(WrapKeyRequest {
1893                                    wrapping,
1894                                    target,
1895                                    format,
1896                                    algorithm,
1897                                    hash,
1898                                    iv,
1899                                    counter,
1900                                    additional,
1901                                    tag_length,
1902                                }));
1903                            });
1904                            let bytes = receiver.await.map_err(io::Error::other)??;
1905                            Ok::<_, io::Error>(ToJsBuffer::from(bytes))
1906                        }
1907                    },
1908                )
1909            ),
1910            with_store!(
1911                store,
1912                Op::asynchronous(
1913                    "op_crypto_unwrap_key",
1914                    move |(
1915                        wrapping_id,
1916                        format,
1917                        wrapped,
1918                        algorithm,
1919                        hash,
1920                        curve,
1921                        usages,
1922                        extractable,
1923                        iv,
1924                        counter,
1925                        additional,
1926                        tag_length,
1927                        length,
1928                    ): UnwrapKeyOpArgs| {
1929                        let store = store.clone();
1930                        let wrapped = wrapped.as_ref().to_vec();
1931                        let iv = iv.as_ref().to_vec();
1932                        let counter = counter.as_ref().to_vec();
1933                        let additional = additional.as_ref().to_vec();
1934                        let wrapping = store.get(wrapping_id);
1935                        async move {
1936                            let wrapping = wrapping?;
1937                            let (sender, receiver) = futures::channel::oneshot::channel();
1938                            spawn_crypto(move || {
1939                                let result = unwrap_key(
1940                                    &store,
1941                                    UnwrapKeyRequest {
1942                                        wrapping,
1943                                        format,
1944                                        wrapped,
1945                                        algorithm,
1946                                        hash,
1947                                        curve,
1948                                        usages,
1949                                        extractable,
1950                                        iv,
1951                                        counter,
1952                                        additional,
1953                                        tag_length,
1954                                        length,
1955                                    },
1956                                )
1957                                .and_then(|id| store.metadata(id));
1958                                send_key_result(sender, result, &store, |value| vec![value.id]);
1959                            });
1960                            receiver.await.map_err(io::Error::other)?
1961                        }
1962                    },
1963                )
1964            ),
1965            with_store!(
1966                store,
1967                Op::asynchronous(
1968                    "op_crypto_run",
1969                    move |(
1970                        operation,
1971                        id,
1972                        hash,
1973                        iv,
1974                        counter,
1975                        additional,
1976                        tag_length,
1977                        iterations,
1978                        salt,
1979                        info,
1980                        input,
1981                        peer_id,
1982                    ): (
1983                        String,
1984                        u64,
1985                        String,
1986                        JsBuffer,
1987                        JsBuffer,
1988                        JsBuffer,
1989                        usize,
1990                        u32,
1991                        JsBuffer,
1992                        JsBuffer,
1993                        JsBuffer,
1994                        u64,
1995                    )| {
1996                        let input = input.as_ref().to_vec();
1997                        let iv = iv.as_ref().to_vec();
1998                        let counter = counter.as_ref().to_vec();
1999                        let additional = additional.as_ref().to_vec();
2000                        let salt = salt.as_ref().to_vec();
2001                        let info = info.as_ref().to_vec();
2002                        let key = store.get(id);
2003                        let peer = match &key {
2004                            Ok(key)
2005                                if operation == "deriveBits"
2006                                    && matches!(key.algorithm.as_str(), "ECDH" | "X25519") =>
2007                            {
2008                                store.get(peer_id).map(Some)
2009                            }
2010                            _ => Ok(None),
2011                        };
2012                        async move {
2013                            let key = key?;
2014                            let peer = peer?;
2015                            let (sender, receiver) = futures::channel::oneshot::channel();
2016                            spawn_crypto(move || {
2017                                let result = crypt(CryptoOperation {
2018                                    operation,
2019                                    key,
2020                                    peer,
2021                                    hash,
2022                                    iv,
2023                                    counter,
2024                                    additional,
2025                                    tag_length,
2026                                    iterations,
2027                                    salt,
2028                                    info,
2029                                    input,
2030                                });
2031                                let _ = sender.send(result);
2032                            });
2033                            let bytes = receiver.await.map_err(io::Error::other)??;
2034                            Ok::<_, io::Error>(ToJsBuffer::from(bytes))
2035                        }
2036                    },
2037                )
2038            ),
2039            Op::asynchronous("op_crypto_digest", |(name, input): (String, JsBuffer)| {
2040                let name = name.to_ascii_uppercase();
2041                let input = input.as_ref().to_vec();
2042                async move {
2043                    let (sender, receiver) = futures::channel::oneshot::channel();
2044                    spawn_crypto(move || {
2045                        let result = digest(&name, &input);
2046                        let _ = sender.send(result);
2047                    });
2048                    let output = receiver.await.map_err(io::Error::other)??;
2049                    Ok::<_, io::Error>(ToJsBuffer::from(output))
2050                }
2051            }),
2052        ],
2053        modules: &[(
2054            "ext:crypto/main.js",
2055            include_str!(concat!(env!("OUT_DIR"), "/main.js")),
2056        )],
2057        entry_point: Some("ext:crypto/main.js"),
2058    }
2059}
2060
2061#[cfg(test)]
2062mod tests {
2063    use super::*;
2064
2065    fn test_key(bytes: Vec<u8>) -> Key {
2066        Key {
2067            bytes,
2068            algorithm: "AES-GCM".into(),
2069            hash: String::new(),
2070            usages: vec!["encrypt".into()],
2071            extractable: false,
2072            key_type: "secret".into(),
2073            length: 128,
2074            curve: String::new(),
2075            public_exponent: Vec::new(),
2076        }
2077    }
2078
2079    #[test]
2080    fn dropping_a_runtime_store_releases_its_native_keys() {
2081        let store = Arc::new(KeyStore::new());
2082        let id = store.insert(test_key(vec![7; 16])).unwrap();
2083        let key = store.get(id).unwrap();
2084        let weak = Arc::downgrade(&key);
2085        drop(key);
2086        drop(store);
2087        assert!(weak.upgrade().is_none());
2088    }
2089
2090    #[test]
2091    fn stores_are_isolated_and_handles_are_safe_integers() {
2092        let first = KeyStore::new();
2093        let second = KeyStore::new();
2094        let first_id = first.insert(test_key(vec![1; 16])).unwrap();
2095        let second_id = second.insert(test_key(vec![2; 16])).unwrap();
2096        assert_eq!(first_id, second_id);
2097        assert_eq!(first.get(first_id).unwrap().bytes, vec![1; 16]);
2098        assert_eq!(second.get(second_id).unwrap().bytes, vec![2; 16]);
2099        first
2100            .next_id
2101            .store(9_007_199_254_740_991, Ordering::Relaxed);
2102        assert!(first.next_id().is_err());
2103    }
2104
2105    #[test]
2106    fn imported_public_keys_preserve_nonextractability() {
2107        let store = KeyStore::new();
2108        let mut key = test_key(vec![4; 32]);
2109        key.algorithm = "ED25519".into();
2110        key.usages = vec!["verify".into()];
2111        key.key_type = "public".into();
2112        key.length = 256;
2113        key.extractable = false;
2114        let id = insert_key(&store, key).unwrap();
2115        assert!(!store.get(id).unwrap().extractable);
2116    }
2117
2118    #[test]
2119    fn invalid_secret_metadata_is_rejected_natively() {
2120        let store = KeyStore::new();
2121        let mut key = test_key(vec![4; 16]);
2122        key.usages.clear();
2123        assert!(insert_key(&store, key).is_err());
2124        let mut key = test_key(vec![4; 16]);
2125        key.algorithm = "HKDF".into();
2126        key.usages = vec!["deriveBits".into()];
2127        key.extractable = true;
2128        assert!(insert_key(&store, key).is_err());
2129    }
2130
2131    #[test]
2132    fn native_gcm_accepts_only_webcrypto_tag_lengths() {
2133        for bits in [32, 64, 96, 104, 112, 120, 128] {
2134            let encrypted = aes_gcm_encrypt(&[0; 16], &[0; 12], &[], &[], bits).unwrap();
2135            assert!(
2136                aes_gcm_decrypt(&[0; 16], &[0; 12], &encrypted, &[], bits)
2137                    .unwrap()
2138                    .is_empty()
2139            );
2140        }
2141        for bits in [0, 31, 33, 48, 129] {
2142            assert!(aes_gcm_encrypt(&[0; 16], &[0; 12], &[], &[], bits).is_err());
2143            assert!(aes_gcm_decrypt(&[0; 16], &[0; 12], &[], &[], bits).is_err());
2144        }
2145    }
2146
2147    #[test]
2148    fn raw_hmac_unwrap_enforces_and_applies_requested_bits() {
2149        let store = KeyStore::new();
2150        let id = import_unwrapped_raw(
2151            &store,
2152            &[0xff, 0xff],
2153            "HMAC".into(),
2154            "SHA-256".into(),
2155            vec!["sign".into()],
2156            false,
2157            9,
2158        )
2159        .unwrap();
2160        let key = store.get(id).unwrap();
2161        assert_eq!(key.length, 9);
2162        assert_eq!(key.bytes, [0xff, 0x80]);
2163        assert!(
2164            import_unwrapped_raw(
2165                &store,
2166                &[0xff, 0xff],
2167                "HMAC".into(),
2168                "SHA-256".into(),
2169                vec!["sign".into()],
2170                false,
2171                8
2172            )
2173            .is_err()
2174        );
2175        assert!(
2176            import_unwrapped_raw(
2177                &store,
2178                &[0xff, 0xff],
2179                "HMAC".into(),
2180                "SHA-256".into(),
2181                vec!["sign".into()],
2182                false,
2183                17
2184            )
2185            .is_err()
2186        );
2187    }
2188
2189    #[test]
2190    fn jwk_padding_is_only_applied_for_aes_kw() {
2191        let mut key = test_key(vec![0x31; 16]);
2192        key.algorithm = "HMAC".into();
2193        key.hash = "SHA-256".into();
2194        key.extractable = true;
2195        key.length = 128;
2196        let raw = key_material(&key, "jwk").unwrap();
2197        assert_eq!(raw.last(), Some(&b'}'));
2198        let kw = wrapping_material(&key, "jwk", "AES-KW").unwrap();
2199        assert_eq!(kw.len() % 8, 0);
2200        assert!(serde_json::from_slice::<serde_json::Value>(&kw).is_ok());
2201        let gcm = wrapping_material(&key, "jwk", "AES-GCM").unwrap();
2202        assert_eq!(&*gcm, &raw);
2203    }
2204
2205    #[test]
2206    fn worker_panics_do_not_poison_the_crypto_pool() {
2207        let (sender, receiver) = std::sync::mpsc::channel();
2208        spawn_crypto(|| panic!("worker panic regression"));
2209        spawn_crypto(move || {
2210            let _ = sender.send(digest("SHA-256", b"abc"));
2211        });
2212        assert_eq!(
2213            receiver
2214                .recv_timeout(std::time::Duration::from_secs(2))
2215                .unwrap()
2216                .unwrap(),
2217            Sha256::digest(b"abc").to_vec()
2218        );
2219    }
2220
2221    #[test]
2222    fn gcm_ext_iv_matches_nist_vector() {
2223        // From the NIST CAVS `gcmEncryptExtIV128.rsp` vectors (1-byte IV).
2224        let key = [
2225            0x15, 0xb2, 0xd4, 0x14, 0x82, 0x64, 0x53, 0xf9, 0xe1, 0xc7, 0xdd, 0x0b, 0x69, 0xd8,
2226            0xd1, 0xeb,
2227        ];
2228        let iv = [0xb6];
2229        let plaintext = [
2230            0x8c, 0xfa, 0x25, 0x55, 0x30, 0xc6, 0xfb, 0xc1, 0x9d, 0x51, 0xbd, 0x4a, 0xeb, 0x39,
2231            0xc9, 0x1b,
2232        ];
2233        let ciphertext = aes_gcm_encrypt(&key, &iv, &plaintext, &[], 128).unwrap();
2234        assert_eq!(
2235            ciphertext,
2236            [
2237                0x48, 0x22, 0xcb, 0x98, 0xbd, 0x5f, 0x5d, 0x92, 0x1e, 0xe1, 0x92, 0x85, 0xc9, 0x03,
2238                0x23, 0x75, 0x8a, 0x40, 0x67, 0x0e, 0xba, 0xc9, 0x8c, 0xf4, 0xe9, 0xcc, 0x1b, 0xf8,
2239                0xf8, 0x03, 0x16, 0x7d,
2240            ]
2241        );
2242        assert_eq!(
2243            aes_gcm_decrypt(&key, &iv, &ciphertext, &[], 128).unwrap(),
2244            plaintext
2245        );
2246    }
2247
2248    #[test]
2249    fn long_gcm_ivs_round_trip() {
2250        let iv = [7; 65];
2251        let ciphertext = aes_gcm_encrypt(&[0; 16], &iv, b"abc", &[], 128).unwrap();
2252        assert_eq!(
2253            aes_gcm_decrypt(&[0; 16], &iv, &ciphertext, &[], 128).unwrap(),
2254            b"abc"
2255        );
2256    }
2257
2258    #[test]
2259    fn pinned_operations_survive_finalizer_disposal_until_finished() {
2260        let store = KeyStore::new();
2261        let id = store.insert(test_key(vec![3; 16])).unwrap();
2262        let pinned = store.get(id).unwrap();
2263        let weak = Arc::downgrade(&pinned);
2264        store.close(id);
2265        assert!(weak.upgrade().is_some());
2266        drop(pinned);
2267        assert!(weak.upgrade().is_none());
2268    }
2269}