use std::io; use ed25519_dalek::Signer as _; use p256::elliptic_curve::sec1::ToEncodedPoint; use pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey}; use rand::RngCore; use rand::rngs::OsRng; use rsa::{ RsaPrivateKey, RsaPublicKey, hazmat::{rsa_decrypt_and_check as rsa_decrypt_raw, rsa_encrypt as rsa_encrypt_raw}, traits::{PrivateKeyParts, PublicKeyParts}, }; use sha2::Digest as _; use signature::Verifier; use subtle::ConstantTimeEq as _; use zeroize::{Zeroize, Zeroizing}; pub(crate) struct KeyPairDer { pub private: Zeroizing>, pub public: Vec, pub length: usize, pub exponent: Vec, } fn unsupported() -> io::Error { io::Error::other("Unsupported asymmetric crypto operation") } fn hash_bytes(name: &str, data: &[u8]) -> io::Result> { match name { "SHA-1" => Ok(sha1::Sha1::digest(data).to_vec()), "SHA-256" => Ok(sha2::Sha256::digest(data).to_vec()), "SHA-384" => Ok(sha2::Sha384::digest(data).to_vec()), "SHA-512" => Ok(sha2::Sha512::digest(data).to_vec()), _ => Err(unsupported()), } } pub(crate) fn generate( name: &str, length: usize, exponent: &[u8], curve: &str, ) -> io::Result { let mut rng = OsRng; match name { "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" => { if !(512..=8192).contains(&length) || !length.is_multiple_of(8) || exponent.is_empty() { return Err(io::Error::other("Invalid RSA key parameters")); } let exp = rsa::BigUint::from_bytes_be(exponent); let exponent_bytes = exp.to_bytes_be(); if exp < rsa::BigUint::from(3u8) || exponent_bytes.last().is_none_or(|byte| byte % 2 == 0) { return Err(io::Error::other("Invalid RSA public exponent")); } let private = RsaPrivateKey::new_with_exp(&mut rng, length, &exp).map_err(io::Error::other)?; let public = RsaPublicKey::from(&private); let private_der = private.to_pkcs8_der().map_err(io::Error::other)?; let public_der = public.to_public_key_der().map_err(io::Error::other)?; Ok(KeyPairDer { private: Zeroizing::new(private_der.as_bytes().to_vec()), public: public_der.as_bytes().to_vec(), length, exponent: exp.to_bytes_be(), }) } "ECDSA" | "ECDH" => match curve { "P-256" => { let private = p256::SecretKey::random(&mut rng); let public = private.public_key(); Ok(KeyPairDer { private: Zeroizing::new( private .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), ), public: public .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 256, exponent: Vec::new(), }) } "P-384" => { let private = p384::SecretKey::random(&mut rng); let public = private.public_key(); Ok(KeyPairDer { private: Zeroizing::new( private .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), ), public: public .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 384, exponent: Vec::new(), }) } "P-521" => { let private = p521::SecretKey::random(&mut rng); let public = private.public_key(); Ok(KeyPairDer { private: Zeroizing::new( private .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), ), public: public .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 521, exponent: Vec::new(), }) } _ => Err(unsupported()), }, "ED25519" => { let private = ed25519_dalek::SigningKey::generate(&mut rng); let public = private.verifying_key(); Ok(KeyPairDer { private: Zeroizing::new( private .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), ), public: public .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 256, exponent: Vec::new(), }) } "X25519" => { let mut secret = [0u8; 32]; rng.fill_bytes(&mut secret); let private = x25519_dalek::StaticSecret::from(secret); secret.zeroize(); let public = x25519_dalek::PublicKey::from(&private); let private_der = Zeroizing::new(private.to_bytes().to_vec()); Ok(KeyPairDer { private: private_der, public: public.to_bytes().to_vec(), length: 256, exponent: Vec::new(), }) } _ => Err(unsupported()), } } pub(crate) struct ImportedKey { pub bytes: Vec, pub length: usize, pub exponent: Vec, pub curve: String, } fn x25519_der(private: bool, key: &[u8]) -> io::Result> { let prefix: &[u8] = if private { &[ 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20, ] } else { &[ 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00, ] }; if key.len() != 32 { return Err(unsupported()); } let mut der = prefix.to_vec(); der.extend_from_slice(key); Ok(der) } fn x25519_from_der(private: bool, der: &[u8]) -> io::Result> { let prefix: &[u8] = if private { &[ 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20, ] } else { &[ 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00, ] }; if der.len() != prefix.len() + 32 || !der.starts_with(prefix) { return Err(unsupported()); } Ok(der[prefix.len()..].to_vec()) } fn ec_public_sec1(der: &[u8]) -> io::Result> { let info = pkcs8::spki::SubjectPublicKeyInfoRef::try_from(der).map_err(io::Error::other)?; info.subject_public_key .as_bytes() .map(ToOwned::to_owned) .ok_or_else(unsupported) } pub(crate) fn import_key( format: &str, data: &[u8], name: &str, curve: &str, ) -> io::Result { let private = format == "pkcs8"; if format == "raw" && private { return Err(unsupported()); } if !matches!(format, "raw" | "spki" | "pkcs8") { return Err(unsupported()); } match name { "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" => { let (bytes, length, exponent) = if private { let key = RsaPrivateKey::from_pkcs8_der(data).map_err(io::Error::other)?; if !(512..=8192).contains(&key.n().bits()) { return Err(io::Error::other("Invalid RSA modulus size")); } let bytes = key .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(); (bytes, key.n().bits(), key.e().to_bytes_be()) } else { let key = RsaPublicKey::from_public_key_der(data).map_err(io::Error::other)?; if !(512..=8192).contains(&key.n().bits()) { return Err(io::Error::other("Invalid RSA modulus size")); } let bytes = key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(); (bytes, key.n().bits(), key.e().to_bytes_be()) }; Ok(ImportedKey { bytes, length, exponent, curve: String::new(), }) } "ECDSA" | "ECDH" => match (curve, format) { ("P-256", "raw") => { let key = p256::PublicKey::from_sec1_bytes(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 256, exponent: Vec::new(), curve: curve.into(), }) } ("P-256", "pkcs8") => { let key = p256::SecretKey::from_pkcs8_der(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 256, exponent: Vec::new(), curve: curve.into(), }) } ("P-256", "spki") => { let key = p256::PublicKey::from_public_key_der(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 256, exponent: Vec::new(), curve: curve.into(), }) } ("P-384", "raw") => { let key = p384::PublicKey::from_sec1_bytes(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 384, exponent: Vec::new(), curve: curve.into(), }) } ("P-384", "pkcs8") => { let key = p384::SecretKey::from_pkcs8_der(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 384, exponent: Vec::new(), curve: curve.into(), }) } ("P-384", "spki") => { let key = p384::PublicKey::from_public_key_der(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 384, exponent: Vec::new(), curve: curve.into(), }) } ("P-521", "raw") => { let key = p521::PublicKey::from_sec1_bytes(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 521, exponent: Vec::new(), curve: curve.into(), }) } ("P-521", "pkcs8") => { let key = p521::SecretKey::from_pkcs8_der(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 521, exponent: Vec::new(), curve: curve.into(), }) } ("P-521", "spki") => { let sec1 = ec_public_sec1(data)?; let key = p521::PublicKey::from_sec1_bytes(&sec1).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 521, exponent: Vec::new(), curve: curve.into(), }) } _ => Err(unsupported()), }, "ED25519" => match format { "raw" => { let public: [u8; 32] = data .try_into() .map_err(|_| io::Error::other("Invalid key length"))?; let key = ed25519_dalek::VerifyingKey::from_bytes(&public).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 256, exponent: Vec::new(), curve: String::new(), }) } "spki" => { let key = ed25519_dalek::VerifyingKey::from_public_key_der(data) .map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 256, exponent: Vec::new(), curve: String::new(), }) } "pkcs8" => { let key = ed25519_dalek::SigningKey::from_pkcs8_der(data).map_err(io::Error::other)?; Ok(ImportedKey { bytes: key .to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length: 256, exponent: Vec::new(), curve: String::new(), }) } _ => Err(unsupported()), }, "X25519" => { let bytes = match format { "raw" | "spki" if !private => { if format == "raw" { data.to_vec() } else { x25519_from_der(false, data)? } } "pkcs8" if private => x25519_from_der(true, data)?, _ => return Err(unsupported()), }; if bytes.len() != 32 { return Err(unsupported()); } Ok(ImportedKey { bytes, length: 256, exponent: Vec::new(), curve: String::new(), }) } _ => Err(unsupported()), } } pub(crate) fn export_key( format: &str, data: &[u8], name: &str, key_type: &str, curve: &str, ) -> io::Result> { if (format == "raw" && key_type == "private") || (format == "pkcs8" && key_type != "private") || (format == "spki" && key_type != "public") { return Err(io::Error::other( "InvalidAccessError: key type is incompatible with key format", )); } match (format, name, key_type) { ( "pkcs8", "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" | "ECDSA" | "ECDH" | "ED25519", "private", ) => Ok(data.to_vec()), ( "spki", "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" | "ECDSA" | "ECDH" | "ED25519", "public", ) => Ok(data.to_vec()), ("raw", "ECDSA" | "ECDH", "public") => match curve { "P-256" => Ok(p256::PublicKey::from_public_key_der(data) .map_err(io::Error::other)? .to_encoded_point(false) .as_bytes() .to_vec()), "P-384" => Ok(p384::PublicKey::from_public_key_der(data) .map_err(io::Error::other)? .to_encoded_point(false) .as_bytes() .to_vec()), "P-521" => Ok(ec_public_sec1(data)?), _ => Err(unsupported()), }, ("raw", "ED25519", "public") => Ok(ed25519_dalek::VerifyingKey::from_public_key_der(data) .map_err(io::Error::other)? .to_bytes() .to_vec()), ("raw", "X25519", "public") => Ok(data.to_vec()), ("spki", "X25519", "public") => x25519_der(false, data), ("pkcs8", "X25519", "private") => x25519_der(true, data), _ => Err(unsupported()), } } fn jwk_bytes(value: &serde_json::Value, field: &str) -> io::Result> { use base64::Engine; use base64::engine::general_purpose::URL_SAFE_NO_PAD; let encoded = value .get(field) .and_then(serde_json::Value::as_str) .ok_or_else(|| io::Error::other("JWK field is missing"))?; URL_SAFE_NO_PAD.decode(encoded).map_err(io::Error::other) } fn b64(bytes: &[u8]) -> String { use base64::Engine; use base64::engine::general_purpose::URL_SAFE_NO_PAD; URL_SAFE_NO_PAD.encode(bytes) } pub(crate) fn import_jwk( jwk: &serde_json::Value, name: &str, hash: &str, curve: &str, ) -> io::Result<(ImportedKey, String)> { match name { "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" => { if jwk.get("kty").and_then(serde_json::Value::as_str) != Some("RSA") { return Err(io::Error::other("JWK kty does not match RSA")); } if jwk.get("oth").is_some() { return Err(io::Error::other("Multiprime RSA JWKs are unsupported")); } let n_bytes = jwk_bytes(jwk, "n")?; let e_bytes = jwk_bytes(jwk, "e")?; if n_bytes.is_empty() || e_bytes.is_empty() || n_bytes[0] == 0 || e_bytes[0] == 0 { return Err(io::Error::other("Invalid RSA JWK integer encoding")); } let n = rsa::BigUint::from_bytes_be(&n_bytes); if n.bits() < 512 || n.bits() > 8192 { return Err(io::Error::other("Invalid RSA modulus size")); } let e = rsa::BigUint::from_bytes_be(&e_bytes); let private = jwk.get("d").is_some(); let (bytes, length) = if private { let d = rsa::BigUint::from_bytes_be(&jwk_bytes(jwk, "d")?); let p = rsa::BigUint::from_bytes_be(&jwk_bytes(jwk, "p")?); let q = rsa::BigUint::from_bytes_be(&jwk_bytes(jwk, "q")?); let dp = rsa::BigUint::from_bytes_be(&jwk_bytes(jwk, "dp")?); let dq = rsa::BigUint::from_bytes_be(&jwk_bytes(jwk, "dq")?); let qi = rsa::BigUint::from_bytes_be(&jwk_bytes(jwk, "qi")?); let mut key = RsaPrivateKey::from_components(n, e, d, vec![p, q]) .map_err(io::Error::other)?; key.precompute().map_err(io::Error::other)?; key.validate().map_err(io::Error::other)?; if key.dp() != Some(&dp) || key.dq() != Some(&dq) || key.qinv().and_then(|v| v.to_biguint()).as_ref() != Some(&qi) { return Err(io::Error::other("JWK RSA CRT parameters do not match")); } let length = key.n().bits(); ( key.to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length, ) } else { let key = RsaPublicKey::new(n, e).map_err(io::Error::other)?; let length = key.n().bits(); ( key.to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), length, ) }; let expected_alg = match name { "RSA-OAEP" => { if hash == "SHA-1" { "RSA-OAEP".to_owned() } else { format!("RSA-OAEP-{}", &hash[4..]) } } "RSA-PSS" => format!("PS{}", &hash[4..]), _ => format!("RS{}", &hash[4..]), }; if let Some(alg) = jwk.get("alg") && alg.as_str() != Some(expected_alg.as_str()) { return Err(io::Error::other("JWK alg does not match RSA hash")); } Ok(( ImportedKey { bytes, length, exponent: e_bytes, curve: String::new(), }, if private { "private" } else { "public" }.into(), )) } "ECDSA" | "ECDH" => { if jwk.get("kty").and_then(serde_json::Value::as_str) != Some("EC") { return Err(io::Error::other("JWK kty does not match EC")); } let expected_curve = match curve { "P-256" => "P-256", "P-384" => "P-384", "P-521" => "P-521", _ => return Err(unsupported()), }; if jwk.get("crv").and_then(serde_json::Value::as_str) != Some(expected_curve) { return Err(io::Error::other("JWK curve does not match")); } let x = jwk_bytes(jwk, "x")?; let y = jwk_bytes(jwk, "y")?; let width = match curve { "P-256" => 32, "P-384" => 48, "P-521" => 66, _ => return Err(unsupported()), }; if x.len() != width || y.len() != width { return Err(io::Error::other("Invalid EC JWK coordinate length")); } let private = jwk.get("d").is_some(); if private && jwk_bytes(jwk, "d")?.len() != width { return Err(io::Error::other("Invalid EC JWK private key length")); } let mut point = Vec::with_capacity(1 + x.len() + y.len()); point.push(4); point.extend_from_slice(&x); point.extend_from_slice(&y); let (bytes, length) = match (curve, private) { ("P-256", true) => { let key = p256::SecretKey::from_slice(&jwk_bytes(jwk, "d")?) .map_err(io::Error::other)?; if key.public_key().to_encoded_point(false).as_bytes() != point { return Err(io::Error::other( "JWK EC public and private values disagree", )); } ( key.to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), 256, ) } ("P-256", false) => ( p256::PublicKey::from_sec1_bytes(&point) .map_err(io::Error::other)? .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), 256, ), ("P-384", true) => { let key = p384::SecretKey::from_slice(&jwk_bytes(jwk, "d")?) .map_err(io::Error::other)?; if key.public_key().to_encoded_point(false).as_bytes() != point { return Err(io::Error::other( "JWK EC public and private values disagree", )); } ( key.to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), 384, ) } ("P-384", false) => ( p384::PublicKey::from_sec1_bytes(&point) .map_err(io::Error::other)? .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), 384, ), ("P-521", true) => { let key = p521::SecretKey::from_slice(&jwk_bytes(jwk, "d")?) .map_err(io::Error::other)?; if key.public_key().to_encoded_point(false).as_bytes() != point { return Err(io::Error::other( "JWK EC public and private values disagree", )); } ( key.to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), 521, ) } ("P-521", false) => ( p521::PublicKey::from_sec1_bytes(&point) .map_err(io::Error::other)? .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec(), 521, ), _ => return Err(unsupported()), }; Ok(( ImportedKey { bytes, length, exponent: Vec::new(), curve: curve.into(), }, if private { "private" } else { "public" }.into(), )) } "ED25519" => { if jwk.get("kty").and_then(serde_json::Value::as_str) != Some("OKP") || jwk.get("crv").and_then(serde_json::Value::as_str) != Some("Ed25519") { return Err(io::Error::other("JWK is not Ed25519")); } let x: [u8; 32] = jwk_bytes(jwk, "x")? .try_into() .map_err(|_| io::Error::other("Invalid key length"))?; let private = jwk.get("d").is_some(); let bytes = if private { let d: [u8; 32] = jwk_bytes(jwk, "d")? .try_into() .map_err(|_| io::Error::other("Invalid key length"))?; let key = ed25519_dalek::SigningKey::from_bytes(&d); if key.verifying_key().to_bytes() != x { return Err(io::Error::other("JWK public and private values disagree")); } key.to_pkcs8_der() .map_err(io::Error::other)? .as_bytes() .to_vec() } else { ed25519_dalek::VerifyingKey::from_bytes(&x) .map_err(io::Error::other)? .to_public_key_der() .map_err(io::Error::other)? .as_bytes() .to_vec() }; Ok(( ImportedKey { bytes, length: 256, exponent: Vec::new(), curve: String::new(), }, if private { "private" } else { "public" }.into(), )) } "X25519" => { if jwk.get("kty").and_then(serde_json::Value::as_str) != Some("OKP") || jwk.get("crv").and_then(serde_json::Value::as_str) != Some("X25519") { return Err(io::Error::other("JWK is not X25519")); } let x: [u8; 32] = jwk_bytes(jwk, "x")? .try_into() .map_err(|_| io::Error::other("Invalid key length"))?; let private = jwk.get("d").is_some(); let bytes = if private { let d: [u8; 32] = jwk_bytes(jwk, "d")? .try_into() .map_err(|_| io::Error::other("Invalid key length"))?; let secret = x25519_dalek::StaticSecret::from(d); if x25519_dalek::PublicKey::from(&secret).to_bytes() != x { return Err(io::Error::other("JWK public and private values disagree")); } d.to_vec() } else { x.to_vec() }; Ok(( ImportedKey { bytes, length: 256, exponent: Vec::new(), curve: String::new(), }, if private { "private" } else { "public" }.into(), )) } _ => Err(unsupported()), } } pub(crate) fn export_jwk( data: &[u8], name: &str, hash: &str, key_type: &str, curve: &str, usages: &[String], extractable: bool, ) -> io::Result { let mut value = serde_json::Map::new(); match name { "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-V1_5" => { let (n, e) = if key_type == "private" { let key = RsaPrivateKey::from_pkcs8_der(data).map_err(io::Error::other)?; (key.n().to_bytes_be(), key.e().to_bytes_be()) } else { let key = RsaPublicKey::from_public_key_der(data).map_err(io::Error::other)?; (key.n().to_bytes_be(), key.e().to_bytes_be()) }; value.insert("kty".into(), "RSA".into()); value.insert("n".into(), b64(&n).into()); value.insert("e".into(), b64(&e).into()); if key_type == "private" { let key = RsaPrivateKey::from_pkcs8_der(data).map_err(io::Error::other)?; let primes = key.primes(); if primes.len() != 2 { return Err(unsupported()); } value.insert("d".into(), b64(&key.d().to_bytes_be()).into()); value.insert("p".into(), b64(&primes[0].to_bytes_be()).into()); value.insert("q".into(), b64(&primes[1].to_bytes_be()).into()); value.insert( "dp".into(), b64(&key.dp().ok_or_else(unsupported)?.to_bytes_be()).into(), ); value.insert( "dq".into(), b64(&key.dq().ok_or_else(unsupported)?.to_bytes_be()).into(), ); value.insert( "qi".into(), b64(&key.crt_coefficient().ok_or_else(unsupported)?.to_bytes_be()).into(), ); } let prefix = match name { "RSA-OAEP" => "RSA-OAEP", "RSA-PSS" => "PS", _ => "RS", }; let alg = if name == "RSA-OAEP" { if hash == "SHA-1" { prefix.into() } else { format!("{prefix}-{}", &hash[4..]) } } else { format!("{prefix}{}", &hash[4..]) }; value.insert("alg".into(), alg.into()); } "ECDSA" | "ECDH" => { let (x, y, d) = match (curve, key_type) { ("P-256", "private") => { let k = p256::SecretKey::from_pkcs8_der(data).map_err(io::Error::other)?; let p = k.public_key().to_encoded_point(false); ( p.x().ok_or_else(unsupported)?.to_vec(), p.y().ok_or_else(unsupported)?.to_vec(), Some(k.to_bytes().to_vec()), ) } ("P-256", _) => { let k = p256::PublicKey::from_public_key_der(data).map_err(io::Error::other)?; let p = k.to_encoded_point(false); ( p.x().ok_or_else(unsupported)?.to_vec(), p.y().ok_or_else(unsupported)?.to_vec(), None, ) } ("P-384", "private") => { let k = p384::SecretKey::from_pkcs8_der(data).map_err(io::Error::other)?; let p = k.public_key().to_encoded_point(false); ( p.x().ok_or_else(unsupported)?.to_vec(), p.y().ok_or_else(unsupported)?.to_vec(), Some(k.to_bytes().to_vec()), ) } ("P-384", _) => { let k = p384::PublicKey::from_public_key_der(data).map_err(io::Error::other)?; let p = k.to_encoded_point(false); ( p.x().ok_or_else(unsupported)?.to_vec(), p.y().ok_or_else(unsupported)?.to_vec(), None, ) } ("P-521", "private") => { let k = p521::SecretKey::from_pkcs8_der(data).map_err(io::Error::other)?; let p = k.public_key().to_encoded_point(false); ( p.x().ok_or_else(unsupported)?.to_vec(), p.y().ok_or_else(unsupported)?.to_vec(), Some(k.to_bytes().to_vec()), ) } ("P-521", _) => { let p = p521::PublicKey::from_sec1_bytes(&ec_public_sec1(data)?) .map_err(io::Error::other)? .to_encoded_point(false); ( p.x().ok_or_else(unsupported)?.to_vec(), p.y().ok_or_else(unsupported)?.to_vec(), None, ) } _ => return Err(unsupported()), }; value.insert("kty".into(), "EC".into()); value.insert("crv".into(), curve.into()); value.insert("x".into(), b64(&x).into()); value.insert("y".into(), b64(&y).into()); if let Some(d) = d { value.insert("d".into(), b64(&d).into()); } if name == "ECDSA" { let alg = match curve { "P-256" => "ES256", "P-384" => "ES384", "P-521" => "ES512", _ => return Err(unsupported()), }; value.insert("alg".into(), alg.into()); } } "ED25519" => { let (x, d) = if key_type == "private" { let k = ed25519_dalek::SigningKey::from_pkcs8_der(data).map_err(io::Error::other)?; ( k.verifying_key().to_bytes().to_vec(), Some(k.to_bytes().to_vec()), ) } else { ( ed25519_dalek::VerifyingKey::from_public_key_der(data) .map_err(io::Error::other)? .to_bytes() .to_vec(), None, ) }; value.insert("kty".into(), "OKP".into()); value.insert("crv".into(), "Ed25519".into()); value.insert("x".into(), b64(&x).into()); if let Some(d) = d { value.insert("d".into(), b64(&d).into()); } value.insert("alg".into(), "EdDSA".into()); } "X25519" => { let (x, d) = if key_type == "private" { let d: [u8; 32] = data .try_into() .map_err(|_| io::Error::other("Invalid key length"))?; let s = x25519_dalek::StaticSecret::from(d); ( x25519_dalek::PublicKey::from(&s).to_bytes().to_vec(), Some(d.to_vec()), ) } else { (data.to_vec(), None) }; value.insert("kty".into(), "OKP".into()); value.insert("crv".into(), "X25519".into()); value.insert("x".into(), b64(&x).into()); if let Some(d) = d { value.insert("d".into(), b64(&d).into()); } } _ => return Err(unsupported()), } value.insert( "key_ops".into(), serde_json::to_value(usages).map_err(io::Error::other)?, ); value.insert("ext".into(), extractable.into()); Ok(value.into()) } fn mgf1(seed: &[u8], output: &mut [u8]) where D: sha2::Digest, { let mut counter = 0u32; let mut offset = 0; while offset < output.len() { let mut hasher = D::new(); hasher.update(seed); hasher.update(counter.to_be_bytes()); let digest = hasher.finalize(); let digest = digest.as_slice(); let take = (output.len() - offset).min(digest.len()); output[offset..offset + take].copy_from_slice(&digest[..take]); offset += take; counter = counter.wrapping_add(1); } } fn oaep_encrypt(public: &RsaPublicKey, label: &[u8], input: &[u8]) -> io::Result> where D: sha2::Digest, { let key_size = public.size(); let hash_size = ::output_size(); if input.len() + 2 * hash_size + 2 > key_size { return Err(io::Error::other("Invalid RSA-OAEP plaintext length")); } let db_size = key_size - hash_size - 1; let mut db = vec![0; db_size]; db[..hash_size].copy_from_slice(D::digest(label).as_slice()); db[db_size - input.len() - 1] = 1; db[db_size - input.len()..].copy_from_slice(input); let mut seed = Zeroizing::new(vec![0; hash_size]); OsRng.fill_bytes(seed.as_mut_slice()); let mut db_mask = vec![0; db_size]; mgf1::(&seed, &mut db_mask); for (byte, mask) in db.iter_mut().zip(&db_mask) { *byte ^= *mask; } let mut seed_mask = Zeroizing::new(vec![0; hash_size]); mgf1::(&db, &mut seed_mask); for (byte, mask) in seed.iter_mut().zip(seed_mask.iter()) { *byte ^= *mask; } let mut encoded = Vec::with_capacity(key_size); encoded.push(0); encoded.extend_from_slice(&seed); encoded.extend_from_slice(&db); let encoded = rsa::BigUint::from_bytes_be(&encoded); let encrypted = rsa_encrypt_raw(public, &encoded).map_err(io::Error::other)?; let bytes = encrypted.to_bytes_be(); if bytes.len() > key_size { return Err(io::Error::other("Invalid RSA-OAEP ciphertext")); } let mut output = vec![0; key_size]; output[key_size - bytes.len()..].copy_from_slice(&bytes); Ok(output) } fn oaep_decrypt(private: &RsaPrivateKey, label: &[u8], input: &[u8]) -> io::Result> where D: sha2::Digest, { let key_size = private.size(); let hash_size = ::output_size(); if key_size < 2 * hash_size + 2 || input.len() != key_size { return Err(io::Error::other("Invalid RSA-OAEP ciphertext")); } let ciphertext = rsa::BigUint::from_bytes_be(input); let mut rng = OsRng; let encoded = rsa_decrypt_raw(private, Some(&mut rng), &ciphertext).map_err(io::Error::other)?; let bytes = encoded.to_bytes_be(); if bytes.len() > key_size { return Err(io::Error::other("Invalid RSA-OAEP ciphertext")); } let mut padded = Zeroizing::new(vec![0; key_size]); padded[key_size - bytes.len()..].copy_from_slice(&bytes); let mut failure = (padded[0] != 0) as u8; let (masked_seed, masked_db) = padded[1..].split_at(hash_size); let mut seed = Zeroizing::new(vec![0; hash_size]); let mut seed_mask = vec![0; hash_size]; mgf1::(masked_db, &mut seed_mask); for ((byte, masked), mask) in seed .iter_mut() .zip(masked_seed.iter()) .zip(seed_mask.iter()) { *byte = *masked ^ *mask; } let mut db = Zeroizing::new(vec![0; key_size - hash_size - 1]); let mut db_mask = vec![0; db.len()]; mgf1::(&seed, &mut db_mask); for ((byte, masked), mask) in db.iter_mut().zip(masked_db.iter()).zip(db_mask.iter()) { *byte = *masked ^ *mask; } failure |= (!bool::from(db[..hash_size].ct_eq(D::digest(label).as_slice()))) as u8; let mut message_start = 0usize; let mut looking = 1u8; for (index, &byte) in db.iter().enumerate().skip(hash_size) { let is_zero = (byte == 0) as u8; let is_one = (byte == 1) as u8; failure |= looking & (1 - (is_zero | is_one)); message_start |= (looking & is_one) as usize * (index + 1); looking &= 1 - is_one; } failure |= looking; if failure != 0 { return Err(io::Error::other("Invalid RSA-OAEP ciphertext")); } Ok(db[message_start..].to_vec()) } pub(crate) fn rsa_encrypt( name: &str, public_der: &[u8], hash: &str, label: &[u8], input: &[u8], ) -> io::Result> { if name != "RSA-OAEP" { return Err(unsupported()); } let key = RsaPublicKey::from_public_key_der(public_der).map_err(io::Error::other)?; macro_rules! encrypt { ($digest:ty) => { oaep_encrypt::<$digest>(&key, label, input) }; } match hash { "SHA-1" => encrypt!(sha1::Sha1), "SHA-256" => encrypt!(sha2::Sha256), "SHA-384" => encrypt!(sha2::Sha384), "SHA-512" => encrypt!(sha2::Sha512), _ => Err(unsupported()), } } pub(crate) fn rsa_decrypt( name: &str, private_der: &[u8], hash: &str, label: &[u8], input: &[u8], ) -> io::Result> { if name != "RSA-OAEP" { return Err(unsupported()); } let key = RsaPrivateKey::from_pkcs8_der(private_der).map_err(io::Error::other)?; macro_rules! decrypt { ($digest:ty) => { oaep_decrypt::<$digest>(&key, label, input) }; } match hash { "SHA-1" => decrypt!(sha1::Sha1), "SHA-256" => decrypt!(sha2::Sha256), "SHA-384" => decrypt!(sha2::Sha384), "SHA-512" => decrypt!(sha2::Sha512), _ => Err(unsupported()), } } pub(crate) fn rsa_sign( name: &str, private_der: &[u8], hash: &str, salt_length: usize, input: &[u8], ) -> io::Result> { let hashed = hash_bytes(hash, input)?; if name == "RSA-PSS" { let mut rng = OsRng; macro_rules! sign { ($digest:ty) => {{ let key = RsaPrivateKey::from_pkcs8_der(private_der).map_err(io::Error::other)?; let signing_key = rsa::pss::SigningKey::<$digest>::new_with_salt_len(key, salt_length); signature::hazmat::RandomizedPrehashSigner::sign_prehash_with_rng( &signing_key, &mut rng, &hashed, ) .map_err(io::Error::other) .map(|signature| Box::<[u8]>::from(signature).into_vec()) }}; } match hash { "SHA-1" => sign!(sha1::Sha1), "SHA-256" => sign!(sha2::Sha256), "SHA-384" => sign!(sha2::Sha384), "SHA-512" => sign!(sha2::Sha512), _ => Err(unsupported()), } } else if name == "RSASSA-PKCS1-V1_5" { macro_rules! sign { ($digest:ty) => {{ let key = RsaPrivateKey::from_pkcs8_der(private_der).map_err(io::Error::other)?; let signing_key = rsa::pkcs1v15::SigningKey::<$digest>::new(key); signature::hazmat::PrehashSigner::sign_prehash(&signing_key, &hashed) .map_err(io::Error::other) .map(|signature| Box::<[u8]>::from(signature).into_vec()) }}; } match hash { "SHA-1" => sign!(sha1::Sha1), "SHA-256" => sign!(sha2::Sha256), "SHA-384" => sign!(sha2::Sha384), "SHA-512" => sign!(sha2::Sha512), _ => Err(unsupported()), } } else { Err(unsupported()) } } pub(crate) fn rsa_verify( name: &str, public_der: &[u8], hash: &str, salt_length: usize, input: &[u8], signature: &[u8], ) -> io::Result { let hashed = hash_bytes(hash, input)?; if name == "RSA-PSS" { macro_rules! verify { ($digest:ty) => {{ let key = RsaPublicKey::from_public_key_der(public_der).map_err(io::Error::other)?; let verifying_key = rsa::pss::VerifyingKey::<$digest>::new_with_salt_len(key, salt_length); let signature = rsa::pss::Signature::try_from(signature) .map_err(|_| io::Error::other("Invalid RSA-PSS signature"))?; Ok(signature::hazmat::PrehashVerifier::verify_prehash( &verifying_key, &hashed, &signature, ) .is_ok()) }}; } match hash { "SHA-1" => verify!(sha1::Sha1), "SHA-256" => verify!(sha2::Sha256), "SHA-384" => verify!(sha2::Sha384), "SHA-512" => verify!(sha2::Sha512), _ => Err(unsupported()), } } else if name == "RSASSA-PKCS1-V1_5" { macro_rules! verify { ($digest:ty) => {{ let key = RsaPublicKey::from_public_key_der(public_der).map_err(io::Error::other)?; let verifying_key = rsa::pkcs1v15::VerifyingKey::<$digest>::new(key); let signature = rsa::pkcs1v15::Signature::try_from(signature) .map_err(|_| io::Error::other("Invalid RSASSA-PKCS1-V1_5 signature"))?; Ok(signature::hazmat::PrehashVerifier::verify_prehash( &verifying_key, &hashed, &signature, ) .is_ok()) }}; } match hash { "SHA-1" => verify!(sha1::Sha1), "SHA-256" => verify!(sha2::Sha256), "SHA-384" => verify!(sha2::Sha384), "SHA-512" => verify!(sha2::Sha512), _ => Err(unsupported()), } } else { Err(unsupported()) } } pub(crate) fn sign( name: &str, private_der: &[u8], hash: &str, curve: &str, input: &[u8], ) -> io::Result> { if name == "ED25519" { let key = ed25519_dalek::SigningKey::from_pkcs8_der(private_der).map_err(io::Error::other)?; return Ok(key.sign(input).to_bytes().to_vec()); } macro_rules! sign_curve { ($module:ident) => {{ let key = $module::ecdsa::SigningKey::from_pkcs8_der(private_der) .map_err(io::Error::other)?; let digest = hash_bytes(hash, input)?; let signature: $module::ecdsa::Signature = signature::hazmat::PrehashSigner::sign_prehash(&key, &digest) .map_err(io::Error::other)?; Ok(signature.to_bytes().to_vec()) }}; } match (name, curve) { ("ECDSA", "P-256") => match hash { "SHA-1" => sign_curve!(p256), "SHA-256" => sign_curve!(p256), "SHA-384" => sign_curve!(p256), "SHA-512" => sign_curve!(p256), _ => Err(unsupported()), }, ("ECDSA", "P-384") => match hash { "SHA-1" => sign_curve!(p384), "SHA-256" => sign_curve!(p384), "SHA-384" => sign_curve!(p384), "SHA-512" => sign_curve!(p384), _ => Err(unsupported()), }, ("ECDSA", "P-521") => { let private = p521::SecretKey::from_pkcs8_der(private_der).map_err(io::Error::other)?; let key = p521::ecdsa::SigningKey::from_slice(private.to_bytes().as_slice()) .map_err(io::Error::other)?; let digest = hash_bytes(hash, input)?; let signature: p521::ecdsa::Signature = signature::hazmat::PrehashSigner::sign_prehash(&key, &digest) .map_err(io::Error::other)?; Ok(signature.to_bytes().to_vec()) } _ => Err(unsupported()), } } pub(crate) fn verify( name: &str, public_der: &[u8], hash: &str, curve: &str, input: &[u8], signature_bytes: &[u8], ) -> io::Result { if name == "ED25519" { let key = ed25519_dalek::VerifyingKey::from_public_key_der(public_der) .map_err(io::Error::other)?; let Ok(sig) = ed25519_dalek::Signature::from_slice(signature_bytes) else { return Ok(false); }; return Ok(key.verify(input, &sig).is_ok()); } macro_rules! verify_curve { ($module:ident) => {{ let key = $module::ecdsa::VerifyingKey::from_public_key_der(public_der) .map_err(io::Error::other)?; let Ok(sig) = $module::ecdsa::Signature::from_slice(signature_bytes) else { return Ok(false); }; let digest = hash_bytes(hash, input)?; Ok(signature::hazmat::PrehashVerifier::verify_prehash(&key, &digest, &sig).is_ok()) }}; } match (name, curve) { ("ECDSA", "P-256") => verify_curve!(p256), ("ECDSA", "P-384") => verify_curve!(p384), ("ECDSA", "P-521") => { let info = pkcs8::spki::SubjectPublicKeyInfoRef::try_from(public_der) .map_err(io::Error::other)?; let encoded = info.subject_public_key.as_bytes().ok_or_else(unsupported)?; let key = p521::ecdsa::VerifyingKey::from_sec1_bytes(encoded).map_err(io::Error::other)?; let Ok(sig) = p521::ecdsa::Signature::from_slice(signature_bytes) else { return Ok(false); }; let digest = hash_bytes(hash, input)?; Ok(signature::hazmat::PrehashVerifier::verify_prehash(&key, &digest, &sig).is_ok()) } _ => Err(unsupported()), } } pub(crate) fn ecdh( name: &str, curve: &str, private_der: &[u8], public_der: &[u8], ) -> io::Result> { macro_rules! derive { ($module:ident) => {{ let private = $module::SecretKey::from_pkcs8_der(private_der).map_err(io::Error::other)?; let public = $module::PublicKey::from_public_key_der(public_der).map_err(io::Error::other)?; Ok( $module::ecdh::diffie_hellman(private.to_nonzero_scalar(), public.as_affine()) .raw_secret_bytes() .to_vec(), ) }}; } match (name, curve) { ("ECDH", "P-256") => derive!(p256), ("ECDH", "P-384") => derive!(p384), ("ECDH", "P-521") => { let private = p521::SecretKey::from_pkcs8_der(private_der).map_err(io::Error::other)?; let info = pkcs8::spki::SubjectPublicKeyInfoRef::try_from(public_der) .map_err(io::Error::other)?; let encoded = info.subject_public_key.as_bytes().ok_or_else(unsupported)?; let public = p521::PublicKey::from_sec1_bytes(encoded).map_err(io::Error::other)?; Ok( p521::ecdh::diffie_hellman(private.to_nonzero_scalar(), public.as_affine()) .raw_secret_bytes() .to_vec(), ) } _ => Err(unsupported()), } } pub(crate) fn x25519(private: &[u8], public: &[u8]) -> io::Result> { let private: [u8; 32] = private .try_into() .map_err(|_| io::Error::other("Invalid key length"))?; let public: [u8; 32] = public .try_into() .map_err(|_| io::Error::other("Invalid key length"))?; let shared = x25519_dalek::StaticSecret::from(private) .diffie_hellman(&x25519_dalek::PublicKey::from(public)); if !shared.was_contributory() { return Err(io::Error::other( "X25519 produced an all-zero shared secret", )); } Ok(shared.to_bytes().to_vec()) } #[cfg(test)] mod tests { use super::*; #[test] fn rsa_generation_rejects_invalid_public_exponents_before_keygen() { for exponent in [&[0][..], &[1][..], &[2][..], &[1, 0, 2][..]] { assert!(generate("RSA-PSS", 1024, exponent, "").is_err()); } } #[test] fn asymmetric_export_rejects_incompatible_key_types() { for (format, key_type) in [("raw", "private"), ("pkcs8", "public"), ("spki", "private")] { let error = export_key(format, &[], "ECDSA", key_type, "P-256").unwrap_err(); assert!(error.to_string().starts_with("InvalidAccessError:")); } } #[test] fn rsa_signing_uses_requested_pss_salt_and_pkcs1() { let pair = generate("RSA-PSS", 1024, &[1, 0, 1], "").unwrap(); let message = b"private RSA signatures use OpenSSL"; let signature = rsa_sign("RSA-PSS", &pair.private, "SHA-256", 32, message).unwrap(); assert!(rsa_verify("RSA-PSS", &pair.public, "SHA-256", 32, message, &signature).unwrap()); assert!(!rsa_verify("RSA-PSS", &pair.public, "SHA-256", 20, message, &signature).unwrap()); let signature = rsa_sign("RSASSA-PKCS1-V1_5", &pair.private, "SHA-256", 0, message).unwrap(); assert!( rsa_verify( "RSASSA-PKCS1-V1_5", &pair.public, "SHA-256", 0, message, &signature ) .unwrap() ); } }