Browse Source

Initial commit.

main
Neal H. Walfield 2 months ago
commit
e5004abe05
13 changed files with 4900 additions and 0 deletions
  1. +43
    -0
      Cargo.toml
  2. +5
    -0
      README.md
  3. +11
    -0
      sequoia-pep-engine-crypto.pc.in
  4. +48
    -0
      src/buffer.rs
  5. +6
    -0
      src/constants.rs
  6. +72
    -0
      src/ffi.rs
  7. +894
    -0
      src/keystore.rs
  8. +2363
    -0
      src/lib.rs
  9. +89
    -0
      src/log.rs
  10. +407
    -0
      src/pep.rs
  11. +337
    -0
      src/pep/identity.rs
  12. +164
    -0
      src/pep/session.rs
  13. +461
    -0
      src/pep/stringlist.rs

+ 43
- 0
Cargo.toml View File

@ -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' }

+ 5
- 0
README.md View File

@ -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

+ 11
- 0
sequoia-pep-engine-crypto.pc.in View File

@ -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

+ 48
- 0
src/buffer.rs View File

@ -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
}
}

+ 6
- 0
src/constants.rs View File

@ -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" ];

+ 72
- 0
src/ffi.rs View File

@ -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()
}
};
}

+ 894
- 0
src/keystore.rs View File

@ -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)
}
}

+ 2363
- 0
src/lib.rs
File diff suppressed because it is too large
View File


+ 89
- 0
src/log.rs View File

@ -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();
}
}

+ 407
- 0
src/pep.rs View File

@ -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.
//
// https://gitea.pep.foundation/pEp.foundation/pEpEngine/src/branch/master/src/pEpEngine.h#L697
#[repr(C)]
#[allow(unused)]
#[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Debug)]
pub enum PepCommType {
Unknown = 0,
// range 0x01 to 0x09: no encryption, 0x0a to 0x0e: nothing reasonable
NoEncryption = 0x01, // generic
NoEncryptedChannel = 0x02,
KeyNotFound = 0x03,
KeyExpired = 0x04,
KeyRevoked = 0x05,
KeyB0rken = 0x06,
KeyExpiredButConfirmed = 0x07, // NOT with confirmed bit. Just retaining info here in case of renewal.
MyKeyNotIncluded = 0x09,
SecurityByObscurity = 0x0a,
B0rkenCrypto = 0x0b,
KeyTooShort = 0x0c,
Compromised = 0x0e, // known compromised connection
Mistrusted = 0x0f, // known mistrusted key
// range 0x10 to 0x3f: unconfirmed encryption
UnconfirmedEncryption = 0x10, // generic
OpenPgpWeakUnconfirmed = 0x11, // RSA 1024 is weak
ToBeChecked = 0x20, // generic
SMimeUnconfirmed = 0x21,
CmsUnconfirmed = 0x22,
StrongButUnconfirmed = 0x30, // generic
OpenPgpUnconfirmed = 0x38, // key at least 2048 bit RSA or EC
OtrUnconfirmed = 0x3a,
// range 0x40 to 0x7f: unconfirmed encryption and anonymization
UnconfirmedEncAnon = 0x40, // generic
PepUnconfirmed = 0x7f,
Confirmed = 0x80, // this bit decides if trust is confirmed
// range 0x81 to 0x8f: reserved
// range 0x90 to 0xbf: confirmed encryption
ConfirmedEncryption = 0x90, // generic
OpenPgpWeak = 0x91, // RSA 1024 is weak (unused)
ToBeCheckedConfirmed = 0xa0, // generic
SMime = 0xa1,
Cms = 0xa2,
StrongEncryption = 0xb0, // generic
OpenPgp = 0xb8, // key at least 2048 bit RSA or EC
Otr = 0xba,
// range 0xc0 to 0xff: confirmed encryption and anonymization
ConfirmedEncAnon = 0xc0, // generic
Pep = 0xff
}
// See pEpEngine/src/pEpEngine.h:PEP_enc_format.
//
// https://gitea.pep.foundation/pEp.foundation/pEpEngine/src/branch/master/src/pEpEngine.h#L179
#[repr(C)]
#[allow(unused)]
pub enum PepEncFormat {
None = 0, // message is not encrypted
Pieces = 1, // inline PGP + PGP extensions, was removed
// Inline = 1, // still there
SMime = 2, // RFC5751
PgpMime = 3, // RFC3156
Pep = 4, // pEp encryption format
PgpMimeOutlook1 = 5, // Message B0rken by Outlook type 1
InlineEA = 6,
Auto = 255 // figure out automatically where possible
}
// See pEpEngine/src/pEpEngine.h:identity_flags.
//
// https://gitea.pep.foundation/pEp.foundation/pEpEngine/src/branch/master/src/pEpEngine.h#L765
#[repr(C)]
#[allow(unused)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PepIdentityFlags {
// the first octet flags are app defined settings:
// don't use this identity for sync
NotForSync = 0x0001,
// identity of list of persons
List = 0x0002,
// the second octet flags are calculated:
// identity of a device group member
DeviceGroup = 0x0100,
// identity is associated with an org (i.e. NOT a private account
// - could be company email)
OrgIdent = 0x0200,
// identity is a group identity (e.g. mailing list) - N.B. not
// related to device group!
GroupIdent = 0x0400,
}
impl BitAnd for PepIdentityFlags {
type Output = usize;
fn bitand(self, rhs: Self) -> Self::Output {
(self as usize) & (rhs as usize)
}
}