use crate::error::{self, check, Result};
use crate::ffi;
use crate::keygen::{Cipher, Hash};
use crate::ops::Output;
use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;
use super::{ExportFlags, Key, RemoveFlags, RemoveSignaturesFlags};
#[derive(Clone, Debug, Default)]
pub struct RevocationReason {
pub code: RevocationCode,
pub reason: Option<String>,
}
impl RevocationReason {
pub fn new(code: RevocationCode) -> Self {
RevocationReason { code, reason: None }
}
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = Some(reason.into());
self
}
pub(crate) fn code_str(&self) -> &'static str {
self.code.as_str()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum RevocationCode {
#[default]
NoReason,
Superseded,
Compromised,
Retired,
}
impl RevocationCode {
pub fn as_str(self) -> &'static str {
match self {
RevocationCode::NoReason => "no",
RevocationCode::Superseded => "superseded",
RevocationCode::Compromised => "compromised",
RevocationCode::Retired => "retired",
}
}
}
#[derive(Default)]
pub struct ProtectOptions {
pub(crate) password: Option<String>,
pub(crate) cipher: Option<Cipher>,
pub(crate) mode: Option<String>,
pub(crate) hash: Option<Hash>,
pub(crate) iterations: Option<usize>,
}
impl ProtectOptions {
pub fn new() -> Self {
Self::default()
}
pub fn password(mut self, p: impl Into<String>) -> Self {
self.password = Some(p.into());
self
}
pub fn cipher(mut self, c: Cipher) -> Self {
self.cipher = Some(c);
self
}
pub fn mode(mut self, m: impl Into<String>) -> Self {
self.mode = Some(m.into());
self
}
pub fn hash(mut self, h: Hash) -> Self {
self.hash = Some(h);
self
}
pub fn iterations(mut self, n: usize) -> Self {
self.iterations = Some(n);
self
}
}
#[derive(Default)]
pub struct AddUidOptions {
pub(crate) hash: Option<Hash>,
pub(crate) key_flags: Option<u32>,
pub(crate) key_expiration: Option<u32>,
pub(crate) primary: bool,
}
impl AddUidOptions {
pub fn new() -> Self {
Self::default()
}
pub fn hash(mut self, h: Hash) -> Self {
self.hash = Some(h);
self
}
pub fn key_flags(mut self, flags: u32) -> Self {
self.key_flags = Some(flags);
self
}
pub fn key_expiration(mut self, seconds: u32) -> Self {
self.key_expiration = Some(seconds);
self
}
pub fn primary(mut self, p: bool) -> Self {
self.primary = p;
self
}
}
impl<'ctx> Key<'ctx> {
pub fn protect(&self, options: &ProtectOptions) -> Result<()> {
let pw = options
.password
.as_ref()
.ok_or(error::Error::NullPointer)?;
let pw_c = CString::new(pw.as_str()).map_err(|_| error::Error::NulByte)?;
let cipher_c = options
.cipher
.map(|c| CString::new(c.as_str()).unwrap())
.unwrap_or_else(|| CString::new("AES256").unwrap());
let mode_c = options
.mode
.as_ref()
.map(|m| CString::new(m.as_str()).unwrap())
.unwrap_or_else(|| CString::new("CFB").unwrap());
let hash_c = options
.hash
.map(|h| CString::new(h.as_str()).unwrap())
.unwrap_or_else(|| CString::new("SHA256").unwrap());
unsafe {
check(ffi::rnp_key_protect(
self.handle,
pw_c.as_ptr(),
cipher_c.as_ptr(),
mode_c.as_ptr(),
hash_c.as_ptr(),
options.iterations.unwrap_or(0),
))?;
}
Ok(())
}
pub fn unprotect(&self, password: Option<&str>) -> Result<()> {
let pw_c = password
.map(|p| CString::new(p).map_err(|_| error::Error::NulByte))
.transpose()?;
let pw_ptr = pw_c.as_ref().map(|c| c.as_ptr()).unwrap_or(ptr::null());
unsafe { check(ffi::rnp_key_unprotect(self.handle, pw_ptr)) }
}
pub fn lock(&self) -> Result<()> {
unsafe { check(ffi::rnp_key_lock(self.handle)) }
}
pub fn unlock(&self, password: Option<&str>) -> Result<()> {
let pw_c = password
.map(|p| CString::new(p).map_err(|_| error::Error::NulByte))
.transpose()?;
let pw_ptr = pw_c.as_ref().map(|c| c.as_ptr()).unwrap_or(ptr::null());
unsafe { check(ffi::rnp_key_unlock(self.handle, pw_ptr)) }
}
pub fn add_uid(&self, uid: &str, options: &AddUidOptions) -> Result<()> {
let uid_c = CString::new(uid).map_err(|_| error::Error::NulByte)?;
let hash_c = options
.hash
.map(|h| CString::new(h.as_str()).unwrap())
.unwrap_or_else(|| CString::new("SHA256").unwrap());
unsafe {
check(ffi::rnp_key_add_uid(
self.handle,
uid_c.as_ptr(),
hash_c.as_ptr(),
options.key_expiration.unwrap_or(0),
options.key_flags.unwrap_or(0) as u8,
options.primary,
))?;
}
Ok(())
}
pub fn revoke(&self, reason: RevocationReason, hash: Hash) -> Result<()> {
let reason_c = CString::new(reason.code_str()).unwrap();
let hash_c = CString::new(hash.as_str()).unwrap();
let reason_text_c = match &reason.reason {
Some(s) => Some(CString::new(s.as_str()).map_err(|_| error::Error::NulByte)?),
None => None,
};
let reason_text_ptr = reason_text_c
.as_ref()
.map(|c| c.as_ptr())
.unwrap_or(ptr::null());
unsafe {
check(ffi::rnp_key_revoke(
self.handle,
0,
hash_c.as_ptr(),
reason_c.as_ptr(),
reason_text_ptr,
))?;
}
Ok(())
}
pub fn set_expiration(&self, seconds: u32) -> Result<()> {
unsafe { check(ffi::rnp_key_set_expiration(self.handle, seconds)) }
}
pub fn remove(&self, flags: RemoveFlags) -> Result<()> {
unsafe { check(ffi::rnp_key_remove(self.handle, flags.bits())) }
}
pub fn remove_signatures(&self, flags: RemoveSignaturesFlags) -> Result<()> {
unsafe {
check(ffi::rnp_key_remove_signatures(
self.handle,
flags.bits(),
None,
ptr::null_mut(),
))
}
}
pub fn set_25519_bits_tweak(&self) -> Result<()> {
unsafe { check(ffi::rnp_key_25519_bits_tweak(self.handle)) }
}
}
pub(crate) fn export_revocation_impl(
handle: ffi::rnp_key_handle_t,
flags: ExportFlags,
reason: RevocationReason,
hash: Hash,
) -> Result<Vec<u8>> {
let reason_c = CString::new(reason.code_str()).unwrap();
let hash_c = CString::new(hash.as_str()).unwrap();
let reason_text_c = match &reason.reason {
Some(s) => Some(CString::new(s.as_str()).map_err(|_| error::Error::NulByte)?),
None => None,
};
let reason_text_ptr = reason_text_c
.as_ref()
.map(|c| c.as_ptr())
.unwrap_or(ptr::null());
let output = Output::to_memory()?;
unsafe {
check(ffi::rnp_key_export_revocation(
handle,
output.as_ptr(),
flags.bits(),
hash_c.as_ptr(),
reason_c.as_ptr(),
reason_text_ptr,
))?;
}
output.into_bytes()
}
#[allow(dead_code)]
fn leak_cstring(c: CString) -> *const c_char {
let ptr = c.as_ptr();
std::mem::forget(c);
ptr
}