#![forbid(unsafe_code)]
use bergshamra_core::{algorithm, Error};
use kryptering::algorithm::{
HashAlgorithm, KeyTransportAlgorithm as KKeyTransportAlgorithm, OaepConfig,
};
pub trait KeyTransportAlgorithm: Send {
fn uri(&self) -> &'static str;
fn encrypt(&self, public_key: &rsa::RsaPublicKey, key_data: &[u8]) -> Result<Vec<u8>, Error>;
fn decrypt(&self, private_key: &rsa::RsaPrivateKey, encrypted: &[u8])
-> Result<Vec<u8>, Error>;
}
#[derive(Debug, Clone, Default)]
pub struct OaepParams {
pub digest_uri: Option<String>,
pub mgf_uri: Option<String>,
pub oaep_params: Option<Vec<u8>>,
}
pub fn from_uri(uri: &str) -> Result<Box<dyn KeyTransportAlgorithm>, Error> {
from_uri_with_params(uri, OaepParams::default())
}
pub fn from_uri_with_params(
uri: &str,
params: OaepParams,
) -> Result<Box<dyn KeyTransportAlgorithm>, Error> {
match uri {
algorithm::RSA_PKCS1 => Ok(Box::new(KrypteringKeyTransport {
uri: algorithm::RSA_PKCS1,
algo: KKeyTransportAlgorithm::RsaPkcs1v15,
label: None,
})),
algorithm::RSA_OAEP | algorithm::RSA_OAEP_ENC11 => {
let static_uri = if uri == algorithm::RSA_OAEP {
algorithm::RSA_OAEP
} else {
algorithm::RSA_OAEP_ENC11
};
let digest = resolve_digest(params.digest_uri.as_deref())?;
let mgf = resolve_oaep_mgf(uri, ¶ms, digest)?;
let config = OaepConfig {
digest,
mgf_digest: mgf,
};
Ok(Box::new(KrypteringKeyTransport {
uri: static_uri,
algo: KKeyTransportAlgorithm::RsaOaep(config),
label: params.oaep_params,
}))
}
_ => Err(Error::UnsupportedAlgorithm(format!("key transport: {uri}"))),
}
}
fn resolve_digest(uri: Option<&str>) -> Result<HashAlgorithm, Error> {
match uri {
None | Some(algorithm::SHA1) => Ok(HashAlgorithm::Sha1),
Some(algorithm::SHA256) => Ok(HashAlgorithm::Sha256),
Some(algorithm::SHA384) => Ok(HashAlgorithm::Sha384),
Some(algorithm::SHA512) => Ok(HashAlgorithm::Sha512),
Some(algorithm::SHA224) => Ok(HashAlgorithm::Sha224),
#[cfg(feature = "legacy-algorithms")]
Some(algorithm::RIPEMD160) => Ok(HashAlgorithm::Ripemd160),
#[cfg(feature = "legacy-algorithms")]
Some(algorithm::MD5) => Ok(HashAlgorithm::Md5),
Some(other) => {
Err(Error::UnsupportedAlgorithm(format!(
"unsupported OAEP DigestMethod: {other}"
)))
}
}
}
fn resolve_mgf(uri: Option<&str>) -> Result<Option<HashAlgorithm>, Error> {
match uri {
None => Ok(None),
Some(algorithm::MGF1_SHA1) => Ok(Some(HashAlgorithm::Sha1)),
Some(algorithm::MGF1_SHA224) => Ok(Some(HashAlgorithm::Sha224)),
Some(algorithm::MGF1_SHA256) => Ok(Some(HashAlgorithm::Sha256)),
Some(algorithm::MGF1_SHA384) => Ok(Some(HashAlgorithm::Sha384)),
Some(algorithm::MGF1_SHA512) => Ok(Some(HashAlgorithm::Sha512)),
Some(other) => Err(Error::UnsupportedAlgorithm(format!(
"unsupported OAEP MGF: {other}"
))),
}
}
fn resolve_oaep_mgf(
uri: &str,
params: &OaepParams,
digest: HashAlgorithm,
) -> Result<HashAlgorithm, Error> {
if let Some(mgf) = resolve_mgf(params.mgf_uri.as_deref())? {
return Ok(mgf);
}
if uri == algorithm::RSA_OAEP {
return Ok(HashAlgorithm::Sha1);
}
Ok(digest)
}
struct KrypteringKeyTransport {
uri: &'static str,
algo: KKeyTransportAlgorithm,
label: Option<Vec<u8>>,
}
impl KeyTransportAlgorithm for KrypteringKeyTransport {
fn uri(&self) -> &'static str {
self.uri
}
fn encrypt(&self, public_key: &rsa::RsaPublicKey, key_data: &[u8]) -> Result<Vec<u8>, Error> {
kryptering::keytransport::kt_encrypt(self.algo, public_key, key_data, self.label.as_deref())
.map_err(crate::map_kryptering_err)
}
fn decrypt(
&self,
private_key: &rsa::RsaPrivateKey,
encrypted: &[u8],
) -> Result<Vec<u8>, Error> {
kryptering::keytransport::kt_decrypt(
self.algo,
private_key,
encrypted,
self.label.as_deref(),
)
.map_err(crate::map_kryptering_err)
}
}
#[cfg(test)]
mod tests {
use super::{from_uri_with_params, resolve_digest, resolve_mgf, OaepParams};
use bergshamra_core::algorithm;
use kryptering::algorithm::HashAlgorithm;
#[test]
fn resolve_digest_defaults_only_when_absent() {
assert_eq!(resolve_digest(None).unwrap(), HashAlgorithm::Sha1);
assert!(resolve_digest(Some("urn:test:unsupported-digest")).is_err());
}
#[cfg(feature = "legacy-algorithms")]
#[test]
fn resolve_digest_rejects_substring_match_uris() {
assert!(resolve_digest(Some("urn:test:ripemd160")).is_err());
assert!(resolve_digest(Some("urn:test:md5")).is_err());
}
#[test]
fn resolve_mgf_rejects_unknown_uri() {
assert_eq!(resolve_mgf(None).unwrap(), None);
assert_eq!(
resolve_mgf(Some(algorithm::MGF1_SHA256)).unwrap(),
Some(HashAlgorithm::Sha256)
);
assert!(resolve_mgf(Some("urn:test:unsupported-mgf")).is_err());
}
#[test]
fn from_uri_with_params_rejects_unknown_oaep_mgf() {
let params = OaepParams {
digest_uri: Some(algorithm::SHA256.to_owned()),
mgf_uri: Some("urn:test:unsupported-mgf".to_owned()),
oaep_params: None,
};
assert!(from_uri_with_params(algorithm::RSA_OAEP_ENC11, params).is_err());
}
}