use openssl::{
ec::EcKey,
hash::MessageDigest,
pkey::{PKey, Private},
sign::Signer,
};
use zeroize::Zeroizing;
use crate::{
RawSigner, RawSignerError, SigningAlg,
ec_utils::{der_to_p1363, ec_curve_from_private_key_der},
openssl::OpenSslMutex,
};
enum EcdsaSigningAlg {
Es256,
Es384,
Es512,
}
pub(crate) struct EcdsaSigner {
alg: EcdsaSigningAlg,
private_key: EcKey<Private>,
}
impl EcdsaSigner {
pub(crate) fn from_private_key(
private_key: &[u8],
alg: SigningAlg,
) -> Result<Self, RawSignerError> {
let alg = match alg {
SigningAlg::Es256 => EcdsaSigningAlg::Es256,
SigningAlg::Es384 => EcdsaSigningAlg::Es384,
SigningAlg::Es512 => EcdsaSigningAlg::Es512,
_ => {
return Err(RawSignerError::InternalError(
"EcdsaSigner should be used only for SigningAlg::Es***".to_string(),
));
}
};
let _openssl = OpenSslMutex::acquire()?;
let private_key = EcKey::private_key_from_pem(private_key)?;
Ok(EcdsaSigner { alg, private_key })
}
}
impl RawSigner for EcdsaSigner {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>, RawSignerError> {
let _openssl = OpenSslMutex::acquire()?;
let private_key = PKey::from_ec_key(self.private_key.clone())?;
let pkcs8_private_key =
Zeroizing::new(private_key.private_key_to_pkcs8().map_err(|_| {
RawSignerError::InvalidSigningCredentials("unsupported EC curve".to_string())
})?);
let curve = ec_curve_from_private_key_der(&pkcs8_private_key).ok_or(
RawSignerError::InvalidSigningCredentials("unsupported EC curve".to_string()),
)?;
let sig_len = curve.p1363_sig_len();
let mut signer = match self.alg {
EcdsaSigningAlg::Es256 => Signer::new(MessageDigest::sha256(), &private_key)?,
EcdsaSigningAlg::Es384 => Signer::new(MessageDigest::sha384(), &private_key)?,
EcdsaSigningAlg::Es512 => Signer::new(MessageDigest::sha512(), &private_key)?,
};
signer.update(data)?;
let der_sig = signer.sign_to_vec()?;
der_to_p1363(&der_sig, sig_len)
}
fn alg(&self) -> SigningAlg {
match self.alg {
EcdsaSigningAlg::Es256 => SigningAlg::Es256,
EcdsaSigningAlg::Es384 => SigningAlg::Es384,
EcdsaSigningAlg::Es512 => SigningAlg::Es512,
}
}
fn max_signature_size(&self) -> usize {
match self.alg {
EcdsaSigningAlg::Es256 => 64,
EcdsaSigningAlg::Es384 => 96,
EcdsaSigningAlg::Es512 => 132,
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::panic)]
use super::*;
#[test]
fn rejects_non_ecdsa_alg() {
let key = include_bytes!("../../../tests/fixtures/raw_signature/es256.priv");
let Err(err) = EcdsaSigner::from_private_key(key, SigningAlg::Ps256) else {
panic!("expected error");
};
assert!(matches!(err, RawSignerError::InternalError(_)));
}
#[test]
fn sign_rejects_unsupported_curve() {
let key = include_bytes!("../../../tests/fixtures/raw_signature/secp256k1.priv");
let Ok(signer) = EcdsaSigner::from_private_key(key, SigningAlg::Es256) else {
panic!("expected the key to load");
};
let Err(err) = signer.sign(b"some sample content to sign") else {
panic!("expected a signing error");
};
assert!(matches!(err, RawSignerError::InvalidSigningCredentials(_)));
}
#[test]
fn rejects_bad_pem() {
let Err(err) = EcdsaSigner::from_private_key(b"not a PEM key", SigningAlg::Es256) else {
panic!("expected error");
};
assert!(matches!(err, RawSignerError::CryptoLibraryError(_)));
}
}