Merge branch 'malte/keyserver_lookup' into 'main'
Start milestone 3 See merge request husk-project/husk-milter!1
This commit is contained in:
Generated
+2422
-123
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -12,12 +12,13 @@ rust-version = "1.79"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
bytes = "1.10.1"
|
||||
bytes = "1.11.1"
|
||||
indymilter = "0.3.0"
|
||||
log = "0.4.27"
|
||||
log4rs = "1.3.0"
|
||||
sequoia-cert-store = "0.7.1"
|
||||
sequoia-directories = "0.1.0"
|
||||
sequoia-net = "0.30.0"
|
||||
sequoia-openpgp = "2.1.0"
|
||||
sequoia-wot = "0.15.0"
|
||||
serde = "1.0.228"
|
||||
|
||||
@@ -6,3 +6,5 @@ connection = "localhost:3000"
|
||||
sequoia_home = "/tmp/sq_home"
|
||||
|
||||
logfile_config = "./config/log4rs.yml"
|
||||
|
||||
keyservers = [ "hkps://keys.openpgp.org/", "hkps://mail-api.proton.me", "hkps://keys.mailvelope.com", "hkps://keyserver.ubuntu.com", "hkps://sks.pod01.fleetstreetops.com" ]
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
##
|
||||
# build e2e tests
|
||||
##
|
||||
|
||||
##
|
||||
# build hockeypuck (keyserver), as it's not a debian package
|
||||
##
|
||||
FROM golang:1.24 AS gobuilder
|
||||
|
||||
## install build dependencies
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update -yqq && \
|
||||
apt-get install -yqq \
|
||||
git \
|
||||
make
|
||||
|
||||
WORKDIR /hockeypuck
|
||||
|
||||
RUN git clone https://github.com/hockeypuck/hockeypuck /hockeypuck
|
||||
RUN make build
|
||||
|
||||
#
|
||||
# the e2e tests need a rust toolchain
|
||||
#
|
||||
|
||||
FROM docker.io/library/rust:latest
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
## ensure language settings
|
||||
RUN apt-get update -yqq && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \
|
||||
&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
|
||||
ENV LANG=en_US.utf8
|
||||
|
||||
## install build dependencies
|
||||
RUN apt-get update -yqq && \
|
||||
apt-get install -yqq \
|
||||
clang \
|
||||
git \
|
||||
libclang-dev \
|
||||
libsqlite3-dev \
|
||||
libssl-dev \
|
||||
llvm \
|
||||
nettle-dev \
|
||||
pkg-config
|
||||
|
||||
##
|
||||
RUN apt-get update -yqq && \
|
||||
apt-get install -yqq adduser \
|
||||
passwd \
|
||||
bash \
|
||||
openssh-client \
|
||||
sudo \
|
||||
postfix \
|
||||
rsyslog \
|
||||
postgresql \
|
||||
openssl \
|
||||
sq
|
||||
|
||||
RUN adduser --shell /bin/bash --disabled-password --gecos "" hockeypuck
|
||||
|
||||
COPY mta_provision/ /
|
||||
|
||||
# copy hockeypuck
|
||||
COPY --from=gobuilder /hockeypuck/bin/hockeypuck /usr/bin/hockeypuck
|
||||
COPY --from=gobuilder /hockeypuck/contrib/templates/* /var/www/templates/
|
||||
|
||||
RUN /usr/bin/gen_hockeypuck.sh example.com 127.0.0.2
|
||||
|
||||
# tls certificate for example.com
|
||||
RUN /usr/bin/gen_tls_cert.sh example.com && \
|
||||
chown hockeypuck /etc/local_certs/example.com.key
|
||||
|
||||
RUN update-ca-certificates
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Content
|
||||
|
||||
This directory contains docker images and related provisioning files for setting
|
||||
up test environments.
|
||||
|
||||
## MTA
|
||||
- docker container file: MTAContainerFile
|
||||
- provisioning: inside mta_provision/
|
||||
|
||||
### Build
|
||||
```
|
||||
$ docker build -f MTAContainerFile -t localhost/mta .
|
||||
```
|
||||
|
||||
### Use
|
||||
|
||||
Check the image interactively
|
||||
```
|
||||
$ docker run -ti --rm localhost/mta bash -i
|
||||
```
|
||||
|
||||
Use locally, no git clone and cargo build. This use case assumes that a binary
|
||||
has been locally build and can be mapped into the docker image.
|
||||
|
||||
The mapped binary will be started, alongside `postfix` and `hockeypuck`.
|
||||
|
||||
```
|
||||
$ docker run -ti --rm \
|
||||
--mount type=bind,src=$(pwd),dst=/opt/husk/mapped/
|
||||
localhost/mta bash -c "/usr/bin/prepare.sh && bash -i"
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
#
|
||||
# minimal postfix configuration with enabled milter
|
||||
#
|
||||
|
||||
# Debian specific: Specifying a file name will cause the first
|
||||
# line of that file to be used as the name. The Debian default
|
||||
# is /etc/mailname.
|
||||
#myorigin = /etc/mailname
|
||||
|
||||
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
|
||||
biff = no
|
||||
|
||||
# appending .domain is the MUA's job.
|
||||
append_dot_mydomain = no
|
||||
|
||||
# Uncomment the next line to generate "delayed mail" warnings
|
||||
#delay_warning_time = 4h
|
||||
|
||||
#readme_directory = /usr/share/doc/postfix
|
||||
|
||||
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 3.6 on
|
||||
# fresh installs.
|
||||
compatibility_level = 3.6
|
||||
|
||||
# Per Debian policy, default to /etc/postfix for Cyrus SASL config
|
||||
cyrus_sasl_config_path = /etc/postfix/sasl
|
||||
|
||||
|
||||
# TLS parameters
|
||||
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
|
||||
smtpd_tls_security_level=may
|
||||
|
||||
smtp_tls_CApath=/etc/ssl/certs
|
||||
smtp_tls_security_level=may
|
||||
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
|
||||
|
||||
|
||||
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
|
||||
myhostname = example.com
|
||||
myorigin = example.com
|
||||
alias_maps = hash:/etc/aliases
|
||||
alias_database = hash:/etc/aliases
|
||||
mydestination = $myhostname, localhost.localdomain, localhost
|
||||
#relayhost =
|
||||
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 192.168.1.0/24
|
||||
mailbox_size_limit = 0
|
||||
recipient_delimiter = +
|
||||
inet_interfaces = all
|
||||
inet_protocols = all
|
||||
|
||||
smtpd_milters = inet:localhost:3000
|
||||
non_smtpd_milters = $smtpd_milters
|
||||
@@ -0,0 +1 @@
|
||||
user ALL=(ALL) NOPASSWD:ALL
|
||||
@@ -0,0 +1,7 @@
|
||||
connection = "localhost:3000"
|
||||
|
||||
sequoia_home = "/tmp/sq_home"
|
||||
|
||||
logfile_config = "./log4rs.yml"
|
||||
|
||||
keyservers = [ "hkps://example.com" ]
|
||||
@@ -0,0 +1,17 @@
|
||||
appenders:
|
||||
stdout_appender:
|
||||
kind: console
|
||||
encoder:
|
||||
pattern: "{h({d(%Y-%m-%d %H:%M:%S)(utc)} - {l}: {m}{n})}"
|
||||
|
||||
file_appender:
|
||||
kind: file
|
||||
path: "/tmp/husk.log"
|
||||
encoder:
|
||||
pattern: "{h({d(%Y-%m-%d %H:%M:%S)(utc)} - {l}: {m}{n})}"
|
||||
|
||||
root:
|
||||
level: debug
|
||||
appenders:
|
||||
- file_appender
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
DOMAIN=$1
|
||||
IP=$2
|
||||
|
||||
if [ "$DOMAIN" == "" ] ; then
|
||||
echo "USAGE: $0 DOMAIN IP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$IP" == "" ] ; then
|
||||
echo "USAGE: $0 DOMAIN IP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/usr/bin/gen_tls_cert.sh $DOMAIN
|
||||
/usr/bin/gen_hockeypuck_conf.sh $DOMAIN $IP
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
DOMAIN=$1
|
||||
IP=$2
|
||||
CERTS=/etc/local_certs
|
||||
DIR=/etc/hockeypuck.d
|
||||
CONF_NAME=hockeypuck_
|
||||
|
||||
if [ "$DOMAIN" == "" ] ; then
|
||||
echo "USAGE: $0 DOMAIN IP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$IP" == "" ] ; then
|
||||
echo "USAGE: $0 DOMAIN IP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d $DIR ] ; then
|
||||
mkdir -p $DIR
|
||||
fi
|
||||
|
||||
DB_NAME=$(echo $DOMAIN | tr -d '.-')
|
||||
|
||||
cat << EO_CONF > $DIR/$CONF_NAME$DOMAIN.conf
|
||||
[hockeypuck]
|
||||
loglevel="INFO"
|
||||
logfile="/tmp/hockeypuck_$DOMAIN.log"
|
||||
indexTemplate="/var/www/templates/index.html.tmpl"
|
||||
vindexTemplate="/var/www/templates/index.html.tmpl"
|
||||
statsTemplate="/var/www/templates/stats.html.tmpl"
|
||||
webroot="/var/www/"
|
||||
|
||||
[hockeypuck.hkp]
|
||||
bind="$IP:11371"
|
||||
|
||||
[hockeypuck.hkps]
|
||||
bind="$IP:443"
|
||||
logRequestDetails=false
|
||||
cert="$CERTS/$DOMAIN.crt"
|
||||
key="$CERTS/$DOMAIN.key"
|
||||
|
||||
[hockeypuck.openpgp.db]
|
||||
driver="postgres-jsonb"
|
||||
dsn="database=hkp_$DB_NAME host=/var/run/postgresql port=5432 sslmode=disable"
|
||||
|
||||
[hockeypuck.conflux.recon.leveldb]
|
||||
path="/tmp/${DOMAIN}_recon.db"
|
||||
EO_CONF
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
export SEQUOIA_HOME=$(mktemp -d)
|
||||
export TMP=$(mktemp -d)
|
||||
export SEQUOIA_TARGET=/tmp/sq_home
|
||||
|
||||
mkdir -p $SEQUOIA_TARGET
|
||||
|
||||
# create keys
|
||||
ALICE_FPR=$(sq key generate --own-key --without-password --name "Alice" --email "alice@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
|
||||
BOB_FPR=$(sq key generate --own-key --without-password --name "Bob" --email "bob@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
|
||||
CAROL_FPR=$(sq key generate --own-key --without-password --name "Carol" --email "carol@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
|
||||
DAVE_FPR=$(sq key generate --own-key --without-password --name "Dave" --email "dave@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
|
||||
|
||||
CA_FPR=$(sq key generate --own-key --without-password --name "CA" --email "ca@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
|
||||
|
||||
# vouch for bob
|
||||
sq pki vouch add --certifier $CA_FPR --cert $BOB_FPR --email "bob@example.com"
|
||||
# vouch for carol
|
||||
sq pki vouch add --certifier $CA_FPR --cert $CAROL_FPR --email "carol@example.com"
|
||||
|
||||
# export
|
||||
sq key export --cert $ALICE_FPR > $TMP/alice_sk.pgp
|
||||
sq key export --cert $CA_FPR > $TMP/ca_sk.pgp
|
||||
# only certs
|
||||
sq cert export --cert $BOB_FPR > $TMP/bob_pk.pgp
|
||||
sq network keyserver publish --server hkps://example.com --cert $CAROL_FPR
|
||||
sq network keyserver publish --server hkps://example.com --cert $DAVE_FPR
|
||||
|
||||
# create store
|
||||
export SEQUOIA_HOME=$SEQUOIA_TARGET
|
||||
sq key import $TMP/alice_sk.pgp
|
||||
sq key import $TMP/ca_sk.pgp
|
||||
sq cert import $TMP/bob_pk.pgp
|
||||
|
||||
sq pki link authorize --unconstrained --cert $CA_FPR --all --domain example.com
|
||||
|
||||
# Outcome:
|
||||
# ca@example.com is authenticated with an unlimited trust depth and a restriction for domain example.com
|
||||
# alice@example.com has a local key, but it is not authenticated
|
||||
# bob@example.com has a local cert, which is certified by ca@example.com
|
||||
# carol@example.com is published on a keyserver and certified by ca@example.com
|
||||
# dave@example.com is published on a keyserver but uncertified
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
DOMAIN=$1
|
||||
DIR=/etc/local_certs
|
||||
USER=hockeypuck
|
||||
|
||||
if [ "$DOMAIN" == "" ] ; then
|
||||
echo "USAGE: $0 DOMAIN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d $DIR ] ; then
|
||||
mkdir -p $DIR
|
||||
fi
|
||||
|
||||
echo "create key material for $DOMAIN ..."
|
||||
openssl req -newkey rsa:2048 -keyout $DIR/$DOMAIN.key -nodes -x509 -days 365 -out $DIR/$DOMAIN.crt -subj "/C=DE/ST=HH/L=Earth/O=CompanyName/OU=IT/CN=$DOMAIN/emailAddress=email@example.com"
|
||||
chown $USER:$USER $DIR/$DOMAIN.key
|
||||
|
||||
cp $DIR/$DOMAIN.crt /usr/share/ca-certificates/
|
||||
echo $DOMAIN.crt >> /etc/ca-certificates.conf
|
||||
|
||||
# dont forget to run `update-ca-certificates` after all DOMAINs are done
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
BRANCH=$1
|
||||
|
||||
## create domain
|
||||
echo "127.0.0.1 " $(hostname) > /etc/hosts
|
||||
echo "127.0.0.2 example.com" >> /etc/hosts
|
||||
|
||||
## build milter
|
||||
if [ -f /opt/husk/mapped/Cargo.toml ] ; then
|
||||
HUSK_BIN=/opt/husk/mapped/target/release/husk
|
||||
else
|
||||
cd /opt/husk
|
||||
git clone https://gitlab.com/husk-project/husk-milter.git
|
||||
cd husk-milter
|
||||
if [ "$BRANCH" != "" ] ; then
|
||||
git checkout $BRANCH
|
||||
fi
|
||||
cargo build --release
|
||||
|
||||
HUSK_BIN=/opt/husk/husk-milter/target/release/husk
|
||||
fi
|
||||
|
||||
## start rsyslogd
|
||||
/usr/sbin/rsyslogd -n -iNONE &
|
||||
|
||||
## start postfix
|
||||
sudo postfix start
|
||||
|
||||
## start and prepare postgres
|
||||
DOMAINS="example.com"
|
||||
sudo /etc/init.d/postgresql start
|
||||
echo "create user hockeypuck;" | sudo -u postgres psql -d template1 -U postgres
|
||||
for domain in $DOMAINS ; do
|
||||
DB_NAME=$(echo $domain | tr -d '.-')
|
||||
echo "
|
||||
create database hkp_$DB_NAME with owner 'hockeypuck';
|
||||
GRANT ALL PRIVILEGES ON DATABASE hkp_$DB_NAME to hockeypuck;
|
||||
" | sudo -u postgres psql -d template1 -U postgres
|
||||
done
|
||||
|
||||
#
|
||||
# start hockeypuck
|
||||
#
|
||||
echo "start hockeypuck"
|
||||
for domain in $DOMAINS ; do
|
||||
echo " $domain..."
|
||||
sudo -u hockeypuck hockeypuck --config /etc/hockeypuck.d/hockeypuck_$domain.conf 2>&1 > /tmp/hockeypuck_$domain.out &
|
||||
done
|
||||
|
||||
## create sequoia_home
|
||||
bash /usr/bin/gen_sq_home.sh
|
||||
|
||||
## start milter
|
||||
cd /opt/husk
|
||||
$HUSK_BIN config.toml &
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
TEST_DIR=$1
|
||||
|
||||
if [ "$TEST_DIR" == "" ] ; then
|
||||
echo "USAGE: $0 DIRECTORY_WITH_TESTS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for TEST in $TEST_DIR/* ; do
|
||||
if bash $TEST ; then
|
||||
echo "$TEST: success"
|
||||
else
|
||||
echo "$TEST: failure"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
echo "test message" | sendmail -t root@example.com
|
||||
sleep 1
|
||||
|
||||
## simple test if mail passes
|
||||
if [ -f /var/mail/root ] ; then
|
||||
echo "success"
|
||||
rm /var/mail/root
|
||||
else
|
||||
echo "failure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## test encryption
|
||||
## bob@example.com has an authenticated certificate in
|
||||
## the cert store
|
||||
echo "test message" | sendmail -t bob@example.com
|
||||
sleep 1
|
||||
if [ -f /var/mail/bob ] ; then
|
||||
if grep -q "BEGIN PGP MESSAGE" /var/mail/bob ; then
|
||||
echo "encryption test: success"
|
||||
else
|
||||
echo "encryption test: failure"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "failure: mail didn't pass"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## test encryption 2
|
||||
## alice@example.com has a certificate in the cert store, but
|
||||
## it is not authenticated -> this should fail
|
||||
echo "test message" | sendmail -t alice@example.com
|
||||
sleep 1
|
||||
if [ -f /var/mail/alice ] ; then
|
||||
if grep -q "BEGIN PGP MESSAGE" /var/mail/alice ; then
|
||||
echo "encryption test 2: failure"
|
||||
exit 1
|
||||
else
|
||||
echo "encryption test 2: success"
|
||||
fi
|
||||
else
|
||||
echo "failure: mail didn't pass"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
cargo build --release
|
||||
|
||||
docker run -ti \
|
||||
--rm \
|
||||
--mount type=bind,src=$(pwd),dst=/opt/husk/mapped/ \
|
||||
localhost/mta \
|
||||
bash -c "/usr/bin/prepare.sh && bash /opt/husk/mapped/e2e-tests/runner.sh"
|
||||
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
for TEST in ./tests/* ; do
|
||||
if bash $TEST ; then
|
||||
echo "success"
|
||||
else
|
||||
echo "failure"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
echo "Basic: Test if MTA is working"
|
||||
|
||||
adduser --shell /bin/bash --disabled-password --gecos "" mtatest
|
||||
echo "test mail" | sendmail mtatest@example.com
|
||||
|
||||
sleep 1
|
||||
|
||||
if [ -f /var/mail/mtatest ] ; then
|
||||
RESULT_CODE=0
|
||||
else
|
||||
RESULT_CODE=1
|
||||
fi
|
||||
|
||||
deluser --remove-home mtatest
|
||||
|
||||
exit $RESULT_CODE
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
echo "Simple: Test encryption for available certificate"
|
||||
|
||||
# There is an authenticated certificate for bob in the cert store
|
||||
adduser --shell /bin/bash --disabled-password --gecos "" bob
|
||||
echo "test mail" | sendmail bob@example.com
|
||||
|
||||
sleep 1
|
||||
|
||||
if [ -f /var/mail/bob ] ; then
|
||||
if grep -q "BEGIN PGP MESSAGE" /var/mail/bob ; then
|
||||
RESULT_CODE=0
|
||||
else
|
||||
echo "no encryption detected"
|
||||
RESULT_CODE=1
|
||||
fi
|
||||
else
|
||||
echo "no mail detected"
|
||||
RESULT_CODE=1
|
||||
fi
|
||||
|
||||
deluser --remove-home bob
|
||||
|
||||
exit $RESULT_CODE
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
echo "Simple: Test encryption for unavailable certificate"
|
||||
|
||||
# There is no authenticated certificate for alice in the cert store
|
||||
adduser --shell /bin/bash --disabled-password --gecos "" alice
|
||||
echo "test mail" | sendmail alice@example.com
|
||||
|
||||
sleep 1
|
||||
|
||||
if [ -f /var/mail/alice ] ; then
|
||||
if grep -q "BEGIN PGP MESSAGE" /var/mail/alice ; then
|
||||
echo "encryption detected - that is wrong"
|
||||
RESULT_CODE=1
|
||||
else
|
||||
RESULT_CODE=0
|
||||
fi
|
||||
else
|
||||
echo "no mail detected"
|
||||
RESULT_CODE=1
|
||||
fi
|
||||
|
||||
deluser --remove-home alice
|
||||
|
||||
exit $RESULT_CODE
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
echo "Test encryption for certificate an a keyserver"
|
||||
|
||||
# There is a certificate for carol on the keyserver
|
||||
# it's certified by an introducer
|
||||
adduser --shell /bin/bash --disabled-password --gecos "" carol
|
||||
echo "test mail" | sendmail carol@example.com
|
||||
|
||||
sleep 1
|
||||
|
||||
if [ -f /var/mail/carol ] ; then
|
||||
if grep -q "BEGIN PGP MESSAGE" /var/mail/carol ; then
|
||||
RESULT_CODE=0
|
||||
else
|
||||
echo "no encryption detected"
|
||||
RESULT_CODE=1
|
||||
fi
|
||||
else
|
||||
echo "no mail detected"
|
||||
RESULT_CODE=1
|
||||
fi
|
||||
|
||||
deluser --remove-home carol
|
||||
|
||||
exit $RESULT_CODE
|
||||
@@ -14,6 +14,7 @@ pub struct HuskConfig {
|
||||
pub connection: String,
|
||||
pub sequoia_home: String,
|
||||
logfile_config: Option<String>,
|
||||
pub keyservers: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl HuskConfig {
|
||||
@@ -43,6 +44,15 @@ impl HuskConfig {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_keyservers(&self) -> Vec<String> {
|
||||
if let Some(kss) = &self.keyservers {
|
||||
kss.clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub type HuskConfigContainer = Arc<Mutex<HuskConfig>>;
|
||||
|
||||
+180
-4
@@ -6,22 +6,27 @@
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
|
||||
use anyhow;
|
||||
use tokio::task::JoinSet;
|
||||
use bytes::Bytes;
|
||||
use sequoia_net::{KeyServer, wkd, dane};
|
||||
use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse};
|
||||
use sequoia_openpgp::policy::StandardPolicy;
|
||||
use sequoia_openpgp::serialize::stream::{Message, Armorer, Encryptor, LiteralWriter};
|
||||
use sequoia_openpgp::{Fingerprint, Cert, Packet};
|
||||
use sequoia_openpgp::cert::raw::RawCertParser;
|
||||
use sequoia_cert_store::{Store, CertStore, LazyCert};
|
||||
use sequoia_cert_store::{Store, CertStore, LazyCert, StoreUpdate};
|
||||
use sequoia_wot::{self as wot};
|
||||
use wot::store::Store as _;
|
||||
use wot::{Depth, Path};
|
||||
|
||||
use crate::types::errors::HuskError;
|
||||
use crate::types::husk_context::HuskContext;
|
||||
use crate::types::introducer::Introducer;
|
||||
use crate::types::introducer::{self, Introducer};
|
||||
use crate::types::recipient::Recipient;
|
||||
|
||||
/// Returns the local trust root from the cert_store. If the trust root
|
||||
@@ -61,12 +66,14 @@ pub fn get_local_trust_root(cert_store: &CertStore) -> anyhow::Result<Cert> {
|
||||
|
||||
}
|
||||
|
||||
/// Returns all certificates with an authenticated binding to user id 'email'.
|
||||
/// Returns all local certificates with an authenticated binding to user id 'email'.
|
||||
/// The certificates are not checked for usefulness (for instance, they might
|
||||
/// miss an encryption subkey).
|
||||
pub fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str)
|
||||
pub fn get_local_certificates<'hc>(context: &HuskContext<'hc>, email: &str)
|
||||
-> Vec<Arc<LazyCert<'hc>>> {
|
||||
|
||||
log::debug!("local lookup for {}", email);
|
||||
|
||||
let cert_store = &context.cert_store;
|
||||
let trust_roots = vec![(context.local_trust_root.fingerprint(), wot::FULLY_TRUSTED)];
|
||||
let wot_store = wot::store::CertStore::from_store(cert_store, &context.policy, None);
|
||||
@@ -95,6 +102,175 @@ pub fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str)
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
const USER_AGENT: &'static str = concat!("husk/", env!("CARGO_PKG_VERSION"));
|
||||
const CONNECT_TIMEOUT: Duration = Duration::new(5, 0);
|
||||
const REQUEST_TIMEOUT: Duration = Duration::new(5, 0);
|
||||
|
||||
/// Try to fetch certificates for `email` from online sources (keyservers, wkd,
|
||||
/// dane).
|
||||
pub async fn lookup_certificates<'hc>(context: &HuskContext<'hc>, email: &str)
|
||||
-> anyhow::Result<Vec<Cert>> {
|
||||
|
||||
log::debug!("remote lookup for {}", email);
|
||||
|
||||
let mut jobs = JoinSet::new();
|
||||
let http_client = sequoia_net::reqwest::Client::builder()
|
||||
.user_agent(USER_AGENT)
|
||||
.connect_timeout(CONNECT_TIMEOUT)
|
||||
.timeout(REQUEST_TIMEOUT)
|
||||
.build()?;
|
||||
|
||||
// local function to check certificates for usability
|
||||
let collect_certs = |certs: Vec<Result<Cert, anyhow::Error>>, email: String, policy: &StandardPolicy| {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for cert in certs {
|
||||
if let Ok(c) = cert {
|
||||
if let Ok(vc) = c.with_policy(policy, None) {
|
||||
|
||||
// check if the returned certificate contains a userid with the
|
||||
// email address in question.
|
||||
let mut userid_found = false;
|
||||
for userid in vc.userids() {
|
||||
if let Ok(Some(u)) = userid.userid().email() {
|
||||
if u == email {
|
||||
userid_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if userid_found {
|
||||
// check if the certificate can be used (for encryption).
|
||||
vc.keys()
|
||||
.supported()
|
||||
.alive()
|
||||
.revoked(false)
|
||||
.for_transport_encryption()
|
||||
.for_each(|_| {
|
||||
// XXX: cert gets returned for each transport key
|
||||
// once would be enough
|
||||
result.push(c.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
};
|
||||
|
||||
// Keyservers
|
||||
for keyserver in &context.keyservers {
|
||||
|
||||
log::debug!("querying {}", keyserver);
|
||||
|
||||
if let Ok(ks) = KeyServer::with_client(keyserver.as_str(), http_client.clone()) {
|
||||
|
||||
let e = email.to_string();
|
||||
let p = StandardPolicy::new();
|
||||
|
||||
jobs.spawn(async move {
|
||||
let mut partial_result = Vec::new();
|
||||
|
||||
if let Ok(certs) = ks.search(e.as_str()).await {
|
||||
partial_result = collect_certs(certs, e, &p);
|
||||
}
|
||||
partial_result
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// WKD
|
||||
let e = email.to_string();
|
||||
let p = StandardPolicy::new();
|
||||
jobs.spawn(async move {
|
||||
let mut partial_result = Vec::new();
|
||||
|
||||
if let Ok(certs) = wkd::get(&http_client.clone(), e.as_str()).await {
|
||||
partial_result = collect_certs(certs, e, &p);
|
||||
}
|
||||
partial_result
|
||||
});
|
||||
|
||||
// DANE
|
||||
let e = email.to_string();
|
||||
let p = StandardPolicy::new();
|
||||
jobs.spawn(async move {
|
||||
let mut partial_result = Vec::new();
|
||||
|
||||
if let Ok(certs) = dane::get(e.as_str()).await {
|
||||
partial_result = collect_certs(certs, e, &p);
|
||||
}
|
||||
partial_result
|
||||
});
|
||||
|
||||
// join the partial results. As the same certificate might be returned by
|
||||
// several sources, dedup and merge the certificates.
|
||||
let mut set: HashMap<Fingerprint, Cert> = HashMap::new();
|
||||
let results = jobs.join_all().await;
|
||||
results.into_iter().flatten()
|
||||
.for_each(|c| {
|
||||
let fpr = c.fingerprint();
|
||||
|
||||
log::debug!("found cert {}", fpr);
|
||||
|
||||
match set.entry(fpr.clone()) {
|
||||
Occupied(entry) => {
|
||||
let existing_cert = entry.into_mut();
|
||||
if let Ok(merged_cert) = existing_cert.clone().merge_public(c) {
|
||||
*existing_cert = merged_cert;
|
||||
}
|
||||
},
|
||||
Vacant(_entry) => {
|
||||
set.insert(fpr.clone(), c);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
let result = set.into_values().collect();
|
||||
Ok(result)
|
||||
|
||||
}
|
||||
|
||||
|
||||
pub async fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str)
|
||||
-> Vec<Arc<LazyCert<'hc>>> {
|
||||
|
||||
log::debug!("get certificate for {}", email);
|
||||
|
||||
let locals = get_local_certificates(context, email);
|
||||
if locals.len() > 0 {
|
||||
return locals;
|
||||
}
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
for introducer in &context.introducers {
|
||||
log::debug!("consider introducer {:?}",
|
||||
introducer.cert.userids().map(|u| u.userid()).collect::<Vec<_>>());
|
||||
if introducer.can_introduce(format!("<{}>", email).as_str()) {
|
||||
log::debug!(" introducer regex matches!");
|
||||
match lookup_certificates(context, email).await {
|
||||
Ok(certs) => {
|
||||
for cert in certs {
|
||||
if introducer.is_introducing(&cert) {
|
||||
let lazy_cert = Arc::new(LazyCert::from_cert(cert));
|
||||
if context.cert_store.update(lazy_cert.clone()).is_ok() {
|
||||
result.push(lazy_cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("{}", HuskError::KeyLookupError(email.to_string(), e.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns all introducers available in cert_store.
|
||||
pub fn get_introducers<'c>(cert_store: &CertStore<'c>, policy: &StandardPolicy, local_trust_root: Fingerprint) -> Vec<Introducer> {
|
||||
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ impl Daemon {
|
||||
if rcpt.is_none() {
|
||||
Status::Tempfail
|
||||
} else {
|
||||
context.add_recipient(rcpt.unwrap()).unwrap();
|
||||
context.add_recipient(rcpt.unwrap()).await;
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
|
||||
@@ -18,4 +18,6 @@ pub enum HuskError {
|
||||
NoCertStore,
|
||||
#[error("No encryption keys found")]
|
||||
NoEncryptionKeys,
|
||||
#[error("Error while looking up email {0}: {1}")]
|
||||
KeyLookupError(String, String),
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct HuskContext<'hc> {
|
||||
pub cert_store: CertStore<'hc>,
|
||||
pub local_trust_root: Cert,
|
||||
pub introducers: Vec<Introducer>,
|
||||
pub keyservers: Vec<String>,
|
||||
pub mail: MailContext<'hc>,
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ impl<'hc> HuskContext<'hc> {
|
||||
cert_store,
|
||||
local_trust_root,
|
||||
introducers,
|
||||
keyservers: config.get_keyservers(),
|
||||
mail: MailContext::new(),
|
||||
})
|
||||
}
|
||||
@@ -55,9 +57,9 @@ impl<'hc> HuskContext<'hc> {
|
||||
self.mail = MailContext::new();
|
||||
}
|
||||
|
||||
pub fn add_recipient(&mut self, rcpt: String) -> anyhow::Result<()> {
|
||||
pub async fn add_recipient(&mut self, rcpt: String) {
|
||||
|
||||
let certs = crypto::get_certificates(&self, rcpt.as_str());
|
||||
let certs = crypto::get_certificates(&self, rcpt.as_str()).await;
|
||||
|
||||
self.mail.add_recipient(rcpt, certs)
|
||||
}
|
||||
|
||||
@@ -29,5 +29,29 @@ impl Introducer {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_introducing(&self, cert: &Cert) -> bool {
|
||||
|
||||
let policy = StandardPolicy::new();
|
||||
let mut result = false;
|
||||
|
||||
let my_keyid = self.cert.keyid();
|
||||
|
||||
if let Ok(vc) = cert.with_policy(&policy, None) {
|
||||
vc.userids()
|
||||
.revoked(false)
|
||||
.for_each(|u| {
|
||||
u.certifications().for_each(|s| {
|
||||
if s.signature_alive(None, None).is_ok() {
|
||||
s.issuers().for_each(|i| {
|
||||
if i == &my_keyid { result = true; }
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,11 @@ impl<'mc> MailContext<'mc> {
|
||||
self.sender = Some(sender);
|
||||
}
|
||||
|
||||
pub fn add_recipient(&mut self, rcpt: String, certs: Vec<Arc<LazyCert<'mc>>>) -> anyhow::Result<()> {
|
||||
pub fn add_recipient(&mut self, rcpt: String, certs: Vec<Arc<LazyCert<'mc>>>) {
|
||||
let mut recipient: Recipient = rcpt.into();
|
||||
recipient.certs = certs;
|
||||
|
||||
self.recipients.push(recipient);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_chunk(&mut self, chunk: Bytes) {
|
||||
|
||||
Reference in New Issue
Block a user