use std::{
fmt,
io,
};
pub mod ops;
use ops::*;
pub mod errors;
use errors::*;
pub mod plumbing;
#[cfg(any(feature = "cli", feature = "cliv"))]
pub mod cli;
pub trait Load<'s, S: SOP<'s>> {
fn from_stdin(sop: &'s S) -> Result<Self>
where
Self: Sized,
{
Self::from_reader(sop, &mut io::stdin(), Some("/dev/stdin".into()))
}
fn from_file<P>(sop: &'s S, path: P) -> Result<Self>
where
Self: Sized,
P: AsRef<std::path::Path>,
{
let path = path.as_ref();
Self::from_reader(sop, &mut std::fs::File::open(path)?,
Some(path.display().to_string()))
}
fn from_reader(sop: &'s S, source: &mut (dyn io::Read + Send + Sync),
source_name: Option<String>)
-> Result<Self>
where Self: Sized;
fn from_bytes(sop: &'s S, source: &[u8]) -> Result<Self>
where
Self: Sized,
{
Self::from_reader(sop, &mut io::Cursor::new(source), None)
}
fn source_name(&self) -> Option<&str>;
}
pub trait Save {
fn to_stdout(&self, armored: bool) -> Result<()> {
self.to_writer(armored, &mut io::stdout())
}
fn to_writer(&self, armored: bool, sink: &mut (dyn io::Write + Send + Sync))
-> Result<()>;
fn to_vec(&self, armored: bool) -> Result<Vec<u8>> {
let mut sink = vec![];
self.to_writer(armored, &mut sink)?;
Ok(sink)
}
}
pub trait SOP<'s>: Sized {
type Keys: Load<'s, Self> + Save + plumbing::SopRef<'s, Self>;
type Certs: Load<'s, Self> + Save + plumbing::SopRef<'s, Self>;
type Sigs: Load<'s, Self> + Save + plumbing::SopRef<'s, Self>;
fn debug(&mut self, enable: bool) {
let _ = enable;
}
fn spec_version(&'s self) -> &'static str {
"~draft-dkg-openpgp-stateless-cli-11"
}
fn sopv_version(&'s self) -> Result<&'static str> {
Err(Error::UnsupportedOption)
}
fn version(&self) -> Result<Box<dyn Version>>;
fn generate_key(&'s self)
-> Result<Box<dyn GenerateKey<Self, Self::Keys> + 's>>;
fn change_key_password(&'s self)
-> Result<Box<dyn ChangeKeyPassword<Self, Self::Keys> + 's>>;
fn revoke_key(&'s self)
-> Result<Box<dyn RevokeKey<Self, Self::Certs, Self::Keys> + 's>>;
fn extract_cert(&'s self) -> Result<Box<
dyn ExtractCert<Self, Self::Certs, Self::Keys> + 's>>;
fn update_key(&'s self) -> Result<Box<
dyn UpdateKey<Self, Self::Certs, Self::Keys> + 's>>;
fn merge_certs(&'s self)
-> Result<Box<dyn MergeCerts<Self, Self::Certs> + 's>>;
fn certify_userid(&'s self) -> Result<Box<
dyn CertifyUserID<Self, Self::Certs, Self::Keys> + 's>>;
fn validate_userid(&'s self) -> Result<Box<
dyn ValidateUserID<Self, Self::Certs> + 's>>;
fn sign(&'s self)
-> Result<Box<dyn Sign<Self, Self::Keys, Self::Sigs> + 's>>;
fn verify(&'s self)
-> Result<Box<dyn Verify<Self, Self::Certs, Self::Sigs> + 's>>;
fn encrypt(&'s self)
-> Result<Box<dyn Encrypt<Self, Self::Certs, Self::Keys> + 's>>;
fn decrypt(&'s self)
-> Result<Box<dyn Decrypt<Self, Self::Certs, Self::Keys> + 's>>;
fn armor(&'s self) -> Result<Box<dyn Armor + 's>>;
fn dearmor(&'s self) -> Result<Box<dyn Dearmor + 's>>;
fn inline_detach(&'s self)
-> Result<Box<dyn InlineDetach<Self::Sigs> + 's>>;
fn inline_verify(&'s self)
-> Result<Box<dyn InlineVerify<Self, Self::Certs> + 's>>;
fn inline_sign(&'s self)
-> Result<Box<dyn InlineSign<Self, Self::Keys> + 's>>;
}
pub struct Password(Box<[u8]>);
impl Password {
pub fn new(password: Vec<u8>) -> Result<Password> {
fn securely_erase(mut p: Vec<u8>) {
unsafe {
memsec::memzero(p.as_mut_ptr(), p.len());
}
}
let mut s = String::from_utf8(password)
.map_err(|e| {
securely_erase(e.into_bytes());
Error::PasswordNotHumanReadable
})?;
if s.trim_start().len() != s.len() {
securely_erase(s.into_bytes());
return Err(Error::PasswordNotHumanReadable);
}
s.truncate(s.trim_end().len());
if s.chars().any(|c| c.is_whitespace() && c != ' ') {
securely_erase(s.into_bytes());
return Err(Error::PasswordNotHumanReadable);
}
Ok(Password(s.into_bytes().into()))
}
pub fn new_unchecked(password: Vec<u8>) -> Password {
Password(password.into())
}
}
impl plumbing::PasswordsAreHumanReadable for Password {
fn normalized(&self) -> &[u8] {
if let Ok(p) = std::str::from_utf8(&self.0) {
p.trim_end().as_bytes()
} else {
let mut p = &self.0[..];
while ! p.is_empty() && p[p.len() - 1].is_ascii_whitespace() {
p = &p[..p.len() - 1];
}
p
}
}
fn variants(&self) -> Box<dyn Iterator<Item = &[u8]> + '_> {
Box::new(std::iter::once(self.normalized())
.filter_map(move |normalized| {
if normalized.len() < self.0.len() {
Some(normalized)
} else {
None
}
})
.chain(std::iter::once(&self.0[..])))
}
}
impl Drop for Password {
fn drop(&mut self) {
unsafe {
memsec::memzero(self.0.as_mut_ptr(), self.0.len());
}
}
}
pub struct SessionKey {
algorithm: u8,
key: Box<[u8]>,
}
impl SessionKey {
pub fn new<A, K>(algorithm: A, key: K) -> Result<Self>
where A: Into<u8>,
K: AsRef<[u8]>,
{
Ok(SessionKey {
algorithm: algorithm.into(),
key: key.as_ref().to_vec().into(),
})
}
pub fn algorithm(&self) -> u8 {
self.algorithm
}
pub fn key(&self) -> &[u8] {
&self.key
}
}
impl fmt::Display for SessionKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:", self.algorithm)?;
for b in &self.key[..] {
write!(f, "{:02X}", b)?
}
Ok(())
}
}
impl Drop for SessionKey {
fn drop(&mut self) {
unsafe {
memsec::memzero(self.key.as_mut_ptr(), self.key.len());
}
}
}
impl std::str::FromStr for SessionKey {
type Err = ParseError;
fn from_str(sk: &str) -> ParseResult<Self> {
let fields = sk.rsplit(':').rev().collect::<Vec<_>>();
if fields.len() != 2 {
return Err(ParseError(format!(
"Expected two colon-separated fields, got {:?}",
fields)));
}
let algo: u8 = fields[0].parse().map_err(
|e| ParseError(format!("Failed to parse algorithm: {}", e)))?;
let sk = from_hex(&fields[1], true)?;
Self::new(algo, sk).map_err(
|e| ParseError(format!("Bad session key: {}", e)))
}
}
fn from_hex(hex: &str, pretty: bool) -> ParseResult<Vec<u8>> {
const BAD: u8 = 255u8;
const X: u8 = 'x' as u8;
let mut nibbles = hex.chars().filter_map(|x| {
match x {
'0' => Some(0u8),
'1' => Some(1u8),
'2' => Some(2u8),
'3' => Some(3u8),
'4' => Some(4u8),
'5' => Some(5u8),
'6' => Some(6u8),
'7' => Some(7u8),
'8' => Some(8u8),
'9' => Some(9u8),
'a' | 'A' => Some(10u8),
'b' | 'B' => Some(11u8),
'c' | 'C' => Some(12u8),
'd' | 'D' => Some(13u8),
'e' | 'E' => Some(14u8),
'f' | 'F' => Some(15u8),
'x' | 'X' if pretty => Some(X),
_ if pretty && x.is_whitespace() => None,
_ => Some(BAD),
}
}).collect::<Vec<u8>>();
if pretty && nibbles.len() >= 2 && nibbles[0] == 0 && nibbles[1] == X {
nibbles.remove(0);
nibbles.remove(0);
}
if nibbles.iter().any(|&b| b == BAD || b == X) {
return Err(ParseError("Invalid characters".into()));
}
if nibbles.len() % 2 != 0 {
return Err(ParseError("Odd number of nibbles".into()));
}
let bytes = nibbles.chunks(2).map(|nibbles| {
(nibbles[0] << 4) | nibbles[1]
}).collect::<Vec<u8>>();
Ok(bytes)
}
pub type Result<T> = std::result::Result<T, Error>;
type ParseResult<T> = std::result::Result<T, ParseError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_key_roundtrip() -> Result<()> {
for algo in &[9, 13] {
let sk = SessionKey::new(
*algo,
&[0xE1, 0x48, 0x97, 0x81, 0xAA, 0x22, 0xE1, 0xBF,
0x6E, 0x3E, 0x61, 0x74, 0x8C, 0x8D, 0x3F, 0x35,
0x50, 0x7C, 0x80, 0x9E, 0x95, 0x64, 0x86, 0x87,
0xC7, 0xE4, 0xB9, 0xAF, 0x86, 0x17, 0xD3, 0xAE])?;
let sk_s = sk.to_string();
let sk_p: SessionKey = sk_s.parse().unwrap();
assert_eq!(sk.algorithm(), sk_p.algorithm());
assert_eq!(sk.key(), sk_p.key());
}
Ok(())
}
#[test]
fn sign_as_roundtrip() -> Result<()> {
use SignAs::*;
for a in &[Text, Binary] {
let s = a.to_string();
let b: SignAs = s.parse().unwrap();
assert_eq!(a, &b);
}
Ok(())
}
#[test]
fn encrypt_as_roundtrip() -> Result<()> {
use EncryptAs::*;
for a in &[Text, Binary] {
let s = a.to_string();
let b: EncryptAs = s.parse().unwrap();
assert_eq!(a, &b);
}
Ok(())
}
#[test]
fn armor_label_roundtrip() -> Result<()> {
use ArmorLabel::*;
for a in &[Auto, Sig, Key, Cert, Message] {
let s = a.to_string();
let b: ArmorLabel = s.parse().unwrap();
assert_eq!(a, &b);
}
Ok(())
}
}