use crate::error::Result;
use crate::pki::{self, SigAlgKind};
use botan::RandomNumberGenerator;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SignerBackend {
#[default]
Software,
}
pub trait Signer: Send + Sync {
fn backend_name(&self) -> &'static str;
fn sign(
&self,
kind: SigAlgKind,
msg: &[u8],
rng: &mut RandomNumberGenerator,
) -> Result<Vec<u8>>;
}
pub fn build_signer(backend: SignerBackend, privkey_pem: &str) -> Result<Box<dyn Signer>> {
match backend {
SignerBackend::Software => Ok(Box::new(SoftwareSigner::new(privkey_pem))),
}
}
pub struct SoftwareSigner {
privkey_pem: String,
}
impl SoftwareSigner {
pub fn new(privkey_pem: &str) -> Self {
Self {
privkey_pem: privkey_pem.to_string(),
}
}
}
impl Signer for SoftwareSigner {
fn backend_name(&self) -> &'static str {
"software"
}
fn sign(
&self,
kind: SigAlgKind,
msg: &[u8],
rng: &mut RandomNumberGenerator,
) -> Result<Vec<u8>> {
pki::sign(kind, &self.privkey_pem, msg, rng)
}
}
pub fn default_signer(privkey_pem: &str) -> Result<Box<dyn Signer>> {
build_signer(SignerBackend::default(), privkey_pem)
}
impl SignerBackend {
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"software" | "SoftwareSigner" | "default" => Some(SignerBackend::Software),
_ => None,
}
}
}
pub const SUPPORTED_BACKENDS: &[SignerBackend] = &[SignerBackend::Software];
#[cfg(test)]
mod tests {
use super::*;
use crate::pki;
fn rng() -> botan::RandomNumberGenerator {
botan::RandomNumberGenerator::new_system().unwrap()
}
#[test]
fn software_signer_round_trip() {
let (priv_pem, _pub_pem) = pki::keygen(SigAlgKind::Ed25519, &mut rng()).unwrap();
let signer = SoftwareSigner::new(&priv_pem);
let msg = b"hello hardware-backed signer";
let sig = signer.sign(SigAlgKind::Ed25519, msg, &mut rng()).unwrap();
assert!(pki::verify(SigAlgKind::Ed25519, &_pub_pem, msg, &sig).unwrap());
}
#[test]
fn build_signer_dispatches_to_software() {
let (priv_pem, _) = pki::keygen(SigAlgKind::Ed25519, &mut rng()).unwrap();
let signer = build_signer(SignerBackend::Software, &priv_pem).unwrap();
assert_eq!(signer.backend_name(), "software");
}
#[test]
fn default_signer_is_software() {
assert_eq!(SignerBackend::default(), SignerBackend::Software);
}
#[test]
fn parse_accepts_kebab_case_only() {
assert_eq!(
SignerBackend::parse("software"),
Some(SignerBackend::Software)
);
assert!(SignerBackend::parse("pkcs11").is_none());
}
}