use ecdsa::signature::Signer;
use p256::ecdsa::{Signature as P256Signature, SigningKey as P256SigningKey};
use p384::ecdsa::{Signature as P384Signature, SigningKey as P384SigningKey};
use p521::ecdsa::{Signature as P512Signature, SigningKey as P512SigningKey};
use pkcs8::DecodePrivateKey;
use zeroize::ZeroizeOnDrop;
use crate::{RawSigner, RawSignerError, SigningAlg};
enum EcdsaSigningAlg {
Es256,
Es384,
Es512,
}
#[derive(ZeroizeOnDrop)]
pub(crate) enum EcdsaSigningKey {
Es256(P256SigningKey),
Es384(P384SigningKey),
Es512(#[zeroize(skip)] P512SigningKey),
}
#[derive(ZeroizeOnDrop)]
pub(crate) struct EcdsaSigner {
#[zeroize(skip)]
alg: EcdsaSigningAlg,
signing_key: EcdsaSigningKey,
}
impl EcdsaSigner {
pub(crate) fn from_private_key(
private_key: &[u8],
algorithm: SigningAlg,
) -> Result<Self, RawSignerError> {
let private_key_pem = std::str::from_utf8(private_key).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!("invalid private key: {e}"))
})?;
let (signing_key, alg) = match algorithm {
SigningAlg::Es256 => {
let key = P256SigningKey::from_pkcs8_pem(private_key_pem).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!(
"invalid ES256 private key: {e}"
))
})?;
(EcdsaSigningKey::Es256(key), EcdsaSigningAlg::Es256)
}
SigningAlg::Es384 => {
let key = P384SigningKey::from_pkcs8_pem(private_key_pem).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!(
"invalid ES384 private key: {e}"
))
})?;
(EcdsaSigningKey::Es384(key), EcdsaSigningAlg::Es384)
}
SigningAlg::Es512 => {
let secret = p521::SecretKey::from_pkcs8_pem(private_key_pem).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!(
"invalid ES512 private key: {e}"
))
})?;
let key = P512SigningKey::from_slice(&secret.to_bytes()).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!(
"invalid ES512 private key: {e}"
))
})?;
(EcdsaSigningKey::Es512(key), EcdsaSigningAlg::Es512)
}
_ => {
return Err(RawSignerError::InvalidSigningCredentials(
"Unsupported algorithm".to_string(),
));
}
};
Ok(EcdsaSigner { alg, signing_key })
}
}
impl RawSigner for EcdsaSigner {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>, RawSignerError> {
match self.signing_key {
EcdsaSigningKey::Es256(ref key) => {
let signature: P256Signature = key.sign(data);
Ok(signature.to_vec())
}
EcdsaSigningKey::Es384(ref key) => {
let signature: P384Signature = key.sign(data);
Ok(signature.to_vec())
}
EcdsaSigningKey::Es512(ref key) => {
let signature: P512Signature = key.sign(data);
Ok(signature.to_vec())
}
}
}
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::expect_used)]
#![allow(clippy::panic)]
#![allow(clippy::unwrap_used)]
use super::*;
use crate::SigningAlg;
#[test]
fn test_es512_supported() {
let private_key = include_bytes!("../../../tests/fixtures/raw_signature/es512.priv");
let algorithm = SigningAlg::Es512;
let result = EcdsaSigner::from_private_key(private_key, algorithm);
assert!(result.is_ok());
if let Ok(ecdsa_signer) = result {
assert_eq!(ecdsa_signer.alg(), SigningAlg::Es512);
} else {
unreachable!("Expected InvalidSigningCredentials error");
}
}
#[test]
fn test_other_not_supported() {
let private_key = include_bytes!("../../../tests/fixtures/raw_signature/ps256.priv");
let algorithm = SigningAlg::Ps256;
let result = EcdsaSigner::from_private_key(private_key, algorithm);
assert!(result.is_err());
if let Err(RawSignerError::InvalidSigningCredentials(err_msg)) = result {
assert_eq!(err_msg, "Unsupported algorithm");
} else {
unreachable!("Expected InvalidSigningCredentials error");
}
}
const BAD_PEM: &[u8] = b"-----BEGIN PRIVATE KEY-----\nMA==\n-----END PRIVATE KEY-----\n";
#[test]
fn rejects_invalid_utf8() {
let Err(err) = EcdsaSigner::from_private_key(&[0xff, 0xfe, 0xfd], SigningAlg::Es256) else {
panic!("expected error");
};
assert!(matches!(err, RawSignerError::InvalidSigningCredentials(_)));
}
#[test]
fn rejects_invalid_es256_key() {
let Err(err) = EcdsaSigner::from_private_key(BAD_PEM, SigningAlg::Es256) else {
panic!("expected error");
};
assert!(matches!(err, RawSignerError::InvalidSigningCredentials(_)));
}
#[test]
fn rejects_invalid_es384_key() {
let Err(err) = EcdsaSigner::from_private_key(BAD_PEM, SigningAlg::Es384) else {
panic!("expected error");
};
assert!(matches!(err, RawSignerError::InvalidSigningCredentials(_)));
}
#[test]
fn rejects_invalid_es512_key() {
let Err(err) = EcdsaSigner::from_private_key(BAD_PEM, SigningAlg::Es512) else {
panic!("expected error");
};
assert!(matches!(err, RawSignerError::InvalidSigningCredentials(_)));
}
#[test]
fn es512_sign_round_trip() {
let private_key = include_bytes!("../../../tests/fixtures/raw_signature/es512.priv");
let Ok(signer) = EcdsaSigner::from_private_key(private_key, SigningAlg::Es512) else {
panic!("expected a signer");
};
assert_eq!(signer.max_signature_size(), 132);
let data = b"some sample content to sign";
let signature = signer.sign(data).unwrap();
assert!(!signature.is_empty());
assert!(signature.len() <= signer.max_signature_size());
let pub_key = include_bytes!("../../../tests/fixtures/raw_signature/es512.pub_key");
let validator =
crate::rust_native::validators::validator_for_signing_alg(SigningAlg::Es512).unwrap();
validator.validate(&signature, data, pub_key).unwrap();
}
}