use crate::algorithm::{Algorithm, Cipher, Compression, Curve, Hash, KeyUsage};
use crate::context::Context;
use crate::error::{self, check, Result};
use crate::ffi;
use crate::key::Key;
use std::ffi::CString;
use std::ptr;
use super::ProtectConfig;
#[cfg(feature = "pqc")]
use super::PqcAlgorithm;
use super::SubkeyBuilder;
pub struct KeyBuilder {
pub(crate) alg: Algorithm,
pub(crate) bits: Option<u32>,
pub(crate) hash: Option<Hash>,
pub(crate) dsa_qbits: Option<u32>,
pub(crate) curve: Option<Curve>,
pub(crate) userid: Option<String>,
pub(crate) expiration: Option<u32>,
pub(crate) usages: Vec<KeyUsage>,
pub(crate) pref_hashes: Vec<Hash>,
pub(crate) pref_ciphers: Vec<Cipher>,
pub(crate) pref_compressions: Vec<Compression>,
pub(crate) pref_keyserver: Option<String>,
pub(crate) protection: Option<ProtectConfig>,
pub(crate) request_password: bool,
pub(crate) subkeys: Vec<SubkeyBuilder>,
#[cfg(feature = "crypto-refresh")]
pub(crate) v6: bool,
}
impl KeyBuilder {
pub fn new(alg: Algorithm) -> Self {
KeyBuilder {
alg,
bits: None,
hash: None,
dsa_qbits: None,
curve: None,
userid: None,
expiration: None,
usages: Vec::new(),
pref_hashes: Vec::new(),
pref_ciphers: Vec::new(),
pref_compressions: Vec::new(),
pref_keyserver: None,
protection: None,
request_password: false,
subkeys: Vec::new(),
#[cfg(feature = "crypto-refresh")]
v6: false,
}
}
pub fn bits(mut self, n: u32) -> Self {
self.bits = Some(n);
self
}
pub fn hash(mut self, h: Hash) -> Self {
self.hash = Some(h);
self
}
pub fn dsa_qbits(mut self, n: u32) -> Self {
self.dsa_qbits = Some(n);
self
}
pub fn curve(mut self, c: Curve) -> Self {
self.curve = Some(c);
self
}
pub fn userid(mut self, uid: impl Into<String>) -> Self {
self.userid = Some(uid.into());
self
}
pub fn expiration(mut self, seconds: u32) -> Self {
self.expiration = Some(seconds);
self
}
pub fn add_usage(mut self, u: KeyUsage) -> Self {
self.usages.push(u);
self
}
pub fn add_pref_hash(mut self, h: Hash) -> Self {
self.pref_hashes.push(h);
self
}
pub fn add_pref_cipher(mut self, c: Cipher) -> Self {
self.pref_ciphers.push(c);
self
}
pub fn add_pref_compression(mut self, c: Compression) -> Self {
self.pref_compressions.push(c);
self
}
pub fn clear_usage(mut self) -> Self {
self.usages.clear();
self
}
pub fn clear_pref_hash(mut self) -> Self {
self.pref_hashes.clear();
self
}
pub fn clear_pref_cipher(mut self) -> Self {
self.pref_ciphers.clear();
self
}
pub fn clear_pref_compression(mut self) -> Self {
self.pref_compressions.clear();
self
}
pub fn pref_keyserver(mut self, s: impl Into<String>) -> Self {
self.pref_keyserver = Some(s.into());
self
}
pub fn protection(mut self, opts: &crate::key::ProtectOptions) -> Self {
self.protection = Some(ProtectConfig::from_options(opts));
self
}
pub fn request_password(mut self) -> Self {
self.request_password = true;
self
}
#[cfg(feature = "crypto-refresh")]
pub fn v6(mut self) -> Self {
self.v6 = true;
self
}
pub fn add_subkey(mut self, sub: SubkeyBuilder) -> Self {
self.subkeys.push(sub);
self
}
#[cfg(feature = "pqc")]
pub fn add_pqc_encryption_subkey(self, alg: PqcAlgorithm) -> Self {
self.add_subkey(
SubkeyBuilder::new_str(alg.as_str()).add_usage(KeyUsage::EncryptComms),
)
}
#[cfg(feature = "pqc")]
pub fn add_pqc_signing_subkey(self, alg: PqcAlgorithm) -> Self {
self.add_subkey(SubkeyBuilder::new_str(alg.as_str()).add_usage(KeyUsage::Sign))
}
pub fn build<'ctx>(self, ctx: &'ctx Context) -> Result<Key<'ctx>> {
let alg_c = CString::new(self.alg.as_str()).unwrap();
let mut op: ffi::rnp_op_generate_t = ptr::null_mut();
unsafe {
check(ffi::rnp_op_generate_create(&mut op, ctx.ffi, alg_c.as_ptr()))?;
}
let setters = unsafe { apply_setters(op, &self) };
if let Err(e) = setters {
unsafe {
let _ = ffi::rnp_op_generate_destroy(op);
}
return Err(e);
}
let primary_handle = unsafe {
check(ffi::rnp_op_generate_execute(op))?;
let mut handle: ffi::rnp_key_handle_t = ptr::null_mut();
check(ffi::rnp_op_generate_get_key(op, &mut handle))?;
let _ = ffi::rnp_op_generate_destroy(op);
if handle.is_null() {
return Err(error::Error::NullPointer);
}
handle
};
for sub_spec in self.subkeys {
sub_spec.build_with_ffi(ctx, primary_handle)?;
}
Ok(Key::from_handle(primary_handle))
}
}
unsafe fn apply_setters(op: ffi::rnp_op_generate_t, b: &KeyBuilder) -> Result<()> {
unsafe {
if let Some(n) = b.bits {
check(ffi::rnp_op_generate_set_bits(op, n))?;
}
if let Some(h) = b.hash {
let c = CString::new(h.as_str()).unwrap();
check(ffi::rnp_op_generate_set_hash(op, c.as_ptr()))?;
}
if let Some(q) = b.dsa_qbits {
check(ffi::rnp_op_generate_set_dsa_qbits(op, q))?;
}
if let Some(c) = b.curve {
let cs = CString::new(c.as_str()).unwrap();
check(ffi::rnp_op_generate_set_curve(op, cs.as_ptr()))?;
}
if let Some(uid) = &b.userid {
let c = CString::new(uid.as_str()).map_err(|_| error::Error::NulByte)?;
check(ffi::rnp_op_generate_set_userid(op, c.as_ptr()))?;
}
if let Some(exp) = b.expiration {
check(ffi::rnp_op_generate_set_expiration(op, exp))?;
}
for u in &b.usages {
let c = CString::new(u.as_str()).unwrap();
check(ffi::rnp_op_generate_add_usage(op, c.as_ptr()))?;
}
for h in &b.pref_hashes {
let c = CString::new(h.as_str()).unwrap();
check(ffi::rnp_op_generate_add_pref_hash(op, c.as_ptr()))?;
}
for c2 in &b.pref_ciphers {
let c = CString::new(c2.as_str()).unwrap();
check(ffi::rnp_op_generate_add_pref_cipher(op, c.as_ptr()))?;
}
for cz in &b.pref_compressions {
let c = CString::new(cz.as_str()).unwrap();
check(ffi::rnp_op_generate_add_pref_compression(op, c.as_ptr()))?;
}
if let Some(ks) = &b.pref_keyserver {
let c = CString::new(ks.as_str()).map_err(|_| error::Error::NulByte)?;
check(ffi::rnp_op_generate_set_pref_keyserver(op, c.as_ptr()))?;
}
if let Some(cfg) = &b.protection {
super::apply_protection(op, cfg)?;
}
if b.request_password {
check(ffi::rnp_op_generate_set_request_password(op, true))?;
}
#[cfg(feature = "crypto-refresh")]
if b.v6 {
check(ffi::rnp_op_generate_set_v6_key(op))?;
}
Ok(())
}
}