commit
e5004abe05
@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "sequoia-pep-engine-crypto"
|
||||
description = "Cryptographic backend for the PEP Engine"
|
||||
version = "0.1.0"
|
||||
authors = ["Neal H. Walfield <neal@pep.foundation>"]
|
||||
homepage = "https://sequoia-pgp.org/"
|
||||
repository = "https://gitlab.com/sequoia-pgp/sequoia-pep-engine-crypto"
|
||||
readme = "README.md"
|
||||
keywords = ["cryptography", "openpgp", "pgp", "encryption", "signing"]
|
||||
categories = ["cryptography", "authentication", "email"]
|
||||
license = "GPL-2.0-or-later"
|
||||
edition = "2018"
|
||||
|
||||
[badges]
|
||||
gitlab = { repository = "sequoia-pgp/sequoia-pep-engine-crypto" }
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
backtrace = "0.3.61"
|
||||
buffered-reader = "1"
|
||||
chrono = "0.4"
|
||||
configparser = "2"
|
||||
csv = "1.1"
|
||||
enumber = "0.2.1-alpha"
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
lru = "0.6.6"
|
||||
memmem = "0.1"
|
||||
memoffset = "0.6"
|
||||
num_cpus = "1"
|
||||
sequoia-openpgp = "1.1"
|
||||
thiserror = "1"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.25"
|
||||
features = ["bundled", "collation", "blob"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[patch.crates-io]
|
||||
enumber = { path = '/home/us/neal/src/enumber' }
|
@ -0,0 +1,5 @@
|
||||
This implements the [p≡p Engine]'s [cryptotech] interface using [Sequoia].
|
||||
|
||||
[p≡p Engine]: https://gitea.pep.foundation/pEp.foundation/pEpEngine
|
||||
[cryptotech]: https://gitea.pep.foundation/pEp.foundation/pEpEngine/src/branch/master/src/cryptotech.h
|
||||
[Sequoia]: https://sequoia-pgp.org
|
@ -0,0 +1,11 @@
|
||||
prefix=PREFIX
|
||||
libdir=${prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: pEp-Engine-sequoia
|
||||
Description: pEp Engine's cryptographic backend using Sequoia
|
||||
URL: https://sequoia-pgp.org/
|
||||
Version: VERSION
|
||||
Requires: nettle sequoia-openpgp
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lsequoia_pep_engine_crypto
|
@ -0,0 +1,48 @@
|
||||
//! Buffer management.
|
||||
//!
|
||||
//! Some convenience functions to copying data between Rust and C.
|
||||
//! When moving data to C, we use libc's allocator, which means that
|
||||
//! the C code can free the memory in the usual way, i.e., using
|
||||
//! libc's free.
|
||||
|
||||
use std::{
|
||||
ptr::{
|
||||
copy_nonoverlapping,
|
||||
},
|
||||
};
|
||||
|
||||
use libc::{
|
||||
malloc,
|
||||
c_char,
|
||||
};
|
||||
|
||||
/// Copies a Rust string to a buffer, adding a terminating zero.
|
||||
pub fn rust_str_to_c_str<S: AsRef<str>>(s: S) -> *mut c_char {
|
||||
let s = s.as_ref();
|
||||
let bytes = s.as_bytes();
|
||||
unsafe {
|
||||
let buf = malloc(bytes.len() + 1);
|
||||
copy_nonoverlapping(bytes.as_ptr(), buf as *mut _, bytes.len());
|
||||
*((buf as *mut u8).add(bytes.len())) = 0; // Terminate.
|
||||
buf as *mut c_char
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies a C string to a buffer, adding a terminating zero.
|
||||
///
|
||||
/// Replaces embedded zeros with '_'.
|
||||
pub fn rust_bytes_to_c_str_lossy<S: AsRef<[u8]>>(s: S) -> *mut c_char {
|
||||
let bytes = s.as_ref();
|
||||
unsafe {
|
||||
let buf = malloc(bytes.len() + 1);
|
||||
copy_nonoverlapping(bytes.as_ptr(), buf as *mut _, bytes.len());
|
||||
|
||||
// Replace embedded zeros.
|
||||
let bytes_mut = std::slice::from_raw_parts_mut(buf as *mut u8,
|
||||
bytes.len());
|
||||
bytes_mut.iter_mut().for_each(|b| if *b == 0 { *b = b'_' });
|
||||
|
||||
*((buf as *mut u8).add(bytes.len())) = 0; // Terminate.
|
||||
buf as *mut c_char
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
// Maximum busy wait time.
|
||||
pub const BUSY_WAIT_TIME: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
|
||||
// The location of the keys DB relative to the user's home directory.
|
||||
pub const KEYS_DB: &[ &str ] = &[ ".pEp", "keys.db" ];
|
||||
|
@ -0,0 +1,72 @@
|
||||
// Wraps an ffi function.
|
||||
//
|
||||
// This wrapper allows the function to return a Result. The Ok
|
||||
// variant should be (). It may be something else. In that case, the
|
||||
// value is simply discarded. The Error variant must be convertable
|
||||
// to a `crate::ErrorCode` using `Into`.
|
||||
macro_rules! ffi {
|
||||
(fn $f:ident( $( $v:ident: $t:ty ),* ) -> $rt:ty $body:block ) => {
|
||||
// The wrapper. It calls $f and turns the result into an
|
||||
// error code.
|
||||
#[no_mangle] pub extern "C"
|
||||
fn $f($($v: $t,)*) -> crate::ErrorCode {
|
||||
tracer!(*crate::TRACE, stringify!($f));
|
||||
|
||||
// The actual function.
|
||||
fn inner($($v: $t,)*) -> $rt { $body };
|
||||
|
||||
t!("entered");
|
||||
// We use AssertUnwindSafe. This is safe, because if we
|
||||
// catch a panic, we abort. If we turn the panic into an
|
||||
// error, then we need to reexamine this assumption.
|
||||
let r = std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
|
||||
match inner($($v,)*) {
|
||||
Ok(_) => {
|
||||
t!("-> success");
|
||||
ErrorCode::from(crate::Error::StatusOk)
|
||||
}
|
||||
Err(err) => {
|
||||
t!("-> error: {}{}",
|
||||
err,
|
||||
{
|
||||
use std::error::Error;
|
||||
|
||||
let mut causes = String::new();
|
||||
let mut cause = err.source();
|
||||
while let Some(e) = cause {
|
||||
causes.push_str("\n because: ");
|
||||
causes.push_str(&e.to_string());
|
||||
cause = e.source();
|
||||
}
|
||||
causes
|
||||
});
|
||||
|
||||
ErrorCode::from(err)
|
||||
}
|
||||
}
|
||||
}));
|
||||
match r {
|
||||
Ok(code) => code,
|
||||
Err(_) => {
|
||||
t!("-> panic!");
|
||||
unsafe { ::libc::abort() };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a stub for a ffi, which returns an error.
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! stub {
|
||||
($f:ident) => {
|
||||
#[no_mangle] pub extern "C"
|
||||
fn $f() -> crate::ErrorCode {
|
||||
tracer!(*crate::TRACE, stringify!($f));
|
||||
t!("{} is a stub\n", stringify!($f));
|
||||
crate::Error::UnknownError(
|
||||
anyhow::anyhow!("Function not implemented"),
|
||||
stringify!($f).into()).into()
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,894 @@
|
||||
use std::cmp;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rusqlite::{
|
||||
CachedStatement,
|
||||
Connection,
|
||||
OpenFlags,
|
||||
OptionalExtension,
|
||||
params,
|
||||
Row,
|
||||
};
|
||||
|
||||
use lru::LruCache;
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::{
|
||||
Cert,
|
||||
Fingerprint,
|
||||
KeyHandle,
|
||||
KeyID,
|
||||
packet::UserID,
|
||||
parse::Parse,
|
||||
serialize::Serialize,
|
||||
types::HashAlgorithm,
|
||||
types::RevocationStatus,
|
||||
};
|
||||
|
||||
use crate::Result;
|
||||
use crate::Error;
|
||||
use crate::constants::{
|
||||
KEYS_DB,
|
||||
BUSY_WAIT_TIME,
|
||||
};
|
||||
use crate::pep::PepIdentityTemplate;
|
||||
|
||||
const MAGIC: u64 = 0xE3F3_05AD_48EE_0DF5;
|
||||
|
||||
// Pick the fastest hash function from the SHA2 family for the
|
||||
// architecture's word size. On 64-bit architectures, SHA512 is
|
||||
// almost twice as fast, but on 32-bit ones, SHA256 is faster.
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const CACHE_HASH: HashAlgorithm = HashAlgorithm::SHA512;
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
const CACHE_HASH: HashAlgorithm = HashAlgorithm::SHA256;
|
||||
|
||||
// The number of entries in the cache. Do not make this too big since
|
||||
// we iterate over the cache to purge stale entries.
|
||||
const CERT_CACHE_ENTRIES: usize = 32;
|
||||
|
||||
type CertCache = LruCache<Vec<u8>, Cert>;
|
||||
|
||||
pub struct Keystore {
|
||||
magic: u64,
|
||||
conn: rusqlite::Connection,
|
||||
// The certificate cache.
|
||||
//
|
||||
// This cache maps:
|
||||
//
|
||||
// HASH(keydata) -> Cert
|
||||
//
|
||||
// That is, it is keyed by the hash of the keydata as read from
|
||||
// the keys DB, *not* by the certificate's fingerprint. This
|
||||
// means that if we update a certificate we don't have to worry
|
||||
// about invalidating the cache, which is error prone. This
|
||||
// probably costs us a few cycles, but that's a price worth paying
|
||||
// for not having to worry about cache invalidation.
|
||||
cert_cache: CertCache,
|
||||
}
|
||||
|
||||
pub fn fingerprint_to_keyid(fpr: Fingerprint) -> KeyID {
|
||||
let bytes = fpr.as_bytes();
|
||||
// XXX: Take the last 8 bytes. This is wrong for V5 keys where
|
||||
// the first 8 bytes correspond to the keyid.
|
||||
let bytes = &bytes[cmp::max(8, bytes.len()) - 8..];
|
||||
KeyID::from_bytes(bytes)
|
||||
}
|
||||
|
||||
// Generates a convenience method that returns a prepared statement
|
||||
// for the specified sql. If preparing the statement results in an
|
||||
// error, the error is converted to out native error type.
|
||||
macro_rules! sql_stmt {
|
||||
($name:ident, $sql:expr) => {
|
||||
fn $name(conn: &Connection) -> Result<CachedStatement<'_>> {
|
||||
let mut name: &str = stringify!($name);
|
||||
if name.ends_with("_stmt") {
|
||||
name = &name[..name.len() - "_stmt".len()];
|
||||
}
|
||||
wrap_err!(
|
||||
conn.prepare_cached(
|
||||
$sql),
|
||||
UnknownDbError,
|
||||
format!("preparing {} query", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Keystore {
|
||||
/// Converts the raw pointer to a Rust reference.
|
||||
///
|
||||
/// This does *not* take ownership of the object.
|
||||
///
|
||||
/// Sanity checks the data structure.
|
||||
pub fn as_mut(ptr: *mut Self) -> &'static mut Self {
|
||||
let ks = unsafe { ptr.as_mut() }.expect("NULL pointer");
|
||||
assert_eq!(ks.magic, MAGIC, "magic");
|
||||
|
||||
ks
|
||||
}
|
||||
|
||||
/// Converts a raw pointer back into a Rust object.
|
||||
///
|
||||
/// Takes ownership of the object.
|
||||
pub fn to_rust(ptr: *mut Self) -> Box<Self> {
|
||||
assert!(! ptr.is_null());
|
||||
let ks = unsafe { Box::from_raw(ptr) };
|
||||
assert_eq!(ks.magic, MAGIC, "magic");
|
||||
|
||||
ks
|
||||
}
|
||||
|
||||
/// Converts the Rust object to a raw pointer.
|
||||
///
|
||||
/// Transfers ownership to the caller.
|
||||
pub fn to_c(self) -> *mut Self {
|
||||
Box::into_raw(Box::new(self))
|
||||
}
|
||||
|
||||
// Compares two User IDs.
|
||||
//
|
||||
// Extracts the email address or URI stored in each User ID and
|
||||
// compares them. A User ID that does not contain an email
|
||||
// address or URI is sorted earlier than one that does.
|
||||
//
|
||||
// This is used as the collation function.
|
||||
fn email_cmp(a: &str, b: &str) -> std::cmp::Ordering {
|
||||
let a_userid = UserID::from(a);
|
||||
let b_userid = UserID::from(b);
|
||||
|
||||
let a_email = a_userid
|
||||
.email_normalized()
|
||||
.or_else(|_| a_userid.uri())
|
||||
.ok();
|
||||
let b_email = b_userid
|
||||
.email_normalized()
|
||||
.or_else(|_| b_userid.uri())
|
||||
.ok();
|
||||
|
||||
match (a_email, b_email) {
|
||||
(None, None) => std::cmp::Ordering::Equal,
|
||||
(None, Some(_)) => std::cmp::Ordering::Less,
|
||||
(Some(_), None) => std::cmp::Ordering::Greater,
|
||||
(Some(a), Some(b)) => a.cmp(&b)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the key store.
|
||||
///
|
||||
/// This opens the keys.db and initializes it, if necessary.
|
||||
pub fn init() -> Result<Self> {
|
||||
Self::init_(false)
|
||||
}
|
||||
|
||||
/// Initializes an in-memory key store.
|
||||
///
|
||||
/// This is used for the unit tests.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn init_in_memory() -> Result<Self> {
|
||||
Self::init_(true)
|
||||
}
|
||||
|
||||
fn init_(in_memory: bool) -> Result<Self> {
|
||||
let keys_db = PathBuf::new();
|
||||
|
||||
let conn = if in_memory {
|
||||
wrap_err!(
|
||||
Connection::open_in_memory(),
|
||||
InitCannotOpenDB,
|
||||
"Creating in-memory keys DB")?
|
||||
} else {
|
||||
let mut home: Option<PathBuf> = None;
|
||||
|
||||
// Create the home directory.
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if cfg!(debug_assertions) {
|
||||
home = env::var("PEP_HOME").ok().map(|s| PathBuf::from(s));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
if home.is_none() {
|
||||
home = env::home_dir();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Get the home directory on Windows.
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
let home = match home {
|
||||
None => return Err(Error::InitCryptoLibInitFailed(
|
||||
"HOME environment variable unset".into()).into()),
|
||||
Some(home) => home,
|
||||
};
|
||||
|
||||
let mut keys_db = home;
|
||||
for n in KEYS_DB {
|
||||
keys_db.push(n);
|
||||
}
|
||||
|
||||
wrap_err!(
|
||||
Connection::open_with_flags(
|
||||
&keys_db,
|
||||
OpenFlags::SQLITE_OPEN_READ_WRITE
|
||||
| OpenFlags::SQLITE_OPEN_CREATE
|
||||
| OpenFlags::SQLITE_OPEN_FULL_MUTEX
|
||||
| OpenFlags::SQLITE_OPEN_PRIVATE_CACHE),
|
||||
InitCannotOpenDB,
|
||||
format!("Opening keys DB ('{}')", keys_db.display()))?
|
||||
};
|
||||
|
||||
wrap_err!(
|
||||
conn.execute_batch("PRAGMA secure_delete=true;
|
||||
PRAGMA foreign_keys=true;
|
||||
PRAGMA locking_mode=NORMAL;
|
||||
PRAGMA journal_mode=WAL;"),
|
||||
InitCannotOpenDB,
|
||||
format!("Setting pragmas on keys DB ('{}')",
|
||||
keys_db.display()))?;
|
||||
|
||||
wrap_err!(
|
||||
conn.busy_timeout(BUSY_WAIT_TIME),
|
||||
InitCannotOpenDB,
|
||||
format!("Setting busy time ('{}')", keys_db.display()))?;
|
||||
|
||||
wrap_err!(
|
||||
conn.create_collation("EMAIL", Self::email_cmp),
|
||||
InitCannotOpenDB,
|
||||
format!("Registering EMAIL collation function"))?;
|
||||
|
||||
wrap_err!(
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS keys (
|
||||
primary_key TEXT UNIQUE PRIMARY KEY,
|
||||
secret BOOLEAN,
|
||||
tpk BLOB
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS keys_index
|
||||
ON keys (primary_key, secret)"),
|
||||
InitCannotOpenDB,
|
||||
format!("Creating keys table ('{}')",
|
||||
keys_db.display()))?;
|
||||
|
||||
wrap_err!(
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS subkeys (
|
||||
subkey TEXT NOT NULL,
|
||||
primary_key TEXT NOT NULL,
|
||||
UNIQUE(subkey, primary_key),
|
||||
FOREIGN KEY (primary_key)
|
||||
REFERENCES keys(primary_key)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS subkeys_index
|
||||
ON subkeys (subkey, primary_key)"),
|
||||
InitCannotOpenDB,
|
||||
format!("Creating subkeys table ('{}')",
|
||||
keys_db.display()))?;
|
||||
|
||||
wrap_err!(
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS userids (
|
||||
userid TEXT NOT NULL COLLATE EMAIL,
|
||||
primary_key TEXT NOT NULL,
|
||||
UNIQUE(userid, primary_key),
|
||||
FOREIGN KEY (primary_key)
|
||||
REFERENCES keys(primary_key)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS userids_index
|
||||
ON userids (userid COLLATE EMAIL, primary_key)"),
|
||||
InitCannotOpenDB,
|
||||
format!("Creating userids table ('{}')",
|
||||
keys_db.display()))?;
|
||||
|
||||
Ok(Keystore {
|
||||
magic: MAGIC,
|
||||
conn,
|
||||
cert_cache: LruCache::new(CERT_CACHE_ENTRIES),
|
||||
})
|
||||
}
|
||||
|
||||
// Returns a prepared statement for finding a certificate by
|
||||
// primary key fingerprint.
|
||||
sql_stmt!(cert_find_stmt,
|
||||
"SELECT tpk, secret FROM keys WHERE primary_key == ?");
|
||||
|
||||
// Returns a prepared statement for finding a key by primary key
|
||||
// fingerprint.
|
||||
sql_stmt!(tsk_find_stmt,
|
||||
"SELECT tpk, secret FROM keys
|
||||
WHERE primary_key == ? and secret == 1");
|
||||
|
||||
// Returns a prepared statement for finding a certificate that
|
||||
// contains a key with the specified key id. That is, this
|
||||
// matches on the primary key's key ID as well as any subkeys' key
|
||||
// ID.
|
||||
sql_stmt!(cert_find_with_key_stmt,
|
||||
"SELECT tpk, secret FROM subkeys
|
||||
LEFT JOIN keys
|
||||
ON subkeys.primary_key == keys.primary_key
|
||||
WHERE subkey == ?");
|
||||
|
||||
// Returns a prepared statement for finding a certificate with
|
||||
// secret key material that contains a key (with or without secret
|
||||
// key material) with the specified key id. That is, this matches
|
||||
// on the primary key's key ID as well as any subkeys' key ID.
|
||||
sql_stmt!(tsk_find_with_key_stmt,
|
||||
"SELECT tpk, secret FROM subkeys
|
||||
LEFT JOIN keys
|
||||
ON subkeys.primary_key == keys.primary_key
|
||||
WHERE subkey == ? and keys.secret == 1");
|
||||
|
||||
// Returns a prepared statement for finding a certificate with the
|
||||
// specified email address.
|
||||
sql_stmt!(cert_find_by_email_stmt,
|
||||
"SELECT tpk, secret FROM userids
|
||||
LEFT JOIN keys
|
||||
ON userids.primary_key == keys.primary_key
|
||||
WHERE userid == ?");
|
||||
|
||||
// Returns a prepared statement for finding a key with the
|
||||
// specified email address.
|
||||
sql_stmt!(tsk_find_by_email_stmt,
|
||||
"SELECT tpk, secret FROM userids
|
||||
LEFT JOIN keys
|
||||
ON userids.primary_key == keys.primary_key
|
||||
WHERE userid == ? and keys.secret == 1");
|
||||
|
||||
// Returns a prepared statement for returning all certificates in
|
||||
// the database.
|
||||
sql_stmt!(cert_all_stmt,
|
||||
"select tpk, secret from keys");
|
||||
|
||||
// Returns a prepared statement for returning all certificates in
|
||||
// the database, which contain secret key material.
|
||||
sql_stmt!(tsk_all_stmt,
|
||||
"select tpk, secret from keys where secret = 1");
|
||||
|
||||
// Returns a prepared statement for updating the keys table.
|
||||
sql_stmt!(cert_save_insert_primary_stmt,
|
||||
"INSERT OR REPLACE INTO keys (primary_key, secret, tpk)
|
||||
VALUES (?, ?, ?)");
|
||||
|
||||
// Returns a prepared statement for updating the subkeys table.
|
||||
sql_stmt!(cert_save_insert_subkeys_stmt,
|
||||
"INSERT OR REPLACE INTO subkeys (subkey, primary_key)
|
||||
VALUES (?, ?)");
|
||||
|
||||
// Returns a prepared statement for updating the userids table.
|
||||
sql_stmt!(cert_save_insert_userids_stmt,
|
||||
"INSERT OR REPLACE INTO userids (userid, primary_key)
|
||||
VALUES (?, ?)");
|
||||
|
||||
// Returns a prepared statement for deleting a certificate.
|
||||
//
|
||||
// Note: due to the use of foreign keys, when a key is removed
|
||||
// from the keys table, the subkeys and userids tables are also
|
||||
// automatically update.
|
||||
sql_stmt!(cert_delete_stmt,
|
||||
"DELETE FROM keys WHERE primary_key = ?");
|
||||
|
||||
// The callback used by functions returning a certificate and
|
||||
// whether the certificate contains any secret key material.
|
||||
fn key_load(row: &Row) -> rusqlite::Result<(Vec<u8>, bool)> {
|
||||
let cert = row.get(0)?;
|
||||
let secret_key_material = row.get(1)?;
|
||||
Ok((cert, secret_key_material))
|
||||
}
|
||||
|
||||
// Caches a parsed certificate.
|
||||
//
|
||||
// This causes the keydata to be associated with the supplied
|
||||
// certificate.
|
||||
fn cache_cert(cache: &mut CertCache, bytes: &[u8], cert: Cert) {
|
||||
tracer!(*crate::TRACE, "Keystore::cache_cert");
|
||||
|
||||
let mut hash = CACHE_HASH.context().expect("hash must be implemented");
|
||||
hash.update(bytes);
|
||||
|
||||
let mut digest = Vec::new();
|
||||
digest.resize(hash.digest_size(), 0);
|
||||
let _ = hash.digest(&mut digest);
|
||||
|
||||
if let Some(cert_in_cache) = cache.peek(&digest) {
|
||||
// It's already in the cache.
|
||||
assert_eq!(&cert, cert_in_cache);
|
||||
return;
|
||||
}
|
||||
|
||||
// Purge any stale entries.
|
||||
let fpr = cert.fingerprint();
|
||||
let purge = cache
|
||||
.iter()
|
||||
.map(|(digest, cert)| (digest, cert.fingerprint()))
|
||||
.filter(|(_digest, other)| &fpr == other)
|
||||
.map(|(digest, _)| digest)
|
||||
.collect::<Vec<_>>();
|
||||
if purge.len() > 0 {
|
||||
t!("Purging {} stale entries that also map to {}",
|
||||
purge.len(), fpr);
|
||||
}
|
||||
for stale_digest in purge {
|
||||
cache.pop(stale_digest);
|
||||
}
|
||||
|
||||
// Add the new entry to the cache.
|
||||
cache.put(digest, cert);
|
||||
}
|
||||
|
||||
// Looks up keydata in the certificate cache.
|
||||
//
|
||||
// If the keydata is in the certificate cache, returns the
|
||||
// certificate. Otherwise, parses the keydata, adds the
|
||||
// certifiate to the cache, and returns the certificate.
|
||||
fn parse_cert(cache: &mut CertCache, bytes: &[u8]) -> Result<Cert> {
|
||||
tracer!(*crate::TRACE, "Keystore::parse_cert");
|
||||
|
||||
let mut hash = CACHE_HASH.context().expect("hash must be implemented");
|
||||
hash.update(bytes);
|
||||
|
||||
let mut digest = Vec::new();
|
||||
digest.resize(hash.digest_size(), 0);
|
||||
|
||||
let _ = hash.digest(&mut digest);
|
||||
|
||||
let cache_entries = cache.len();
|
||||
|
||||
if let Some(cert) = cache.get(&digest) {
|
||||
t!("Looking up {} in cache (w/{} of {} entries) -> hit!",
|
||||
cert.fingerprint(),
|
||||
cache_entries, CERT_CACHE_ENTRIES);
|
||||
|
||||
Ok(cert.clone())
|
||||
} else {
|
||||
let cert = wrap_err!(
|
||||
Cert::from_bytes(bytes),
|
||||
GetKeyFailed,
|
||||
format!("Parsing certificate"))?;
|
||||
|
||||
t!("Looking up {} in cache (w/{} of {} entries) -> miss!",
|
||||
cert.fingerprint(),
|
||||
cache_entries, CERT_CACHE_ENTRIES);
|
||||
|
||||
cache.put(digest, cert.clone());
|
||||
Ok(cert)
|
||||
}
|
||||
}
|
||||
|
||||
fn cert_find_(conn: &Connection, fpr: Fingerprint, private_only: bool,
|
||||
cert_cache: &mut CertCache)
|
||||
-> Result<(Cert, bool)>
|
||||
{
|
||||
tracer!(*crate::TRACE, "Keystore::cert_find_");
|
||||
|
||||
let r = wrap_err!(
|
||||
if private_only {
|
||||
Self::tsk_find_stmt(conn)?
|
||||
.query_row(&[ &fpr.to_hex() ], Self::key_load)
|
||||
} else {
|
||||
Self::cert_find_stmt(conn)?
|
||||
.query_row(&[ &fpr.to_hex() ], Self::key_load)
|
||||
}.optional(),
|
||||
UnknownDbError,
|
||||
"executing query")?;
|
||||
|
||||
if let Some((keydata, secret_key_material)) = r {
|
||||
t!("Got {} bytes of certificate data", keydata.len());
|
||||
let cert = Self::parse_cert(cert_cache, &keydata)?;
|
||||
Ok((cert, secret_key_material))
|
||||
} else {
|
||||
Err(Error::KeyNotFound(fpr.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Looks up the specified certificate by fingerprint.
|
||||
///
|
||||
/// This only considers the certificate's fingerprint (i.e., the
|
||||
/// primary key's fingerprint), not any subkeys.
|
||||
///
|
||||
/// If `private_only` is true, then only certificates are returned
|
||||
/// that include some secret key material.
|
||||
pub fn cert_find(&mut self, fpr: Fingerprint, private_only: bool)
|
||||
-> Result<(Cert, bool)>
|
||||
{
|
||||
Self::cert_find_(&self.conn, fpr, private_only, &mut self.cert_cache)
|
||||
}
|
||||
|
||||
/// Returns the certificate that includes a key identified by the
|
||||
/// keyid.
|
||||
///
|
||||
/// This function matches on both primary keys and subkeys! There
|
||||
/// can be multiple certificates for a given keyid. This can
|
||||
/// occur if a key is bound to multiple certificates. Also, it is
|
||||
/// possible to collide key ids. If there are multiple keys for a
|
||||
/// given key id, this just returns one of them.
|
||||
///
|
||||
/// XXX: It would be better to return all of them and have the
|
||||
/// caller try each in turn.
|
||||
pub fn cert_find_with_key<H>(&mut self, kh: H, private_only: bool)
|
||||
-> Result<(Cert, bool)>
|
||||
where H: Into<KeyHandle>
|
||||
{
|
||||
let kh = kh.into();
|
||||
let keyid = match kh.clone() {
|
||||
KeyHandle::KeyID(keyid) => keyid,
|
||||
KeyHandle::Fingerprint(fpr) => fingerprint_to_keyid(fpr),
|
||||
};
|
||||
|
||||
let mut stmt = if private_only {
|
||||
Self::tsk_find_with_key_stmt(&self.conn)?
|
||||
} else {
|
||||
Self::cert_find_with_key_stmt(&self.conn)?
|
||||
};
|
||||
|
||||
let row = wrap_err!(
|
||||
stmt.query_row(&[ &keyid.to_hex() ], Self::key_load).optional(),
|
||||
UnknownDbError,
|
||||
"executing query")?;
|
||||
|
||||
if let Some((keydata, secret_key_material)) = row {
|
||||
let cert = Self::parse_cert(&mut self.cert_cache, &keydata)?;
|
||||
Ok((cert, secret_key_material))
|
||||
} else {
|
||||
Err(Error::KeyNotFound(kh))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all the certificates.
|
||||
///
|
||||
/// If private_only is set, then only keys with private key
|
||||
/// material are returned.
|
||||
pub fn cert_all(&mut self, private_only: bool)
|
||||
-> Result<Vec<(Cert, bool)>>
|
||||
{
|
||||
let mut results: Vec<(Cert, bool)> = Vec::new();
|
||||
|
||||
let mut stmt = if private_only {
|
||||
Self::tsk_all_stmt(&self.conn)?
|
||||
} else {
|
||||
Self::cert_all_stmt(&self.conn)?
|
||||
};
|
||||
|
||||
let rows = wrap_err!(
|
||||
stmt.query_map([], Self::key_load),
|
||||
UnknownDbError,
|
||||
"executing query")?;
|
||||
|
||||
for row in rows {
|
||||
let (keydata, private) = wrap_err!(
|
||||
row,
|
||||
UnknownError,
|
||||
"parsing result")?;
|
||||
let cert = Self::parse_cert(&mut self.cert_cache, &keydata)?;
|
||||
results.push((cert, private));
|
||||
};
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Returns certificates that include a User ID matching the
|
||||
/// pattern.
|
||||
///
|
||||
/// If private_only is set, then only keys with private key
|
||||
/// material are returned.
|
||||
pub fn cert_find_by_email(&mut self, pattern: &str, private_only: bool)
|
||||
-> Result<Vec<(Cert, bool)>>
|
||||
{
|
||||
let mut results: Vec<(Cert, bool)> = Vec::new();
|
||||
|
||||
let mut stmt = if private_only {
|
||||
Self::tsk_find_by_email_stmt(&self.conn)?
|
||||
} else {
|
||||
Self::cert_find_by_email_stmt(&self.conn)?
|
||||
};
|
||||
|
||||
let rows = wrap_err!(
|
||||
stmt.query_map(&[ pattern ], Self::key_load),
|
||||
UnknownDbError,
|
||||
"executing query")?;
|
||||
|
||||
for row in rows {
|
||||
let (keydata, private) = wrap_err!(
|
||||
row,
|
||||
UnknownError,
|
||||
"parsing result")?;
|
||||
let cert = Self::parse_cert(&mut self.cert_cache, &keydata)?;
|
||||
results.push((cert, private));
|
||||
};
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Saves the certificate to the database.
|
||||
///
|
||||
/// If the certificate is already present, it is merged with the
|
||||
/// saved copy.
|
||||
///
|
||||
/// If the certificate includes private key material, returns a
|
||||
/// PepIdentity. This also returns whether the certificate is
|
||||
/// changed relative to the copy on disk. (If there was no copy,
|
||||
/// then this returns true.)
|
||||
///
|
||||
/// Whether the certificate has changed is a heuristic. It may
|
||||
/// indicate that the certificate has changed when it hasn't
|
||||
/// (false positive), but it will never say that the certificate
|
||||
/// has not changed when it has (false negative).
|
||||
pub fn cert_save(&mut self, mut cert: Cert)
|
||||
-> Result<(Option<PepIdentityTemplate>, bool)>
|
||||
{
|
||||
tracer!(*crate::TRACE, "Keystore::cert_save");
|
||||
|
||||
let fpr = cert.fingerprint();
|
||||
t!("Saving {}", fpr);
|
||||
|
||||
let tx = wrap_err!(
|
||||
self.conn.transaction(),
|
||||
UnknownDbError,
|
||||
"starting transaction"
|
||||
)?;
|
||||
|
||||
// Merge any existing data into the existing certificate.
|
||||
let current = match Self::cert_find_(&tx, fpr.clone(), false,
|
||||
&mut self.cert_cache)
|
||||
{
|
||||
Ok((current, _)) => Some(current),
|
||||
Err(Error::KeyNotFound(_)) => None,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let changed = if let Some(ref current) = current {
|
||||
// We want to compare current and cert. Eq only considers
|
||||
// the data that is serialized. For a Cert, any secret
|
||||
// key material is not serialized so it is not considered,
|
||||
// but we want to consider secret key material. So, we
|
||||
// need to be a bit smarter.
|
||||
match (current.is_tsk(), cert.is_tsk()) {
|
||||
(true, true) =>
|
||||
current.clone().into_packets().collect::<Vec<_>>()
|
||||
!= cert.clone().into_packets().collect::<Vec<_>>(),
|
||||
(true, false) => true,
|
||||
(false, true) => true,
|
||||
(false, false) => current != &cert,
|
||||
}
|
||||
} else {
|
||||
// If we go from nothing to something, consider it a
|
||||
// change.
|
||||
true
|
||||
};
|
||||
t!("changed: {}", changed);
|
||||
|
||||
if changed {
|
||||
if let Some(current) = current {
|
||||
cert = wrap_err!(
|
||||
cert.merge_public_and_secret(current),
|
||||
UnknownDbError,
|
||||
"Merging certificate with existing certificate")?;
|
||||
}
|
||||
} else {
|
||||
// If we have private key material, then we need to return
|
||||
// a pep identity. Otherwise, we can shortcircuit.
|
||||
if ! cert.is_tsk() {
|
||||
return Ok((None, changed));
|
||||
}
|
||||
}
|
||||
|
||||
let mut keydata = Vec::new();
|
||||
wrap_err!(
|
||||
cert.as_tsk().serialize(&mut keydata),
|
||||
UnknownDbError,
|
||||
"Serializing certificate")?;
|
||||
t!("Serializing {} bytes ({:X})",
|
||||
keydata.len(),
|
||||
{
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
hasher.write(&keydata);
|
||||
hasher.finish()
|
||||
});
|
||||
|
||||
// Save the certificate.
|
||||
if changed {
|
||||
let mut stmt = Self::cert_save_insert_primary_stmt(&tx)?;
|
||||
wrap_err!(
|
||||
stmt.execute(params![fpr.to_hex(), cert.is_tsk(), &keydata]),
|
||||
UnknownDbError,
|
||||
"Executing cert save insert primary")?;
|
||||
}
|
||||
|
||||
let mut ident = None;
|
||||
|
||||
if let Ok(vc) = cert.with_policy(crate::P, None) {
|
||||
// Update the subkey table.
|
||||
if changed {
|
||||
let mut stmt = Self::cert_save_insert_subkeys_stmt(&tx)?;
|
||||
for (i, ka) in vc.keys().enumerate() {
|
||||
t!(" {}key: {} ({} secret key material)",
|
||||
if i == 0 { "primary " } else { "sub" },
|
||||
ka.keyid(),
|
||||
if ka.has_secret() { "has" } else { "no" });
|
||||
wrap_err!(
|
||||
stmt.execute(
|
||||
params![ka.keyid().to_hex(), fpr.to_hex()]),
|
||||
UnknownDbError,
|
||||
"Executing cert save insert subkeys")?;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the userid table.
|
||||
{
|
||||
let mut stmt = Self::cert_save_insert_userids_stmt(&tx)?;
|
||||
|
||||
for ua in vc.userids() {
|
||||
let uid = if let Ok(Some(email)) = ua.email_normalized() {
|
||||
email
|
||||
} else if let Ok(Some(uri)) = ua.uri() {
|
||||
uri
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
t!(" User ID: {}", uid);
|
||||
|
||||
if changed {
|
||||
wrap_err!(
|
||||
stmt.execute(params![uid, fpr.to_hex()]),
|
||||
UnknownDbError,
|
||||
"Executing cert save insert userids")?;
|
||||
}
|
||||
|
||||
if ident.is_none() && vc.is_tsk() {
|
||||
ident = Some(PepIdentityTemplate::new(
|
||||
&uid, fpr.clone(), ua.name().unwrap_or(None)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_err!(
|
||||
tx.commit(),
|
||||
UnknownDbError,
|
||||
"commiting transaction"
|
||||
)?;
|
||||
|
||||
// Cache the updated certificate. It will likely be used in
|
||||
// the near future.
|
||||
Self::cache_cert(&mut self.cert_cache, &keydata, cert);
|
||||
|
||||
t!("saved");
|
||||
|
||||
Ok((ident, changed))
|
||||
}
|
||||
|
||||
/// Deletes the specified certificate from the database.
|
||||
pub fn cert_delete(&mut self, fpr: Fingerprint) -> Result<()> {
|
||||
let changes = wrap_err!(
|
||||
Self::cert_delete_stmt(&self.conn)?
|
||||
.execute(params![ fpr.to_hex() ]),
|
||||
CannotDeleteKey,
|
||||
format!("Deleting {}", fpr))?;
|
||||
|
||||
if changes == 0 {
|
||||
Err(Error::KeyNotFound(fpr.into()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// list keys whose uids contain the input pattern or whose
|
||||
/// fingerprints match a fingerprint contained in the pattern
|
||||
///
|
||||
/// Returns the revocation status and primary user id (if the
|
||||
/// certificate is not revoked and there is one). If a
|
||||
/// certificate is not valid according to the policy, it is still
|
||||
/// returned, and it is marked as revoked.
|
||||
pub fn list_keys(&mut self, pattern: &str, private_only: bool)
|
||||
-> Result<Vec<(Fingerprint, Option<UserID>, bool)>>
|
||||
{
|
||||
tracer!(*crate::TRACE, "Keystore::list_keys");
|
||||
t!("pattern: {}, private only: {}", pattern, private_only);
|
||||
|
||||
let mut certs: Vec<(Fingerprint, Option<UserID>, bool)> = Vec::new();
|
||||
let mut add_key = |cert: &Cert| {
|
||||
match cert.with_policy(crate::P, None) {
|
||||
Ok(vc) => {
|
||||
let revoked = matches!(vc.revocation_status(),
|
||||
RevocationStatus::Revoked(_));
|
||||
|
||||
let userid = if revoked {
|
||||
vc
|
||||
.primary_userid()
|
||||
.map(|userid| userid.userid().clone())
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
certs.push((cert.fingerprint(), userid, revoked));
|
||||
}
|
||||
Err(err) => {
|
||||
t!("warning: certificate {}: \
|
||||
rejected by policy: {}",
|
||||
cert.fingerprint(), err);
|
||||
certs.push((cert.fingerprint(), None, true));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Trim any leading space. This also makes it easier to recognize
|
||||
// a string that is only whitespace.
|
||||
let pattern = pattern.trim_start();
|
||||
|
||||
if pattern.chars().any(|c| c == '@' || c == ':') {
|
||||
// Looks like a mailbox or URI.
|
||||
self.cert_find_by_email(pattern, private_only)?
|
||||
.into_iter()
|
||||
.for_each(|(cert, _private)| add_key(&cert));
|
||||
|
||||
if certs.len() == 0 {
|
||||
// If match failed, check to see if we've got a dotted
|
||||
// address in the pattern. If so, try again without
|
||||
// dots.
|
||||
match (pattern.find("."), pattern.find("@")) {
|
||||
(Some(dotpos), Some(atpos)) if dotpos < atpos =>
|
||||
{
|
||||
t!("Retrying list_keys with undotted pattern");
|
||||
|
||||
// Return a string which, if the input string
|
||||
// is in the form of a user.name@address email
|
||||
// string, contains copy of the email address
|
||||
// string with the username undotted, and
|
||||
// otherwise, contains a copy of the whole
|
||||
// string, undotted.
|
||||
let left = &pattern[..atpos];
|
||||
let right = &pattern[atpos..];
|
||||
|
||||
let mut pattern = String::from(left).replace(".", "");
|
||||
pattern.push_str(right);
|
||||
|
||||
return self.list_keys(&pattern, private_only);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
} else if pattern.len() >= 16
|
||||
&& pattern.chars()
|
||||
.all(|c| {
|
||||
match c {
|
||||
'0' | '1' | '2' | '3' | '4'
|
||||
| '5' | '6' | '7' | '8' | '9'
|
||||
| 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
|
||||
| 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
|
||||
| ' ' => {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
{
|
||||
// Only hex characters and spaces and a fair amount of
|
||||
// them. This is probably a fingerprint. Note: the pep
|
||||
// engine never looks keys up by keyid, so we don't handle
|
||||
// them.
|
||||
let fpr = Fingerprint::from_hex(pattern).expect("valid fingerprint");
|
||||
let (cert, _private) = self.cert_find_with_key(fpr, private_only)?;
|
||||
add_key(&cert);
|
||||
} else if pattern.len() == 0 {
|
||||
// Empty string. Return everything.
|
||||
self.cert_all(private_only)?
|
||||
.into_iter()
|
||||
.for_each(|(cert, _private)| add_key(&cert));
|
||||
} else {
|
||||
// Do not throw an error; return the empty set (i.e.,
|
||||
// pattern matches nothing).
|
||||
t!("unsupported pattern '{}'", pattern);
|
||||
}
|
||||
|
||||
t!("{} matches", certs.len());
|
||||
Ok(certs)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,89 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
// Like eprintln!
|
||||
macro_rules! log {
|
||||
($dst:expr $(,)?) => (
|
||||
eprintln!("{}", $dst)
|
||||
);
|
||||
($dst:expr, $($arg:tt)*) => (
|
||||
eprintln!("{}", std::format!($dst, $($arg)*))
|
||||
);
|
||||
}
|
||||
|
||||
// The indent level. It is increased with each call to tracer and
|
||||
// decremented when the tracker goes out of scope.
|
||||
thread_local! {
|
||||
pub static INDENT_LEVEL: RefCell<usize> = RefCell::new(0);
|
||||
}
|
||||
|
||||
// Like eprintln!, but the first argument is a boolean, which
|
||||
// indicates if the string should actually be printed.
|
||||
macro_rules! trace {
|
||||
( $TRACE:expr, $fmt:expr, $($pargs:expr),* ) => {
|
||||
if $TRACE {
|
||||
let indent_level = crate::log::INDENT_LEVEL.with(|i| {
|
||||
*i.borrow()
|
||||
});
|
||||
|
||||
let ws = " ";
|
||||
|
||||
log!("{}{}",
|
||||
&ws[0..std::cmp::min(ws.len(), cmp::max(1, indent_level) - 1)],
|
||||
format!($fmt, $($pargs),*));
|
||||
}
|
||||
};
|
||||
( $TRACE:expr, $fmt:expr ) => {
|
||||
trace!($TRACE, $fmt, );
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! tracer {
|
||||
( $TRACE:expr, $func:expr ) => {
|
||||
// Currently, Rust doesn't support $( ... ) in a nested
|
||||
// macro's definition. See:
|
||||
// https://users.rust-lang.org/t/nested-macros-issue/8348/2
|
||||
macro_rules! t {
|
||||
( $fmt:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, $fmt) };
|
||||
( $fmt:expr, $a:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a)) };
|
||||
( $fmt:expr, $a:expr, $b:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j)) };
|
||||
( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr, $k:expr ) =>
|
||||
{ trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k)) };
|
||||
}
|
||||
struct Indent {};
|
||||
impl Indent {
|
||||
fn init() -> Self {
|
||||
crate::log::INDENT_LEVEL.with(|i| {
|
||||
i.replace_with(|i| *i + 1);
|
||||
});
|
||||
Indent {}
|
||||
}
|
||||
}
|
||||
impl Drop for Indent {
|
||||
fn drop(&mut self) {
|
||||
crate::log::INDENT_LEVEL.with(|i| {
|
||||
i.replace_with(|i| *i - 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
let _indent = Indent::init();
|
||||
}
|
||||
}
|
@ -0,0 +1,407 @@
|
||||
use std::ops::BitAnd;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use libc::tm;
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::KeyHandle;
|
||||
|
||||
mod session;
|
||||
pub use session::Session;
|
||||
mod identity;
|
||||
pub use identity::{
|
||||
PepIdentityTemplate,
|
||||
PepIdentity,
|
||||
PepIdentityListItem,
|
||||
PepIdentityList,
|
||||
};
|
||||
|
||||
mod stringlist;
|
||||
pub use stringlist::{
|
||||
StringListItem,
|
||||
StringList,
|
||||
StringListIterMut,
|
||||
StringListIter,
|
||||
};
|
||||
|
||||
// Transforms an error from some error type to the pep::Error.
|
||||
macro_rules! wrap_err {
|
||||
($e:expr, $err:ident, $msg:expr) => {
|
||||
$e.map_err(|err| {
|
||||
eprintln!("Error: {}: {}\n{:?}",
|
||||
err, $msg, backtrace::Backtrace::new());
|
||||
crate::pep::Error::$err(
|
||||
anyhow::Error::from(err).into(),
|
||||
String::from($msg))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// We use Error rather than anyhow's error so that we force the
|
||||
// function to convert the error into a form that we can easily pass
|
||||
// back to the engine.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// The pEp engine's error type.
|
||||
pub type ErrorCode = i32;
|
||||
|
||||
#[enumber::into]
|
||||
// XXX: This should be ErrorCode, but we can't use type aliases here :/.
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[allow(unused)]
|
||||
pub enum Error {
|
||||
#[error("Success")]
|
||||
StatusOk = 0,
|
||||
#[error("Initializing the crypto library failed: {0}")]
|
||||
InitCryptoLibInitFailed(String) = 0x0111,
|
||||
|
||||
// PEP_INIT_CANNOT_LOAD_CRYPTO_LIB = 0x0110,
|
||||
// PEP_INIT_CRYPTO_LIB_INIT_FAILED = 0x0111,
|
||||
// PEP_INIT_NO_CRYPTO_HOME = 0x0112,
|
||||
// PEP_INIT_NETPGP_INIT_FAILED = 0x0113,
|
||||
// PEP_INIT_CANNOT_DETERMINE_CRYPTO_VERSION = 0x0114,
|
||||
// PEP_INIT_UNSUPPORTED_CRYPTO_VERSION = 0x0115,
|
||||
// PEP_INIT_CANNOT_CONFIG_CRYPTO_AGENT = 0x0116,
|
||||
//
|
||||
// PEP_INIT_SQLITE3_WITHOUT_MUTEX = 0x0120,
|
||||
|
||||
#[error("Opening the database failed: {1}")]
|
||||
InitCannotOpenDB(#[source] anyhow::Error, String) = 0x0121,
|
||||
|
||||
// PEP_INIT_CANNOT_OPEN_SYSTEM_DB = 0x0122,
|
||||
// PEP_INIT_DB_DOWNGRADE_VIOLATION = 0x0123,
|
||||
|
||||
#[error("Database error: {1}")]
|
||||
UnknownDbError(#[source] anyhow::Error, String) = 0x01ff,
|
||||
|
||||
#[error("Key {0} not present in database")]
|
||||
KeyNotFound(KeyHandle) = 0x0201,
|
||||
// PEP_KEY_HAS_AMBIG_NAME = 0x0202,
|
||||
|
||||
#[error("Getting key: {1}")]
|
||||
GetKeyFailed(#[source] anyhow::Error, String) = 0x0203,
|
||||
|
||||
// PEP_CANNOT_EXPORT_KEY = 0x0204,
|
||||
// PEP_CANNOT_EDIT_KEY = 0x0205,
|
||||
#[error("Key unsuitable: {0}")]
|
||||
KeyUnsuitable(#[source] anyhow::Error, String) = 0x0206,
|
||||
|
||||
// PEP_MALFORMED_KEY_RESET_MSG = 0x0210,
|
||||
// PEP_KEY_NOT_RESET = 0x0211,
|
||||
#[error("Cannot delete key: {0}")]
|
||||
CannotDeleteKey(#[source] anyhow::Error, String) = 0x0212,
|
||||
#[error("Imported key")]
|
||||
KeyImported = 0x0220,
|
||||
#[error("No key imported")]
|
||||
NoKeyImported = 0x0221,
|
||||
// PEP_KEY_IMPORT_STATUS_UNKNOWN = 0x0222,
|
||||
#[error("Some keys imported")]
|
||||
SomeKeysImported = 0x0223,
|
||||
|
||||
// PEP_CANNOT_FIND_IDENTITY = 0x0301,
|
||||
// PEP_CANNOT_SET_PERSON = 0x0381,
|
||||
// PEP_CANNOT_SET_PGP_KEYPAIR = 0x0382,
|
||||
// PEP_CANNOT_SET_IDENTITY = 0x0383,
|
||||
// PEP_CANNOT_SET_TRUST = 0x0384,
|
||||
// PEP_KEY_BLACKLISTED = 0x0385,
|
||||
// PEP_CANNOT_FIND_PERSON = 0x0386,
|
||||
// PEP_CANNOT_SET_PEP_VERSION = 0X0387,
|
||||
//
|
||||
// PEP_CANNOT_FIND_ALIAS = 0x0391,
|
||||
// PEP_CANNOT_SET_ALIAS = 0x0392,
|
||||
// PEP_NO_OWN_USERID_FOUND = 0x0393,
|
||||
//
|
||||
#[error("Message not encrypted and not verified")]
|
||||
Unencrypted = 0x0400,
|
||||
#[error("Message not encrypted, but verified")]
|
||||
Verified = 0x0401,
|
||||
#[error("Decrypted message")]
|
||||
Decrypted = 0x0402,
|
||||
#[error("Decrypted and verified message")]
|
||||
DecryptedAndVerified = 0x0403,
|
||||
#[error("Decrypted failed: wrong format")]
|
||||
DecryptWrongFormat = 0x0404,
|
||||
#[error("Decrypted failed: no key")]
|
||||
DecryptNoKey(#[source] anyhow::Error) = 0x0405,
|
||||
#[error("Decrypted failed: signature does not match")]
|
||||
DecryptSignatureDoesNotMatch = 0x0406,
|
||||
#[error("Verification failed: no key")]
|
||||
VerifyNoKey(#[source] anyhow::Error) = 0x0407,
|
||||
// PEP_VERIFIED_AND_TRUSTED = 0x0408,
|
||||
// PEP_CANNOT_REENCRYPT = 0x0409,
|
||||
#[error("Signer's key is revoked")]
|
||||
VerifySignerKeyRevoked = 0x040a,
|
||||
#[error("Cannot decrypt: {0}")]
|
||||
CannotDecryptUnknown(String) = 0x04ff,
|
||||
//
|
||||
//
|
||||
// PEP_TRUSTWORD_NOT_FOUND = 0x0501,
|
||||
// PEP_TRUSTWORDS_FPR_WRONG_LENGTH = 0x0502,
|
||||
// PEP_TRUSTWORDS_DUPLICATE_FPR = 0x0503,
|
||||
//
|
||||
#[error("Cannot create key")]
|
||||
CannotCreateKey(#[source] anyhow::Error, String) = 0x0601,
|
||||
// PEP_CANNOT_SEND_KEY = 0x0602,
|
||||
//
|
||||
// PEP_PHRASE_NOT_FOUND = 0x0701,
|
||||
//
|
||||
// PEP_SEND_FUNCTION_NOT_REGISTERED = 0x0801,
|
||||
// PEP_CONTRAINTS_VIOLATED = 0x0802,
|
||||
// PEP_CANNOT_ENCODE = 0x0803,
|
||||
//
|
||||
// PEP_SYNC_NO_NOTIFY_CALLBACK = 0x0901,
|
||||
// PEP_SYNC_ILLEGAL_MESSAGE = 0x0902,
|
||||
// PEP_SYNC_NO_INJECT_CALLBACK = 0x0903,
|
||||
// PEP_SYNC_NO_CHANNEL = 0x0904,
|
||||
// PEP_SYNC_CANNOT_ENCRYPT = 0x0905,
|
||||
// PEP_SYNC_NO_MESSAGE_SEND_CALLBACK = 0x0906,
|
||||
// PEP_SYNC_CANNOT_START = 0x0907,
|
||||
//
|
||||
// PEP_CANNOT_INCREASE_SEQUENCE = 0x0971,
|
||||
//
|
||||
// PEP_STATEMACHINE_ERROR = 0x0980,
|
||||
// PEP_NO_TRUST = 0x0981,
|
||||
// PEP_STATEMACHINE_INVALID_STATE = 0x0982,
|
||||
// PEP_STATEMACHINE_INVALID_EVENT = 0x0983,
|
||||
// PEP_STATEMACHINE_INVALID_CONDITION = 0x0984,
|
||||
// PEP_STATEMACHINE_INVALID_ACTION = 0x0985,
|
||||
// PEP_STATEMACHINE_INHIBITED_EVENT = 0x0986,
|
||||
// PEP_STATEMACHINE_CANNOT_SEND = 0x0987,
|
||||
//
|
||||
#[error("Passphrase required")]
|
||||
PassphraseRequired = 0x0a00,
|
||||
#[error("Bad passphrase")]
|
||||
WrongPassphrase(#[source] anyhow::Error, String) = 0x0a01,
|
||||
#[error("Passphrase required for new keys")]
|
||||
PassphraseForNewKeysRequired = 0x0a02,
|
||||
//
|
||||
// PEP_CANNOT_CREATE_GROUP = 0x0b00,
|
||||
// PEP_CANNOT_FIND_GROUP_ENTRY = 0x0b01,
|
||||
// PEP_GROUP_EXISTS = 0x0b02,
|
||||
// PEP_GROUP_NOT_FOUND = 0x0b03,
|
||||
// PEP_CANNOT_ENABLE_GROUP = 0x0b04,
|
||||
// PEP_CANNOT_DISABLE_GROUP = 0x0b05,
|
||||
// PEP_CANNOT_ADD_GROUP_MEMBER = 0x0b06,
|
||||
// PEP_CANNOT_DEACTIVATE_GROUP_MEMBER = 0x0b07,
|
||||
// PEP_NO_MEMBERSHIP_STATUS_FOUND = 0x0b08,
|
||||
// PEP_CANNOT_LEAVE_GROUP = 0x0b09,
|
||||
// PEP_CANNOT_JOIN_GROUP = 0x0b0a,
|
||||
// PEP_CANNOT_RETRIEVE_MEMBERSHIP_INFO = 0x0b0b,
|
||||
//
|
||||
// PEP_DISTRIBUTION_ILLEGAL_MESSAGE = 0x1002,
|
||||
// PEP_STORAGE_ILLEGAL_MESSAGE = 0x1102,
|
||||
//
|
||||
// PEP_COMMIT_FAILED = 0xff01,
|
||||
// PEP_MESSAGE_CONSUME = 0xff02,
|
||||
// PEP_MESSAGE_IGNORE = 0xff03,
|
||||
#[error("Invalid configuration: {0}")]
|
||||
CannotConfig(String) = 0xff04,
|
||||
//
|
||||
// PEP_RECORD_NOT_FOUND = -6,
|
||||
// PEP_CANNOT_CREATE_TEMP_FILE = -5,
|
||||
#[error("Illegal value: {0}")]
|
||||
IllegalValue(String) = -4,
|
||||
|
||||
// PEP_BUFFER_TOO_SMALL = -3,
|
||||
#[error("Out of memory: {1} bytes for {0}")]
|
||||
OutOfMemory(String, usize) = -2,
|
||||
#[error("Unknown error: {1}")]
|
||||
UnknownError(#[source] anyhow::Error, String) = -1,
|
||||
|
||||
// PEP_VERSION_MISMATCH = -7,
|
||||
}
|
||||
|
||||
// See pEpEngine/src/timestamp.h
|
||||
//
|
||||
// https://gitea.pep.foundation/pEp.foundation/pEpEngine/src/branch/master/src/timestamp.h
|
||||
#[cfg(not(windows))]
|
||||
pub type Timestamp = tm;
|
||||
#[cfg(windows)]
|
||||
#[repr(C)]
|
||||
// for time values all functions are using POSIX struct tm
|
||||
pub struct Timestamp {
|
||||
tm_sec: c_int,
|
||||
tm_min: c_int,
|
||||
tm_hour: c_int,
|
||||
tm_mday: c_int,
|
||||
tm_mon: c_int,
|
||||
tm_year: c_int,
|
||||
tm_wday: c_int,
|
||||
tm_yday: c_int,
|
||||
tm_isdst: c_int,
|
||||
tm_gmtoff: c_long, // offset from GMT in seconds
|
||||
}
|
||||
|
||||
// See pEpEngine/src/pEpEngine.h:PEP_comm_format.
|
||||
//
|
||||