use js_sys::{Array, Object, Reflect};
use std::convert::TryFrom;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
#[cfg(all(feature = "cloudflare", target_arch = "wasm32"))]
use web_sys::Crypto;
use web_sys::{CryptoKey, SubtleCrypto};
use crate::error::{Error, Result};
use crate::jwk::{Algorithm, EcCurve, Key, KeyOperation, KeyParams};
#[cfg(test)]
use crate::jwks::KeyMatcher;
pub fn get_subtle_crypto() -> Result<SubtleCrypto> {
#[cfg(all(feature = "cloudflare", target_arch = "wasm32"))]
{
let global = js_sys::global();
let crypto_field_name = JsValue::from_str("crypto");
if let Ok(crypto_field) = Reflect::get(&global, &crypto_field_name)
&& let Ok(crypto) = crypto_field.dyn_into::<Crypto>()
{
return Ok(crypto.subtle());
}
}
if let Some(window) = web_sys::window()
&& let Ok(crypto) = window.crypto()
{
return Ok(crypto.subtle());
}
let global = js_sys::global();
if let Ok(worker_scope) = global.dyn_into::<web_sys::WorkerGlobalScope>()
&& let Ok(crypto) = worker_scope.crypto()
{
return Ok(crypto.subtle());
}
Err(Error::WebCrypto(
"crypto API not available in this context".to_string(),
))
}
impl TryFrom<&Key> for web_sys::JsonWebKey {
type Error = Error;
fn try_from(key: &Key) -> Result<Self> {
key.params().validate()?;
validate_webcrypto_support(key)?;
let jwk = web_sys::JsonWebKey::new(key.kty().as_str());
if let Some(alg) = key.alg() {
jwk.set_alg(alg.as_str());
}
if let Some(key_use) = key.key_use() {
jwk.set_use(key_use.as_str());
}
if let Some(key_ops) = key.key_ops() {
let ops = Array::new();
for op in key_ops {
ops.push(&JsValue::from_str(op.as_str()));
}
jwk.set_key_ops(&ops);
}
match key.params() {
KeyParams::Rsa(params) => {
jwk.set_n(¶ms.n.to_base64url());
jwk.set_e(¶ms.e.to_base64url());
if let Some(d) = ¶ms.d {
jwk.set_d(&d.to_base64url());
}
if let Some(p) = ¶ms.p {
jwk.set_p(&p.to_base64url());
}
if let Some(q) = ¶ms.q {
jwk.set_q(&q.to_base64url());
}
if let Some(dp) = ¶ms.dp {
jwk.set_dp(&dp.to_base64url());
}
if let Some(dq) = ¶ms.dq {
jwk.set_dq(&dq.to_base64url());
}
if let Some(qi) = ¶ms.qi {
jwk.set_qi(&qi.to_base64url());
}
}
KeyParams::Ec(params) => {
jwk.set_crv(params.crv.as_str());
jwk.set_x(¶ms.x.to_base64url());
jwk.set_y(¶ms.y.to_base64url());
if let Some(d) = ¶ms.d {
jwk.set_d(&d.to_base64url());
}
}
KeyParams::Symmetric(params) => {
jwk.set_k(¶ms.k.to_base64url());
}
KeyParams::Okp(_) => {
return Err(Error::UnsupportedForWebCrypto {
reason: "OKP keys (Ed25519, Ed448, X25519, X448) are not supported by WebCrypto",
});
}
}
Ok(jwk)
}
}
fn validate_webcrypto_support(key: &Key) -> Result<()> {
match key.params() {
KeyParams::Okp(_) => Err(Error::UnsupportedForWebCrypto {
reason: "OKP keys (Ed25519, Ed448, X25519, X448) are not supported by WebCrypto",
}),
KeyParams::Ec(params) => {
if params.crv == EcCurve::Secp256k1 {
Err(Error::UnsupportedForWebCrypto {
reason: "secp256k1 curve is not supported by WebCrypto",
})
} else {
Ok(())
}
}
KeyParams::Rsa(_) | KeyParams::Symmetric(_) => Ok(()),
}
}
fn build_algorithm_object(key: &Key, usage: KeyUsage) -> Result<Object> {
build_algorithm_object_with_alg(key, usage, None)
}
fn build_algorithm_object_for_alg(key: &Key, alg: &Algorithm, usage: KeyUsage) -> Result<Object> {
build_algorithm_object_with_alg(key, usage, Some(alg))
}
fn build_algorithm_object_with_alg(
key: &Key,
usage: KeyUsage,
alg_override: Option<&Algorithm>,
) -> Result<Object> {
match key.params() {
KeyParams::Rsa(_) => build_rsa_algorithm(key, usage, alg_override),
KeyParams::Ec(params) => {
if let Some(alg) = alg_override {
let expected_curve = match alg {
Algorithm::Es256 => Some(EcCurve::P256),
Algorithm::Es384 => Some(EcCurve::P384),
Algorithm::Es512 => Some(EcCurve::P521),
_ => None,
};
match expected_curve {
Some(curve) if curve != params.crv => {
return Err(Error::WebCrypto(format!(
"algorithm {} requires curve {}, but the key uses {}",
alg.as_str(),
curve.as_str(),
params.crv.as_str(),
)));
}
None => {
return Err(Error::WebCrypto(format!(
"algorithm {} is not supported for EC key import in WebCrypto",
alg.as_str(),
)));
}
_ => {} }
}
build_ec_algorithm(params.crv, usage)
}
KeyParams::Symmetric(_) => build_symmetric_algorithm(key, usage, alg_override),
KeyParams::Okp(_) => Err(Error::UnsupportedForWebCrypto {
reason: "OKP keys are not supported by WebCrypto",
}),
}
}
fn validate_usage_algorithm_compatibility(usage: KeyUsage, alg: &Algorithm) -> Result<()> {
let allowed = match usage {
KeyUsage::Verify => matches!(
alg,
Algorithm::Rs256
| Algorithm::Rs384
| Algorithm::Rs512
| Algorithm::Ps256
| Algorithm::Ps384
| Algorithm::Ps512
| Algorithm::Es256
| Algorithm::Es384
| Algorithm::Es512
| Algorithm::Hs256
| Algorithm::Hs384
| Algorithm::Hs512
),
KeyUsage::Sign => matches!(
alg,
Algorithm::Rs256
| Algorithm::Rs384
| Algorithm::Rs512
| Algorithm::Ps256
| Algorithm::Ps384
| Algorithm::Ps512
| Algorithm::Es256
| Algorithm::Es384
| Algorithm::Es512
| Algorithm::Hs256
| Algorithm::Hs384
| Algorithm::Hs512
),
KeyUsage::Encrypt => matches!(
alg,
Algorithm::RsaOaep
| Algorithm::RsaOaep256
| Algorithm::RsaOaep384
| Algorithm::RsaOaep512
| Algorithm::A128gcm
| Algorithm::A192gcm
| Algorithm::A256gcm
),
KeyUsage::Decrypt => matches!(
alg,
Algorithm::RsaOaep
| Algorithm::RsaOaep256
| Algorithm::RsaOaep384
| Algorithm::RsaOaep512
| Algorithm::A128gcm
| Algorithm::A192gcm
| Algorithm::A256gcm
),
KeyUsage::WrapKey => matches!(
alg,
Algorithm::RsaOaep
| Algorithm::RsaOaep256
| Algorithm::RsaOaep384
| Algorithm::RsaOaep512
| Algorithm::A128kw
| Algorithm::A192kw
| Algorithm::A256kw
),
KeyUsage::UnwrapKey => matches!(
alg,
Algorithm::RsaOaep
| Algorithm::RsaOaep256
| Algorithm::RsaOaep384
| Algorithm::RsaOaep512
| Algorithm::A128kw
| Algorithm::A192kw
| Algorithm::A256kw
),
};
if allowed {
Ok(())
} else {
Err(Error::UnsupportedForWebCrypto {
reason: "algorithm is not compatible with requested key usage",
})
}
}
fn validate_key_for_webcrypto_usage_with_alg(
key: &Key,
usage: KeyUsage,
alg: &Algorithm,
) -> Result<()> {
validate_usage_algorithm_compatibility(usage, alg)?;
key.validate_for_use_with_alg_override(alg, [key_operation_for_usage(usage)])
}
fn validate_key_for_webcrypto_usage(key: &Key, usage: KeyUsage) -> Result<()> {
let requested_op = key_operation_for_usage(usage);
if let Some(alg) = key.alg() {
validate_usage_algorithm_compatibility(usage, alg)?;
key.validate_for_use(alg, [requested_op])?;
return Ok(());
}
key.validate()?;
key.check_operation_capability(std::slice::from_ref(&requested_op))?;
key.validate_operation_intent_for_all(std::slice::from_ref(&requested_op))?;
Ok(())
}
fn key_operation_for_usage(usage: KeyUsage) -> KeyOperation {
match usage {
KeyUsage::Sign => KeyOperation::Sign,
KeyUsage::Verify => KeyOperation::Verify,
KeyUsage::Encrypt => KeyOperation::Encrypt,
KeyUsage::Decrypt => KeyOperation::Decrypt,
KeyUsage::WrapKey => KeyOperation::WrapKey,
KeyUsage::UnwrapKey => KeyOperation::UnwrapKey,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum KeyUsage {
Sign,
Verify,
Encrypt,
Decrypt,
WrapKey,
UnwrapKey,
}
fn usage_strings_for_usage(usage: KeyUsage) -> &'static [&'static str] {
match usage {
KeyUsage::Sign => &["sign"],
KeyUsage::Verify => &["verify"],
KeyUsage::Encrypt => &["encrypt"],
KeyUsage::Decrypt => &["decrypt"],
KeyUsage::WrapKey => &["wrapKey"],
KeyUsage::UnwrapKey => &["unwrapKey"],
}
}
fn build_rsa_algorithm(
key: &Key,
_usage: KeyUsage,
alg_override: Option<&Algorithm>,
) -> Result<Object> {
let obj = Object::new();
let effective_alg = alg_override.or(key.alg());
let (alg_name, hash) = match effective_alg {
Some(Algorithm::Rs256) => ("RSASSA-PKCS1-v1_5", "SHA-256"),
Some(Algorithm::Rs384) => ("RSASSA-PKCS1-v1_5", "SHA-384"),
Some(Algorithm::Rs512) => ("RSASSA-PKCS1-v1_5", "SHA-512"),
Some(Algorithm::Ps256) => ("RSA-PSS", "SHA-256"),
Some(Algorithm::Ps384) => ("RSA-PSS", "SHA-384"),
Some(Algorithm::Ps512) => ("RSA-PSS", "SHA-512"),
Some(Algorithm::RsaOaep) => ("RSA-OAEP", "SHA-1"),
Some(Algorithm::RsaOaep256) => ("RSA-OAEP", "SHA-256"),
Some(Algorithm::RsaOaep384) => ("RSA-OAEP", "SHA-384"),
Some(Algorithm::RsaOaep512) => ("RSA-OAEP", "SHA-512"),
_ => {
return Err(Error::WebCrypto(
"RSA key import requires an algorithm to determine the hash function; \
set the `alg` field on the key or use an import function that accepts \
an explicit algorithm (e.g., `import_verify_key_for_alg`)"
.to_string(),
));
}
};
Reflect::set(&obj, &"name".into(), &alg_name.into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
let hash_obj = Object::new();
Reflect::set(&hash_obj, &"name".into(), &hash.into())
.map_err(|e| Error::WebCrypto(format!("failed to set hash name: {:?}", e)))?;
Reflect::set(&obj, &"hash".into(), &hash_obj.into())
.map_err(|e| Error::WebCrypto(format!("failed to set hash: {:?}", e)))?;
Ok(obj)
}
fn build_ec_algorithm(curve: EcCurve, usage: KeyUsage) -> Result<Object> {
let obj = Object::new();
let alg_name = match usage {
KeyUsage::Sign | KeyUsage::Verify => "ECDSA",
KeyUsage::Encrypt | KeyUsage::Decrypt | KeyUsage::WrapKey | KeyUsage::UnwrapKey => {
return Err(Error::UnsupportedForWebCrypto {
reason: "EC key derivation (ECDH) and direct encrypt/decrypt/wrap/unwrap \
are not yet supported by this library; \
only ECDSA sign/verify is currently implemented for EC keys",
});
}
};
Reflect::set(&obj, &"name".into(), &alg_name.into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
let named_curve = match curve {
EcCurve::P256 => "P-256",
EcCurve::P384 => "P-384",
EcCurve::P521 => "P-521",
EcCurve::Secp256k1 => {
return Err(Error::UnsupportedForWebCrypto {
reason: "secp256k1 curve is not supported by WebCrypto",
});
}
};
Reflect::set(&obj, &"namedCurve".into(), &named_curve.into())
.map_err(|e| Error::WebCrypto(format!("failed to set namedCurve: {:?}", e)))?;
Ok(obj)
}
fn build_symmetric_algorithm(
key: &Key,
_usage: KeyUsage,
alg_override: Option<&Algorithm>,
) -> Result<Object> {
let obj = Object::new();
let effective_alg = alg_override.or(key.alg());
let (alg_name, extra) = match effective_alg {
Some(Algorithm::Hs256) => ("HMAC", Some(("hash", "SHA-256"))),
Some(Algorithm::Hs384) => ("HMAC", Some(("hash", "SHA-384"))),
Some(Algorithm::Hs512) => ("HMAC", Some(("hash", "SHA-512"))),
Some(Algorithm::A128kw) | Some(Algorithm::A192kw) | Some(Algorithm::A256kw) => {
("AES-KW", None)
}
Some(Algorithm::A128gcm) | Some(Algorithm::A192gcm) | Some(Algorithm::A256gcm) => {
("AES-GCM", None)
}
Some(Algorithm::A128cbcHs256)
| Some(Algorithm::A192cbcHs384)
| Some(Algorithm::A256cbcHs512) => {
return Err(Error::UnsupportedForWebCrypto {
reason: "AES-CBC-HS algorithms (A128CBC-HS256, A192CBC-HS384, A256CBC-HS512) \
are composite authenticated encryption algorithms requiring split-key \
handling (AES-CBC + HMAC) which WebCrypto does not natively support",
});
}
_ => {
return Err(Error::WebCrypto(
"symmetric key import requires an algorithm to determine the operation; \
set the `alg` field on the key or use an import function that accepts \
an explicit algorithm (e.g., `import_verify_key_for_alg`)"
.to_string(),
));
}
};
Reflect::set(&obj, &"name".into(), &alg_name.into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
if let Some((prop, val)) = extra {
debug_assert_eq!(prop, "hash", "only HMAC uses extra parameters");
let hash_obj = Object::new();
Reflect::set(&hash_obj, &"name".into(), &val.into())
.map_err(|e| Error::WebCrypto(format!("failed to set hash name: {:?}", e)))?;
Reflect::set(&obj, &"hash".into(), &hash_obj.into())
.map_err(|e| Error::WebCrypto(format!("failed to set hash: {:?}", e)))?;
}
Ok(obj)
}
pub fn build_verify_algorithm(alg: &Algorithm) -> Result<Object> {
let obj = Object::new();
match alg {
Algorithm::Rs256 | Algorithm::Rs384 | Algorithm::Rs512 => {
Reflect::set(&obj, &"name".into(), &"RSASSA-PKCS1-v1_5".into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
}
Algorithm::Ps256 => {
Reflect::set(&obj, &"name".into(), &"RSA-PSS".into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
Reflect::set(&obj, &"saltLength".into(), &32.into())
.map_err(|e| Error::WebCrypto(format!("failed to set saltLength: {:?}", e)))?;
}
Algorithm::Ps384 => {
Reflect::set(&obj, &"name".into(), &"RSA-PSS".into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
Reflect::set(&obj, &"saltLength".into(), &48.into())
.map_err(|e| Error::WebCrypto(format!("failed to set saltLength: {:?}", e)))?;
}
Algorithm::Ps512 => {
Reflect::set(&obj, &"name".into(), &"RSA-PSS".into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
Reflect::set(&obj, &"saltLength".into(), &64.into())
.map_err(|e| Error::WebCrypto(format!("failed to set saltLength: {:?}", e)))?;
}
Algorithm::Es256 | Algorithm::Es384 | Algorithm::Es512 => {
Reflect::set(&obj, &"name".into(), &"ECDSA".into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
let hash = match alg {
Algorithm::Es256 => "SHA-256",
Algorithm::Es384 => "SHA-384",
Algorithm::Es512 => "SHA-512",
_ => unreachable!(),
};
let hash_obj = Object::new();
Reflect::set(&hash_obj, &"name".into(), &hash.into())
.map_err(|e| Error::WebCrypto(format!("failed to set hash name: {:?}", e)))?;
Reflect::set(&obj, &"hash".into(), &hash_obj.into())
.map_err(|e| Error::WebCrypto(format!("failed to set hash: {:?}", e)))?;
}
Algorithm::Hs256 | Algorithm::Hs384 | Algorithm::Hs512 => {
Reflect::set(&obj, &"name".into(), &"HMAC".into())
.map_err(|e| Error::WebCrypto(format!("failed to set algorithm name: {:?}", e)))?;
}
_ => {
return Err(Error::UnsupportedForWebCrypto {
reason: "algorithm not supported for WebCrypto verify",
});
}
}
Ok(obj)
}
pub async fn import_verify_key(key: &Key) -> Result<CryptoKey> {
import_key_for_usage(key, KeyUsage::Verify).await
}
pub async fn import_sign_key(key: &Key) -> Result<CryptoKey> {
import_key_for_usage(key, KeyUsage::Sign).await
}
pub async fn import_encrypt_key(key: &Key) -> Result<CryptoKey> {
if matches!(key.params(), KeyParams::Ec(_)) {
return Err(Error::UnsupportedForWebCrypto {
reason: "EC keys do not support direct encryption; \
use ECDH key agreement (deriveKey/deriveBits) instead",
});
}
import_key_for_usage(key, KeyUsage::Encrypt).await
}
pub async fn import_decrypt_key(key: &Key) -> Result<CryptoKey> {
if matches!(key.params(), KeyParams::Ec(_)) {
return Err(Error::UnsupportedForWebCrypto {
reason: "EC keys do not support direct decryption; \
use ECDH key agreement (deriveKey/deriveBits) instead",
});
}
import_key_for_usage(key, KeyUsage::Decrypt).await
}
pub async fn import_wrap_key(key: &Key) -> Result<CryptoKey> {
if matches!(key.params(), KeyParams::Ec(_)) {
return Err(Error::UnsupportedForWebCrypto {
reason: "EC keys do not support direct key wrapping; \
use ECDH key agreement (deriveKey/deriveBits) instead",
});
}
import_key_for_usage(key, KeyUsage::WrapKey).await
}
pub async fn import_unwrap_key(key: &Key) -> Result<CryptoKey> {
if matches!(key.params(), KeyParams::Ec(_)) {
return Err(Error::UnsupportedForWebCrypto {
reason: "EC keys do not support direct key unwrapping; \
use ECDH key agreement (deriveKey/deriveBits) instead",
});
}
import_key_for_usage(key, KeyUsage::UnwrapKey).await
}
pub async fn import_verify_key_for_alg(key: &Key, alg: &Algorithm) -> Result<CryptoKey> {
import_key_for_usage_with_alg(key, KeyUsage::Verify, alg).await
}
pub async fn import_sign_key_for_alg(key: &Key, alg: &Algorithm) -> Result<CryptoKey> {
import_key_for_usage_with_alg(key, KeyUsage::Sign, alg).await
}
pub async fn import_key_for_usage(key: &Key, usage: KeyUsage) -> Result<CryptoKey> {
validate_key_for_webcrypto_usage(key, usage)?;
let jwk = web_sys::JsonWebKey::try_from(key)?;
let algorithm = build_algorithm_object(key, usage)?;
import_crypto_key(jwk, &algorithm, usage_strings_for_usage(usage)).await
}
pub async fn import_key_for_usage_with_alg(
key: &Key,
usage: KeyUsage,
alg: &Algorithm,
) -> Result<CryptoKey> {
validate_key_for_webcrypto_usage_with_alg(key, usage, alg)?;
let jwk = web_sys::JsonWebKey::try_from(key)?;
jwk.set_alg(alg.as_str());
let algorithm = build_algorithm_object_for_alg(key, alg, usage)?;
import_crypto_key(jwk, &algorithm, usage_strings_for_usage(usage)).await
}
async fn import_crypto_key(
jwk: web_sys::JsonWebKey,
algorithm: &Object,
usages: &[&str],
) -> Result<CryptoKey> {
let key_usages = Array::new();
for u in usages {
key_usages.push(&JsValue::from_str(u));
}
let subtle = get_subtle_crypto()?;
let promise = subtle
.import_key_with_object("jwk", &jwk.into(), algorithm, false, &key_usages)
.map_err(|e| Error::WebCrypto(format!("import_key failed: {:?}", e)))?;
let result = JsFuture::from(promise)
.await
.map_err(|e| Error::WebCrypto(format!("import_key promise rejected: {:?}", e)))?;
Ok(result.unchecked_into())
}
impl Key {
#[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
pub fn is_web_crypto_compatible(&self) -> bool {
validate_webcrypto_support(self).is_ok()
}
#[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
pub async fn import_as_verify_key(&self) -> Result<CryptoKey> {
import_verify_key(self).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
pub async fn import_as_sign_key(&self) -> Result<CryptoKey> {
import_sign_key(self).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
pub async fn import_as_encrypt_key(&self) -> Result<CryptoKey> {
import_encrypt_key(self).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
pub async fn import_as_decrypt_key(&self) -> Result<CryptoKey> {
import_decrypt_key(self).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
pub async fn import_as_verify_key_for_alg(&self, alg: &Algorithm) -> Result<CryptoKey> {
import_verify_key_for_alg(self, alg).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
pub async fn import_as_sign_key_for_alg(&self, alg: &Algorithm) -> Result<CryptoKey> {
import_sign_key_for_alg(self, alg).await
}
}
#[cfg(test)]
mod validation_tests {
use super::*;
use crate::jwks::KeySet;
const RFC_RSA_PUBLIC_KEY: &str = r#"{
"kty": "RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e": "AQAB"
}"#;
const RFC_EC_P256_PUBLIC_KEY: &str = r#"{
"kty": "EC",
"crv": "P-256",
"x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}"#;
const EC_SECP256K1_KEY: &str = r#"{
"kty": "EC",
"crv": "secp256k1",
"x": "WbbXwISW8TLWM3IDLGm1cX_3IrYgWl_bzcLe0tSCDj4",
"y": "KGk8DRQHPeV4S3Oq2jVJLNSV_3ngGgbfHTKsS5aw30c"
}"#;
const OKP_ED25519_KEY: &str = r#"{
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
}"#;
const SYMMETRIC_KEY: &str = r#"{
"kty": "oct",
"k": "AyM32w-8O0TGsGDYX0MlWy-9XQP-xrryrP7gkXKfY5WhoLxmT3fzfVr7LXqgDDFSfowWBY-u6bSH5f9kBZ_n7Q",
"alg": "HS256"
}"#;
#[test]
fn test_validate_rsa_supported() {
let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
assert!(validate_webcrypto_support(&key).is_ok());
}
#[test]
fn test_validate_ec_p256_supported() {
let key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
assert!(validate_webcrypto_support(&key).is_ok());
}
#[test]
fn test_validate_symmetric_supported() {
let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
assert!(validate_webcrypto_support(&key).is_ok());
}
#[test]
fn test_validate_okp_unsupported() {
let key: Key = serde_json::from_str(OKP_ED25519_KEY).unwrap();
let result = validate_webcrypto_support(&key);
assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
}
#[test]
fn test_validate_secp256k1_unsupported() {
let key: Key = serde_json::from_str(EC_SECP256K1_KEY).unwrap();
let result = validate_webcrypto_support(&key);
assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
}
#[test]
fn test_is_web_crypto_compatible_rsa() {
let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
assert!(key.is_web_crypto_compatible());
}
#[test]
fn test_is_web_crypto_compatible_okp() {
let key: Key = serde_json::from_str(OKP_ED25519_KEY).unwrap();
assert!(!key.is_web_crypto_compatible());
}
#[test]
fn test_is_web_crypto_compatible_secp256k1() {
let key: Key = serde_json::from_str(EC_SECP256K1_KEY).unwrap();
assert!(!key.is_web_crypto_compatible());
}
#[test]
fn test_usage_algorithm_compatibility_rejects_mismatch() {
let result = validate_usage_algorithm_compatibility(KeyUsage::Encrypt, &Algorithm::Rs256);
assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
let result =
validate_usage_algorithm_compatibility(KeyUsage::Verify, &Algorithm::RsaOaep256);
assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
}
#[test]
fn test_usage_algorithm_compatibility_accepts_valid_pairs() {
assert!(
validate_usage_algorithm_compatibility(KeyUsage::Verify, &Algorithm::Rs256).is_ok()
);
assert!(
validate_usage_algorithm_compatibility(KeyUsage::Encrypt, &Algorithm::RsaOaep256)
.is_ok()
);
assert!(
validate_usage_algorithm_compatibility(KeyUsage::WrapKey, &Algorithm::A128kw).is_ok()
);
}
#[test]
fn test_import_usage_validation_enforces_metadata_when_alg_present() {
let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
let key = key.with_key_ops([crate::KeyOperation::Sign, crate::KeyOperation::Sign]);
let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Sign);
assert!(result.is_err(), "duplicate key_ops must be rejected");
}
#[test]
fn test_validate_key_for_webcrypto_usage_rejects_incompatible_use() {
let json = r#"{
"kty": "RSA",
"use": "enc",
"alg": "RS256",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e": "AQAB"
}"#;
let key: Key = serde_json::from_str(json).unwrap();
let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Verify);
assert!(result.is_err());
}
#[test]
fn test_validate_key_for_webcrypto_usage_allows_missing_optional_metadata() {
let json = r#"{
"kty": "RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e": "AQAB"
}"#;
let key: Key = serde_json::from_str(json).unwrap();
let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Verify);
assert!(result.is_ok());
}
#[test]
fn test_validate_key_for_webcrypto_usage_rejects_incompatible_use_without_alg() {
let json = r#"{
"kty": "RSA",
"use": "enc",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e": "AQAB"
}"#;
let key: Key = serde_json::from_str(json).unwrap();
let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Verify);
assert!(result.is_err());
}
#[test]
fn test_validate_key_for_webcrypto_usage_with_alg_allows_declared_algorithm_override() {
let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
let result =
validate_key_for_webcrypto_usage_with_alg(&key, KeyUsage::Verify, &Algorithm::Hs384);
assert!(
result.is_ok(),
"explicit alg override should skip declared-algorithm mismatch check: {result:?}"
);
}
#[test]
fn test_validate_key_for_webcrypto_usage_rejects_public_sign_key_without_alg() {
let json = r#"{
"kty": "RSA",
"use": "sig",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e": "AQAB"
}"#;
let key: Key = serde_json::from_str(json).unwrap();
let result = validate_key_for_webcrypto_usage(&key, KeyUsage::Sign);
assert!(result.is_err());
}
#[test]
fn test_select_verify_key_strict_for_web_crypto_flow() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "rsa-verify", "use": "sig", "alg": "RS256", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let key = jwks
.selector(&[Algorithm::Rs256])
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Rs256).with_kid("rsa-verify"))
.unwrap();
assert_eq!(key.kid(), Some("rsa-verify"));
}
#[test]
fn test_select_signing_key_strict_for_web_crypto_flow() {
let json = r#"{"keys": [
{"kty": "EC", "kid": "ec-sign", "use": "sig", "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let key = jwks
.selector(&[])
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Es256).with_kid("ec-sign"))
.unwrap();
assert_eq!(key.kid(), Some("ec-sign"));
}
}
#[cfg(all(test, target_arch = "wasm32"))]
mod tests {
use super::*;
const RFC_RSA_PUBLIC_KEY: &str = r#"{
"kty": "RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e": "AQAB"
}"#;
const RFC_EC_P256_PUBLIC_KEY: &str = r#"{
"kty": "EC",
"crv": "P-256",
"x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}"#;
const EC_SECP256K1_KEY: &str = r#"{
"kty": "EC",
"crv": "secp256k1",
"x": "WbbXwISW8TLWM3IDLGm1cX_3IrYgWl_bzcLe0tSCDj4",
"y": "KGk8DRQHPeV4S3Oq2jVJLNSV_3ngGgbfHTKsS5aw30c"
}"#;
const OKP_ED25519_KEY: &str = r#"{
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
}"#;
const SYMMETRIC_KEY: &str = r#"{
"kty": "oct",
"k": "AyM32w-8O0TGsGDYX0MlWy-9XQP-xrryrP7gkXKfY5WhoLxmT3fzfVr7LXqgDDFSfowWBY-u6bSH5f9kBZ_n7Q",
"alg": "HS256"
}"#;
#[test]
fn test_rsa_key_to_json_web_key() {
let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
let jwk = web_sys::JsonWebKey::try_from(&key).unwrap();
assert_eq!(jwk.get_kty(), "RSA");
assert!(jwk.get_n().is_some());
assert!(jwk.get_e().is_some());
assert!(jwk.get_d().is_none()); }
#[test]
fn test_ec_p256_key_to_json_web_key() {
let key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
let jwk = web_sys::JsonWebKey::try_from(&key).unwrap();
assert_eq!(jwk.get_kty(), "EC");
assert_eq!(jwk.get_crv(), Some("P-256".to_string()));
assert!(jwk.get_x().is_some());
assert!(jwk.get_y().is_some());
}
#[test]
fn test_symmetric_key_to_json_web_key() {
let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
let jwk = web_sys::JsonWebKey::try_from(&key).unwrap();
assert_eq!(jwk.get_kty(), "oct");
assert!(jwk.get_k().is_some());
}
#[test]
fn test_okp_key_unsupported() {
let key: Key = serde_json::from_str(OKP_ED25519_KEY).unwrap();
let result = web_sys::JsonWebKey::try_from(&key);
assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
}
#[test]
fn test_secp256k1_key_unsupported() {
let key: Key = serde_json::from_str(EC_SECP256K1_KEY).unwrap();
let result = web_sys::JsonWebKey::try_from(&key);
assert!(matches!(result, Err(Error::UnsupportedForWebCrypto { .. })));
}
#[test]
fn test_is_web_crypto_compatible() {
let rsa_key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
assert!(rsa_key.is_web_crypto_compatible());
let ec_key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
assert!(ec_key.is_web_crypto_compatible());
let okp_key: Key = serde_json::from_str(OKP_ED25519_KEY).unwrap();
assert!(!okp_key.is_web_crypto_compatible());
let secp256k1_key: Key = serde_json::from_str(EC_SECP256K1_KEY).unwrap();
assert!(!secp256k1_key.is_web_crypto_compatible());
}
#[test]
fn test_validate_webcrypto_support_rsa() {
let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
assert!(validate_webcrypto_support(&key).is_ok());
}
#[test]
fn test_validate_webcrypto_support_ec_p256() {
let key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
assert!(validate_webcrypto_support(&key).is_ok());
}
#[test]
fn test_validate_webcrypto_support_symmetric() {
let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
assert!(validate_webcrypto_support(&key).is_ok());
}
#[test]
fn test_build_rsa_algorithm_with_explicit_alg() {
let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
let alg =
build_algorithm_object_for_alg(&key, &Algorithm::Rs256, KeyUsage::Verify).unwrap();
let name = Reflect::get(&alg, &"name".into()).unwrap();
assert_eq!(name.as_string().unwrap(), "RSASSA-PKCS1-v1_5");
}
#[test]
fn test_build_rsa_algorithm_without_alg_errors() {
let key: Key = serde_json::from_str(RFC_RSA_PUBLIC_KEY).unwrap();
let result = build_algorithm_object(&key, KeyUsage::Verify);
assert!(result.is_err(), "RSA key without alg should error");
}
#[test]
fn test_build_ec_algorithm() {
let key: Key = serde_json::from_str(RFC_EC_P256_PUBLIC_KEY).unwrap();
let alg = build_algorithm_object(&key, KeyUsage::Verify).unwrap();
let name = Reflect::get(&alg, &"name".into()).unwrap();
assert_eq!(name.as_string().unwrap(), "ECDSA");
let curve = Reflect::get(&alg, &"namedCurve".into()).unwrap();
assert_eq!(curve.as_string().unwrap(), "P-256");
}
#[test]
fn test_build_hmac_algorithm() {
let key: Key = serde_json::from_str(SYMMETRIC_KEY).unwrap();
let alg = build_algorithm_object(&key, KeyUsage::Sign).unwrap();
let name = Reflect::get(&alg, &"name".into()).unwrap();
assert_eq!(name.as_string().unwrap(), "HMAC");
}
#[test]
fn test_build_verify_algorithm_rs256() {
let alg = Algorithm::Rs256;
let obj = build_verify_algorithm(&alg).unwrap();
let name = Reflect::get(&obj, &"name".into()).unwrap();
assert_eq!(name.as_string().unwrap(), "RSASSA-PKCS1-v1_5");
let hash = Reflect::get(&obj, &"hash".into()).unwrap();
assert!(hash.is_undefined());
}
#[test]
fn test_build_verify_algorithm_ps256() {
let alg = Algorithm::Ps256;
let obj = build_verify_algorithm(&alg).unwrap();
let name = Reflect::get(&obj, &"name".into()).unwrap();
assert_eq!(name.as_string().unwrap(), "RSA-PSS");
let salt_length = Reflect::get(&obj, &"saltLength".into()).unwrap();
assert_eq!(salt_length.as_f64().unwrap() as u32, 32);
}
#[test]
fn test_build_verify_algorithm_ps384() {
let alg = Algorithm::Ps384;
let obj = build_verify_algorithm(&alg).unwrap();
let salt_length = Reflect::get(&obj, &"saltLength".into()).unwrap();
assert_eq!(salt_length.as_f64().unwrap() as u32, 48);
}
#[test]
fn test_build_verify_algorithm_ps512() {
let alg = Algorithm::Ps512;
let obj = build_verify_algorithm(&alg).unwrap();
let salt_length = Reflect::get(&obj, &"saltLength".into()).unwrap();
assert_eq!(salt_length.as_f64().unwrap() as u32, 64);
}
#[test]
fn test_build_verify_algorithm_es256() {
let alg = Algorithm::Es256;
let obj = build_verify_algorithm(&alg).unwrap();
let name = Reflect::get(&obj, &"name".into()).unwrap();
assert_eq!(name.as_string().unwrap(), "ECDSA");
let hash = Reflect::get(&obj, &"hash".into()).unwrap();
let hash_name = Reflect::get(&hash, &"name".into()).unwrap();
assert_eq!(hash_name.as_string().unwrap(), "SHA-256");
}
#[test]
fn test_build_verify_algorithm_es384() {
let alg = Algorithm::Es384;
let obj = build_verify_algorithm(&alg).unwrap();
let hash = Reflect::get(&obj, &"hash".into()).unwrap();
let hash_name = Reflect::get(&hash, &"name".into()).unwrap();
assert_eq!(hash_name.as_string().unwrap(), "SHA-384");
}
#[test]
fn test_build_verify_algorithm_es512() {
let alg = Algorithm::Es512;
let obj = build_verify_algorithm(&alg).unwrap();
let hash = Reflect::get(&obj, &"hash".into()).unwrap();
let hash_name = Reflect::get(&hash, &"name".into()).unwrap();
assert_eq!(hash_name.as_string().unwrap(), "SHA-512");
}
#[test]
fn test_build_verify_algorithm_hs256() {
let alg = Algorithm::Hs256;
let obj = build_verify_algorithm(&alg).unwrap();
let name = Reflect::get(&obj, &"name".into()).unwrap();
assert_eq!(name.as_string().unwrap(), "HMAC");
}
#[test]
fn test_build_verify_algorithm_unsupported() {
let alg = Algorithm::EdDsa;
let result = build_verify_algorithm(&alg);
assert!(result.is_err());
let alg = Algorithm::Ed25519;
let result = build_verify_algorithm(&alg);
assert!(result.is_err());
let alg = Algorithm::Ed448;
let result = build_verify_algorithm(&alg);
assert!(result.is_err());
}
}