use std::borrow::Cow;
use std::sync::Arc;
use bon::bon;
use der::Decode as _;
use google_cloud_kms_v1::{
client::KeyManagementService, model::crypto_key_version::CryptoKeyVersionAlgorithm,
};
use huskarl_core::crypto::signer::{
AsymmetricJwsSigner, AsymmetricJwsSignerSelector, JwsSigner, JwsSignerSelector,
};
use huskarl_core::jwk::{self, PublicJwk};
use p256::ecdsa::signature;
use p256::elliptic_curve::pkcs8::DecodePublicKey as _;
use p256::elliptic_curve::sec1::ToSec1Point as _;
use snafu::prelude::*;
use super::super::version::VersionStrategy;
type KidMapper = Arc<dyn Fn(&str) -> String + Send + Sync>;
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum SetupError {
VersionResolution {
source: super::super::version::VersionResolutionError,
},
UnsupportedAlgorithm {
algorithm: CryptoKeyVersionAlgorithm,
},
InvalidKeyVersionName,
GetPublicKey {
source: google_cloud_kms_v1::Error,
},
PublicKeyParse {
source: PublicKeyParseError,
},
ListCryptoKeyVersions {
source: google_cloud_kms_v1::Error,
},
NoEnabledCryptoKeyVersions,
PrimaryVersionNotFound,
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum PublicKeyParseError {
PemDecode {
source: pem_rfc7468::Error,
},
#[snafu(display("failed to decode {algorithm} public key PEM"))]
EcDecode {
algorithm: &'static str,
source: spki::Error,
},
SpkiParse {
source: der::Error,
},
RsaParse {
source: der::Error,
},
#[snafu(display("Ed25519 public key is {length} bytes, expected 32"))]
Ed25519Length {
length: usize,
},
#[snafu(display("missing {algorithm} point coordinate"))]
MissingCoordinate {
algorithm: &'static str,
},
Thumbprint,
#[snafu(display("no public key parser for algorithm {algorithm}"))]
UnsupportedParseAlgorithm {
algorithm: String,
},
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum SigningError {
AsymmetricSign {
source: google_cloud_kms_v1::Error,
},
SignatureConversion {
source: signature::Error,
},
MismatchedKeyInfo,
}
impl huskarl_core::Error for SigningError {
fn is_retryable(&self) -> bool {
match self {
SigningError::AsymmetricSign { source } => source.is_timeout() || source.is_exhausted(),
SigningError::SignatureConversion { .. } | SigningError::MismatchedKeyInfo => false,
}
}
}
#[derive(Debug, Clone)]
pub struct KeyVersion {
kms_client: KeyManagementService,
resource_name: String,
jws_algorithm: String,
key_id: Option<String>,
public_key_jwk: PublicJwk,
thumbprint: String,
}
#[bon]
impl KeyVersion {
#[builder(finish_fn = build)]
pub async fn builder(
#[builder(into)]
resource_name: String,
kms_client: KeyManagementService,
#[builder(default = true)]
use_fully_specified_jws_algorithm: bool,
#[builder(with = |f: impl Fn(&str) -> String + Send + Sync + 'static| Arc::new(f))]
with_kid_from_key_version: Option<KidMapper>,
) -> Result<Self, SetupError> {
build_key_version(
resource_name,
kms_client,
use_fully_specified_jws_algorithm,
with_kid_from_key_version,
)
.await
}
}
impl JwsSignerSelector for KeyVersion {
type Signer = Self;
fn select_signer(&self) -> Self::Signer {
self.clone()
}
}
impl JwsSigner for KeyVersion {
type Error = SigningError;
fn jws_algorithm(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.jws_algorithm)
}
fn key_id(&self) -> Option<Cow<'_, str>> {
self.key_id.as_deref().map(Cow::Borrowed)
}
async fn sign(&self, input: &[u8]) -> Result<Vec<u8>, Self::Error> {
let response = self
.kms_client
.asymmetric_sign()
.set_name(&self.resource_name)
.set_data(input.to_vec())
.send()
.await
.context(AsymmetricSignSnafu)?;
ensure!(response.name == self.resource_name, MismatchedKeyInfoSnafu);
let signature = response.signature.to_vec();
match self.jws_algorithm.as_str() {
"ES256" => convert_ecdsa_der_to_fixed(&signature, EcDsaVariant::P256)
.context(SignatureConversionSnafu),
"ES384" => convert_ecdsa_der_to_fixed(&signature, EcDsaVariant::P384)
.context(SignatureConversionSnafu),
_ => Ok(signature),
}
}
}
impl AsymmetricJwsSignerSelector for KeyVersion {
fn select_signer_by_thumbprint(&self, thumbprint: &str) -> Option<Self> {
if self.thumbprint == thumbprint {
Some(self.clone())
} else {
None
}
}
}
impl AsymmetricJwsSigner for KeyVersion {
fn public_key_jwk(&self) -> Cow<'_, PublicJwk> {
Cow::Borrowed(&self.public_key_jwk)
}
}
#[derive(Debug, Clone)]
pub struct SigningKey {
primary: KeyVersion,
additional: Vec<KeyVersion>,
}
#[bon]
impl SigningKey {
#[builder(finish_fn = build)]
pub async fn builder(
#[builder(into)]
key_name: String,
kms_client: KeyManagementService,
#[builder(default)]
strategy: VersionStrategy,
#[builder(default = true)]
use_fully_specified_jws_algorithm: bool,
#[builder(with = |f: impl Fn(&str) -> String + Send + Sync + 'static| Arc::new(f))]
with_kid_from_key_version: Option<KidMapper>,
max_versions: Option<usize>,
) -> Result<Self, SetupError> {
let (primary_version_id, all_versions) = futures_util::try_join!(
async {
super::super::version::resolve_version(&key_name, &strategy, &kms_client)
.await
.context(VersionResolutionSnafu)
},
async {
super::super::version::list_enabled_kms_versions(
&kms_client,
&key_name,
max_versions,
Some("name desc"),
)
.await
.context(ListCryptoKeyVersionsSnafu)
},
)?;
ensure!(!all_versions.is_empty(), NoEnabledCryptoKeyVersionsSnafu);
let primary_resource_name = format!("{key_name}/cryptoKeyVersions/{primary_version_id}");
let kms_ref = &kms_client;
let kid_mapper = with_kid_from_key_version.as_ref();
let futures: Vec<_> = all_versions
.iter()
.filter_map(|version| {
let alg = get_jws_algorithm(&version.algorithm)?;
let version_id =
super::super::version::version_id_from_resource_name(&version.name);
let kid = kid_mapper.map(|f| f(version_id));
let name = &version.name;
Some(async move {
let pk_response = kms_ref
.get_public_key()
.set_name(name)
.send()
.await
.context(GetPublicKeySnafu)?;
let jws_algorithm = if !use_fully_specified_jws_algorithm && alg == "Ed25519" {
"EdDSA"
} else {
alg
};
let public_key_jwk = parse_public_key_pem(
&pk_response.pem,
jws_algorithm,
kid.as_deref(),
jwk::KeyUse::Sign,
)
.context(PublicKeyParseSnafu)?;
let thumbprint = public_key_jwk.thumbprint();
Ok::<_, SetupError>(KeyVersion {
kms_client: kms_ref.clone(),
resource_name: name.clone(),
jws_algorithm: jws_algorithm.to_string(),
key_id: kid,
public_key_jwk,
thumbprint,
})
})
})
.collect();
let all_key_versions = futures_util::future::try_join_all(futures).await?;
let mut primary = None;
let mut additional = Vec::with_capacity(all_key_versions.len().saturating_sub(1));
for kv in all_key_versions {
if kv.resource_name == primary_resource_name {
primary = Some(kv);
} else {
additional.push(kv);
}
}
let primary = primary.context(PrimaryVersionNotFoundSnafu)?;
Ok(Self {
primary,
additional,
})
}
}
impl JwsSignerSelector for SigningKey {
type Signer = KeyVersion;
fn select_signer(&self) -> KeyVersion {
self.primary.clone()
}
}
impl AsymmetricJwsSignerSelector for SigningKey {
fn select_signer_by_thumbprint(&self, thumbprint: &str) -> Option<KeyVersion> {
if self.primary.thumbprint == thumbprint {
return Some(self.primary.clone());
}
self.additional
.iter()
.find(|kv| kv.thumbprint == thumbprint)
.cloned()
}
}
async fn build_key_version(
resource_name: String,
kms_client: KeyManagementService,
use_fully_specified_jws_algorithm: bool,
with_kid_from_key_version: Option<KidMapper>,
) -> Result<KeyVersion, SetupError> {
let public_key_response = kms_client
.get_public_key()
.set_name(&resource_name)
.send()
.await
.context(GetPublicKeySnafu)?;
let resolved_name = if public_key_response.name.is_empty() {
resource_name
} else {
public_key_response.name.clone()
};
let version_id = super::super::version::version_id_from_resource_name(&resolved_name);
let key_id = with_kid_from_key_version.map(|f| f(version_id));
let jws_algorithm = get_jws_algorithm(&public_key_response.algorithm).with_context(|| {
UnsupportedAlgorithmSnafu {
algorithm: public_key_response.algorithm,
}
})?;
let jws_algorithm = if !use_fully_specified_jws_algorithm && jws_algorithm == "Ed25519" {
"EdDSA"
} else {
jws_algorithm
};
let public_key_jwk = parse_public_key_pem(
&public_key_response.pem,
jws_algorithm,
key_id.as_deref(),
jwk::KeyUse::Sign,
)
.context(PublicKeyParseSnafu)?;
let thumbprint = public_key_jwk.thumbprint();
Ok(KeyVersion {
kms_client,
resource_name: resolved_name,
jws_algorithm: jws_algorithm.to_string(),
key_id,
public_key_jwk,
thumbprint,
})
}
pub(super) fn get_jws_algorithm(algorithm: &CryptoKeyVersionAlgorithm) -> Option<&'static str> {
use CryptoKeyVersionAlgorithm::{
EcSignEd25519, EcSignP256Sha256, EcSignP384Sha384, RsaSignPkcs12048Sha256,
RsaSignPkcs13072Sha256, RsaSignPkcs14096Sha256, RsaSignPkcs14096Sha512,
RsaSignPss2048Sha256, RsaSignPss3072Sha256, RsaSignPss4096Sha256, RsaSignPss4096Sha512,
};
match algorithm {
RsaSignPss2048Sha256 | RsaSignPss3072Sha256 | RsaSignPss4096Sha256 => Some("PS256"),
RsaSignPss4096Sha512 => Some("PS512"),
RsaSignPkcs12048Sha256 | RsaSignPkcs13072Sha256 | RsaSignPkcs14096Sha256 => Some("RS256"),
RsaSignPkcs14096Sha512 => Some("RS512"),
EcSignP256Sha256 => Some("ES256"),
EcSignP384Sha384 => Some("ES384"),
EcSignEd25519 => Some("Ed25519"),
_ => None,
}
}
pub(super) fn get_jwe_algorithm(algorithm: &CryptoKeyVersionAlgorithm) -> Option<&'static str> {
use CryptoKeyVersionAlgorithm::{
RsaDecryptOaep2048Sha1, RsaDecryptOaep2048Sha256, RsaDecryptOaep3072Sha1,
RsaDecryptOaep3072Sha256, RsaDecryptOaep4096Sha1, RsaDecryptOaep4096Sha256,
RsaDecryptOaep4096Sha512,
};
match algorithm {
RsaDecryptOaep2048Sha1 | RsaDecryptOaep3072Sha1 | RsaDecryptOaep4096Sha1 => {
Some("RSA-OAEP")
}
RsaDecryptOaep2048Sha256 | RsaDecryptOaep3072Sha256 | RsaDecryptOaep4096Sha256 => {
Some("RSA-OAEP-256")
}
RsaDecryptOaep4096Sha512 => Some("RSA-OAEP-512"),
_ => None,
}
}
pub(super) fn parse_public_key_pem(
pem: &str,
algorithm: &str,
kid: Option<&str>,
key_use: jwk::KeyUse,
) -> Result<PublicJwk, PublicKeyParseError> {
match algorithm {
"ES256" => parse_ec_p256_public_key(pem, kid, key_use),
"ES384" => parse_ec_p384_public_key(pem, kid, key_use),
"RS256" | "RS512" | "PS256" | "PS512" | "RSA-OAEP" | "RSA-OAEP-256" | "RSA-OAEP-512" => {
parse_rsa_public_key(pem, algorithm, kid, key_use)
}
"Ed25519" | "EdDSA" => parse_ed25519_public_key(pem, algorithm, kid, key_use),
_ => UnsupportedParseAlgorithmSnafu {
algorithm: algorithm.to_owned(),
}
.fail(),
}
}
fn parse_ec_p256_public_key(
pem: &str,
kid: Option<&str>,
key_use: jwk::KeyUse,
) -> Result<PublicJwk, PublicKeyParseError> {
let pk =
p256::PublicKey::from_public_key_pem(pem).context(EcDecodeSnafu { algorithm: "ES256" })?;
let point = pk.to_sec1_point(false);
let x = point
.x()
.context(MissingCoordinateSnafu { algorithm: "ES256" })?;
let y = point
.y()
.context(MissingCoordinateSnafu { algorithm: "ES256" })?;
Ok(PublicJwk::builder()
.algorithm("ES256")
.maybe_kid(kid)
.key_use(key_use)
.key(
jwk::EcPublicKey::builder()
.crv("P-256")
.x(x.to_vec())
.y(y.to_vec()),
)
.build())
}
fn parse_ec_p384_public_key(
pem: &str,
kid: Option<&str>,
key_use: jwk::KeyUse,
) -> Result<PublicJwk, PublicKeyParseError> {
let pk =
p384::PublicKey::from_public_key_pem(pem).context(EcDecodeSnafu { algorithm: "ES384" })?;
let point = pk.to_sec1_point(false);
let x = point
.x()
.context(MissingCoordinateSnafu { algorithm: "ES384" })?;
let y = point
.y()
.context(MissingCoordinateSnafu { algorithm: "ES384" })?;
Ok(PublicJwk::builder()
.algorithm("ES384")
.maybe_kid(kid)
.key_use(key_use)
.key(
jwk::EcPublicKey::builder()
.crv("P-384")
.x(x.to_vec())
.y(y.to_vec()),
)
.build())
}
fn parse_rsa_public_key(
pem: &str,
algorithm: &str,
kid: Option<&str>,
key_use: jwk::KeyUse,
) -> Result<PublicJwk, PublicKeyParseError> {
let der_bytes = decode_pem(pem).context(PemDecodeSnafu)?;
let spki = spki::SubjectPublicKeyInfoRef::from_der(&der_bytes).context(SpkiParseSnafu)?;
let pk_bytes = spki.subject_public_key.raw_bytes();
let rsa_pk = RsaPublicKeyAsn1::from_der(pk_bytes).context(RsaParseSnafu)?;
Ok(PublicJwk::builder()
.algorithm(algorithm)
.maybe_kid(kid)
.key_use(key_use)
.key(
jwk::RsaPublicKey::builder()
.n(rsa_pk.modulus.as_bytes().to_vec())
.e(rsa_pk.public_exponent.as_bytes().to_vec()),
)
.build())
}
fn parse_ed25519_public_key(
pem: &str,
algorithm: &str,
kid: Option<&str>,
key_use: jwk::KeyUse,
) -> Result<PublicJwk, PublicKeyParseError> {
let der_bytes = decode_pem(pem).context(PemDecodeSnafu)?;
let spki = spki::SubjectPublicKeyInfoRef::from_der(&der_bytes).context(SpkiParseSnafu)?;
let pk_bytes = spki.subject_public_key.raw_bytes();
ensure!(
pk_bytes.len() == 32,
Ed25519LengthSnafu {
length: pk_bytes.len()
}
);
Ok(PublicJwk::builder()
.algorithm(algorithm)
.maybe_kid(kid)
.key_use(key_use)
.key(
jwk::OkpPublicKey::builder()
.crv("Ed25519")
.x(pk_bytes.to_vec()),
)
.build())
}
fn decode_pem(pem: &str) -> Result<Vec<u8>, pem_rfc7468::Error> {
pem_rfc7468::decode_vec(pem.as_bytes()).map(|(_label, der)| der)
}
#[derive(der::Sequence)]
struct RsaPublicKeyAsn1<'a> {
modulus: der::asn1::UintRef<'a>,
public_exponent: der::asn1::UintRef<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum EcDsaVariant {
P256,
P384,
}
fn convert_ecdsa_der_to_fixed(
der_sig: &[u8],
variant: EcDsaVariant,
) -> Result<Vec<u8>, signature::Error> {
match variant {
EcDsaVariant::P256 => {
let sig = p256::ecdsa::Signature::from_der(der_sig)?;
Ok(sig.to_bytes().to_vec())
}
EcDsaVariant::P384 => {
let sig = p384::ecdsa::Signature::from_der(der_sig)?;
Ok(sig.to_bytes().to_vec())
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
const RSA_2048_PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY-----\n\
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n\
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n\
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\n\
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n\
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\n\
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\n\
mwIDAQAB\n\
-----END PUBLIC KEY-----";
const P256_PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY-----\n\
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEh07Vhy18exUbbDOWC8KFtcUnw1nL\n\
hU0zM/L+vXZ2QJRykZKgVHVizTVnAw2jEszcMCY6CiAR2TU2SNhNhASV/g==\n\
-----END PUBLIC KEY-----";
const P384_PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY-----\n\
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE0W/oUiIVHc69FmdLEAnBm6J5xXDBjhBh\n\
3YOaHjc6bQ9Rqqiinpvq5s4K3ob4WtZrrHQQNldYsxRCeoW5imtuhz55J8nrXyh1\n\
hYo8wqhEAWj4k4lWZQ4F+eFa4dzRkgUP\n\
-----END PUBLIC KEY-----";
const ED25519_PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY-----\n\
MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=\n\
-----END PUBLIC KEY-----";
#[test]
fn parse_p256_public_key() {
let jwk = parse_public_key_pem(
P256_PUBLIC_KEY_PEM,
"ES256",
Some("test-kid"),
jwk::KeyUse::Sign,
)
.unwrap();
assert_eq!(jwk.algorithm.as_deref(), Some("ES256"));
assert_eq!(jwk.kid.as_deref(), Some("test-kid"));
assert_eq!(jwk.key_use, Some(jwk::KeyUse::Sign));
}
#[test]
fn parse_p384_public_key() {
let jwk =
parse_public_key_pem(P384_PUBLIC_KEY_PEM, "ES384", None, jwk::KeyUse::Sign).unwrap();
assert_eq!(jwk.algorithm.as_deref(), Some("ES384"));
assert!(jwk.kid.is_none());
}
#[test]
fn parse_rsa_public_key_rs256() {
let jwk = parse_public_key_pem(
RSA_2048_PUBLIC_KEY_PEM,
"RS256",
Some("rsa-kid"),
jwk::KeyUse::Sign,
)
.unwrap();
assert_eq!(jwk.algorithm.as_deref(), Some("RS256"));
assert_eq!(jwk.kid.as_deref(), Some("rsa-kid"));
}
#[test]
fn parse_rsa_public_key_ps256() {
let jwk = parse_public_key_pem(RSA_2048_PUBLIC_KEY_PEM, "PS256", None, jwk::KeyUse::Sign)
.unwrap();
assert_eq!(jwk.algorithm.as_deref(), Some("PS256"));
}
#[test]
fn parse_ed25519_public_key_success() {
let jwk = parse_public_key_pem(
ED25519_PUBLIC_KEY_PEM,
"Ed25519",
Some("ed-kid"),
jwk::KeyUse::Sign,
)
.unwrap();
assert_eq!(jwk.algorithm.as_deref(), Some("Ed25519"));
assert_eq!(jwk.kid.as_deref(), Some("ed-kid"));
}
#[test]
fn parse_ed25519_public_key_with_eddsa_algorithm() {
let jwk = parse_public_key_pem(
ED25519_PUBLIC_KEY_PEM,
"EdDSA",
Some("ed-kid"),
jwk::KeyUse::Sign,
)
.unwrap();
assert_eq!(jwk.algorithm.as_deref(), Some("EdDSA"));
assert_eq!(jwk.kid.as_deref(), Some("ed-kid"));
}
#[test]
fn parse_wrong_algorithm_for_pem_fails() {
let result = parse_public_key_pem(P256_PUBLIC_KEY_PEM, "RS256", None, jwk::KeyUse::Sign);
assert!(result.is_err());
}
#[test]
fn parse_invalid_pem_fails() {
let result = parse_public_key_pem("not a PEM", "ES256", None, jwk::KeyUse::Sign);
assert!(result.is_err());
}
#[test]
fn parse_unsupported_algorithm_fails() {
let result = parse_public_key_pem(P256_PUBLIC_KEY_PEM, "HS256", None, jwk::KeyUse::Sign);
assert!(matches!(
result.unwrap_err(),
PublicKeyParseError::UnsupportedParseAlgorithm { .. }
));
}
#[test]
fn parse_p256_without_kid() {
let jwk =
parse_public_key_pem(P256_PUBLIC_KEY_PEM, "ES256", None, jwk::KeyUse::Sign).unwrap();
assert!(jwk.kid.is_none());
}
#[test]
fn parse_rsa_and_ec_produce_different_thumbprints() {
let rsa_jwk =
parse_public_key_pem(RSA_2048_PUBLIC_KEY_PEM, "RS256", None, jwk::KeyUse::Sign)
.unwrap();
let ec_jwk =
parse_public_key_pem(P256_PUBLIC_KEY_PEM, "ES256", None, jwk::KeyUse::Sign).unwrap();
assert_ne!(rsa_jwk.thumbprint(), ec_jwk.thumbprint());
}
#[test]
fn parse_rsa_public_key_with_encrypt_use() {
let jwk = parse_public_key_pem(
RSA_2048_PUBLIC_KEY_PEM,
"RSA-OAEP-256",
Some("enc-kid"),
jwk::KeyUse::Encrypt,
)
.unwrap();
assert_eq!(jwk.algorithm.as_deref(), Some("RSA-OAEP-256"));
assert_eq!(jwk.kid.as_deref(), Some("enc-kid"));
assert_eq!(jwk.key_use, Some(jwk::KeyUse::Encrypt));
}
#[test]
fn get_jwe_algorithm_rsa_oaep_sha1() {
use CryptoKeyVersionAlgorithm::*;
assert_eq!(get_jwe_algorithm(&RsaDecryptOaep2048Sha1), Some("RSA-OAEP"));
assert_eq!(get_jwe_algorithm(&RsaDecryptOaep3072Sha1), Some("RSA-OAEP"));
assert_eq!(get_jwe_algorithm(&RsaDecryptOaep4096Sha1), Some("RSA-OAEP"));
}
#[test]
fn get_jwe_algorithm_rsa_oaep_sha256() {
use CryptoKeyVersionAlgorithm::*;
assert_eq!(
get_jwe_algorithm(&RsaDecryptOaep2048Sha256),
Some("RSA-OAEP-256")
);
assert_eq!(
get_jwe_algorithm(&RsaDecryptOaep3072Sha256),
Some("RSA-OAEP-256")
);
assert_eq!(
get_jwe_algorithm(&RsaDecryptOaep4096Sha256),
Some("RSA-OAEP-256")
);
}
#[test]
fn get_jwe_algorithm_rsa_oaep_sha512() {
use CryptoKeyVersionAlgorithm::*;
assert_eq!(
get_jwe_algorithm(&RsaDecryptOaep4096Sha512),
Some("RSA-OAEP-512")
);
}
#[test]
fn get_jwe_algorithm_signing_key_returns_none() {
use CryptoKeyVersionAlgorithm::*;
assert_eq!(get_jwe_algorithm(&EcSignP256Sha256), None);
assert_eq!(get_jwe_algorithm(&RsaSignPss2048Sha256), None);
}
}