Skip to main content

jwk_simple/integrations/
web_crypto.rs

1//! WebCrypto integration for browser/WASM environments.
2//!
3//! This module provides conversions from jwk-simple [`Key`] types to
4//! [`web_sys::JsonWebKey`] for use with the browser's SubtleCrypto API,
5//! as well as helper functions for importing keys as [`web_sys::CryptoKey`].
6//!
7//! # Supported Key Types
8//!
9//! | Key Type | Curve/Algorithm | WebCrypto Support |
10//! |----------|-----------------|-------------------|
11//! | RSA | RS256/RS384/RS512 | Yes (RSASSA-PKCS1-v1_5) |
12//! | RSA | PS256/PS384/PS512 | Yes (RSA-PSS) |
13//! | EC | P-256 | Yes (ECDSA) |
14//! | EC | P-384 | Yes (ECDSA) |
15//! | EC | P-521 | Yes (ECDSA) |
16//! | EC | secp256k1 | **No** |
17//! | OKP | Ed25519/Ed448 | **No** |
18//! | OKP | X25519/X448 | **No** |
19//! | Symmetric | HMAC | Yes |
20//! | Symmetric | AES-GCM, AES-KW | Yes |
21//!
22//! # Examples
23//!
24//! ## Converting a Key to JsonWebKey
25//!
26//! ```ignore
27//! use jwk_simple::Key;
28//! use std::convert::TryInto;
29//!
30//! let key: Key = serde_json::from_str(jwk_json)?;
31//! let web_jwk: web_sys::JsonWebKey = (&key).try_into()?;
32//! ```
33//!
34//! ## Importing a Key for Signature Verification
35//!
36//! ```ignore
37//! use jwk_simple::{Key, web_crypto};
38//! use jwk_simple::Algorithm;
39//!
40//! let key: Key = serde_json::from_str(jwk_json)?;
41//! let crypto_key = web_crypto::import_verify_key_for_alg(&key, &Algorithm::Rs256).await?;
42//!
43//! // Use with SubtleCrypto.verify()
44//! let subtle = web_crypto::get_subtle_crypto()?;
45//! // ... perform verification
46//! ```
47//!
48//! # Limitations
49//!
50//! WebCrypto does not support:
51//! - **OKP keys** (Ed25519, Ed448, X25519, X448) - These use Edwards/Montgomery curves
52//!   which are not part of the WebCrypto specification.
53//! - **secp256k1 curve** - While popular in cryptocurrency applications, this curve
54//!   is not supported by WebCrypto.
55//!
56//! Attempting to convert these key types will return an
57//! [`Error::UnsupportedForWebCrypto`] error.
58
59use js_sys::{Array, Object, Reflect};
60use std::convert::TryFrom;
61use wasm_bindgen::prelude::*;
62use wasm_bindgen_futures::JsFuture;
63#[cfg(all(feature = "cloudflare", target_arch = "wasm32"))]
64use web_sys::Crypto;
65use web_sys::{CryptoKey, SubtleCrypto};
66
67use crate::error::{Error, Result};
68use crate::jwk::{Algorithm, EcCurve, Key, KeyOperation, KeyParams};
69#[cfg(test)]
70use crate::jwks::KeyMatcher;
71
72// ============================================================================
73// SubtleCrypto Access
74// ============================================================================
75
76/// Gets the SubtleCrypto interface from the current environment.
77///
78/// This function works in both browser (Window) and Web Worker contexts.
79///
80/// # Errors
81///
82/// Returns an error if the crypto API is not available in the current context.
83///
84/// # Examples
85///
86/// ```ignore
87/// let subtle = web_crypto::get_subtle_crypto()?;
88/// ```
89pub fn get_subtle_crypto() -> Result<SubtleCrypto> {
90    #[cfg(all(feature = "cloudflare", target_arch = "wasm32"))]
91    {
92        // Cloudflare does expose `crypto` in the global scope but
93        // the global scope may not be able to cast into `WorkerGlobalScope`.
94        let global = js_sys::global();
95        let crypto_field_name = JsValue::from_str("crypto");
96        if let Ok(crypto_field) = Reflect::get(&global, &crypto_field_name)
97            && let Ok(crypto) = crypto_field.dyn_into::<Crypto>()
98        {
99            return Ok(crypto.subtle());
100        }
101    }
102
103    // Try window first (browser context)
104    if let Some(window) = web_sys::window()
105        && let Ok(crypto) = window.crypto()
106    {
107        return Ok(crypto.subtle());
108    }
109
110    // Try WorkerGlobalScope (Web Worker context)
111    let global = js_sys::global();
112    if let Ok(worker_scope) = global.dyn_into::<web_sys::WorkerGlobalScope>()
113        && let Ok(crypto) = worker_scope.crypto()
114    {
115        return Ok(crypto.subtle());
116    }
117
118    Err(Error::WebCrypto(
119        "crypto API not available in this context".to_string(),
120    ))
121}
122
123// ============================================================================
124// Key to JsonWebKey Conversion
125// ============================================================================
126
127/// Conversion from [`Key`] to [`web_sys::JsonWebKey`] for WebCrypto usage.
128///
129/// # Supported Key Types
130///
131/// - **RSA**: All RSA keys are supported
132/// - **EC**: P-256, P-384, P-521 curves are supported; secp256k1 is NOT supported
133/// - **Symmetric**: All symmetric keys are supported
134/// - **OKP**: NOT supported (Ed25519, Ed448, X25519, X448)
135///
136/// # Errors
137///
138/// Returns [`Error::UnsupportedForWebCrypto`] if the key type or curve is not
139/// supported by WebCrypto.
140///
141/// # Examples
142///
143/// ```ignore
144/// use jwk_simple::Key;
145/// use std::convert::TryInto;
146///
147/// let key: Key = serde_json::from_str(r#"{"kty":"RSA","n":"...","e":"AQAB"}"#)?;
148/// let jwk: web_sys::JsonWebKey = (&key).try_into()?;
149/// assert_eq!(jwk.get_kty(), "RSA");
150/// ```
151impl TryFrom<&Key> for web_sys::JsonWebKey {
152    type Error = Error;
153
154    fn try_from(key: &Key) -> Result<Self> {
155        // Keep conversion-level validation focused on key material shape.
156        // Full JWK metadata validation (including `use`/`key_ops`/x509 checks)
157        // is context-dependent and should be performed by callers that need it.
158        // This also avoids enforcing `key.alg` in explicit-alg import flows.
159        key.params().validate()?;
160
161        // Validate that the key type is supported
162        validate_webcrypto_support(key)?;
163
164        let jwk = web_sys::JsonWebKey::new(key.kty().as_str());
165
166        // Set common optional fields
167        // Note: `kid` is not part of the WebCrypto JsonWebKey dictionary,
168        // so it is not set here.
169
170        if let Some(alg) = key.alg() {
171            jwk.set_alg(alg.as_str());
172        }
173
174        if let Some(key_use) = key.key_use() {
175            jwk.set_use(key_use.as_str());
176        }
177
178        if let Some(key_ops) = key.key_ops() {
179            let ops = Array::new();
180            for op in key_ops {
181                ops.push(&JsValue::from_str(op.as_str()));
182            }
183            jwk.set_key_ops(&ops);
184        }
185
186        // Set type-specific parameters
187        match key.params() {
188            KeyParams::Rsa(params) => {
189                // Public key components (always present)
190                jwk.set_n(&params.n.to_base64url());
191                jwk.set_e(&params.e.to_base64url());
192
193                // Private key components (optional)
194                if let Some(d) = &params.d {
195                    jwk.set_d(&d.to_base64url());
196                }
197                if let Some(p) = &params.p {
198                    jwk.set_p(&p.to_base64url());
199                }
200                if let Some(q) = &params.q {
201                    jwk.set_q(&q.to_base64url());
202                }
203                if let Some(dp) = &params.dp {
204                    jwk.set_dp(&dp.to_base64url());
205                }
206                if let Some(dq) = &params.dq {
207                    jwk.set_dq(&dq.to_base64url());
208                }
209                if let Some(qi) = &params.qi {
210                    jwk.set_qi(&qi.to_base64url());
211                }
212                // Note: 'oth' (other primes) is not supported by web_sys::JsonWebKey
213            }
214            KeyParams::Ec(params) => {
215                jwk.set_crv(params.crv.as_str());
216                jwk.set_x(&params.x.to_base64url());
217                jwk.set_y(&params.y.to_base64url());
218
219                if let Some(d) = &params.d {
220                    jwk.set_d(&d.to_base64url());
221                }
222            }
223            KeyParams::Symmetric(params) => {
224                jwk.set_k(&params.k.to_base64url());
225            }
226            KeyParams::Okp(_) => {
227                // This should never be reached due to validate_webcrypto_support
228                return Err(Error::UnsupportedForWebCrypto {
229                    reason: "OKP keys (Ed25519, Ed448, X25519, X448) are not supported by WebCrypto",
230                });
231            }
232        }
233
234        Ok(jwk)
235    }
236}
237
238/// Validates that a key is supported by WebCrypto.
239fn validate_webcrypto_support(key: &Key) -> Result<()> {
240    match key.params() {
241        KeyParams::Okp(_) => Err(Error::UnsupportedForWebCrypto {
242            reason: "OKP keys (Ed25519, Ed448, X25519, X448) are not supported by WebCrypto",
243        }),
244        KeyParams::Ec(params) => {
245            if params.crv == EcCurve::Secp256k1 {
246                Err(Error::UnsupportedForWebCrypto {
247                    reason: "secp256k1 curve is not supported by WebCrypto",
248                })
249            } else {
250                Ok(())
251            }
252        }
253        KeyParams::Rsa(_) | KeyParams::Symmetric(_) => Ok(()),
254    }
255}
256
257// ============================================================================
258// Algorithm Object Builders
259// ============================================================================
260
261/// Builds a WebCrypto algorithm object for the given key.
262///
263/// The algorithm object is used with `SubtleCrypto.importKey()`.
264fn build_algorithm_object(key: &Key, usage: KeyUsage) -> Result<Object> {
265    build_algorithm_object_with_alg(key, usage, None)
266}
267
268fn build_algorithm_object_for_alg(key: &Key, alg: &Algorithm, usage: KeyUsage) -> Result<Object> {
269    build_algorithm_object_with_alg(key, usage, Some(alg))
270}
271
272fn build_algorithm_object_with_alg(
273    key: &Key,
274    usage: KeyUsage,
275    alg_override: Option<&Algorithm>,
276) -> Result<Object> {
277    match key.params() {
278        KeyParams::Rsa(_) => build_rsa_algorithm(key, usage, alg_override),
279        KeyParams::Ec(params) => {
280            // When an explicit algorithm is provided, validate that it is
281            // compatible with the key's curve before building the import
282            // algorithm object. This catches mismatches like ES384 with a
283            // P-256 key early, instead of letting them surface as opaque
284            // WebCrypto errors during verify/sign.
285            if let Some(alg) = alg_override {
286                let expected_curve = match alg {
287                    Algorithm::Es256 => Some(EcCurve::P256),
288                    Algorithm::Es384 => Some(EcCurve::P384),
289                    Algorithm::Es512 => Some(EcCurve::P521),
290                    _ => None,
291                };
292                match expected_curve {
293                    Some(curve) if curve != params.crv => {
294                        return Err(Error::WebCrypto(format!(
295                            "algorithm {} requires curve {}, but the key uses {}",
296                            alg.as_str(),
297                            curve.as_str(),
298                            params.crv.as_str(),
299                        )));
300                    }
301                    None => {
302                        return Err(Error::WebCrypto(format!(
303                            "algorithm {} is not supported for EC key import in WebCrypto",
304                            alg.as_str(),
305                        )));
306                    }
307                    _ => {} // curve matches, proceed
308                }
309            }
310            build_ec_algorithm(params.crv, usage)
311        }
312        KeyParams::Symmetric(_) => build_symmetric_algorithm(key, usage, alg_override),
313        KeyParams::Okp(_) => Err(Error::UnsupportedForWebCrypto {
314            reason: "OKP keys are not supported by WebCrypto",
315        }),
316    }
317}
318
319fn validate_usage_algorithm_compatibility(usage: KeyUsage, alg: &Algorithm) -> Result<()> {
320    let allowed = match usage {
321        KeyUsage::Verify => matches!(
322            alg,
323            Algorithm::Rs256
324                | Algorithm::Rs384
325                | Algorithm::Rs512
326                | Algorithm::Ps256
327                | Algorithm::Ps384
328                | Algorithm::Ps512
329                | Algorithm::Es256
330                | Algorithm::Es384
331                | Algorithm::Es512
332                | Algorithm::Hs256
333                | Algorithm::Hs384
334                | Algorithm::Hs512
335        ),
336        KeyUsage::Sign => matches!(
337            alg,
338            Algorithm::Rs256
339                | Algorithm::Rs384
340                | Algorithm::Rs512
341                | Algorithm::Ps256
342                | Algorithm::Ps384
343                | Algorithm::Ps512
344                | Algorithm::Es256
345                | Algorithm::Es384
346                | Algorithm::Es512
347                | Algorithm::Hs256
348                | Algorithm::Hs384
349                | Algorithm::Hs512
350        ),
351        KeyUsage::Encrypt => matches!(
352            alg,
353            Algorithm::RsaOaep
354                | Algorithm::RsaOaep256
355                | Algorithm::RsaOaep384
356                | Algorithm::RsaOaep512
357                | Algorithm::A128gcm
358                | Algorithm::A192gcm
359                | Algorithm::A256gcm
360        ),
361        KeyUsage::Decrypt => matches!(
362            alg,
363            Algorithm::RsaOaep
364                | Algorithm::RsaOaep256
365                | Algorithm::RsaOaep384
366                | Algorithm::RsaOaep512
367                | Algorithm::A128gcm
368                | Algorithm::A192gcm
369                | Algorithm::A256gcm
370        ),
371        KeyUsage::WrapKey => matches!(
372            alg,
373            Algorithm::RsaOaep
374                | Algorithm::RsaOaep256
375                | Algorithm::RsaOaep384
376                | Algorithm::RsaOaep512
377                | Algorithm::A128kw
378                | Algorithm::A192kw
379                | Algorithm::A256kw
380        ),
381        KeyUsage::UnwrapKey => matches!(
382            alg,
383            Algorithm::RsaOaep
384                | Algorithm::RsaOaep256
385                | Algorithm::RsaOaep384
386                | Algorithm::RsaOaep512
387                | Algorithm::A128kw
388                | Algorithm::A192kw
389                | Algorithm::A256kw
390        ),
391    };
392
393    if allowed {
394        Ok(())
395    } else {
396        Err(Error::UnsupportedForWebCrypto {
397            reason: "algorithm is not compatible with requested key usage",
398        })
399    }
400}
401
402fn validate_key_for_webcrypto_usage_with_alg(
403    key: &Key,
404    usage: KeyUsage,
405    alg: &Algorithm,
406) -> Result<()> {
407    validate_usage_algorithm_compatibility(usage, alg)?;
408    // Use the override variant: the caller explicitly provides the algorithm,
409    // so we must not reject keys whose declared `alg` differs from the
410    // requested one.
411    key.validate_for_use_with_alg_override(alg, [key_operation_for_usage(usage)])
412}
413
414fn validate_key_for_webcrypto_usage(key: &Key, usage: KeyUsage) -> Result<()> {
415    let requested_op = key_operation_for_usage(usage);
416
417    if let Some(alg) = key.alg() {
418        validate_usage_algorithm_compatibility(usage, alg)?;
419        key.validate_for_use(alg, [requested_op])?;
420        return Ok(());
421    }
422
423    // No algorithm on key: structural validation + operation intent only.
424    // `validate()` already enforced `use`/`key_ops` consistency and uniqueness,
425    // so we call the intent-only helper directly.
426    key.validate()?;
427    key.check_operation_capability(std::slice::from_ref(&requested_op))?;
428    key.validate_operation_intent_for_all(std::slice::from_ref(&requested_op))?;
429
430    Ok(())
431}
432
433fn key_operation_for_usage(usage: KeyUsage) -> KeyOperation {
434    match usage {
435        KeyUsage::Sign => KeyOperation::Sign,
436        KeyUsage::Verify => KeyOperation::Verify,
437        KeyUsage::Encrypt => KeyOperation::Encrypt,
438        KeyUsage::Decrypt => KeyOperation::Decrypt,
439        KeyUsage::WrapKey => KeyOperation::WrapKey,
440        KeyUsage::UnwrapKey => KeyOperation::UnwrapKey,
441    }
442}
443
444/// Key usage category for determining the appropriate algorithm.
445///
446/// This is used by the low-level [`import_key_for_usage`] and
447/// [`import_key_for_usage_with_alg`] functions to select the correct
448/// WebCrypto algorithm parameters at import time.
449#[derive(Debug, Clone, Copy, PartialEq, Eq)]
450#[non_exhaustive]
451pub enum KeyUsage {
452    /// The key will be used for signing.
453    Sign,
454    /// The key will be used for signature verification.
455    Verify,
456    /// The key will be used for encryption.
457    Encrypt,
458    /// The key will be used for decryption.
459    Decrypt,
460    /// The key will be used for wrapping other keys.
461    WrapKey,
462    /// The key will be used for unwrapping other keys.
463    UnwrapKey,
464}
465
466fn usage_strings_for_usage(usage: KeyUsage) -> &'static [&'static str] {
467    match usage {
468        KeyUsage::Sign => &["sign"],
469        KeyUsage::Verify => &["verify"],
470        KeyUsage::Encrypt => &["encrypt"],
471        KeyUsage::Decrypt => &["decrypt"],
472        KeyUsage::WrapKey => &["wrapKey"],
473        KeyUsage::UnwrapKey => &["unwrapKey"],
474    }
475}
476
477/// Builds an RSA algorithm object.
478///
479/// The algorithm is determined from (in order of priority):
480/// 1. The `alg_override` parameter (if provided)
481/// 2. The key's `alg` field (if present)
482///
483/// If neither is available, an error is returned because WebCrypto requires
484/// the hash algorithm to be specified at import time and a wrong default
485/// (e.g., SHA-256 for a key intended for RS384) would cause silent
486/// verification failures.
487fn build_rsa_algorithm(
488    key: &Key,
489    _usage: KeyUsage,
490    alg_override: Option<&Algorithm>,
491) -> Result<Object> {
492    let obj = Object::new();
493
494    // Use the override first, then fall back to the key's own algorithm
495    let effective_alg = alg_override.or(key.alg());
496
497    // Determine algorithm name and hash based on the effective algorithm
498    let (alg_name, hash) = match effective_alg {
499        Some(Algorithm::Rs256) => ("RSASSA-PKCS1-v1_5", "SHA-256"),
500        Some(Algorithm::Rs384) => ("RSASSA-PKCS1-v1_5", "SHA-384"),
501        Some(Algorithm::Rs512) => ("RSASSA-PKCS1-v1_5", "SHA-512"),
502        Some(Algorithm::Ps256) => ("RSA-PSS", "SHA-256"),
503        Some(Algorithm::Ps384) => ("RSA-PSS", "SHA-384"),
504        Some(Algorithm::Ps512) => ("RSA-PSS", "SHA-512"),
505        Some(Algorithm::RsaOaep) => ("RSA-OAEP", "SHA-1"),
506        Some(Algorithm::RsaOaep256) => ("RSA-OAEP", "SHA-256"),
507        Some(Algorithm::RsaOaep384) => ("RSA-OAEP", "SHA-384"),
508        Some(Algorithm::RsaOaep512) => ("RSA-OAEP", "SHA-512"),
509        _ => {
510            return Err(Error::WebCrypto(
511                "RSA key import requires an algorithm to determine the hash function; \
512                 set the `alg` field on the key or use an import function that accepts \
513                 an explicit algorithm (e.g., `import_verify_key_for_alg`)"
514                    .to_string(),
515            ));
516        }
517    };
518
519    Reflect::set(&obj, &"name".into(), &alg_name.into())
520        .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
521
522    // Set hash algorithm
523    let hash_obj = Object::new();
524    Reflect::set(&hash_obj, &"name".into(), &hash.into())
525        .map_err(|e| Error::WebCrypto(format!("failed to set hash name: {:?}", e)))?;
526    Reflect::set(&obj, &"hash".into(), &hash_obj.into())
527        .map_err(|e| Error::WebCrypto(format!("failed to set hash: {:?}", e)))?;
528
529    Ok(obj)
530}
531
532/// Builds an EC algorithm object.
533fn build_ec_algorithm(curve: EcCurve, usage: KeyUsage) -> Result<Object> {
534    let obj = Object::new();
535
536    let alg_name = match usage {
537        KeyUsage::Sign | KeyUsage::Verify => "ECDSA",
538        KeyUsage::Encrypt | KeyUsage::Decrypt | KeyUsage::WrapKey | KeyUsage::UnwrapKey => {
539            return Err(Error::UnsupportedForWebCrypto {
540                reason: "EC key derivation (ECDH) and direct encrypt/decrypt/wrap/unwrap \
541                         are not yet supported by this library; \
542                         only ECDSA sign/verify is currently implemented for EC keys",
543            });
544        }
545    };
546
547    Reflect::set(&obj, &"name".into(), &alg_name.into())
548        .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
549
550    let named_curve = match curve {
551        EcCurve::P256 => "P-256",
552        EcCurve::P384 => "P-384",
553        EcCurve::P521 => "P-521",
554        EcCurve::Secp256k1 => {
555            return Err(Error::UnsupportedForWebCrypto {
556                reason: "secp256k1 curve is not supported by WebCrypto",
557            });
558        }
559    };
560
561    Reflect::set(&obj, &"namedCurve".into(), &named_curve.into())
562        .map_err(|e| Error::WebCrypto(format!("failed to set namedCurve: {:?}", e)))?;
563
564    Ok(obj)
565}
566
567/// Builds a symmetric key algorithm object.
568///
569/// The algorithm is determined from (in order of priority):
570/// 1. The `alg_override` parameter (if provided)
571/// 2. The key's `alg` field (if present)
572///
573/// If neither is available, an error is returned because WebCrypto requires
574/// the hash algorithm to be specified at import time for HMAC keys, and a
575/// wrong default would cause silent verification failures.
576fn build_symmetric_algorithm(
577    key: &Key,
578    _usage: KeyUsage,
579    alg_override: Option<&Algorithm>,
580) -> Result<Object> {
581    let obj = Object::new();
582
583    // Use the override first, then fall back to the key's own algorithm
584    let effective_alg = alg_override.or(key.alg());
585
586    let (alg_name, extra) = match effective_alg {
587        Some(Algorithm::Hs256) => ("HMAC", Some(("hash", "SHA-256"))),
588        Some(Algorithm::Hs384) => ("HMAC", Some(("hash", "SHA-384"))),
589        Some(Algorithm::Hs512) => ("HMAC", Some(("hash", "SHA-512"))),
590        // AES-KW and AES-GCM importKey takes no algorithm parameters beyond the name.
591        // The key size is determined from the imported key material itself.
592        // See W3C WebCrypto spec sections 30.3.4 (AES-KW) and 29.4.4 (AES-GCM).
593        Some(Algorithm::A128kw) | Some(Algorithm::A192kw) | Some(Algorithm::A256kw) => {
594            ("AES-KW", None)
595        }
596        Some(Algorithm::A128gcm) | Some(Algorithm::A192gcm) | Some(Algorithm::A256gcm) => {
597            ("AES-GCM", None)
598        }
599        Some(Algorithm::A128cbcHs256)
600        | Some(Algorithm::A192cbcHs384)
601        | Some(Algorithm::A256cbcHs512) => {
602            return Err(Error::UnsupportedForWebCrypto {
603                reason: "AES-CBC-HS algorithms (A128CBC-HS256, A192CBC-HS384, A256CBC-HS512) \
604                         are composite authenticated encryption algorithms requiring split-key \
605                         handling (AES-CBC + HMAC) which WebCrypto does not natively support",
606            });
607        }
608        _ => {
609            return Err(Error::WebCrypto(
610                "symmetric key import requires an algorithm to determine the operation; \
611                 set the `alg` field on the key or use an import function that accepts \
612                 an explicit algorithm (e.g., `import_verify_key_for_alg`)"
613                    .to_string(),
614            ));
615        }
616    };
617
618    Reflect::set(&obj, &"name".into(), &alg_name.into())
619        .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
620
621    if let Some((prop, val)) = extra {
622        debug_assert_eq!(prop, "hash", "only HMAC uses extra parameters");
623        let hash_obj = Object::new();
624        Reflect::set(&hash_obj, &"name".into(), &val.into())
625            .map_err(|e| Error::WebCrypto(format!("failed to set hash name: {:?}", e)))?;
626        Reflect::set(&obj, &"hash".into(), &hash_obj.into())
627            .map_err(|e| Error::WebCrypto(format!("failed to set hash: {:?}", e)))?;
628    }
629
630    Ok(obj)
631}
632
633/// Builds a WebCrypto algorithm object for use with `SubtleCrypto.verify()`.
634///
635/// This is different from the import algorithm: `verify()` requires algorithm-specific
636/// parameters like `saltLength` (RSA-PSS) or `hash` (ECDSA), while not needing
637/// parameters like `namedCurve` that are only needed during import.
638///
639/// # Supported Algorithms
640///
641/// | Algorithm | Verify Object |
642/// |-----------|---------------|
643/// | RS256/384/512 | `{ name: "RSASSA-PKCS1-v1_5" }` |
644/// | PS256/384/512 | `{ name: "RSA-PSS", saltLength }` |
645/// | ES256/384/512 | `{ name: "ECDSA", hash }` |
646/// | HS256/384/512 | `{ name: "HMAC" }` |
647///
648/// # Errors
649///
650/// Returns [`Error::UnsupportedForWebCrypto`] if the algorithm is not supported
651/// by WebCrypto (e.g., EdDSA, Ed25519, Ed448, ES256K).
652///
653/// # Examples
654///
655/// ```ignore
656/// use jwk_simple::{Algorithm, web_crypto};
657///
658/// let alg = Algorithm::Rs256;
659/// let verify_algo = web_crypto::build_verify_algorithm(&alg)?;
660///
661/// // Use with SubtleCrypto.verify()
662/// let subtle = web_crypto::get_subtle_crypto()?;
663/// let result = subtle.verify_with_object_and_buffer_source_and_buffer_source(
664///     &verify_algo, &crypto_key, &signature, &data,
665/// )?;
666/// ```
667pub fn build_verify_algorithm(alg: &Algorithm) -> Result<Object> {
668    let obj = Object::new();
669
670    match alg {
671        // RSASSA-PKCS1-v1_5: only needs the algorithm name
672        Algorithm::Rs256 | Algorithm::Rs384 | Algorithm::Rs512 => {
673            Reflect::set(&obj, &"name".into(), &"RSASSA-PKCS1-v1_5".into())
674                .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
675        }
676
677        // RSA-PSS: needs algorithm name and salt length (= hash output size in bytes)
678        Algorithm::Ps256 => {
679            Reflect::set(&obj, &"name".into(), &"RSA-PSS".into())
680                .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
681            Reflect::set(&obj, &"saltLength".into(), &32.into())
682                .map_err(|e| Error::WebCrypto(format!("failed to set saltLength: {:?}", e)))?;
683        }
684        Algorithm::Ps384 => {
685            Reflect::set(&obj, &"name".into(), &"RSA-PSS".into())
686                .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
687            Reflect::set(&obj, &"saltLength".into(), &48.into())
688                .map_err(|e| Error::WebCrypto(format!("failed to set saltLength: {:?}", e)))?;
689        }
690        Algorithm::Ps512 => {
691            Reflect::set(&obj, &"name".into(), &"RSA-PSS".into())
692                .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
693            Reflect::set(&obj, &"saltLength".into(), &64.into())
694                .map_err(|e| Error::WebCrypto(format!("failed to set saltLength: {:?}", e)))?;
695        }
696
697        // ECDSA: needs algorithm name and hash
698        Algorithm::Es256 | Algorithm::Es384 | Algorithm::Es512 => {
699            Reflect::set(&obj, &"name".into(), &"ECDSA".into())
700                .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
701
702            let hash = match alg {
703                Algorithm::Es256 => "SHA-256",
704                Algorithm::Es384 => "SHA-384",
705                Algorithm::Es512 => "SHA-512",
706                _ => unreachable!(),
707            };
708
709            let hash_obj = Object::new();
710            Reflect::set(&hash_obj, &"name".into(), &hash.into())
711                .map_err(|e| Error::WebCrypto(format!("failed to set hash name: {:?}", e)))?;
712            Reflect::set(&obj, &"hash".into(), &hash_obj.into())
713                .map_err(|e| Error::WebCrypto(format!("failed to set hash: {:?}", e)))?;
714        }
715
716        // HMAC: only needs the algorithm name
717        Algorithm::Hs256 | Algorithm::Hs384 | Algorithm::Hs512 => {
718            Reflect::set(&obj, &"name".into(), &"HMAC".into())
719                .map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
720        }
721
722        _ => {
723            return Err(Error::UnsupportedForWebCrypto {
724                reason: "algorithm not supported for WebCrypto verify",
725            });
726        }
727    }
728
729    Ok(obj)
730}
731
732// ============================================================================
733// Key Import Functions
734// ============================================================================
735
736/// Imports a JWK as a [`CryptoKey`] for signature verification.
737///
738/// This requires the key's `alg` field to be set for RSA and HMAC keys, because
739/// WebCrypto locks the hash algorithm at import time. EC keys do not require `alg`
740/// since the curve already determines the algorithm parameters.
741///
742/// **For keys without an `alg` field** (common in JWKS from OIDC providers), use
743/// [`import_verify_key_for_alg`] instead, passing the algorithm from the JWT header.
744///
745/// # Supported Key Types
746///
747/// - RSA public keys (RS256, RS384, RS512, PS256, PS384, PS512) - requires `alg`
748/// - EC public keys (P-256, P-384, P-521)
749/// - HMAC symmetric keys (HS256, HS384, HS512) - requires `alg`
750///
751/// # Errors
752///
753/// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
754/// - [`Error::WebCrypto`] if the import operation fails or the key is missing
755///   a required `alg` field (RSA/HMAC only)
756///
757/// # Examples
758///
759/// ```ignore
760/// use jwk_simple::{web_crypto, KeySet};
761///
762/// let jwks: KeySet = serde_json::from_str(jwks_json)?;
763/// let key = jwks.get_by_kid("my-key-id").unwrap();
764///
765/// // Works when the key has an `alg` field set
766/// let crypto_key = web_crypto::import_verify_key(key).await?;
767/// ```
768pub async fn import_verify_key(key: &Key) -> Result<CryptoKey> {
769    import_key_for_usage(key, KeyUsage::Verify).await
770}
771
772/// Imports a JWK as a [`CryptoKey`] for signing.
773///
774/// This requires a private key (RSA or EC with the `d` parameter) and, for RSA
775/// and HMAC keys, the key's `alg` field must be set because WebCrypto locks the
776/// hash algorithm at import time.
777///
778/// **For keys without an `alg` field**, use [`import_sign_key_for_alg`] instead.
779///
780/// # Errors
781///
782/// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
783/// - [`Error::WebCrypto`] if the import operation fails (e.g., missing private key
784///   or missing `alg` field for RSA/HMAC)
785///
786/// # Examples
787///
788/// ```ignore
789/// let crypto_key = web_crypto::import_sign_key(&private_key).await?;
790/// ```
791pub async fn import_sign_key(key: &Key) -> Result<CryptoKey> {
792    import_key_for_usage(key, KeyUsage::Sign).await
793}
794
795/// Imports a JWK as a [`CryptoKey`] for encryption.
796///
797/// This requires the key's `alg` field to be set for RSA and symmetric keys,
798/// because WebCrypto requires the import algorithm to be specified.
799/// For keys without an `alg` field, use [`import_key_for_usage_with_alg`]
800/// with [`KeyUsage::Encrypt`] and an explicit algorithm.
801///
802/// # Supported Key Types
803///
804/// - RSA public keys (RSA-OAEP)
805/// - Symmetric keys (AES-GCM)
806///
807/// # Errors
808///
809/// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
810/// - [`Error::WebCrypto`] if the import operation fails (including missing `alg`)
811pub async fn import_encrypt_key(key: &Key) -> Result<CryptoKey> {
812    if matches!(key.params(), KeyParams::Ec(_)) {
813        return Err(Error::UnsupportedForWebCrypto {
814            reason: "EC keys do not support direct encryption; \
815                     use ECDH key agreement (deriveKey/deriveBits) instead",
816        });
817    }
818    import_key_for_usage(key, KeyUsage::Encrypt).await
819}
820
821/// Imports a JWK as a [`CryptoKey`] for decryption.
822///
823/// This requires a private key (RSA) or symmetric key.
824/// This also requires the key's `alg` field to be set for RSA and symmetric keys,
825/// because WebCrypto requires the import algorithm to be specified.
826/// For keys without an `alg` field, use [`import_key_for_usage_with_alg`]
827/// with [`KeyUsage::Decrypt`] and an explicit algorithm.
828///
829/// # Errors
830///
831/// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
832/// - [`Error::WebCrypto`] if the import operation fails (including missing `alg`)
833pub async fn import_decrypt_key(key: &Key) -> Result<CryptoKey> {
834    if matches!(key.params(), KeyParams::Ec(_)) {
835        return Err(Error::UnsupportedForWebCrypto {
836            reason: "EC keys do not support direct decryption; \
837                     use ECDH key agreement (deriveKey/deriveBits) instead",
838        });
839    }
840    import_key_for_usage(key, KeyUsage::Decrypt).await
841}
842
843/// Imports a JWK as a [`CryptoKey`] for key wrapping.
844///
845/// This requires the key's `alg` field to be set for RSA and symmetric keys,
846/// because WebCrypto requires the import algorithm to be specified.
847/// For keys without an `alg` field, use [`import_key_for_usage_with_alg`]
848/// with [`KeyUsage::WrapKey`] and an explicit algorithm.
849///
850/// # Supported Key Types
851///
852/// - RSA public keys (RSA-OAEP)
853/// - Symmetric keys (AES-KW)
854pub async fn import_wrap_key(key: &Key) -> Result<CryptoKey> {
855    if matches!(key.params(), KeyParams::Ec(_)) {
856        return Err(Error::UnsupportedForWebCrypto {
857            reason: "EC keys do not support direct key wrapping; \
858                     use ECDH key agreement (deriveKey/deriveBits) instead",
859        });
860    }
861    import_key_for_usage(key, KeyUsage::WrapKey).await
862}
863
864/// Imports a JWK as a [`CryptoKey`] for key unwrapping.
865///
866/// This requires the key's `alg` field to be set for RSA and symmetric keys,
867/// because WebCrypto requires the import algorithm to be specified.
868/// For keys without an `alg` field, use [`import_key_for_usage_with_alg`]
869/// with [`KeyUsage::UnwrapKey`] and an explicit algorithm.
870///
871/// # Supported Key Types
872///
873/// - RSA private keys (RSA-OAEP)
874/// - Symmetric keys (AES-KW)
875pub async fn import_unwrap_key(key: &Key) -> Result<CryptoKey> {
876    if matches!(key.params(), KeyParams::Ec(_)) {
877        return Err(Error::UnsupportedForWebCrypto {
878            reason: "EC keys do not support direct key unwrapping; \
879                     use ECDH key agreement (deriveKey/deriveBits) instead",
880        });
881    }
882    import_key_for_usage(key, KeyUsage::UnwrapKey).await
883}
884
885/// Imports a JWK as a [`CryptoKey`] for signature verification with an explicit algorithm.
886///
887/// This is useful when the key's `alg` field is absent (common in JWKS from OIDC providers).
888/// WebCrypto locks the hash algorithm at import time, so the algorithm must be known
889/// before importing the key. Using this function avoids a potential mismatch between
890/// the import algorithm and the verification algorithm.
891///
892/// # Supported Algorithms
893///
894/// - RSA: RS256, RS384, RS512, PS256, PS384, PS512
895/// - EC: ES256, ES384, ES512
896/// - HMAC: HS256, HS384, HS512
897///
898/// # Errors
899///
900/// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
901/// - [`Error::WebCrypto`] if the import operation fails
902///
903/// # Examples
904///
905/// ```ignore
906/// use jwk_simple::{Algorithm, web_crypto, KeySet};
907///
908/// let jwks: KeySet = serde_json::from_str(jwks_json)?;
909/// let key = jwks.get_by_kid("my-key-id").unwrap();
910/// // Use the algorithm from the JWT header, not the key
911/// let crypto_key = web_crypto::import_verify_key_for_alg(key, &Algorithm::Rs384).await?;
912/// ```
913pub async fn import_verify_key_for_alg(key: &Key, alg: &Algorithm) -> Result<CryptoKey> {
914    import_key_for_usage_with_alg(key, KeyUsage::Verify, alg).await
915}
916
917/// Imports a JWK as a [`CryptoKey`] for signing with an explicit algorithm.
918///
919/// This is useful when the key's `alg` field is absent. See
920/// [`import_verify_key_for_alg`] for more details on why this matters.
921///
922/// # Errors
923///
924/// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
925/// - [`Error::WebCrypto`] if the import operation fails
926pub async fn import_sign_key_for_alg(key: &Key, alg: &Algorithm) -> Result<CryptoKey> {
927    import_key_for_usage_with_alg(key, KeyUsage::Sign, alg).await
928}
929
930/// Imports a JWK as a [`CryptoKey`] for a typed key usage.
931///
932/// The key must have an `alg` field set so that the correct WebCrypto algorithm
933/// parameters can be determined. For RSA and HMAC keys without an `alg` field,
934/// use [`import_key_for_usage_with_alg`] instead.
935///
936/// # Errors
937///
938/// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
939/// - [`Error::WebCrypto`] if the import operation fails or the key is missing
940///   a required `alg` field
941pub async fn import_key_for_usage(key: &Key, usage: KeyUsage) -> Result<CryptoKey> {
942    validate_key_for_webcrypto_usage(key, usage)?;
943
944    let jwk = web_sys::JsonWebKey::try_from(key)?;
945    let algorithm = build_algorithm_object(key, usage)?;
946
947    import_crypto_key(jwk, &algorithm, usage_strings_for_usage(usage)).await
948}
949
950/// Imports a JWK as a [`CryptoKey`] for a typed key usage and an explicit algorithm.
951///
952/// The `alg` parameter overrides the key's own `alg`
953/// field, ensuring the correct WebCrypto algorithm parameters are used at import time.
954///
955/// # Errors
956///
957/// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
958/// - [`Error::WebCrypto`] if the import operation fails
959pub async fn import_key_for_usage_with_alg(
960    key: &Key,
961    usage: KeyUsage,
962    alg: &Algorithm,
963) -> Result<CryptoKey> {
964    validate_key_for_webcrypto_usage_with_alg(key, usage, alg)?;
965    let jwk = web_sys::JsonWebKey::try_from(key)?;
966
967    // Override the JWK's `alg` field to match the explicit algorithm.
968    // WebCrypto validates that the JWK `alg` (if present) is consistent with
969    // the algorithm parameter passed to importKey(). Without this override,
970    // importing a key whose `alg` differs from the explicit algorithm would
971    // fail with a DataError.
972    jwk.set_alg(alg.as_str());
973
974    let algorithm = build_algorithm_object_for_alg(key, alg, usage)?;
975
976    import_crypto_key(jwk, &algorithm, usage_strings_for_usage(usage)).await
977}
978
979/// Internal helper that performs the actual SubtleCrypto.importKey() call.
980async fn import_crypto_key(
981    jwk: web_sys::JsonWebKey,
982    algorithm: &Object,
983    usages: &[&str],
984) -> Result<CryptoKey> {
985    let key_usages = Array::new();
986    for u in usages {
987        key_usages.push(&JsValue::from_str(u));
988    }
989
990    let subtle = get_subtle_crypto()?;
991
992    // Import the key
993    let promise = subtle
994        .import_key_with_object("jwk", &jwk.into(), algorithm, false, &key_usages)
995        .map_err(|e| Error::WebCrypto(format!("import_key failed: {:?}", e)))?;
996
997    let result = JsFuture::from(promise)
998        .await
999        .map_err(|e| Error::WebCrypto(format!("import_key promise rejected: {:?}", e)))?;
1000
1001    Ok(result.unchecked_into())
1002}
1003
1004// ============================================================================
1005// Convenience Methods on Key
1006// ============================================================================
1007
1008impl Key {
1009    /// Returns `true` if this key can be used with WebCrypto.
1010    ///
1011    /// OKP keys and secp256k1 EC keys are not supported by WebCrypto.
1012    ///
1013    /// # Examples
1014    ///
1015    /// ```ignore
1016    /// if key.is_web_crypto_compatible() {
1017    ///     let crypto_key = key.import_as_verify_key_for_alg(&alg).await?;
1018    /// }
1019    /// ```
1020    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
1021    pub fn is_web_crypto_compatible(&self) -> bool {
1022        validate_webcrypto_support(self).is_ok()
1023    }
1024
1025    /// Imports this key as a [`CryptoKey`] for signature verification.
1026    ///
1027    /// RSA and HMAC keys must have their `alg` field set. For keys without `alg`
1028    /// (common in JWKS from OIDC providers), use
1029    /// [`import_as_verify_key_for_alg`](Key::import_as_verify_key_for_alg) instead.
1030    ///
1031    /// # Errors
1032    ///
1033    /// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
1034    /// - [`Error::WebCrypto`] if the import operation fails or the key is missing
1035    ///   a required `alg` field (RSA/HMAC only)
1036    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
1037    pub async fn import_as_verify_key(&self) -> Result<CryptoKey> {
1038        import_verify_key(self).await
1039    }
1040
1041    /// Imports this key as a [`CryptoKey`] for signing.
1042    ///
1043    /// RSA and HMAC keys must have their `alg` field set. For keys without `alg`,
1044    /// use [`import_as_sign_key_for_alg`](Key::import_as_sign_key_for_alg) instead.
1045    ///
1046    /// # Errors
1047    ///
1048    /// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
1049    /// - [`Error::WebCrypto`] if the import operation fails or the key is missing
1050    ///   a required `alg` field (RSA/HMAC only)
1051    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
1052    pub async fn import_as_sign_key(&self) -> Result<CryptoKey> {
1053        import_sign_key(self).await
1054    }
1055
1056    /// Imports this key as a [`CryptoKey`] for encryption.
1057    ///
1058    /// # Errors
1059    ///
1060    /// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
1061    /// - [`Error::WebCrypto`] if the import operation fails
1062    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
1063    pub async fn import_as_encrypt_key(&self) -> Result<CryptoKey> {
1064        import_encrypt_key(self).await
1065    }
1066
1067    /// Imports this key as a [`CryptoKey`] for decryption.
1068    ///
1069    /// # Errors
1070    ///
1071    /// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
1072    /// - [`Error::WebCrypto`] if the import operation fails
1073    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
1074    pub async fn import_as_decrypt_key(&self) -> Result<CryptoKey> {
1075        import_decrypt_key(self).await
1076    }
1077
1078    /// Imports this key as a [`CryptoKey`] for signature verification with an explicit algorithm.
1079    ///
1080    /// This is useful when the key's `alg` field is absent (common in JWKS from
1081    /// OIDC providers). WebCrypto locks the hash algorithm at import time, so the
1082    /// algorithm must be known before importing. The `alg` parameter overrides the
1083    /// key's own `alg` field.
1084    ///
1085    /// # Errors
1086    ///
1087    /// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
1088    /// - [`Error::WebCrypto`] if the import operation fails
1089    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
1090    pub async fn import_as_verify_key_for_alg(&self, alg: &Algorithm) -> Result<CryptoKey> {
1091        import_verify_key_for_alg(self, alg).await
1092    }
1093
1094    /// Imports this key as a [`CryptoKey`] for signing with an explicit algorithm.
1095    ///
1096    /// This is useful when the key's `alg` field is absent. See
1097    /// [`Key::import_as_verify_key_for_alg`] for more details.
1098    ///
1099    /// # Errors
1100    ///
1101    /// - [`Error::UnsupportedForWebCrypto`] if the key type is not supported
1102    /// - [`Error::WebCrypto`] if the import operation fails
1103    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
1104    pub async fn import_as_sign_key_for_alg(&self, alg: &Algorithm) -> Result<CryptoKey> {
1105        import_sign_key_for_alg(self, alg).await
1106    }
1107}
1108
1109// ============================================================================
1110// Tests
1111// ============================================================================
1112
1113// Validation tests that can run on any target (no web_sys dependencies).
1114#[cfg(test)]
1115mod validation_tests {
1116    use super::*;
1117    use crate::jwks::KeySet;
1118
1119    const RFC_RSA_PUBLIC_KEY: &str = r#"{
1120        "kty": "RSA",
1121        "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
1122        "e": "AQAB"
1123    }"#;
1124
1125    const RFC_EC_P256_PUBLIC_KEY: &str = r#"{
1126        "kty": "EC",
1127        "crv": "P-256",
1128        "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
1129        "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
1130    }"#;
1131
1132    const EC_SECP256K1_KEY: &str = r#"{
1133        "kty": "EC",
1134        "crv": "secp256k1",
1135        "x": "WbbXwISW8TLWM3IDLGm1cX_3IrYgWl_bzcLe0tSCDj4",
1136        "y": "KGk8DRQHPeV4S3Oq2jVJLNSV_3ngGgbfHTKsS5aw30c"
1137    }"#;
1138
1139    const OKP_ED25519_KEY: &str = r#"{
1140        "kty": "OKP",
1141        "crv": "Ed25519",
1142        "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
1143    }"#;
1144
1145    const SYMMETRIC_KEY: &str = r#"{
1146        "kty": "oct",
1147        "k": "AyM32w-8O0TGsGDYX0MlWy-9XQP-xrryrP7gkXKfY5WhoLxmT3fzfVr7LXqgDDFSfowWBY-u6bSH5f9kBZ_n7Q",
1148        "alg": "HS256"
1149    }"#;
1150
1151    #[test]
1152    fn test_validate_rsa_supported() {
1153        let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
1154        assert!(validate_webcrypto_support(&key).is_ok());
1155    }
1156
1157    #[test]
1158    fn test_validate_ec_p256_supported() {
1159        let key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
1160        assert!(validate_webcrypto_support(&key).is_ok());
1161    }
1162
1163    #[test]
1164    fn test_validate_symmetric_supported() {
1165        let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
1166        assert!(validate_webcrypto_support(&key).is_ok());
1167    }
1168
1169    #[test]
1170    fn test_validate_okp_unsupported() {
1171        let key: Key = serde_json::from_str(OKP_ED25519_KEY).unwrap();
1172        let result = validate_webcrypto_support(&key);
1173        assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
1174    }
1175
1176    #[test]
1177    fn test_validate_secp256k1_unsupported() {
1178        let key: Key = serde_json::from_str(EC_SECP256K1_KEY).unwrap();
1179        let result = validate_webcrypto_support(&key);
1180        assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
1181    }
1182
1183    #[test]
1184    fn test_is_web_crypto_compatible_rsa() {
1185        let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
1186        assert!(key.is_web_crypto_compatible());
1187    }
1188
1189    #[test]
1190    fn test_is_web_crypto_compatible_okp() {
1191        let key: Key = serde_json::from_str(OKP_ED25519_KEY).unwrap();
1192        assert!(!key.is_web_crypto_compatible());
1193    }
1194
1195    #[test]
1196    fn test_is_web_crypto_compatible_secp256k1() {
1197        let key: Key = serde_json::from_str(EC_SECP256K1_KEY).unwrap();
1198        assert!(!key.is_web_crypto_compatible());
1199    }
1200
1201    #[test]
1202    fn test_usage_algorithm_compatibility_rejects_mismatch() {
1203        let result = validate_usage_algorithm_compatibility(KeyUsage::Encrypt, &Algorithm::Rs256);
1204        assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
1205
1206        let result =
1207            validate_usage_algorithm_compatibility(KeyUsage::Verify, &Algorithm::RsaOaep256);
1208        assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
1209    }
1210
1211    #[test]
1212    fn test_usage_algorithm_compatibility_accepts_valid_pairs() {
1213        assert!(
1214            validate_usage_algorithm_compatibility(KeyUsage::Verify, &Algorithm::Rs256).is_ok()
1215        );
1216        assert!(
1217            validate_usage_algorithm_compatibility(KeyUsage::Encrypt, &Algorithm::RsaOaep256)
1218                .is_ok()
1219        );
1220        assert!(
1221            validate_usage_algorithm_compatibility(KeyUsage::WrapKey, &Algorithm::A128kw).is_ok()
1222        );
1223    }
1224
1225    #[test]
1226    fn test_import_usage_validation_enforces_metadata_when_alg_present() {
1227        let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
1228        let key = key.with_key_ops([crate::KeyOperation::Sign, crate::KeyOperation::Sign]);
1229
1230        let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Sign);
1231        assert!(result.is_err(), "duplicate key_ops must be rejected");
1232    }
1233
1234    #[test]
1235    fn test_validate_key_for_webcrypto_usage_rejects_incompatible_use() {
1236        let json = r#"{
1237            "kty": "RSA",
1238            "use": "enc",
1239            "alg": "RS256",
1240            "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
1241            "e": "AQAB"
1242        }"#;
1243
1244        let key: Key = serde_json::from_str(json).unwrap();
1245        let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Verify);
1246        assert!(result.is_err());
1247    }
1248
1249    #[test]
1250    fn test_validate_key_for_webcrypto_usage_allows_missing_optional_metadata() {
1251        let json = r#"{
1252            "kty": "RSA",
1253            "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
1254            "e": "AQAB"
1255        }"#;
1256
1257        let key: Key = serde_json::from_str(json).unwrap();
1258        let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Verify);
1259        assert!(result.is_ok());
1260    }
1261
1262    #[test]
1263    fn test_validate_key_for_webcrypto_usage_rejects_incompatible_use_without_alg() {
1264        let json = r#"{
1265            "kty": "RSA",
1266            "use": "enc",
1267            "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
1268            "e": "AQAB"
1269        }"#;
1270
1271        let key: Key = serde_json::from_str(json).unwrap();
1272        let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Verify);
1273        assert!(result.is_err());
1274    }
1275
1276    #[test]
1277    fn test_validate_key_for_webcrypto_usage_with_alg_allows_declared_algorithm_override() {
1278        // SYMMETRIC_KEY declares alg: HS256 but the caller explicitly requests
1279        // HS384.  validate_key_for_webcrypto_usage_with_alg uses the override
1280        // path which intentionally skips the declared-algorithm-match check,
1281        // so this must succeed (the 512-bit key satisfies HS384's 384-bit
1282        // minimum).
1283        let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
1284
1285        let result =
1286            validate_key_for_webcrypto_usage_with_alg(&key, KeyUsage::Verify, &Algorithm::Hs384);
1287        assert!(
1288            result.is_ok(),
1289            "explicit alg override should skip declared-algorithm mismatch check: {result:?}"
1290        );
1291    }
1292
1293    #[test]
1294    fn test_validate_key_for_webcrypto_usage_rejects_public_sign_key_without_alg() {
1295        let json = r#"{
1296            "kty": "RSA",
1297            "use": "sig",
1298            "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
1299            "e": "AQAB"
1300        }"#;
1301
1302        let key: Key = serde_json::from_str(json).unwrap();
1303        let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Sign);
1304        assert!(result.is_err());
1305    }
1306
1307    #[test]
1308    fn test_select_verify_key_strict_for_web_crypto_flow() {
1309        let json = r#"{"keys": [
1310            {"kty": "RSA", "kid": "rsa-verify", "use": "sig", "alg": "RS256", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB"}
1311        ]}"#;
1312
1313        let jwks: KeySet = serde_json::from_str(json).unwrap();
1314        let key = jwks
1315            .selector(&[Algorithm::Rs256])
1316            .select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Rs256).with_kid("rsa-verify"))
1317            .unwrap();
1318
1319        assert_eq!(key.kid(), Some("rsa-verify"));
1320    }
1321
1322    #[test]
1323    fn test_select_signing_key_strict_for_web_crypto_flow() {
1324        let json = r#"{"keys": [
1325            {"kty": "EC", "kid": "ec-sign", "use": "sig", "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
1326        ]}"#;
1327
1328        let jwks: KeySet = serde_json::from_str(json).unwrap();
1329        let key = jwks
1330            .selector(&[])
1331            .select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Es256).with_kid("ec-sign"))
1332            .unwrap();
1333
1334        assert_eq!(key.kid(), Some("ec-sign"));
1335    }
1336}
1337
1338// Tests that use web_sys types - only compiled for wasm32 targets.
1339// For WASM integration tests, see tests/web_crypto.rs which uses wasm_bindgen_test.
1340#[cfg(all(test, target_arch = "wasm32"))]
1341mod tests {
1342    use super::*;
1343
1344    // Test RSA public key from RFC 7517 Appendix A.1
1345    const RFC_RSA_PUBLIC_KEY: &str = r#"{
1346        "kty": "RSA",
1347        "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
1348        "e": "AQAB"
1349    }"#;
1350
1351    // Test EC P-256 public key from RFC 7517 Appendix A.1
1352    const RFC_EC_P256_PUBLIC_KEY: &str = r#"{
1353        "kty": "EC",
1354        "crv": "P-256",
1355        "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
1356        "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
1357    }"#;
1358
1359    // Test EC secp256k1 key (unsupported)
1360    const EC_SECP256K1_KEY: &str = r#"{
1361        "kty": "EC",
1362        "crv": "secp256k1",
1363        "x": "WbbXwISW8TLWM3IDLGm1cX_3IrYgWl_bzcLe0tSCDj4",
1364        "y": "KGk8DRQHPeV4S3Oq2jVJLNSV_3ngGgbfHTKsS5aw30c"
1365    }"#;
1366
1367    // Test OKP Ed25519 key (unsupported)
1368    const OKP_ED25519_KEY: &str = r#"{
1369        "kty": "OKP",
1370        "crv": "Ed25519",
1371        "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
1372    }"#;
1373
1374    // Test symmetric key
1375    const SYMMETRIC_KEY: &str = r#"{
1376        "kty": "oct",
1377        "k": "AyM32w-8O0TGsGDYX0MlWy-9XQP-xrryrP7gkXKfY5WhoLxmT3fzfVr7LXqgDDFSfowWBY-u6bSH5f9kBZ_n7Q",
1378        "alg": "HS256"
1379    }"#;
1380
1381    #[test]
1382    fn test_rsa_key_to_json_web_key() {
1383        let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
1384        let jwk = web_sys::JsonWebKey::try_from(&key).unwrap();
1385        assert_eq!(jwk.get_kty(), "RSA");
1386        assert!(jwk.get_n().is_some());
1387        assert!(jwk.get_e().is_some());
1388        assert!(jwk.get_d().is_none()); // Public key only
1389    }
1390
1391    #[test]
1392    fn test_ec_p256_key_to_json_web_key() {
1393        let key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
1394        let jwk = web_sys::JsonWebKey::try_from(&key).unwrap();
1395        assert_eq!(jwk.get_kty(), "EC");
1396        assert_eq!(jwk.get_crv(), Some("P-256".to_string()));
1397        assert!(jwk.get_x().is_some());
1398        assert!(jwk.get_y().is_some());
1399    }
1400
1401    #[test]
1402    fn test_symmetric_key_to_json_web_key() {
1403        let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
1404        let jwk = web_sys::JsonWebKey::try_from(&key).unwrap();
1405        assert_eq!(jwk.get_kty(), "oct");
1406        assert!(jwk.get_k().is_some());
1407    }
1408
1409    #[test]
1410    fn test_okp_key_unsupported() {
1411        let key: Key = serde_json::from_str(OKP_ED25519_KEY).unwrap();
1412        let result = web_sys::JsonWebKey::try_from(&key);
1413        assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
1414    }
1415
1416    #[test]
1417    fn test_secp256k1_key_unsupported() {
1418        let key: Key = serde_json::from_str(EC_SECP256K1_KEY).unwrap();
1419        let result = web_sys::JsonWebKey::try_from(&key);
1420        assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
1421    }
1422
1423    #[test]
1424    fn test_is_web_crypto_compatible() {
1425        let rsa_key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
1426        assert!(rsa_key.is_web_crypto_compatible());
1427
1428        let ec_key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
1429        assert!(ec_key.is_web_crypto_compatible());
1430
1431        let okp_key: Key = serde_json::from_str(OKP_ED25519_KEY).unwrap();
1432        assert!(!okp_key.is_web_crypto_compatible());
1433
1434        let secp256k1_key: Key = serde_json::from_str(EC_SECP256K1_KEY).unwrap();
1435        assert!(!secp256k1_key.is_web_crypto_compatible());
1436    }
1437
1438    #[test]
1439    fn test_validate_webcrypto_support_rsa() {
1440        let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
1441        assert!(validate_webcrypto_support(&key).is_ok());
1442    }
1443
1444    #[test]
1445    fn test_validate_webcrypto_support_ec_p256() {
1446        let key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
1447        assert!(validate_webcrypto_support(&key).is_ok());
1448    }
1449
1450    #[test]
1451    fn test_validate_webcrypto_support_symmetric() {
1452        let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
1453        assert!(validate_webcrypto_support(&key).is_ok());
1454    }
1455
1456    #[test]
1457    fn test_build_rsa_algorithm_with_explicit_alg() {
1458        let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
1459        let alg =
1460            build_algorithm_object_for_alg(&key, &Algorithm::Rs256, KeyUsage::Verify).unwrap();
1461
1462        let name = Reflect::get(&alg, &"name".into()).unwrap();
1463        assert_eq!(name.as_string().unwrap(), "RSASSA-PKCS1-v1_5");
1464    }
1465
1466    #[test]
1467    fn test_build_rsa_algorithm_without_alg_errors() {
1468        let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
1469        let result = build_algorithm_object(&key, KeyUsage::Verify);
1470        assert!(result.is_err(), "RSA key without alg should error");
1471    }
1472
1473    #[test]
1474    fn test_build_ec_algorithm() {
1475        let key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
1476        let alg = build_algorithm_object(&key, KeyUsage::Verify).unwrap();
1477
1478        let name = Reflect::get(&alg, &"name".into()).unwrap();
1479        assert_eq!(name.as_string().unwrap(), "ECDSA");
1480
1481        let curve = Reflect::get(&alg, &"namedCurve".into()).unwrap();
1482        assert_eq!(curve.as_string().unwrap(), "P-256");
1483    }
1484
1485    #[test]
1486    fn test_build_hmac_algorithm() {
1487        let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
1488        let alg = build_algorithm_object(&key, KeyUsage::Sign).unwrap();
1489
1490        let name = Reflect::get(&alg, &"name".into()).unwrap();
1491        assert_eq!(name.as_string().unwrap(), "HMAC");
1492    }
1493
1494    #[test]
1495    fn test_build_verify_algorithm_rs256() {
1496        let alg = Algorithm::Rs256;
1497        let obj = build_verify_algorithm(&alg).unwrap();
1498
1499        let name = Reflect::get(&obj, &"name".into()).unwrap();
1500        assert_eq!(name.as_string().unwrap(), "RSASSA-PKCS1-v1_5");
1501
1502        // RSASSA-PKCS1-v1_5 verify does NOT need hash
1503        let hash = Reflect::get(&obj, &"hash".into()).unwrap();
1504        assert!(hash.is_undefined());
1505    }
1506
1507    #[test]
1508    fn test_build_verify_algorithm_ps256() {
1509        let alg = Algorithm::Ps256;
1510        let obj = build_verify_algorithm(&alg).unwrap();
1511
1512        let name = Reflect::get(&obj, &"name".into()).unwrap();
1513        assert_eq!(name.as_string().unwrap(), "RSA-PSS");
1514
1515        let salt_length = Reflect::get(&obj, &"saltLength".into()).unwrap();
1516        assert_eq!(salt_length.as_f64().unwrap() as u32, 32);
1517    }
1518
1519    #[test]
1520    fn test_build_verify_algorithm_ps384() {
1521        let alg = Algorithm::Ps384;
1522        let obj = build_verify_algorithm(&alg).unwrap();
1523
1524        let salt_length = Reflect::get(&obj, &"saltLength".into()).unwrap();
1525        assert_eq!(salt_length.as_f64().unwrap() as u32, 48);
1526    }
1527
1528    #[test]
1529    fn test_build_verify_algorithm_ps512() {
1530        let alg = Algorithm::Ps512;
1531        let obj = build_verify_algorithm(&alg).unwrap();
1532
1533        let salt_length = Reflect::get(&obj, &"saltLength".into()).unwrap();
1534        assert_eq!(salt_length.as_f64().unwrap() as u32, 64);
1535    }
1536
1537    #[test]
1538    fn test_build_verify_algorithm_es256() {
1539        let alg = Algorithm::Es256;
1540        let obj = build_verify_algorithm(&alg).unwrap();
1541
1542        let name = Reflect::get(&obj, &"name".into()).unwrap();
1543        assert_eq!(name.as_string().unwrap(), "ECDSA");
1544
1545        let hash = Reflect::get(&obj, &"hash".into()).unwrap();
1546        let hash_name = Reflect::get(&hash, &"name".into()).unwrap();
1547        assert_eq!(hash_name.as_string().unwrap(), "SHA-256");
1548    }
1549
1550    #[test]
1551    fn test_build_verify_algorithm_es384() {
1552        let alg = Algorithm::Es384;
1553        let obj = build_verify_algorithm(&alg).unwrap();
1554
1555        let hash = Reflect::get(&obj, &"hash".into()).unwrap();
1556        let hash_name = Reflect::get(&hash, &"name".into()).unwrap();
1557        assert_eq!(hash_name.as_string().unwrap(), "SHA-384");
1558    }
1559
1560    #[test]
1561    fn test_build_verify_algorithm_es512() {
1562        let alg = Algorithm::Es512;
1563        let obj = build_verify_algorithm(&alg).unwrap();
1564
1565        let hash = Reflect::get(&obj, &"hash".into()).unwrap();
1566        let hash_name = Reflect::get(&hash, &"name".into()).unwrap();
1567        assert_eq!(hash_name.as_string().unwrap(), "SHA-512");
1568    }
1569
1570    #[test]
1571    fn test_build_verify_algorithm_hs256() {
1572        let alg = Algorithm::Hs256;
1573        let obj = build_verify_algorithm(&alg).unwrap();
1574
1575        let name = Reflect::get(&obj, &"name".into()).unwrap();
1576        assert_eq!(name.as_string().unwrap(), "HMAC");
1577    }
1578
1579    #[test]
1580    fn test_build_verify_algorithm_unsupported() {
1581        let alg = Algorithm::EdDsa;
1582        let result = build_verify_algorithm(&alg);
1583        assert!(result.is_err());
1584
1585        let alg = Algorithm::Ed25519;
1586        let result = build_verify_algorithm(&alg);
1587        assert!(result.is_err());
1588
1589        let alg = Algorithm::Ed448;
1590        let result = build_verify_algorithm(&alg);
1591        assert!(result.is_err());
1592    }
1593}