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