use native_ossl::cipher::{AeadDecryptCtx, AeadEncryptCtx, CipherAlg};
use native_ossl::digest::DigestAlg;
use native_ossl::pkey::{KeygenCtx, Pkey, Private, Public};
use super::key_transport::{
rsa_oaep_decrypt_with_key, rsa_oaep_encrypt_with_key, rsa_pkcs1_decrypt_with_key,
rsa_pkcs1_encrypt_with_key,
};
use super::OpensslKeyError;
#[derive(Debug)]
pub struct OpensslSymmetricError(pub(super) String);
impl std::fmt::Display for OpensslSymmetricError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for OpensslSymmetricError {}
impl From<native_ossl::error::ErrorStack> for OpensslSymmetricError {
fn from(e: native_ossl::error::ErrorStack) -> Self {
Self(e.to_string())
}
}
fn symmetric_md_from_str(alg: &str) -> Result<DigestAlg, OpensslSymmetricError> {
super::alg_cache::digest_by_str(alg).ok_or_else(|| {
OpensslSymmetricError(format!(
"unknown hash algorithm: {alg} \
(accepted: sha1, sha224, sha256, sha384, sha512, \
sha3-256, sha3-384, sha3-512, md5, \
or their OpenSSL native equivalents SHA1, SHA2-256, SHA3-256, …)"
))
})
}
fn aes_cipher_for_key(key: &[u8]) -> Result<CipherAlg, OpensslSymmetricError> {
let cipher = match key.len() {
16 => super::alg_cache::aes128_cbc(),
24 => super::alg_cache::aes192_cbc(),
32 => super::alg_cache::aes256_cbc(),
n => {
return Err(OpensslSymmetricError(format!(
"invalid AES key length: {n} (must be 16, 24, or 32)"
)))
}
};
cipher.ok_or_else(|| OpensslSymmetricError("AES-CBC cipher not available".to_string()))
}
fn aes_gcm_cipher_for_key(key: &[u8]) -> Result<CipherAlg, OpensslSymmetricError> {
let cipher = match key.len() {
16 => super::alg_cache::aes128_gcm(),
24 => super::alg_cache::aes192_gcm(),
32 => super::alg_cache::aes256_gcm(),
n => {
return Err(OpensslSymmetricError(format!(
"invalid AES key length: {n} (must be 16, 24, or 32)"
)))
}
};
cipher.ok_or_else(|| OpensslSymmetricError("AES-GCM cipher not available".to_string()))
}
fn des3_cipher_for_key(key: &[u8]) -> Result<CipherAlg, OpensslSymmetricError> {
match key.len() {
24 => super::alg_cache::des3_cbc()
.ok_or_else(|| OpensslSymmetricError("DES-EDE3-CBC cipher not available".to_string())),
n => Err(OpensslSymmetricError(format!(
"invalid 3DES key length: {n} (must be 24)"
))),
}
}
fn cipher_encrypt(
cipher: &CipherAlg,
key: &[u8],
iv: &[u8],
plaintext: &[u8],
pad: bool,
) -> Result<Vec<u8>, OpensslSymmetricError> {
use native_ossl::params::ParamBuilder;
let mut ctx = cipher.encrypt(key, iv, None)?;
if !pad {
let params = ParamBuilder::new()?.push_int(c"padding", 0)?.build()?;
ctx.set_params(¶ms)?;
}
let block = cipher.block_size();
let mut out = vec![0u8; plaintext.len() + block];
let n = ctx.update(plaintext, &mut out)?;
let m = ctx.finalize(&mut out[n..])?;
out.truncate(n + m);
Ok(out)
}
fn cipher_decrypt(
cipher: &CipherAlg,
key: &[u8],
iv: &[u8],
ciphertext: &[u8],
unpad: bool,
) -> Result<Vec<u8>, OpensslSymmetricError> {
use native_ossl::params::ParamBuilder;
let mut ctx = cipher.decrypt(key, iv, None)?;
if !unpad {
let params = ParamBuilder::new()?.push_int(c"padding", 0)?.build()?;
ctx.set_params(¶ms)?;
}
let block = cipher.block_size();
let mut out = vec![0u8; ciphertext.len() + block];
let n = ctx.update(ciphertext, &mut out)?;
let m = ctx.finalize(&mut out[n..])?;
out.truncate(n + m);
Ok(out)
}
pub struct OpensslSymmetricCrypto;
impl crate::crypto::DataHasher for OpensslSymmetricCrypto {
type Error = OpensslSymmetricError;
fn hash_data(&self, algorithm: &str, data: &[u8]) -> Result<Vec<u8>, OpensslSymmetricError> {
let md = symmetric_md_from_str(algorithm)?;
md.digest_to_vec(data).map_err(Into::into)
}
}
impl crate::crypto::HmacProvider for OpensslSymmetricCrypto {
type Error = OpensslSymmetricError;
fn hmac_compute(
&self,
algorithm: &str,
key: &[u8],
data: &[u8],
) -> Result<Vec<u8>, OpensslSymmetricError> {
let md = symmetric_md_from_str(algorithm)?;
let mut ctx = native_ossl::mac::HmacCtx::new(&md, key)?;
ctx.update(data)?;
ctx.finish_to_vec().map_err(Into::into)
}
fn hmac_verify(
&self,
algorithm: &str,
key: &[u8],
data: &[u8],
expected: &[u8],
) -> Result<(), OpensslSymmetricError> {
let mac = self.hmac_compute(algorithm, key, data)?;
if !self.constant_time_eq(&mac, expected) {
return Err(OpensslSymmetricError("HMAC verification failed".into()));
}
Ok(())
}
fn constant_time_eq(&self, a: &[u8], b: &[u8]) -> bool {
native_ossl::util::ct_eq(a, b)
}
}
impl crate::crypto::Pbkdf2Provider for OpensslSymmetricCrypto {
type Error = OpensslSymmetricError;
fn pbkdf2_hmac(
&self,
algorithm: &str,
password: &[u8],
salt: &[u8],
iterations: usize,
length: usize,
) -> Result<Vec<u8>, OpensslSymmetricError> {
let md = symmetric_md_from_str(algorithm)?;
native_ossl::kdf::Pbkdf2Builder::new(&md, password, salt)
.iterations(iterations as u32)
.derive_to_vec(length)
.map_err(Into::into)
}
}
impl crate::crypto::BlockCipherProvider for OpensslSymmetricCrypto {
type Error = OpensslSymmetricError;
fn aes_cbc_encrypt(
&self,
key: &[u8],
iv: &[u8],
plaintext: &[u8],
pad: bool,
) -> Result<Vec<u8>, OpensslSymmetricError> {
cipher_encrypt(&aes_cipher_for_key(key)?, key, iv, plaintext, pad)
}
fn aes_cbc_decrypt(
&self,
key: &[u8],
iv: &[u8],
ciphertext: &[u8],
unpad: bool,
) -> Result<Vec<u8>, OpensslSymmetricError> {
cipher_decrypt(&aes_cipher_for_key(key)?, key, iv, ciphertext, unpad)
}
fn des3_cbc_encrypt(
&self,
key: &[u8],
iv: &[u8],
plaintext: &[u8],
pad: bool,
) -> Result<Vec<u8>, OpensslSymmetricError> {
cipher_encrypt(&des3_cipher_for_key(key)?, key, iv, plaintext, pad)
}
fn des3_cbc_decrypt(
&self,
key: &[u8],
iv: &[u8],
ciphertext: &[u8],
unpad: bool,
) -> Result<Vec<u8>, OpensslSymmetricError> {
cipher_decrypt(&des3_cipher_for_key(key)?, key, iv, ciphertext, unpad)
}
fn aes_gcm_encrypt(
&self,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, OpensslSymmetricError> {
let alg = aes_gcm_cipher_for_key(key)?;
let mut ctx = AeadEncryptCtx::new(&alg, key, nonce, None)
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
ctx.set_aad(aad)
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
let mut out = vec![0u8; plaintext.len()];
let n = ctx
.update(plaintext, &mut out)
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
ctx.finalize(&mut out[n..])
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
let mut tag = [0u8; 16];
ctx.tag(&mut tag)
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
out.extend_from_slice(&tag);
Ok(out)
}
fn aes_gcm_decrypt(
&self,
key: &[u8],
nonce: &[u8],
ciphertext_with_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, OpensslSymmetricError> {
if ciphertext_with_tag.len() < 16 {
return Err(OpensslSymmetricError(
"AES-GCM input too short (must be at least 16 bytes for the tag)".to_string(),
));
}
let (ct, tag) = ciphertext_with_tag.split_at(ciphertext_with_tag.len() - 16);
let alg = aes_gcm_cipher_for_key(key)?;
let mut ctx = AeadDecryptCtx::new(&alg, key, nonce, None)
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
ctx.set_aad(aad)
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
let mut out = vec![0u8; ct.len()];
let n = ctx
.update(ct, &mut out)
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
ctx.set_tag(tag)
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
ctx.finalize(&mut out[n..])
.map_err(|e| OpensslSymmetricError(e.to_string()))?;
out.truncate(n);
Ok(out)
}
}
impl crate::crypto::SecureRandom for OpensslSymmetricCrypto {
type Error = OpensslSymmetricError;
fn rand_bytes(&self, out: &mut [u8]) -> Result<(), OpensslSymmetricError> {
native_ossl::rand::Rand::fill(out).map_err(Into::into)
}
}
pub(crate) fn parse_public_key(spki_der: &[u8]) -> Result<Pkey<Public>, OpensslKeyError> {
Pkey::<Public>::from_der(spki_der)
.map_err(|e| OpensslKeyError(format!("failed to load public key: {e}")))
}
pub(crate) fn parse_public_key_from_pem(pem: &[u8]) -> Result<Pkey<Public>, OpensslKeyError> {
Pkey::<Public>::from_pem(pem)
.map_err(|e| OpensslKeyError(format!("failed to load public key from PEM: {e}")))
}
pub(crate) fn parse_private_key(pkcs8_der: &[u8]) -> Result<Pkey<Private>, OpensslKeyError> {
Pkey::<Private>::from_der(pkcs8_der)
.map_err(|e| OpensslKeyError(format!("failed to load private key: {e}")))
}
fn key_type_str(k: &Pkey<Public>) -> Option<&'static str> {
if k.is_a(c"RSA") {
return Some("rsa");
}
if k.is_a(c"EC") {
return Some("ec");
}
if k.is_a(c"ED25519") {
return Some("ed25519");
}
if k.is_a(c"ED448") {
return Some("ed448");
}
if k.is_a(c"DSA") {
return Some("dsa");
}
None }
fn priv_key_type_str(k: &Pkey<Private>) -> Option<&'static str> {
if k.is_a(c"RSA") {
return Some("rsa");
}
if k.is_a(c"EC") {
return Some("ec");
}
if k.is_a(c"ED25519") {
return Some("ed25519");
}
if k.is_a(c"ED448") {
return Some("ed448");
}
if k.is_a(c"DSA") {
return Some("dsa");
}
None }
fn pqc_name_to_key_type(name: &str) -> &'static str {
use crate::names;
match name {
names::ML_DSA_44 => "ml-dsa-44",
names::ML_DSA_65 => "ml-dsa-65",
names::ML_DSA_87 => "ml-dsa-87",
names::ML_KEM_512 => "ml-kem-512",
names::ML_KEM_768 => "ml-kem-768",
names::ML_KEM_1024 => "ml-kem-1024",
_ => "unknown",
}
}
fn pqc_type_from_spki_der(spki_der: &[u8]) -> &'static str {
let mut dec = synta::Decoder::new(spki_der, synta::Encoding::Der);
let Ok(spki) = dec.decode::<crate::SubjectPublicKeyInfo>() else {
return "unknown";
};
match crate::identify_public_key_algorithm(&spki.algorithm.algorithm) {
Some(name) => pqc_name_to_key_type(name),
None => "unknown",
}
}
pub(crate) fn pub_key_type(spki_der: &[u8]) -> &'static str {
parse_public_key(spki_der)
.ok()
.and_then(|k| key_type_str(&k))
.unwrap_or_else(|| pqc_type_from_spki_der(spki_der))
}
pub(crate) fn pub_key_bit_size(spki_der: &[u8]) -> Option<i64> {
let k = parse_public_key(spki_der).ok()?;
if k.is_a(c"ED25519") || k.is_a(c"ED448") {
return None;
}
key_type_str(&k)?;
Some(k.bits() as i64)
}
pub(crate) fn pub_rsa_modulus(spki_der: &[u8]) -> Result<Option<Vec<u8>>, OpensslKeyError> {
let k = parse_public_key(spki_der)?;
if !k.is_a(c"RSA") {
return Ok(None);
}
let params = k.export().map_err(|e| OpensslKeyError(e.to_string()))?;
Ok(Some(
params
.get_bn(c"n")
.map_err(|e| OpensslKeyError(e.to_string()))?,
))
}
pub(crate) fn pub_rsa_public_exponent(spki_der: &[u8]) -> Result<Option<Vec<u8>>, OpensslKeyError> {
let k = parse_public_key(spki_der)?;
if !k.is_a(c"RSA") {
return Ok(None);
}
let params = k.export().map_err(|e| OpensslKeyError(e.to_string()))?;
Ok(Some(
params
.get_bn(c"e")
.map_err(|e| OpensslKeyError(e.to_string()))?,
))
}
pub(crate) fn pub_ec_curve_name(spki_der: &[u8]) -> Result<Option<&'static str>, OpensslKeyError> {
let mut dec = synta::Decoder::new(spki_der, synta::Encoding::Der);
let spki = dec
.decode::<crate::SubjectPublicKeyInfo<'_>>()
.map_err(|e| OpensslKeyError(e.to_string()))?;
if spki.algorithm.algorithm.components() != crate::oids::EC_PUBLIC_KEY {
return Ok(None);
}
let Some(synta::Element::ObjectIdentifier(curve_oid)) = spki.algorithm.parameters else {
return Ok(None);
};
Ok(crate::ec_curve_nist_name(curve_oid.components()))
}
pub(crate) type EcAffineCoords = (Vec<u8>, Vec<u8>);
pub(crate) fn pub_ec_affine_coordinates(
spki_der: &[u8],
) -> Result<Option<EcAffineCoords>, OpensslKeyError> {
let mut dec = synta::Decoder::new(spki_der, synta::Encoding::Der);
let spki = dec
.decode::<crate::SubjectPublicKeyInfo<'_>>()
.map_err(|e| OpensslKeyError(e.to_string()))?;
if spki.algorithm.algorithm.components() != crate::oids::EC_PUBLIC_KEY {
return Ok(None);
}
let point = spki.subject_public_key.as_bytes();
if point.len() < 3 || point[0] != 0x04 {
return Err(OpensslKeyError(
"EC public key is not in uncompressed form".to_string(),
));
}
let coord_len = (point.len() - 1) / 2;
let qx = point[1..1 + coord_len].to_vec();
let qy = point[1 + coord_len..].to_vec();
Ok(Some((qx, qy)))
}
pub(crate) fn pub_rsa_oaep_encrypt(
spki_der: &[u8],
plaintext: &[u8],
hash_alg: &str,
) -> Result<Vec<u8>, OpensslKeyError> {
let k = parse_public_key(spki_der)?;
rsa_oaep_encrypt_with_key(&k, plaintext, hash_alg)
}
pub(crate) fn pub_rsa_pkcs1v15_encrypt(
spki_der: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, OpensslKeyError> {
let k = parse_public_key(spki_der)?;
rsa_pkcs1_encrypt_with_key(&k, plaintext)
}
#[cfg(feature = "pqc")]
pub(crate) fn priv_sign_ml_dsa_with_context(
pkcs8_der: &[u8],
data: &[u8],
context: &[u8],
) -> Result<Vec<u8>, OpensslKeyError> {
#[cfg(ossl_mldsa)]
{
use native_ossl::params::ParamBuilder;
use native_ossl::pkey::{SignInit, Signer};
let key = parse_private_key(pkcs8_der)?;
let kt = priv_key_type(pkcs8_der);
if !matches!(kt, "ml-dsa-44" | "ml-dsa-65" | "ml-dsa-87") {
return Err(OpensslKeyError(format!(
"sign_ml_dsa_with_context requires an ML-DSA key, got {kt}"
)));
}
let params = ParamBuilder::new()
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_octet_slice(c"context-string", context)
.map_err(|e| OpensslKeyError(e.to_string()))?
.build()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let init = SignInit {
digest: None,
params: Some(¶ms),
};
let mut signer = Signer::new(&key, &init).map_err(|e| OpensslKeyError(e.to_string()))?;
signer
.sign_oneshot(data)
.map_err(|e| OpensslKeyError(e.to_string()))
}
#[cfg(not(ossl_mldsa))]
{
let _ = (pkcs8_der, data, context);
Err(OpensslKeyError(
"ML-DSA signing requires OpenSSL with ML-DSA support".to_string(),
))
}
}
#[cfg(not(feature = "pqc"))]
pub(crate) fn priv_sign_ml_dsa_with_context(
_pkcs8_der: &[u8],
_data: &[u8],
_context: &[u8],
) -> Result<Vec<u8>, OpensslKeyError> {
Err(OpensslKeyError(
"ML-DSA signing requires the 'pqc' feature".to_string(),
))
}
#[cfg(feature = "pqc")]
pub(crate) fn pub_verify_ml_dsa_with_context(
spki_der: &[u8],
data: &[u8],
signature: &[u8],
context: &[u8],
) -> Result<(), OpensslKeyError> {
#[cfg(ossl_mldsa)]
{
use native_ossl::params::ParamBuilder;
use native_ossl::pkey::{SignInit, Verifier};
let k = parse_public_key(spki_der)?;
let params = ParamBuilder::new()
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_octet_slice(c"context-string", context)
.map_err(|e| OpensslKeyError(e.to_string()))?
.build()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let init = SignInit {
digest: None,
params: Some(¶ms),
};
let mut ver = Verifier::new(&k, &init).map_err(|e| OpensslKeyError(e.to_string()))?;
if ver
.verify_oneshot(data, signature)
.map_err(|e| OpensslKeyError(e.to_string()))?
{
Ok(())
} else {
Err(OpensslKeyError(
"ML-DSA signature verification failed".into(),
))
}
}
#[cfg(not(ossl_mldsa))]
{
let _ = (spki_der, data, signature, context);
Err(OpensslKeyError(
"ML-DSA verification requires OpenSSL with ML-DSA support".to_string(),
))
}
}
#[cfg(not(feature = "pqc"))]
pub(crate) fn pub_verify_ml_dsa_with_context(
_spki_der: &[u8],
_data: &[u8],
_signature: &[u8],
_context: &[u8],
) -> Result<(), OpensslKeyError> {
Err(OpensslKeyError(
"ML-DSA verification requires the 'pqc' feature".to_string(),
))
}
pub(crate) fn pub_verify_message(
spki_der: &[u8],
data: &[u8],
signature: &[u8],
algorithm: Option<&str>,
) -> Result<(), OpensslKeyError> {
use native_ossl::pkey::{SignInit, Verifier};
let k = parse_public_key(spki_der)?;
#[cfg(all(feature = "pqc", ossl_mldsa))]
if k.is_a(c"ML-DSA-44") || k.is_a(c"ML-DSA-65") || k.is_a(c"ML-DSA-87") {
let init = SignInit {
digest: None,
params: None,
};
let mut ver = Verifier::new(&k, &init).map_err(|e| OpensslKeyError(e.to_string()))?;
return if ver
.verify_oneshot(data, signature)
.map_err(|e| OpensslKeyError(e.to_string()))?
{
Ok(())
} else {
Err(OpensslKeyError(
"ML-DSA signature verification failed".into(),
))
};
}
{
let kt = pub_key_type(spki_der);
if matches!(kt, "ml-kem-512" | "ml-kem-768" | "ml-kem-1024") {
return Err(OpensslKeyError(
"ML-KEM keys cannot verify signatures; use kem_encapsulate() instead".into(),
));
}
}
if k.is_a(c"ED25519") || k.is_a(c"ED448") {
let init = SignInit {
digest: None,
params: None,
};
let mut ver = Verifier::new(&k, &init).map_err(|e| OpensslKeyError(e.to_string()))?;
return if ver
.verify_oneshot(data, signature)
.map_err(|e| OpensslKeyError(e.to_string()))?
{
Ok(())
} else {
Err(OpensslKeyError(
"EdDSA signature verification failed".into(),
))
};
}
let alg = algorithm
.ok_or_else(|| OpensslKeyError("algorithm is required for RSA and ECDSA keys".into()))?;
let md = alg_str_to_digest(alg)?;
let init = SignInit {
digest: Some(&md),
params: None,
};
let mut ver = Verifier::new(&k, &init).map_err(|e| OpensslKeyError(e.to_string()))?;
ver.update(data)
.map_err(|e| OpensslKeyError(e.to_string()))?;
if ver
.verify(signature)
.map_err(|e| OpensslKeyError(e.to_string()))?
{
Ok(())
} else {
Err(OpensslKeyError("signature verification failed".into()))
}
}
#[cfg(feature = "pqc")]
pub(crate) fn pub_ml_kem_encapsulate(
spki_der: &[u8],
) -> Result<(Vec<u8>, Vec<u8>), OpensslKeyError> {
#[cfg(all(ossl320, ossl_mlkem))]
{
use native_ossl::pkey::EncapCtx;
let pkey = parse_public_key(spki_der)?;
let mut enc = EncapCtx::new(&pkey).map_err(|e| OpensslKeyError(e.to_string()))?;
let result = enc
.encapsulate()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok((result.wrapped_key, result.shared_secret))
}
#[cfg(not(all(ossl320, ossl_mlkem)))]
{
let _ = spki_der;
Err(OpensslKeyError(
"ML-KEM encapsulation requires OpenSSL 3.2+ with ML-KEM support".to_string(),
))
}
}
#[cfg(not(feature = "pqc"))]
pub(crate) fn pub_ml_kem_encapsulate(
_spki_der: &[u8],
) -> Result<(Vec<u8>, Vec<u8>), OpensslKeyError> {
Err(OpensslKeyError(
"ML-KEM encapsulation requires the 'pqc' feature".to_string(),
))
}
#[cfg(feature = "pqc")]
pub(crate) fn priv_ml_kem_decapsulate(
pkcs8_der: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>, OpensslKeyError> {
#[cfg(all(ossl320, ossl_mlkem))]
{
use native_ossl::pkey::DecapCtx;
let pkey = parse_private_key(pkcs8_der)?;
let mut dec = DecapCtx::new(&pkey).map_err(|e| OpensslKeyError(e.to_string()))?;
dec.decapsulate(ciphertext)
.map_err(|e| OpensslKeyError(e.to_string()))
}
#[cfg(not(all(ossl320, ossl_mlkem)))]
{
let _ = (pkcs8_der, ciphertext);
Err(OpensslKeyError(
"ML-KEM decapsulation requires OpenSSL 3.2+ with ML-KEM support".to_string(),
))
}
}
#[cfg(not(feature = "pqc"))]
pub(crate) fn priv_ml_kem_decapsulate(
_pkcs8_der: &[u8],
_ciphertext: &[u8],
) -> Result<Vec<u8>, OpensslKeyError> {
Err(OpensslKeyError(
"ML-KEM decapsulation requires the 'pqc' feature".to_string(),
))
}
fn alg_str_to_digest(alg: &str) -> Result<DigestAlg, OpensslKeyError> {
super::alg_cache::digest_by_str(alg).ok_or_else(|| {
OpensslKeyError(format!(
"unsupported hash algorithm: {alg}; \
expected sha1, sha224, sha256, sha384, or sha512"
))
})
}
pub(crate) fn priv_pem_to_pkey_and_pkcs8(
pem: &[u8],
password: Option<&[u8]>,
) -> Result<(Pkey<Private>, Vec<u8>), OpensslKeyError> {
let pkey = match password {
Some(pw) => Pkey::<Private>::from_pem_passphrase(pem, pw),
None => Pkey::<Private>::from_pem(pem),
}
.map_err(|e| OpensslKeyError(format!("failed to load private key from PEM: {e}")))?;
let pkcs8_der = pkey
.to_pkcs8_der()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok((pkey, pkcs8_der))
}
pub(crate) fn priv_pkcs8_der_to_pem(
pkcs8_der: &[u8],
password: Option<&[u8]>,
) -> Result<Vec<u8>, OpensslKeyError> {
let pkey = parse_private_key(pkcs8_der)?;
match password {
Some(pw) => {
let cipher = super::alg_cache::aes256_cbc()
.ok_or_else(|| OpensslKeyError("AES-256-CBC not available".to_string()))?;
pkey.to_pem_encrypted(&cipher, pw)
.map_err(|e| OpensslKeyError(e.to_string()))
}
None => pkey.to_pem().map_err(|e| OpensslKeyError(e.to_string())),
}
}
pub(crate) fn priv_to_pkcs8_encrypted(
pkcs8_der: &[u8],
password: &[u8],
) -> Result<Vec<u8>, OpensslKeyError> {
let pem = priv_pkcs8_der_to_pem(pkcs8_der, Some(password))?;
let mut blocks = crate::pem::pem_to_der(&pem);
blocks.pop().ok_or_else(|| {
OpensslKeyError("internal error: no DER block in encrypted PKCS#8 PEM".into())
})
}
pub(crate) fn priv_from_pkcs8_encrypted_to_pkey_and_der(
data: &[u8],
password: &[u8],
) -> Result<(Pkey<Private>, Vec<u8>), OpensslKeyError> {
let pem = crate::pem::der_to_pem("ENCRYPTED PRIVATE KEY", data);
let pkey = Pkey::<Private>::from_pem_passphrase(&pem, password)
.map_err(|e| OpensslKeyError(format!("failed to decrypt private key: {e}")))?;
let pkcs8_der = pkey
.to_pkcs8_der()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok((pkey, pkcs8_der))
}
pub(crate) fn priv_key_type(pkcs8_der: &[u8]) -> &'static str {
if let Ok(k) = parse_private_key(pkcs8_der) {
if let Some(name) = priv_key_type_str(&k) {
return name;
}
if let Ok(spki) = k.public_key_to_der() {
return pqc_type_from_spki_der(&spki);
}
}
"unknown"
}
pub(crate) fn priv_key_bit_size(pkcs8_der: &[u8]) -> Option<i64> {
let k = parse_private_key(pkcs8_der).ok()?;
if k.is_a(c"ED25519") || k.is_a(c"ED448") {
return None;
}
priv_key_type_str(&k)?;
Some(k.bits() as i64)
}
pub(crate) fn priv_public_key_spki_der(pkcs8_der: &[u8]) -> Result<Vec<u8>, OpensslKeyError> {
parse_private_key(pkcs8_der)?
.public_key_to_der()
.map_err(|e| OpensslKeyError(e.to_string()))
}
pub(crate) fn priv_rsa_oaep_decrypt(
pkcs8_der: &[u8],
ciphertext: &[u8],
hash_alg: &str,
) -> Result<Vec<u8>, OpensslKeyError> {
let k = parse_private_key(pkcs8_der)?;
rsa_oaep_decrypt_with_key(&k, ciphertext, hash_alg)
}
pub(crate) fn priv_rsa_pkcs1v15_decrypt(
pkcs8_der: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>, OpensslKeyError> {
let k = parse_private_key(pkcs8_der)?;
rsa_pkcs1_decrypt_with_key(&k, ciphertext)
}
pub(crate) fn priv_generate_rsa(
key_size: u32,
public_exponent: u32,
) -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
use native_ossl::params::ParamBuilder;
let params = ParamBuilder::new()
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_uint(c"bits", key_size)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_uint(c"e", public_exponent)
.map_err(|e| OpensslKeyError(e.to_string()))?
.build()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let mut kgen = KeygenCtx::new(c"RSA").map_err(|e| OpensslKeyError(e.to_string()))?;
kgen.set_params(¶ms)
.map_err(|e| OpensslKeyError(e.to_string()))?;
let pkey: Pkey<Private> = kgen
.generate()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let spki_der = pkey
.public_key_to_der()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok(crate::crypto::BackendPrivateKey::with_pkey_cache(
spki_der, pkey,
))
}
fn ec_spki_header_cached(curve: &str) -> Option<&'static [u8]> {
fn make(curve_oid_components: &[u32], point_len: usize) -> Vec<u8> {
use synta::types::constructed::Element;
use synta::types::string::BitStringRef;
use synta::{Encode, ObjectIdentifier};
let dummy_point = vec![0u8; point_len];
let spki = crate::SubjectPublicKeyInfo {
algorithm: crate::AlgorithmIdentifier {
algorithm: ObjectIdentifier::new(crate::oids::EC_PUBLIC_KEY)
.expect("EC_PUBLIC_KEY is a valid static OID constant"),
parameters: Some(Element::ObjectIdentifier(
ObjectIdentifier::new(curve_oid_components)
.expect("curve OID components are valid"),
)),
},
subject_public_key: BitStringRef::new(&dummy_point, 0)
.expect("zero-filled dummy point is valid DER"),
};
let mut enc = synta::Encoder::new(synta::Encoding::Der);
spki.encode(&mut enc)
.expect("SubjectPublicKeyInfo encoding is infallible");
let mut der = enc
.finish()
.expect("DER encoder finalisation is infallible");
der.truncate(der.len() - point_len);
der
}
static P256: std::sync::OnceLock<Vec<u8>> = std::sync::OnceLock::new();
static P384: std::sync::OnceLock<Vec<u8>> = std::sync::OnceLock::new();
static P521: std::sync::OnceLock<Vec<u8>> = std::sync::OnceLock::new();
match curve {
"P-256" => Some(P256.get_or_init(|| make(crate::oids::EC_CURVE_P256, 65))),
"P-384" => Some(P384.get_or_init(|| make(crate::oids::EC_CURVE_P384, 97))),
"P-521" => Some(P521.get_or_init(|| make(crate::oids::EC_CURVE_P521, 133))),
_ => None,
}
}
fn ec_spki_from_pkey(curve: &str, pkey: &Pkey<Private>) -> Result<Vec<u8>, OpensslKeyError> {
let header = ec_spki_header_cached(curve).ok_or_else(|| {
OpensslKeyError(format!(
"unsupported EC curve for SPKI construction: {curve}"
))
})?;
let exported = pkey
.export()
.map_err(|e| OpensslKeyError(format!("EC key export failed: {e}")))?;
let pub_point = exported
.get_octet_string(c"pub")
.map_err(|e| OpensslKeyError(format!("EC pub point export failed: {e}")))?;
let mut spki = Vec::with_capacity(header.len() + pub_point.len());
spki.extend_from_slice(header);
spki.extend_from_slice(pub_point);
Ok(spki)
}
pub(crate) fn priv_generate_ec(
curve: &str,
) -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
use native_ossl::params::ParamBuilder;
let curve_cstr: &std::ffi::CStr = match curve {
"P-256" => c"P-256",
"P-384" => c"P-384",
"P-521" => c"P-521",
other => {
return Err(OpensslKeyError(format!(
"unsupported EC curve: {other}; expected one of P-256, P-384, P-521"
)))
}
};
let params = ParamBuilder::new()
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_utf8_string(c"group", curve_cstr)
.map_err(|e| OpensslKeyError(e.to_string()))?
.build()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let mut kgen = KeygenCtx::new(c"EC").map_err(|e| OpensslKeyError(e.to_string()))?;
kgen.set_params(¶ms)
.map_err(|e| OpensslKeyError(e.to_string()))?;
let pkey: Pkey<Private> = kgen
.generate()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let spki_der = ec_spki_from_pkey(curve, &pkey)?;
Ok(crate::crypto::BackendPrivateKey::with_pkey_cache(
spki_der, pkey,
))
}
pub(crate) fn priv_generate_ed25519() -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
let mut kgen = KeygenCtx::new(c"ED25519").map_err(|e| OpensslKeyError(e.to_string()))?;
let pkey: Pkey<Private> = kgen
.generate()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let spki_der = pkey
.public_key_to_der()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok(crate::crypto::BackendPrivateKey::with_pkey_cache(
spki_der, pkey,
))
}
pub(crate) fn priv_generate_ed448() -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
let mut kgen = KeygenCtx::new(c"ED448").map_err(|e| OpensslKeyError(e.to_string()))?;
let pkey: Pkey<Private> = kgen
.generate()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let spki_der = pkey
.public_key_to_der()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok(crate::crypto::BackendPrivateKey::with_pkey_cache(
spki_der, pkey,
))
}
#[cfg(feature = "pqc")]
pub(crate) fn priv_generate_ml_dsa(
parameter_set: &str,
) -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
#[cfg(ossl_mldsa)]
{
let name: &std::ffi::CStr = match parameter_set {
"ML-DSA-44" => c"ML-DSA-44",
"ML-DSA-65" => c"ML-DSA-65",
"ML-DSA-87" => c"ML-DSA-87",
other => {
return Err(OpensslKeyError(format!(
"unsupported ML-DSA parameter set: {other}; \
expected ML-DSA-44, ML-DSA-65, or ML-DSA-87"
)))
}
};
let mut kgen = KeygenCtx::new(name).map_err(|e| OpensslKeyError(e.to_string()))?;
let pkey: Pkey<Private> = kgen
.generate()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let spki_der = pkey
.public_key_to_der()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok(crate::crypto::BackendPrivateKey::with_pkey_cache(
spki_der, pkey,
))
}
#[cfg(not(ossl_mldsa))]
{
let _ = parameter_set;
Err(OpensslKeyError(
"ML-DSA key generation requires OpenSSL with ML-DSA support".to_string(),
))
}
}
#[cfg(not(feature = "pqc"))]
pub(crate) fn priv_generate_ml_dsa(
_parameter_set: &str,
) -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
Err(OpensslKeyError(
"ML-DSA key generation requires the 'pqc' feature".to_string(),
))
}
#[cfg(feature = "pqc")]
pub(crate) fn priv_generate_ml_kem(
parameter_set: &str,
) -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
#[cfg(all(ossl320, ossl_mlkem))]
{
let name: &std::ffi::CStr = match parameter_set {
"ML-KEM-512" => c"ML-KEM-512",
"ML-KEM-768" => c"ML-KEM-768",
"ML-KEM-1024" => c"ML-KEM-1024",
other => {
return Err(OpensslKeyError(format!(
"unsupported ML-KEM parameter set: {other}; \
expected ML-KEM-512, ML-KEM-768, or ML-KEM-1024"
)))
}
};
let mut kgen = KeygenCtx::new(name).map_err(|e| OpensslKeyError(e.to_string()))?;
let pkey: Pkey<Private> = kgen
.generate()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let spki_der = pkey
.public_key_to_der()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok(crate::crypto::BackendPrivateKey::with_pkey_cache(
spki_der, pkey,
))
}
#[cfg(not(all(ossl320, ossl_mlkem)))]
{
let _ = parameter_set;
Err(OpensslKeyError(
"ML-KEM key generation requires OpenSSL 3.2+ with ML-KEM support".to_string(),
))
}
}
#[cfg(not(feature = "pqc"))]
pub(crate) fn priv_generate_ml_kem(
_parameter_set: &str,
) -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
Err(OpensslKeyError(
"ML-KEM key generation requires the 'pqc' feature".to_string(),
))
}
pub(crate) fn pub_rsa_from_components(n: &[u8], e: &[u8]) -> Result<Vec<u8>, OpensslKeyError> {
use native_ossl::params::ParamBuilder;
let params = ParamBuilder::new()
.map_err(|e_| OpensslKeyError(e_.to_string()))?
.push_bn(c"n", n)
.map_err(|e_| OpensslKeyError(e_.to_string()))?
.push_bn(c"e", e)
.map_err(|e_| OpensslKeyError(e_.to_string()))?
.build()
.map_err(|e_| OpensslKeyError(e_.to_string()))?;
let key = Pkey::<Public>::from_params(None, c"RSA", ¶ms)
.map_err(|e_| OpensslKeyError(e_.to_string()))?;
key.public_key_to_der()
.map_err(|e_| OpensslKeyError(e_.to_string()))
}
pub(crate) fn pub_ec_from_components(
x: &[u8],
y: &[u8],
curve: &str,
) -> Result<Vec<u8>, OpensslKeyError> {
use native_ossl::params::ParamBuilder;
let curve_cstr: &std::ffi::CStr = match curve {
"P-256" => c"prime256v1",
"P-384" => c"secp384r1",
"P-521" => c"secp521r1",
_ => return Err(OpensslKeyError(format!("unknown EC curve: {curve}"))),
};
let point = [&[0x04u8][..], x, y].concat();
let params = ParamBuilder::new()
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_utf8_string(c"group", curve_cstr)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_octet_slice(c"pub", &point)
.map_err(|e| OpensslKeyError(e.to_string()))?
.build()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let key = Pkey::<Public>::from_params(None, c"EC", ¶ms)
.map_err(|e| OpensslKeyError(e.to_string()))?;
key.public_key_to_der()
.map_err(|e| OpensslKeyError(e.to_string()))
}
pub(crate) fn priv_rsa_from_components(
c: &crate::crypto::RsaPrivateComponents<'_>,
) -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
use native_ossl::params::ParamBuilder;
let params = ParamBuilder::new()
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"n", c.n)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"e", c.e)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"d", c.d)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"p", c.p)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"q", c.q)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"dmp1", c.dp)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"dmq1", c.dq)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"iqmp", c.qi)
.map_err(|e| OpensslKeyError(e.to_string()))?
.build()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let pkey = Pkey::<Private>::from_params(None, c"RSA", ¶ms)
.map_err(|e| OpensslKeyError(e.to_string()))?;
let spki_der = pkey
.public_key_to_der()
.map_err(|e| OpensslKeyError(e.to_string()))?;
Ok(crate::crypto::BackendPrivateKey::with_pkey_cache(
spki_der, pkey,
))
}
pub(crate) fn priv_ec_from_components(
d: &[u8],
x: &[u8],
y: &[u8],
curve: &str,
) -> Result<crate::crypto::BackendPrivateKey, OpensslKeyError> {
use native_ossl::params::ParamBuilder;
let curve_cstr: &std::ffi::CStr = match curve {
"P-256" => c"prime256v1",
"P-384" => c"secp384r1",
"P-521" => c"secp521r1",
_ => return Err(OpensslKeyError(format!("unknown EC curve: {curve}"))),
};
let point = [&[0x04u8][..], x, y].concat();
let params = ParamBuilder::new()
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_utf8_string(c"group", curve_cstr)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_bn(c"d", d)
.map_err(|e| OpensslKeyError(e.to_string()))?
.push_octet_slice(c"pub", &point)
.map_err(|e| OpensslKeyError(e.to_string()))?
.build()
.map_err(|e| OpensslKeyError(e.to_string()))?;
let pkey = Pkey::<Private>::from_params(None, c"EC", ¶ms)
.map_err(|e| OpensslKeyError(e.to_string()))?;
let spki_der = ec_spki_from_pkey(curve, &pkey)?;
Ok(crate::crypto::BackendPrivateKey::with_pkey_cache(
spki_der, pkey,
))
}
impl crate::crypto::ErasedDataHasher for OpensslSymmetricCrypto {
fn hash_data_erased(
&self,
algorithm: &str,
data: &[u8],
) -> Result<Vec<u8>, crate::crypto::PrivateKeyError> {
use crate::crypto::DataHasher as _;
self.hash_data(algorithm, data)
.map_err(crate::crypto::PrivateKeyError::new)
}
}
impl crate::crypto::ErasedHmacProvider for OpensslSymmetricCrypto {
fn hmac_compute_erased(
&self,
algorithm: &str,
key: &[u8],
data: &[u8],
) -> Result<Vec<u8>, crate::crypto::PrivateKeyError> {
use crate::crypto::HmacProvider as _;
self.hmac_compute(algorithm, key, data)
.map_err(crate::crypto::PrivateKeyError::new)
}
fn hmac_verify_erased(
&self,
algorithm: &str,
key: &[u8],
data: &[u8],
expected: &[u8],
) -> Result<(), crate::crypto::PrivateKeyError> {
use crate::crypto::HmacProvider as _;
self.hmac_verify(algorithm, key, data, expected)
.map_err(crate::crypto::PrivateKeyError::new)
}
}
struct OpensslHashState {
ctx: native_ossl::digest::DigestCtx,
alg: native_ossl::digest::DigestAlg,
}
impl crate::crypto::HashState for OpensslHashState {
fn update(&mut self, data: &[u8]) {
self.ctx.update(data).expect("hash update cannot fail");
}
fn finalize_boxed(mut self: Box<Self>) -> Vec<u8> {
let mut out = vec![0u8; self.alg.output_len()];
let n = self
.ctx
.finish(&mut out)
.expect("hash finalize cannot fail");
out.truncate(n);
out
}
}
unsafe impl Send for OpensslHashState {}
impl crate::crypto::StreamingHasher for OpensslSymmetricCrypto {
type Error = OpensslSymmetricError;
fn new_hash(
&self,
algorithm: &str,
) -> Result<Box<dyn crate::crypto::HashState>, OpensslSymmetricError> {
let alg = symmetric_md_from_str(algorithm)?;
let mut ctx = native_ossl::digest::DigestCtx::new_empty()?;
ctx.reinit(&alg, None)?;
Ok(Box::new(OpensslHashState { ctx, alg }))
}
}
impl crate::crypto::ErasedStreamingHasher for OpensslSymmetricCrypto {
fn new_hash_erased(
&self,
algorithm: &str,
) -> Result<Box<dyn crate::crypto::HashState>, crate::crypto::PrivateKeyError> {
use crate::crypto::StreamingHasher as _;
self.new_hash(algorithm)
.map_err(crate::crypto::PrivateKeyError::new)
}
}
pub(crate) fn openssl_streaming_hasher() -> Box<dyn crate::crypto::ErasedStreamingHasher> {
Box::new(OpensslSymmetricCrypto)
}
pub(crate) fn openssl_symmetric_crypto() -> OpensslSymmetricCrypto {
OpensslSymmetricCrypto
}
pub(crate) fn openssl_data_hasher() -> Box<dyn crate::crypto::ErasedDataHasher> {
Box::new(OpensslSymmetricCrypto)
}
pub(crate) fn openssl_hmac_provider() -> Box<dyn crate::crypto::ErasedHmacProvider> {
Box::new(OpensslSymmetricCrypto)
}
pub struct OpensslHmacState {
ctx: native_ossl::mac::HmacCtx,
}
unsafe impl Send for OpensslHmacState {}
impl crate::crypto::HmacState for OpensslHmacState {
fn update(&mut self, data: &[u8]) {
self.ctx.update(data).expect("HMAC update cannot fail");
}
fn finalize_boxed(mut self: Box<Self>) -> Vec<u8> {
self.ctx.finish_to_vec().expect("HMAC finalize cannot fail")
}
}
impl crate::crypto::StreamingHmacProvider for OpensslSymmetricCrypto {
type Error = OpensslSymmetricError;
fn new_hmac(
&self,
algorithm: &str,
key: &[u8],
) -> Result<Box<dyn crate::crypto::HmacState>, OpensslSymmetricError> {
let md = symmetric_md_from_str(algorithm)?;
let ctx = native_ossl::mac::HmacCtx::new(&md, key)?;
Ok(Box::new(OpensslHmacState { ctx }))
}
}
impl crate::crypto::ErasedStreamingHmacProvider for OpensslSymmetricCrypto {
fn new_hmac_erased(
&self,
algorithm: &str,
key: &[u8],
) -> Result<Box<dyn crate::crypto::HmacState>, crate::crypto::PrivateKeyError> {
use crate::crypto::StreamingHmacProvider as _;
self.new_hmac(algorithm, key)
.map_err(crate::crypto::PrivateKeyError::new)
}
}
pub(crate) fn openssl_streaming_hmac_provider(
) -> Box<dyn crate::crypto::ErasedStreamingHmacProvider> {
Box::new(OpensslSymmetricCrypto)
}