#![forbid(unsafe_code)]
use crate::algorithm::{HashAlgorithm, KeyTransportAlgorithm, OaepConfig};
use crate::backend::{require_supported, Operation};
use crate::error::{Error, Result};
use crate::key::{RustCryptoKey, SoftwareKey};
pub fn kt_encrypt(
algorithm: KeyTransportAlgorithm,
public_key: &SoftwareKey,
key_data: &[u8],
label: Option<&[u8]>,
) -> Result<Vec<u8>> {
require_supported(Operation::TransportEncrypt(algorithm))?;
let public_key = match public_key.inner() {
RustCryptoKey::Rsa { public, .. } => public,
_ => return Err(Error::Key("RSA public key required".into())),
};
crate::key::enforce_rsa_min_bits(Operation::TransportEncrypt(algorithm), public_key)?;
match algorithm {
#[cfg(feature = "legacy")]
KeyTransportAlgorithm::RsaPkcs1v15 => rsa_pkcs1_encrypt(public_key, key_data),
KeyTransportAlgorithm::RsaOaep(config) => {
rsa_oaep_encrypt(public_key, key_data, &config, label)
}
}
}
pub fn kt_decrypt(
algorithm: KeyTransportAlgorithm,
private_key: &SoftwareKey,
encrypted: &[u8],
label: Option<&[u8]>,
) -> Result<Vec<u8>> {
require_supported(Operation::TransportDecrypt(algorithm))?;
#[cfg(not(feature = "legacy-rsa-decryption"))]
{
let _ = (private_key, encrypted, label);
Err(Error::unsupported(
Operation::TransportDecrypt(algorithm),
crate::backend::RSA_DECRYPTION_DISABLED,
))
}
#[cfg(feature = "legacy-rsa-decryption")]
{
let private_key = match private_key.inner() {
RustCryptoKey::Rsa {
private: Some(private),
public,
} => {
crate::key::enforce_rsa_min_bits(Operation::TransportDecrypt(algorithm), public)?;
private
}
_ => return Err(Error::Key("RSA private key required".into())),
};
match algorithm {
#[cfg(feature = "legacy")]
KeyTransportAlgorithm::RsaPkcs1v15 => rsa_pkcs1_decrypt(private_key, encrypted),
KeyTransportAlgorithm::RsaOaep(config) => {
rsa_oaep_decrypt(private_key, encrypted, &config, label)
}
}
}
}
#[cfg(feature = "legacy")]
fn rsa_pkcs1_encrypt(public_key: &rsa::RsaPublicKey, key_data: &[u8]) -> Result<Vec<u8>> {
use rsa::Pkcs1v15Encrypt;
let mut rng = rand::rngs::OsRng;
public_key
.encrypt(&mut rng, Pkcs1v15Encrypt, key_data)
.map_err(|e| Error::Crypto(format!("RSA PKCS#1 encrypt: {e}")))
}
#[cfg(all(feature = "legacy", feature = "legacy-rsa-decryption"))]
fn rsa_pkcs1_decrypt(private_key: &rsa::RsaPrivateKey, encrypted: &[u8]) -> Result<Vec<u8>> {
use rsa::Pkcs1v15Encrypt;
let mut rng = rand::rngs::OsRng;
private_key
.decrypt_blinded(&mut rng, Pkcs1v15Encrypt, encrypted)
.map_err(|e| Error::Crypto(format!("RSA PKCS#1 decrypt: {e}")))
}
fn oaep_label(label: Option<&[u8]>) -> Result<Option<String>> {
match label {
Some(bytes) => {
let s = std::str::from_utf8(bytes).map_err(|e| {
Error::Crypto(format!(
"RSA-OAEP label must be valid UTF-8 (rsa 0.9 limitation): {e}"
))
})?;
Ok(Some(s.to_owned()))
}
None => Ok(None),
}
}
macro_rules! oaep_encrypt {
($public_key:expr, $key_data:expr, $digest:ty, $mgf:ty, $label:expr) => {{
use rsa::Oaep;
let label = oaep_label($label)?;
let mut rng = rand::rngs::OsRng;
let mut padding = Oaep::new_with_mgf_hash::<$digest, $mgf>();
padding.label = label;
$public_key
.encrypt(&mut rng, padding, $key_data)
.map_err(|e| Error::Crypto(format!("RSA-OAEP encrypt: {e}")))
}};
}
#[cfg(feature = "legacy-rsa-decryption")]
macro_rules! oaep_decrypt {
($private_key:expr, $encrypted:expr, $digest:ty, $mgf:ty, $label:expr) => {{
use rsa::Oaep;
let label = oaep_label($label)?;
let mut padding = Oaep::new_with_mgf_hash::<$digest, $mgf>();
padding.label = label;
let mut rng = rand::rngs::OsRng;
$private_key
.decrypt_blinded(&mut rng, padding, $encrypted)
.map_err(|e| Error::Crypto(format!("RSA-OAEP decrypt: {e}")))
}};
}
macro_rules! oaep_dispatch_encrypt {
($pk:expr, $data:expr, $digest:expr, $mgf:expr, $label:expr) => {{
macro_rules! with_mgf {
($d:ty) => {
match $mgf {
HashAlgorithm::Sha1 => oaep_encrypt!($pk, $data, $d, sha1::Sha1, $label),
HashAlgorithm::Sha224 => oaep_encrypt!($pk, $data, $d, sha2::Sha224, $label),
HashAlgorithm::Sha256 => oaep_encrypt!($pk, $data, $d, sha2::Sha256, $label),
HashAlgorithm::Sha384 => oaep_encrypt!($pk, $data, $d, sha2::Sha384, $label),
HashAlgorithm::Sha512 => oaep_encrypt!($pk, $data, $d, sha2::Sha512, $label),
other => Err(Error::unsupported(
Operation::TransportEncrypt(KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: $digest,
mgf_digest: $mgf,
})),
format!("RSA-OAEP MGF1 with {other:?} is not supported"),
)),
}
};
}
match $digest {
HashAlgorithm::Sha1 => with_mgf!(sha1::Sha1),
HashAlgorithm::Sha224 => with_mgf!(sha2::Sha224),
HashAlgorithm::Sha256 => with_mgf!(sha2::Sha256),
HashAlgorithm::Sha384 => with_mgf!(sha2::Sha384),
HashAlgorithm::Sha512 => with_mgf!(sha2::Sha512),
#[cfg(feature = "legacy")]
HashAlgorithm::Md5 => with_mgf!(md5::Md5),
#[cfg(feature = "legacy")]
HashAlgorithm::Ripemd160 => with_mgf!(ripemd::Ripemd160),
other => Err(Error::unsupported(
Operation::TransportEncrypt(KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: $digest,
mgf_digest: $mgf,
})),
format!("RSA-OAEP digest {other:?} is not supported"),
)),
}
}};
}
#[cfg(feature = "legacy-rsa-decryption")]
macro_rules! oaep_dispatch_decrypt {
($pk:expr, $data:expr, $digest:expr, $mgf:expr, $label:expr) => {{
macro_rules! with_mgf {
($d:ty) => {
match $mgf {
HashAlgorithm::Sha1 => oaep_decrypt!($pk, $data, $d, sha1::Sha1, $label),
HashAlgorithm::Sha224 => oaep_decrypt!($pk, $data, $d, sha2::Sha224, $label),
HashAlgorithm::Sha256 => oaep_decrypt!($pk, $data, $d, sha2::Sha256, $label),
HashAlgorithm::Sha384 => oaep_decrypt!($pk, $data, $d, sha2::Sha384, $label),
HashAlgorithm::Sha512 => oaep_decrypt!($pk, $data, $d, sha2::Sha512, $label),
other => Err(Error::unsupported(
Operation::TransportDecrypt(KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: $digest,
mgf_digest: $mgf,
})),
format!("RSA-OAEP MGF1 with {other:?} is not supported"),
)),
}
};
}
match $digest {
HashAlgorithm::Sha1 => with_mgf!(sha1::Sha1),
HashAlgorithm::Sha224 => with_mgf!(sha2::Sha224),
HashAlgorithm::Sha256 => with_mgf!(sha2::Sha256),
HashAlgorithm::Sha384 => with_mgf!(sha2::Sha384),
HashAlgorithm::Sha512 => with_mgf!(sha2::Sha512),
#[cfg(feature = "legacy")]
HashAlgorithm::Md5 => with_mgf!(md5::Md5),
#[cfg(feature = "legacy")]
HashAlgorithm::Ripemd160 => with_mgf!(ripemd::Ripemd160),
other => Err(Error::unsupported(
Operation::TransportDecrypt(KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: $digest,
mgf_digest: $mgf,
})),
format!("RSA-OAEP digest {other:?} is not supported"),
)),
}
}};
}
fn rsa_oaep_encrypt(
public_key: &rsa::RsaPublicKey,
key_data: &[u8],
config: &OaepConfig,
label: Option<&[u8]>,
) -> Result<Vec<u8>> {
oaep_dispatch_encrypt!(
public_key,
key_data,
config.digest,
config.mgf_digest,
label
)
}
#[cfg(feature = "legacy-rsa-decryption")]
fn rsa_oaep_decrypt(
private_key: &rsa::RsaPrivateKey,
encrypted: &[u8],
config: &OaepConfig,
label: Option<&[u8]>,
) -> Result<Vec<u8>> {
oaep_dispatch_decrypt!(
private_key,
encrypted,
config.digest,
config.mgf_digest,
label
)
}
#[cfg(test)]
mod tests {
use super::*;
use rsa::RsaPrivateKey;
fn test_keypair() -> (SoftwareKey, SoftwareKey) {
use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey};
let mut rng = rand::rngs::OsRng;
let private_key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
let public_key = rsa::RsaPublicKey::from(&private_key);
let public = SoftwareKey::from_spki_der(
crate::backend::KeyAlgorithm::Rsa,
public_key.to_public_key_der().unwrap().as_bytes(),
)
.unwrap();
let private = SoftwareKey::from_pkcs8_der(
crate::backend::KeyAlgorithm::Rsa,
private_key.to_pkcs8_der().unwrap().as_bytes(),
)
.unwrap();
(public, private)
}
fn assert_default_refused(result: Result<Vec<u8>>, algorithm: KeyTransportAlgorithm) {
let error = result.unwrap_err();
assert!(matches!(
&error,
Error::UnsupportedAlgorithm { operation, .. }
if *operation == Operation::TransportDecrypt(algorithm)
));
assert!(
error.to_string().contains("legacy-rsa-decryption"),
"{error}"
);
}
fn assert_decrypted_or_refused(
algorithm: KeyTransportAlgorithm,
private: &SoftwareKey,
encrypted: &[u8],
label: Option<&[u8]>,
expected: &[u8],
) {
let result = kt_decrypt(algorithm, private, encrypted, label);
if cfg!(feature = "legacy-rsa-decryption") {
assert_eq!(result.unwrap(), expected);
} else {
assert_default_refused(result, algorithm);
}
}
#[cfg(not(feature = "legacy-rsa-decryption"))]
#[test]
fn default_rsa_decryption_refuses_before_key_ciphertext_and_label_use() {
let wrong_key = SoftwareKey::from_symmetric_bytes(
crate::backend::KeyAlgorithm::Hmac,
b"synthetic hmac key",
)
.unwrap();
let algorithms = [
KeyTransportAlgorithm::RsaOaep(OaepConfig::default()),
KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha1,
mgf_digest: HashAlgorithm::Sha256,
}),
];
for algorithm in algorithms {
assert_default_refused(
kt_decrypt(algorithm, &wrong_key, b"", Some(&[0xff])),
algorithm,
);
}
#[cfg(feature = "legacy")]
assert_default_refused(
kt_decrypt(KeyTransportAlgorithm::RsaPkcs1v15, &wrong_key, b"", None),
KeyTransportAlgorithm::RsaPkcs1v15,
);
}
#[test]
fn test_rsa_oaep_sha1_encrypt_and_decrypt_policy() {
let (pub_key, priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha1,
mgf_digest: HashAlgorithm::Sha1,
});
let key_data = b"16-byte-key!!!!";
let encrypted = kt_encrypt(algo, &pub_key, key_data, None).unwrap();
assert_decrypted_or_refused(algo, &priv_key, &encrypted, None, key_data);
}
#[cfg(not(feature = "legacy"))]
#[test]
fn rejects_rsa_keys_below_2048_bits() {
let private = RsaPrivateKey::new(&mut rand::rngs::OsRng, 1024).unwrap();
let public = private.to_public_key();
let key = SoftwareKey::from(RustCryptoKey::Rsa {
private: Some(private),
public,
});
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig::default());
let err = kt_encrypt(algo, &key, &[0x42; 16], None).unwrap_err();
assert!(err.to_string().contains("1024-bit RSA key"), "{err}");
let err = kt_decrypt(algo, &key, &[0u8; 128], None).unwrap_err();
if cfg!(feature = "legacy-rsa-decryption") {
assert!(err.to_string().contains("1024-bit RSA key"), "{err}");
} else {
assert_default_refused(Err(err), algo);
}
}
#[test]
fn test_oaep_default_is_sha256() {
let cfg = OaepConfig::default();
assert_eq!(cfg.digest, HashAlgorithm::Sha256);
assert_eq!(cfg.mgf_digest, HashAlgorithm::Sha256);
}
#[test]
fn test_rsa_oaep_sha256_encrypt_and_decrypt_policy() {
let (pub_key, priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha256,
mgf_digest: HashAlgorithm::Sha256,
});
let key_data = [0x42u8; 32];
let encrypted = kt_encrypt(algo, &pub_key, &key_data, None).unwrap();
assert_decrypted_or_refused(algo, &priv_key, &encrypted, None, &key_data);
}
#[test]
fn test_rsa_oaep_with_label() {
let (pub_key, priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig::default());
let key_data = b"secret key data!";
let label = b"my-label";
let encrypted = kt_encrypt(algo, &pub_key, key_data, Some(label)).unwrap();
assert_decrypted_or_refused(algo, &priv_key, &encrypted, Some(label), key_data);
}
#[cfg(feature = "legacy-rsa-decryption")]
#[test]
fn test_rsa_oaep_wrong_key_fails() {
let (pub_key, _priv_key) = test_keypair();
let (_pub_key2, priv_key2) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig::default());
let key_data = b"secret key data!";
let encrypted = kt_encrypt(algo, &pub_key, key_data, None).unwrap();
let result = kt_decrypt(algo, &priv_key2, &encrypted, None);
assert!(result.is_err(), "decrypt with wrong key should fail");
}
#[test]
fn test_rsa_oaep_mixed_digests() {
let (pub_key, priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha256,
mgf_digest: HashAlgorithm::Sha1,
});
let key_data = [0xAB; 24];
let encrypted = kt_encrypt(algo, &pub_key, &key_data, None).unwrap();
assert_decrypted_or_refused(algo, &priv_key, &encrypted, None, &key_data);
}
#[test]
fn test_rsa_oaep_rejects_non_utf8_label() {
let (pub_key, priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha256,
mgf_digest: HashAlgorithm::Sha256,
});
let bad_label: &[u8] = &[0xFF, 0xFE, 0xFD];
let err = kt_encrypt(algo, &pub_key, b"k", Some(bad_label)).unwrap_err();
assert!(
matches!(err, Error::Crypto(ref m) if m.contains("UTF-8")),
"got {err:?}"
);
let ct = kt_encrypt(algo, &pub_key, b"k", Some(b"good")).unwrap();
let err = kt_decrypt(algo, &priv_key, &ct, Some(bad_label)).unwrap_err();
if cfg!(feature = "legacy-rsa-decryption") {
assert!(
matches!(err, Error::Crypto(ref m) if m.contains("UTF-8")),
"got {err:?}"
);
} else {
assert_default_refused(Err(err), algo);
}
}
#[test]
fn test_rsa_oaep_label_roundtrip_exact_bytes() {
let (pub_key, priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha256,
mgf_digest: HashAlgorithm::Sha256,
});
let label = "utf8-label-\u{1F600}-end".as_bytes();
let ct = kt_encrypt(algo, &pub_key, b"k", Some(label)).unwrap();
assert_decrypted_or_refused(algo, &priv_key, &ct, Some(label), b"k");
let err = kt_decrypt(algo, &priv_key, &ct, Some(b"other")).unwrap_err();
if cfg!(feature = "legacy-rsa-decryption") {
assert!(matches!(err, Error::Crypto(_)), "got {err:?}");
} else {
assert_default_refused(Err(err), algo);
}
}
#[test]
fn test_rsa_oaep_rejects_sha3_digest() {
let (pub_key, priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha3_256,
mgf_digest: HashAlgorithm::Sha256,
});
let err = kt_encrypt(algo, &pub_key, b"k", None).unwrap_err();
assert!(
matches!(err, Error::UnsupportedAlgorithm { .. }),
"got {err:?}"
);
let good = KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha256,
mgf_digest: HashAlgorithm::Sha256,
});
let ct = kt_encrypt(good, &pub_key, b"k", None).unwrap();
let err = kt_decrypt(algo, &priv_key, &ct, None).unwrap_err();
assert!(
matches!(err, Error::UnsupportedAlgorithm { .. }),
"got {err:?}"
);
}
#[test]
fn test_rsa_oaep_rejects_sha3_mgf() {
let (pub_key, _priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaOaep(OaepConfig {
digest: HashAlgorithm::Sha256,
mgf_digest: HashAlgorithm::Sha3_256,
});
let err = kt_encrypt(algo, &pub_key, b"k", None).unwrap_err();
assert!(
matches!(err, Error::UnsupportedAlgorithm { .. }),
"got {err:?}"
);
}
#[cfg(feature = "legacy")]
#[test]
fn test_rsa_pkcs1v15_encrypt_and_decrypt_policy() {
let (pub_key, priv_key) = test_keypair();
let algo = KeyTransportAlgorithm::RsaPkcs1v15;
let key_data = b"a 24 byte key here!!!!!";
let encrypted = kt_encrypt(algo, &pub_key, key_data, None).unwrap();
assert_decrypted_or_refused(algo, &priv_key, &encrypted, None, key_data);
}
}