47 Commits
0.2.0 ... main

Author SHA1 Message Date
23cbce0232 Merge branch 'deb-with-gbp' into 'main'
Create debian packages with gbp

See merge request mail/exim-encrypt-dlfunc!8
2022-08-26 12:12:36 +02:00
632c3404e6 Create debian packages with gbp, upload to KIT-CERT Repository. 2022-08-26 12:12:36 +02:00
1c84639ac7 README: clarify state of distro packages. 2022-01-15 06:13:33 +01:00
64dc7481cb Added oci labels 2021-11-08 21:57:52 +01:00
a8cbac240a Change umask to besser protect generated key files. 2021-10-13 15:35:10 +02:00
db9d8d6ee4 Merge branch 'ci-signing' into 'main'
Ci signing

See merge request mail/exim-encrypt-dlfunc!7
2021-10-08 04:05:53 +02:00
d224ae8124 Ci signing 2021-10-08 04:05:53 +02:00
05ef7c4467 Switched CI image building to Dockerfile while still using buildah. 2021-10-07 23:49:46 +02:00
ea589221b4 CI: don't install recommends for smaller CI images. 2021-09-17 02:52:13 +02:00
0a5e880f32 Lots of small README fixes and improvements. 2021-09-13 16:47:56 +02:00
b02f9e40ab Merge branch 'code_coverage' into 'main'
Code cleanup & more tests

See merge request mail/exim-encrypt-dlfunc!6
2021-09-13 02:40:13 +02:00
781c716d8e Code cleanup & more tests 2021-09-13 02:40:12 +02:00
1062248787 Merge branch 'code_coverage' into 'main'
Code coverage

See merge request mail/exim-encrypt-dlfunc!5
2021-09-13 00:28:12 +02:00
36fcdfaf42 Code coverage 2021-09-13 00:28:12 +02:00
b32d6fc861 Fixed complaints from Jetbrains Clion :-) 2021-09-12 22:06:09 +02:00
15de3a6204 Added slightly better memory management after using meson […] -Db_sanitize=address. 2021-09-12 21:47:57 +02:00
643f1a6719 Added table of contents to main README.md 2021-09-12 13:17:24 +02:00
5bf5bdecb1 Run all exim string expansion tests with empty config file.
Added a short note on memory management.
2021-09-12 13:14:15 +02:00
b19a1fc673 Added libexim-encrypt-dlfunc-decrypt-sealedbox to GitLab's CI artifacts. 2021-09-12 02:56:30 +02:00
c05f162a28 Moved incomplete python decryption example into contrib directory. 2021-09-12 02:52:19 +02:00
c7eedb7f4c Merge branch 'decryption_tools' into 'main'
Add decryption tools

Closes #1

See merge request mail/exim-encrypt-dlfunc!4
2021-09-12 02:47:42 +02:00
32e060d88d Added documentation and command help messages to decryption tools. 2021-09-12 02:38:34 +02:00
e26daf675b Wrote decrypt tool for sodium_crypto_box_seal plus matching tests.
Lots of code cleanups.
2021-09-12 02:06:10 +02:00
e1968e8f8c Added back meson from Debian packages as a build dependency for package building 2021-09-12 02:04:43 +02:00
a6c6169122 Seitched mmap-based file reader against getline which also works with pipes. 2021-09-11 14:40:43 +02:00
b6a350ef3a debugging meson build tests 2021-09-11 12:49:41 +02:00
0d8fb3dd77 debugging meson build tests 2021-09-11 12:23:15 +02:00
57b829737e debugging meson build tests 2021-09-11 12:20:23 +02:00
b6726dfcd2 Fixed erronous output 2021-09-11 12:04:06 +02:00
8845aaa653 Fixed test numbering 2021-09-11 11:41:12 +02:00
649932c73c Changed test output to conform to the TAP protocol (https://testanything.org) 2021-09-11 03:16:35 +02:00
99ff9e359c Added new files to CI artifacts 2021-09-11 02:59:56 +02:00
b8a5a2c759 2021-09-11 02:48:55 +02:00
1f8aa5fc4c Added test fo password decryption command 2021-09-11 02:46:32 +02:00
5a1bd58452 Renamed test function for library. 2021-09-11 02:26:39 +02:00
bb67fe5ba2 Updated example in README.md to use the actual path from the Debian package. 2021-09-11 02:23:47 +02:00
534db3ad6e Add option to suppress a newline at the end of the output. 2021-09-11 02:13:22 +02:00
ad3437f5df Working version of libexim-encrypt-dlfunc-decrypt-secretbox 2021-09-11 02:06:19 +02:00
41e7c43ab8 Always read password from environment.
Factored base64-decoding into its own function.
2021-09-11 00:24:44 +02:00
0b01283cd9 Refactored file reading into finction. 2021-09-08 03:34:12 +02:00
a82f6d388b “Transport commit” 2021-09-07 12:29:58 +02:00
aaad6b0e4f Renamed generate_encryption_keys binary to libexim-encrypt-dlfunc-genkeys. 2021-09-06 02:07:33 +02:00
71b3950c03 Split apt and pip install stages 2021-09-06 02:02:37 +02:00
aa8f4c5e02 Small README fixes 2021-09-05 14:32:24 +02:00
565aa43fb2 Removed meson from apt-get install because we use the pip version. 2021-09-05 14:13:50 +02:00
8c0340df0e Merge branch 'debian-packaging' into 'master'
Lower Build-Depends on debhelper-compat to version 12 (which should have

See merge request mail/exim-encrypt-dlfunc!3
2021-09-05 13:57:54 +02:00
b944a41f7c Lower Build-Depends on debhelper-compat to version 12 (which should have 2021-09-05 13:57:54 +02:00
26 changed files with 1327 additions and 141 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
/build
debian/*.ex
.gdb_history

View File

@ -1,39 +1,167 @@
---
include:
- remote: 'https://git.scc.kit.edu/KIT-CERT/publish/-/raw/main/gitlab-ci/ssh-config.yml'
- remote: 'https://git.scc.kit.edu/KIT-CERT/publish/-/raw/main/gitlab-ci/build_image.yml'
- remote: 'https://git.scc.kit.edu/KIT-CERT/publish/-/raw/main/gitlab-ci/gbp-pkg.yml'
stages:
- build
- test
- build_image
- build_pkg
.build:
stage: build
before_script:
- apt-get update
- DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get install -y build-essential exim4-dev libsodium-dev pkg-config python3-pip exim4-daemon-heavy openssl
- pip3 install meson ninja
script:
- meson build
- cd build
- ninja
- ninja test
artifacts:
paths:
- build/src/generate_encryption_keys
- build/src/libexim-encrypt-dlfunc.so
variables:
BUILD_CONTEXT: ci_container
BUILD_FILE: Dockerfile
IMAGE_NAME: pkg_build-exim-encrypt-dlfunc/bullseye
TARGET_REPOSITORY: "bullseye"
build:buster:
.image-buster:
variables:
IMAGE_NAME: pkg_build-exim-encrypt-dlfunc/buster
BUILD_FILE: Dockerfile.buster
TARGET_REPOSITORY: "buster"
.image-jammy:
variables:
IMAGE_NAME: pkg_build-exim-encrypt-dlfunc/jammy
TARGET_REPOSITORY: "jammy"
BUILD_FILE: Dockerfile.jammy
.image-focal:
variables:
IMAGE_NAME: pkg_build-exim-encrypt-dlfunc/focal
TARGET_REPOSITORY: "focal"
BUILD_FILE: Dockerfile.focal
build_pkg_image_buster:
extends:
- .build
image: debian:buster
- .image-buster
- build_pkg_image
build:bullseye:
image: debian:bullseye
build_pkg_image_jammy:
extends:
- .build
- .image-jammy
- build_pkg_image
build:bionic:
image: ubuntu:bionic
build_pkg_image_focal:
extends:
- .build
- .image-focal
- build_pkg_image
build:focal:
image: ubuntu:focal
pkg_buster:
extends:
- .build
- .image-buster
- gbp_pkg
pkg_focal:
extends:
- .image-focal
- gbp_pkg
pkg_jammy:
extends:
- .image-jammy
- gbp_pkg
#.code-coverage:
# stage: build
# script:
# - "export PATH=/usr/local/sbin:/usr/local/bin:${PATH}"
# - meson build -Db_coverage=true
# - cd build
# - ninja
# - ninja test
# - ninja coverage-xml
# - grep -Eo 'line-rate="[^"]+"' meson-logs/coverage.xml | head -n 1 |
# grep -Eo '[0-9.]+' | awk '{ printf "coverage\x3a %.2f%% of statements\n", $1 * 100 }'
# - ninja coverage-html
# - mv meson-logs/coveragereport ../coverage
# artifacts:
# paths:
# - coverage/
# reports:
# cobertura: build/meson-logs/coverage.xml
#
#.build:
# stage: build
# script:
# - which meson
# - meson build
# - cd build
# - ninja
# - ninja test
# artifacts:
# paths:
# - build/src/libexim-encrypt-dlfunc.so
# - build/src/libexim-encrypt-dlfunc-genkeys
# - build/src/libexim-encrypt-dlfunc-decrypt-secretbox
# - build/src/libexim-encrypt-dlfunc-decrypt-sealedbox
#
#.debian-package:
# stage: debian-package
# script:
# - mkdir ~/.gnupg; chown root:root ~/.gnupg; chmod 700 ~/.gnupg
# - eval $(gpg-agent --batch --sh --disable-scdaemon)
# - echo "pinentry-mode loopback" > ~/.gnupg/gpg.conf
# - echo "allow-loopback-pinentry" > ~/.gnupg/gpg-agent.conf
# - gpg-connect-agent /bye
# - echo $DEBIAN_SIGNING_KEY_ASC | base64 -d | gpg --batch --import --always-trust --yes
# - echo "1DC7C2770DC111723D505DD61614D5CDEE1555A7:6:" | gpg --import-ownertrust
# - dpkg-buildpackage --sign-key=1DC7C2770DC111723D505DD61614D5CDEE1555A7
# - mv -t . ../*.deb ../*.dsc ../*.tar.gz ../*.changes ../*.buildinfo
# artifacts:
# paths:
# - ./*.deb
# - ./*.dsc
# - ./*.tar.gz
# - ./*.changes
# - ./*.buildinfo
#
#code-coverage:
# extends:
# - .image-bullseye
# - .code-coverage
# needs: [ ]
#
#build:bullseye:
# extends:
# - .image-bullseye
# - .build
# - .code-coverage
# needs: [ ]
#
#build:focal:
# extends:
# - .image-focal
# - .build
# needs: [ ]
#
#build:buster:
# extends:
# - .build
# - .image-buster
# needs: [ ]
#
#debian-package:bullseye:
# extends:
# - .image-bullseye
# - .debian-package
# dependencies:
# - build:bullseye
# needs: [ "build:bullseye" ]
#
#debian-package:focal:
# extends:
# - .image-focal
# - .debian-package
# dependencies:
# - build:focal
# needs: [ "build:focal" ]
#
#debian-package:buster:
# extends:
# - .image-buster
# - .debian-package
# dependencies:
# - build:buster
# needs: [ "build:buster" ]

126
README.md
View File

@ -3,8 +3,14 @@
This library injects functions for string encryption and decryption into [exim4](https://www.exim.org/). It is basically
glue code that exports certain parts of the [libsodium library](https://github.com/jedisct1/libsodium) to exim at runtime.
[[_TOC_]]
## Installation
Beta quality debian packages are currently available as pipeline artifacts. Proper releases are work in progress (#6).
## Compiling
These instructions are currently only tested on [Debian](https://www.debian.org) and
[Ubuntu](https://ubuntu.com).
@ -14,16 +20,16 @@ These instructions are currently only tested on [Debian](https://www.debian.org)
apt-get install -y build-essential exim4-dev libsodium-dev meson pkg-config openssl exim4-daemon-heavy
```
`exim-encrypt-dlfunc` uses [meson](https://mesonbuild.com/) (and [ninja](https://ninja-build.org/)) as build system. To
ensure all features are present (which are lacking in the version packages in older distributions)
it is advised to install from [pypi](https://pypi.org/) via [pip](https://pypi.org/project/pip/):
This project uses [meson](https://mesonbuild.com/) (and [ninja](https://ninja-build.org/)) as build system. To ensure
all features are present (which are lacking in the packaged versions of older distributions)
you are strongly advised to install from [pypi](https://pypi.org/) via [pip](https://pypi.org/project/pip/):
```shell
apt-get install -y python3-pip
pip3 install meson ninja
pip3 install -U meson ninja
```
You may alternatively use your distribution packages:
You may alternatively try to use your distribution packages:
```shell
apt-get install -y meson
@ -44,7 +50,10 @@ meson compile -C build
meson test -C build
```
5. Copy to final destination (feel free to pick another place than `/usr/local/lib/`):
The `ci_container` directory contains a [script](ci_container/build.sh) (and a [short README](ci_container/README.md))
which creates the container images used in continuous integration for this project.
5. Install library and tools (defaults to `/usr/local/bin` and `/usr/local/lib/x86_64-linux-gnu`):
```shell
meson install -C build
@ -54,31 +63,47 @@ meson install -C build
Not every build of exim is able to load libraries at runtime. Please refer to the
[documentation](https://www.exim.org/exim-html-current/doc/html/spec_html/ch-string_expansions.html)
of the `${dlfunc{…}}` function for details. exim from the debian package `exim4-daemon-heavy` meets all the
requirements.
of the `${dlfunc{…}}` function for details. The Debian
package [`exim4-daemon-heavy`](https://packages.debian.org/exim4-daemon-heavy)
meets these requirements.
Try
```shell
exim4 --version | egrep -i --color 'Expand_dlfunc|Content_Scanning'
```
for a preliminary test.
7. Configure exim to use the new functionality
This highly depends on your use case. See [the example below](#xoip-example) and
[`src/test_libexim-encrypt-dlfunc.sh`](src/test_libexim-encrypt-dlfunc.sh).
## Usage
There are currently two pairs of functions:
There are currently two sets of complementary function pairs:
Symmetric encryption that derives its key from an ASCII string:
### Symmetric encryption that derives the key from an ASCII string (aka a “password”)
* `sodium_crypto_secretbox_encrypt_password(password, cleartext) → ciphertext`
* `sodium_crypto_secretbox_decrypt_password(password, ciphertext) → cleartext`
The generated key is only as strong as the provided password.
The generated key is only as strong as the provided password. Use `openssl rand --base64 48 | tr -d =`
to get a passable password.
Public key encryption that uses a key pair that needs to be created beforehand:
### Public key encryption that uses a key pair which needs to be created beforehand
* `sodium_crypto_box_seal(public key, cleartext) → ciphertext`
* `sodium_crypto_box_seal_open(private key, public key, ciphertext) → cleartext`
The second pair needs a proper key pair in the correct format. This is what the
`generate_encryption_keys` utility is for. Simply run it once to generate a pair. Be aware that every invocation will
overwrite the previous key pair without confirmation! Please save both parts in a safe place before proceeding.
The second pair needs a matching key pair in the correct format. This is what the
`libexim-encrypt-dlfunc-genkeys` utility is for. Simply run it once to generate a pair in the current directory. Be
aware that every invocation will overwrite the previous key pair file without confirmation! Make sure to store your
production keys in a safe place!
```shell
$ ./generate_encryption_keys
$ libexim-encrypt-dlfunc-genkeys
=== Creating cryptobox key pair ===
Wrote »cryptobox_recipient_pk_exim.conf«
Wrote »cryptobox_recipient_pk.raw«
@ -87,11 +112,13 @@ $ ./generate_encryption_keys
```
The `*_exim.conf` files contain the keys in a format that can simply be used in
`exim.conf` (the first line contains the key as a C code comment and can usually be discarded):
`exim.conf` (the first line contains the key as a C code comment and can usually be ignored or discarded):
```shell
$ cat cryptobox_recipient_pk_exim.conf
# const unsigned char cryptobox_recipient_pk[32] = { 0xda, 0x46, 0xc8, 0x75, 0x2b, 0x31, 0xd9, 0x0c, 0x83, 0x54, 0x2d, 0x18, 0xda, 0xdc, 0xe5, 0x2d, 0x0e, 0x10, 0xe8, 0x0c, 0x39, 0xde, 0xaf, 0x30, 0x7e, 0xab, 0xca, 0x4d, 0xed, 0x26, 0x4d, 0x6e }; const unsigned int cryptobox_recipient_pk_length = 32;
# const unsigned char cryptobox_recipient_pk[32] = { 0xda, 0x46, 0xc8, 0x75, 0x2b, 0x31, 0xd9, 0x0c, 0x83, 0x54, 0x2d,
# 0x18, 0xda, 0xdc, 0xe5, 0x2d, 0x0e, 0x10, 0xe8, 0x0c, 0x39, 0xde, 0xaf, 0x30, 0x7e, 0xab, 0xca, 0x4d, 0xed, 0x26,
# 0x4d, 0x6e }; const unsigned int cryptobox_recipient_pk_length = 32;
CRYPTOBOX_RECIPIENT_PK=2kbIdSsx2QyDVC0Y2tzlLQ4Q6Aw53q8wfqvKTe0mTW4=
```
@ -100,18 +127,19 @@ generated as convenience when writing your own tools.
### Example: remove `X-Originating-IP:` header
This example's use case was the initial reason to develop this library: remove the X-Originating-IP header to preserve
our user's privacy but also keep the information in the final e-mail to enable response to complaints and abuse (the
original header is usually provided in these cases). Add this snippet to your DATA ACL section in exim:
<a name="xoip-example"></a>
This task was the initial reason to develop this library: remove the X-Originating-IP header to preserve our user's
privacy but also keep the information in the final e-mail to enable response to complaints and abuse (the original
header is usually provided in these cases). Add this snippet to your DATA ACL section in exim:
```
warn log_message = Removing X-Originating-IP: header
condition = ${if def:h_X-originating-IP: {1}{0}}
add_header = X-Orig-IP-PKK: ${dlfunc{/usr/local/lib/libexim-encrypt-dlfunc.so} \
add_header = X-Orig-IP-PKK: ${dlfunc{/usr/lib/x86_64-linux-gnu/libexim-encrypt-dlfunc.so} \
{sodium_crypto_box_seal} \
{ktp1OEEItrgvSfpVTtu+ybyNjzuuN8OzCdfrGAJt4j8=} \
{$h_X-originating-IP:}}
add_header = X-Orig-IP-Pass: ${dlfunc{/usr/local/lib/libexim-encrypt-dlfunc.so} \
add_header = X-Orig-IP-Pass: ${dlfunc{/usr/lib/x86_64-linux-gnu/libexim-encrypt-dlfunc.so} \
{sodium_crypto_secretbox_encrypt_password} \
{Insert your password here} \
{$h_X-originating-IP:}}
@ -120,3 +148,55 @@ warn log_message = Removing X-Originating-IP: header
```
Pick one of the `add_header` lines depending on which kind of encryption you want.
### Decryption Tools
Two additional programs are included:
* `libexim-encrypt-dlfunc-decrypt-secretbox`
* `libexim-encrypt-dlfunc-decrypt-sealedbox`
They can decrypt strings that were encrypted by the two respective functions. Please refer to their `--help` message
(reproduced below) for usage information and to both [test](src/test_libexim-encrypt-dlfunc-decrypt-secretbox.sh)
[scripts](src/test_libexim-encrypt-dlfunc-decrypt-sealedbox.sh) for usage examples.
```shell
$ libexim-encrypt-dlfunc-decrypt-secretbox -h
Usage: libexim-encrypt-dlfunc-decrypt-secretbox [OPTIONS] [CIPHERTEXT]
Password:
-p, --password PASSWORD Decrypt using PASSWORD
If the environment variable LIBEXIM_PASSWORD is set the password is read from there.
Setting a password with -p/--password overwrites this mechanism.
Select input:
-f, --infile FILE Decrypt contents of the first line of file FILE (use - for stdin)
Output:
-n, --no-newline Do not append a newline to the output
Password and ciphertext are expected to be base64-encoded (as produced by the library).
```
```shell
$ libexim-encrypt-dlfunc-decrypt-sealedbox -h
Usage: libexim-encrypt-dlfunc-decrypt-sealedbox [OPTIONS] [CIPHERTEXT]
Secret and public key:
-s, --secret-key SECRETKEY Secret key (base64-encoded)
-p, --public-key PUBLICKEY Public key (base64-encoded)
-S, --secret-key-file FILE Read secret key (raw) from file FILE (use - for stdin)
-P, --public-key-file FILE Read public key (raw) from file FILE (use - for stdin)
The environment variables LIBEXIM_SECRETKEY and LIBEXIM_PUBLICKEY may contain base64-encoded secret/public keys.
Select input:
-f, --infile FILE Decrypt contents of the first line of file FILE (use - for stdin)
Output:
-n, --no-newline Do not append a newline to the output
Keys in arguments and environment variables are expected to be base64 encoded (as produced by the library).
Keys in files need to be raw bytes with no encoding, ciphertext should always be base64-encoded.
```

40
ci_container/Dockerfile Normal file
View File

@ -0,0 +1,40 @@
FROM debian:latest
LABEL org.opencontainers.image.created=${BUILDTIMESTAMP} \
org.opencontainers.image.authors="Heiko Reese <reese@kit.edu>" \
org.opencontainers.image.title="exim build container" \
org.opencontainers.image.description="A buld environment for exim-encrypt-dlfunc" \
org.opencontainers.image.source="https://git.scc.kit.edu/mail/exim-encrypt-dlfunc/" \
org.opencontainers.image.licenses="Apache-2.0"
RUN apt-get update; \
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get install --no-install-recommends -y \
build-essential \
exim4-daemon-heavy \
exim4-dev \
gcovr \
git \
libsodium-dev \
openssl \
pkg-config \
python3-pip \
libxml2-utils \
debhelper \
debsigs \
devscripts \
dh-make \
git-buildpackage \
gpgv1 \
meson \
less \
mc \
rsync \
openssh-client \
vim; \
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get clean; \
rm -rf /var/lib/apt/lists/*;
RUN pip3 install --upgrade meson ninja; \
rm -rf ~/.cache/pip/*;
WORKDIR /mnt/

View File

@ -0,0 +1,40 @@
FROM debian:latest
LABEL org.opencontainers.image.created=${BUILDTIMESTAMP} \
org.opencontainers.image.authors="Heiko Reese <reese@kit.edu>" \
org.opencontainers.image.title="exim build container" \
org.opencontainers.image.description="A buld environment for exim-encrypt-dlfunc" \
org.opencontainers.image.source="https://git.scc.kit.edu/mail/exim-encrypt-dlfunc/" \
org.opencontainers.image.licenses="Apache-2.0"
RUN apt-get update; \
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get install --no-install-recommends -y \
build-essential \
exim4-daemon-heavy \
exim4-dev \
gcovr \
git \
libsodium-dev \
openssl \
pkg-config \
python3-pip \
libxml2-utils \
debhelper \
debsigs \
devscripts \
dh-make \
git-buildpackage \
gpgv1 \
meson \
less \
mc \
rsync \
openssh-client \
vim; \
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get clean; \
rm -rf /var/lib/apt/lists/*;
RUN pip3 install --upgrade meson ninja; \
rm -rf ~/.cache/pip/*;
WORKDIR /mnt/

View File

@ -0,0 +1,41 @@
FROM docker.io/library/ubuntu:focal
LABEL org.opencontainers.image.created=${BUILDTIMESTAMP} \
org.opencontainers.image.authors="Heiko Reese <reese@kit.edu>" \
org.opencontainers.image.title="exim build container" \
org.opencontainers.image.description="A buld environment for exim-encrypt-dlfunc" \
org.opencontainers.image.source="https://git.scc.kit.edu/mail/exim-encrypt-dlfunc/" \
org.opencontainers.image.licenses="Apache-2.0"
RUN apt-get update; \
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get install --no-install-recommends -y \
build-essential \
exim4-daemon-heavy \
exim4-dev \
gcovr \
git \
libsodium-dev \
openssl \
pkg-config \
python3-pip \
libxml2-utils \
debhelper \
debsigs \
devscripts \
dh-make \
git-buildpackage \
gpgv1 \
meson \
less \
mc \
rsync \
openssh-client \
vim; \
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get clean; \
rm -rf /var/lib/apt/lists/*;
RUN pip3 install --upgrade meson ninja; \
rm -rf ~/.cache/pip/*;
WORKDIR /mnt/

View File

@ -0,0 +1,41 @@
FROM docker.io/library/ubuntu:jammy
LABEL org.opencontainers.image.created=${BUILDTIMESTAMP} \
org.opencontainers.image.authors="Heiko Reese <reese@kit.edu>" \
org.opencontainers.image.title="exim build container" \
org.opencontainers.image.description="A buld environment for exim-encrypt-dlfunc" \
org.opencontainers.image.source="https://git.scc.kit.edu/mail/exim-encrypt-dlfunc/" \
org.opencontainers.image.licenses="Apache-2.0"
RUN apt-get update; \
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get install --no-install-recommends -y \
build-essential \
exim4-daemon-heavy \
exim4-dev \
gcovr \
git \
libsodium-dev \
openssl \
pkg-config \
python3-pip \
libxml2-utils \
debhelper \
debsigs \
devscripts \
dh-make \
git-buildpackage \
gpgv1 \
meson \
less \
mc \
rsync \
openssh-client \
vim; \
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get clean; \
rm -rf /var/lib/apt/lists/*;
RUN pip3 install --upgrade meson ninja; \
rm -rf ~/.cache/pip/*;
WORKDIR /mnt/

17
ci_container/README.md Normal file
View File

@ -0,0 +1,17 @@
# How to build and use these images
## Prerequisites
* [buildah](https://buildah.io/)
* [podman](https://podman.io/)
## Build and upload
Run `build.sh` with the required parameters:
* `-r`: Registry to upload to; set to `none` to skip uploading
* `-u`: Username for registry (optional if previous cached login exists)
* `-p`: Passwort for registry (optional if previous cached login exists)
* `-b`: Image basename (optional; defaults to `exim-encrypt-dlfunc-build`)
* `-t`: Image tag (optional, defaults to `latest`)

76
ci_container/build.sh Executable file
View File

@ -0,0 +1,76 @@
#!/bin/bash
# shellcheck disable=SC1004
set -e
images=('debian:buster|buster' 'debian:bullseye|bullseye' 'ubuntu:focal|focal')
BASENAME='exim-encrypt-dlfunc-build'
TAG='latest'
REGISTRY='localhost:5000'
USERNAME='nobody'
PASSWORD='password'
while getopts "r:u:p:b:t:" OPTION; do
case $OPTION in
r)
REGISTRY="${OPTARG}"
;;
u)
USERNAME="${OPTARG}"
;;
p)
PASSWORD="${OPTARG}"
;;
b)
BASENAME="${OPTARG}"
;;
t)
TAG="${OPTARG}"
;;
*)
echo "Usage: $0 [OPTIONS]"
echo
echo "Options:"
echo " -r <registry> (default: \"${REGISTRY}\")"
echo " -u <username> (default: \"${USERNAME}\")"
echo " -p <password> (default: \"${PASSWORD}\")"
echo " -b <basename> (default: \"${BASENAME}\")"
echo " -t <tag> (default: \"${TAG}\")"
echo
exit 127
esac
done
REGHOST="$(echo "${REGISTRY}" | cut -d/ -f1)"
if [ "${REGISTRY}" != "none" ]; then
echo "🔑 Logging into »${REGHOST}«"
if ! buildah login --get-login "${REGHOST}" > /dev/null 2> /dev/null; then
buildah login --password "${PASSWORD}" --username "${USERNAME}" "${REGHOST}"
fi
fi
for i in "${images[@]}"; do
basectr=$(echo "${i}" | cut -d'|' -f1)
name=$(echo "${i}" | cut -d'|' -f2)
IMAGENAME="${BASENAME}-${name}"
IIDFILE=".${IMAGENAME}.iid"
IMAGETAG="${BASENAME}-${name}:${TAG}"
TARGET="${REGISTRY}/${IMAGETAG}"
echo "🔨 Assembling »${IMAGENAME}«"
buildah build-using-dockerfile \
--from "${basectr}" \
--format docker \
--iidfile "${IIDFILE}" \
--tag "${IMAGETAG}"
--build-arg BUILDTIMESTAMP="`date --iso-8601=seconds`"
if [ "${REGISTRY}" != "none" ]; then
echo "🚀 Pushing »${IMAGETAG}« to »${TARGET}«"
buildah push "${IMAGETAG}" "${TARGET}"
echo "✅ Finished »${BASENAME}«"
fi
rm -f "${IIDFILE}"
done

5
debian/changelog vendored Normal file
View File

@ -0,0 +1,5 @@
exim-encrypt-dlfunc (0.2.0) unstable; urgency=medium
* Initial Release.
-- Heiko Reese <heiko.reese@kit.edu> Sun, 22 Aug 2021 20:00:57 +0000

19
debian/control vendored Normal file
View File

@ -0,0 +1,19 @@
Source: exim-encrypt-dlfunc
Priority: optional
Maintainer: Heiko Reese <heiko.reese@kit.edu>
Build-Depends: debhelper-compat (= 12), build-essential, exim4-dev, libsodium-dev, meson, pkg-config, openssl, python3-pip
Standards-Version: 4.5.1
Section: libs
Homepage: https://git.scc.kit.edu/mail/exim-encrypt-dlfunc
Vcs-Browser: https://git.scc.kit.edu/mail/exim-encrypt-dlfunc
Vcs-Git: https://git.scc.kit.edu/mail/exim-encrypt-dlfunc.git
Rules-Requires-Root: no
Package: exim-encrypt-dlfunc
Architecture: any
Multi-Arch: same
Depends: ${shlibs:Depends}, ${misc:Depends}, exim4-daemon-heavy
Description: String encryption library for exim4
This library provides functions to encrypt and decrypt strings within exim4
using either passwords or public/private key pairs. All cryptographic
functionality is provides by libsodium.

29
debian/copyright vendored Normal file
View File

@ -0,0 +1,29 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: exim-encrypt-dlfunc
Upstream-Contact: Heiko Reese <heiko.reese@kit.edu>
Source: https://git.scc.kit.edu/mail/exim-encrypt-dlfunc
Files: *
Copyright: 2021 Heiko Reese <heiko.reese@kit.edu>
License: Apache-2.0
Files: debian/*
Copyright: 2021 Heiko Reese <heiko.reese@kit.edu>
License: Apache-2.0
License: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
https://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian systems, the complete text of the Apache version 2.0 license
can be found in "/usr/share/common-licenses/Apache-2.0".

24
debian/rules vendored Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable)
# output every command that modifies files on the build system.
export DH_VERBOSE = 1
# see FEATURE AREAS in dpkg-buildflags(1)
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# see ENVIRONMENT in dpkg-buildflags(1)
# package maintainers to append CFLAGS
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# package maintainers to append LDFLAGS
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
%:
dh $@
# dh_make generated override targets
# This is example for Cmake (See https://bugs.debian.org/641051 )
#override_dh_auto_configure:
# dh_auto_configure -- \
# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

86
src/common.c Normal file
View File

@ -0,0 +1,86 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sodium.h>
#include "common.h"
char *read_first_line(const char *filename) {
FILE *stream;
char *cipherstring;
size_t len = 0;
ssize_t nread;
bool input_is_stdin = false;
// open file (use stdin for '-')
if (!strncmp(filename, "-", 1)) {
stream = stdin;
input_is_stdin = true;
} else {
stream = fopen(filename, "r");
if (stream == NULL) {
fprintf(stderr, "[ERROR] Error opening file %s\n\n", filename);
exit(EXIT_FAILURE);
}
}
nread = getline(&cipherstring, &len, stream);
if (nread == -1 && !feof(stream)) {
perror("getline: ");
}
if (input_is_stdin == false) {
fclose(stream);
}
// remove trailing newline
cipherstring[strcspn(cipherstring, "\r\n")] = 0;
return cipherstring;
}
char *read_password_file(const char *filename, size_t keysize, size_t *length) {
FILE *stream;
char *contents;
ssize_t nread;
bool input_is_stdin = false;
contents = malloc(keysize + 1);
sodium_memzero(contents, keysize + 1);
// open file (use stdin for '-')
if (!strncmp(filename, "-", 1)) {
stream = stdin;
input_is_stdin = true;
} else {
stream = fopen(filename, "r");
if (stream == NULL) {
fprintf(stderr, "[ERROR] Error opening file %s\n\n", filename);
exit(EXIT_FAILURE);
}
}
nread = fread(contents, sizeof(char), keysize, stream);
if (nread < 0) {
fprintf(stderr, "[ERROR] reading from %s failed\n\n", filename);
exit(EXIT_FAILURE);
} else {
*length = (size_t) nread;
}
if (input_is_stdin == false) {
fclose(stream);
}
return contents;
}
int base64_decode_string(const char *input, unsigned char **outstring, size_t *outlen) {
size_t input_len = strlen(input);
size_t outmaxlen = input_len / 4 * 3;
*outstring = malloc(outmaxlen * sizeof(unsigned char));
return sodium_base642bin(*outstring, outmaxlen, (const char *) input, input_len,
NULL, outlen, NULL, sodium_base64_VARIANT_ORIGINAL);
}

14
src/common.h Normal file
View File

@ -0,0 +1,14 @@
//
// Created by sprawl on 08/09/2021.
//
#ifndef EXIM_ENCRYPT_DLFUNC_COMMON_H
#define EXIM_ENCRYPT_DLFUNC_COMMON_H
char *read_first_line(const char *filename);
char *read_password_file(const char *filename, size_t keysize, size_t *length);
int base64_decode_string(const char *input, unsigned char **outstring, size_t *outlen);
#endif //EXIM_ENCRYPT_DLFUNC_COMMON_H

View File

@ -20,7 +20,7 @@ char *string2hex(unsigned char *input, size_t length) {
* 1. Add this code to the first “breakpoint”:
* log_write(0, LOG_MAIN, "pid: %d", getpid()); int busywait = 0; while (busywait == 0) {}
* 2. Compile.
* 3. Run “exim -be […]” to call the lib; see simple_exim_test.sh for details.
* 3. Run “exim -be […]” to call the lib; see test_libexim-encrypt-dlfunc.sh for details.
* 4. Read exim pid from log output. Attach to the looping exim process with “gdb -p PID”
* 5. Prepare breakpoints, watches, etc. Set busywait to 1 and continue.
*/

View File

@ -0,0 +1,207 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sodium.h>
#include <stdbool.h>
#include "common.c"
#define ENVVAR_SK_NAME "LIBEXIM_SECRETKEY"
#define ENVVAR_PK_NAME "LIBEXIM_PUBLICKEY"
void print_usage(char *progname) {
printf("Usage: %s [OPTIONS] [CIPHERTEXT]\n\n", progname);
printf("Secret and public key:\n");
printf(" -s, --secret-key SECRETKEY Secret key (base64-encoded)\n");
printf(" -p, --public-key PUBLICKEY Public key (base64-encoded)\n");
printf(" -S, --secret-key-file FILE Read secret key (raw) from file FILE (use - for stdin)\n");
printf(" -P, --public-key-file FILE Read public key (raw) from file FILE (use - for stdin)\n");
printf("\n");
printf("The environment variables %s and %s may contain base64-encoded secret/public keys. \n", ENVVAR_SK_NAME,
ENVVAR_PK_NAME);
printf("\n");
printf("Select input:\n");
printf(" -f, --infile FILE Decrypt contents of the first line of file FILE (use - for stdin)\n");
printf("\n");
printf("Output:\n");
printf(" -n, --no-newline Do not append a newline to the output\n");
printf("\n");
printf("Keys in arguments and environment variables are expected to be base64 encoded (as produced by the library).\n");
printf("Keys in files need to be raw bytes with no encoding, ciphertext should always be base64-encoded.\n");
printf("\n");
}
typedef enum {
NONE = 0,
SK = 1,
PK = 2,
SKENV = 4,
PKENV = 8,
SKFILE = 16,
PKFILE = 32,
INFILE = 64,
INSTRING = 128
} seen_sb_args;
int main(int argc, char *argv[]) {
char *prog_basename = basename(argv[0]);
int opt;
unsigned char *secretkey, *publickey;
size_t secretkey_len = 0, publickey_len = 0;
char *b64cipherstring;
unsigned char *cipherstring;
size_t cipherstring_len;
bool add_newline = true;
seen_sb_args publickey_mode = NONE;
seen_sb_args secretkey_mode = NONE;
seen_sb_args input = NONE;
if (sodium_init() < 0) {
fputs("Unable to initialize libsodium", stderr);
exit(128);
}
// define arguments
const char *shortargs = "s:p:S:P:f:nh";
static struct option long_options[] = {
{"secret-key", required_argument, NULL, 's'},
{"public-key", required_argument, NULL, 'p'},
{"secret-key-file", required_argument, NULL, 'S'},
{"public-key-file", required_argument, NULL, 'P'},
{"infile", required_argument, NULL, 'f'},
{"no-newline", required_argument, NULL, 'n'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
// check environment for LIBEXIM_SECRETKEY variable
char *sk_env = getenv(ENVVAR_SK_NAME);
if (sk_env != NULL && strlen(sk_env) > 0) {
if (base64_decode_string(sk_env, &secretkey, &secretkey_len) != 0) {
fprintf(stderr, "[ERROR] Unable to base64-decode secret key.\n\n");
exit(EXIT_FAILURE);
}
secretkey_mode |= SKENV;
}
// check environment for LIBEXIM_PUBLICKEY variable
char *pk_env = getenv(ENVVAR_PK_NAME);
if (pk_env != NULL && strlen(pk_env) > 0) {
if (base64_decode_string(pk_env, &publickey, &publickey_len) != 0) {
fprintf(stderr, "[ERROR] Unable to base64-decode public key.\n\n");
exit(EXIT_FAILURE);
}
publickey_mode |= PKENV;
}
// parse arguments
int long_index = 0;
while ((opt = getopt_long(argc, argv, shortargs,
long_options, &long_index)) != -1) {
switch (opt) {
case 's':
if (base64_decode_string(optarg, &secretkey, &secretkey_len) != 0) {
fprintf(stderr, "[ERROR] Unable to base64-decode secret key.\n\n");
exit(EXIT_FAILURE);
}
secretkey_mode |= SK;
break;
case 'p':
if (base64_decode_string(optarg, &publickey, &publickey_len) != 0) {
fprintf(stderr, "[ERROR] Unable to base64-decode public key.\n\n");
exit(EXIT_FAILURE);
}
publickey_mode |= PK;
break;
case 'S':
secretkey = (unsigned char *) read_password_file(optarg, crypto_box_SECRETKEYBYTES, &secretkey_len);
secretkey_mode |= SKFILE;
break;
case 'P':
publickey = (unsigned char *) read_password_file(optarg, crypto_box_PUBLICKEYBYTES, &publickey_len);
publickey_mode |= PKFILE;
break;
case 'f':
b64cipherstring = read_first_line(optarg);
input |= INFILE;
break;
case 'n':
add_newline = false;
break;
case 'h':
print_usage(prog_basename);
exit(EXIT_SUCCESS);
}
}
// read first non-option argument as ciphertext if present
if (optind < argc) {
size_t b64cipherstring_len = strlen(argv[optind]);
b64cipherstring = malloc(b64cipherstring_len + 1);
sodium_memzero(b64cipherstring, b64cipherstring_len + 1);
strncpy(b64cipherstring, argv[optind], b64cipherstring_len);
input |= INSTRING;
}
// check if a secret key was provided
if (secretkey_mode == NONE) {
fprintf(stderr, "[ERROR] Please specify a secret key.\n\n");
print_usage(prog_basename);
exit(EXIT_FAILURE);
}
// check if the secret key has the correct size
if (secretkey_len != crypto_box_SECRETKEYBYTES) {
fprintf(stderr, "[ERROR] Secret key has wrong size %zu; expected %d.\n\n", secretkey_len,
crypto_box_SECRETKEYBYTES);
exit(EXIT_FAILURE);
}
// check if a public key was provided
if (publickey_mode == NONE) {
fprintf(stderr, "[ERROR] Please specify a public key.\n\n");
print_usage(prog_basename);
exit(EXIT_FAILURE);
}
// check if the public key has the correct size
if (publickey_len != crypto_box_PUBLICKEYBYTES) {
fprintf(stderr, "[ERROR] Secret key has wrong size %zu; expected %d.\n\n", publickey_len,
crypto_box_PUBLICKEYBYTES);
exit(EXIT_FAILURE);
}
// check if a ciphertext was provided
if (input == NONE) {
fprintf(stderr, "[ERROR] Please specify a ciphertext source.\n\n");
print_usage(prog_basename);
exit(EXIT_FAILURE);
}
// base64-decode ciphertext
if (base64_decode_string(b64cipherstring, &cipherstring, &cipherstring_len) != 0) {
fprintf(stderr, "[ERROR] Unable to base64-decode ciphertext.\n\n");
exit(EXIT_FAILURE);
}
free(b64cipherstring);
// prepare buffer for cleartext
size_t cleartext_len = cipherstring_len - crypto_box_SEALBYTES;
unsigned char *cleartext = (unsigned char *) malloc(cleartext_len + 1);
sodium_memzero(cleartext, cleartext_len + 1);
// decrypt message
if (crypto_box_seal_open(cleartext, cipherstring, cipherstring_len, publickey, secretkey) != 0) {
fprintf(stderr, "[ERROR] Unable to decrypt ciphertext.\n\n");
exit(EXIT_FAILURE);
}
// print cleartext to stdout
if (add_newline == true) {
fprintf(stdout, "%s\n", (const char *) cleartext);
} else {
fprintf(stdout, "%s", (const char *) cleartext);
}
free(cleartext);
exit(EXIT_SUCCESS);
}

View File

@ -0,0 +1,163 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sodium.h>
#include <stdbool.h>
#include "common.c"
#define ENVVAR_PASSWORD_NAME "LIBEXIM_PASSWORD"
void print_usage(char *progname) {
printf("Usage: %s [OPTIONS] [CIPHERTEXT]\n\n", progname);
printf("Password:\n");
printf(" -p, --password PASSWORD Decrypt using PASSWORD\n");
printf("\n");
printf(" If the environment variable LIBEXIM_PASSWORD is set the password is read from there.\n");
printf(" Setting a password with -p/--password overwrites this mechanism.\n");
printf("\n");
printf("Select input:\n");
printf(" -f, --infile FILE Decrypt contents of the first line of file FILE (use - for stdin)\n");
printf("\n");
printf("Output:\n");
printf(" -n, --no-newline Do not append a newline to the output\n");
printf("\n");
printf("Password and ciphertext are expected to be base64-encoded (as produced by the library).\n");
printf("\n");
}
typedef enum {
NONE = 0,
PASSARG = 1,
PASSENV = 2,
INSTRING = 4,
INFILE = 8
} seen_args;
int main(int argc, char *argv[]) {
char *prog_basename = basename(argv[0]);
int opt;
char *cipherstring;
size_t password_len;
char *password;
char *password_env;
bool add_newline = true;
seen_args mode = NONE;
seen_args input = NONE;
if (sodium_init() < 0) {
fputs("Unable to initialize libsodium", stderr);
exit(128);
}
// define arguments
const char *shortargs = "p:f:nh";
static struct option long_options[] = {
{"password", required_argument, NULL, 'p'},
{"infile", required_argument, NULL, 'f'},
{"no-newline", no_argument, NULL, 'n'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
// check environment for LIBEXIM_PASSWORD
password_env = getenv(ENVVAR_PASSWORD_NAME);
if (password_env != NULL && strlen(password_env) > 0) {
password = password_env;
password_len = strlen(password);
mode |= PASSENV;
}
// parse arguments
int long_index = 0;
while ((opt = getopt_long(argc, argv, shortargs,
long_options, &long_index)) != -1) {
switch (opt) {
case 'p':
password_len = strlen((const char *) optarg);
password = malloc(password_len + 1);
strncpy(password, optarg, password_len);
mode |= PASSARG;
break;
case 'f':
cipherstring = read_first_line(optarg);
input |= INFILE;
break;
case 'n':
add_newline = false;
break;
case 'h':
print_usage(prog_basename);
exit(EXIT_SUCCESS);
}
}
// read first non-option argument as ciphertext if present
if (optind < argc) {
size_t cipherstring_len = strlen(argv[optind]) + 1;
cipherstring = malloc(cipherstring_len + 1);
strncpy(cipherstring, argv[optind], cipherstring_len);
input |= INSTRING;
}
// check if a password was provided
if (mode == NONE) {
fprintf(stderr, "[ERROR] Please specify a password.\n\n");
print_usage(prog_basename);
exit(EXIT_FAILURE);
}
// fail if neither argument nor filename for ciphertext is present
if (input == NONE) {
fprintf(stderr, "[ERROR] Please specify a ciphertext source.\n\n");
print_usage(prog_basename);
exit(EXIT_FAILURE);
}
// Derive key from the password.
unsigned char keybytes[crypto_secretbox_KEYBYTES];
sodium_memzero(keybytes, crypto_secretbox_KEYBYTES);
crypto_generichash(keybytes, crypto_secretbox_KEYBYTES,
(unsigned char *) password, password_len, NULL, 0);
// base64-decode input
unsigned char *ciphertext;
size_t ciphertext_len;
if (base64_decode_string(cipherstring, &ciphertext, &ciphertext_len) != 0) {
fprintf(stderr, "[ERROR] Unable to base64-decode ciphertext.\n\n");
exit(EXIT_FAILURE);
}
// extract nonce
unsigned char nonce[crypto_secretbox_NONCEBYTES];
memcpy(nonce, ciphertext, crypto_secretbox_NONCEBYTES);
// prepare buffer for cleartext
if (ciphertext_len < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES) {
fprintf(stderr, "[ERROR] Ciphertext is too small to contain any data.\n\n");
exit(EXIT_FAILURE);
}
size_t cleartext_len = ciphertext_len - crypto_secretbox_NONCEBYTES - crypto_secretbox_MACBYTES;
unsigned char *cleartext = (unsigned char *) malloc(cleartext_len + 1);
sodium_memzero(cleartext, cleartext_len + 1);
// decrypt
if (crypto_secretbox_open_easy(cleartext, &ciphertext[crypto_secretbox_NONCEBYTES],
ciphertext_len - crypto_secretbox_NONCEBYTES, nonce, keybytes) == 0) {
} else {
fprintf(stderr, "[ERROR] Unable to decrypt message.\n\n");
exit(EXIT_FAILURE);
}
// print cleartext to stdout
if (add_newline == true) {
fprintf(stdout, "%s\n", (const char *) cleartext);
} else {
fprintf(stdout, "%s", (const char *) cleartext);
}
free(cleartext);
exit(EXIT_SUCCESS);
}

View File

@ -2,38 +2,42 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sodium.h>
bool key_contains_zero(unsigned char *key, unsigned int keylen)
{
bool has_zero = false;
for (unsigned int i = 0; i < keylen; i++) {
if (key[i] == 0) {
has_zero = true;
}
}
return has_zero;
}
//void
//dump_key_as_c_code(FILE * f, const char *name, unsigned char *key,
// unsigned int keylen)
//{
// fprintf(f, "const unsigned char %s[] = { ", name);
// for (unsigned int i = 0; i < keylen; i++) {
// fprintf(f, "0x%02x", key[i]);
// if (i < keylen - 1) {
// fprintf(f, ", ");
// }
// }
// fprintf(f, " };\n");
// fprintf(f, "const unsigned int %s_length = %d;\n", name, keylen);
//}
void
dump_key_as_c_code(FILE * f, const char *name, unsigned char *key,
unsigned int keylen)
{
fprintf(f, "const unsigned char %s[] = { ", name);
for (unsigned int i = 0; i < keylen; i++) {
fprintf(f, "0x%02x", key[i]);
if (i < keylen - 1) {
fprintf(f, ", ");
}
}
fprintf(f, " };\n");
fprintf(f, "const unsigned int %s_length = %d;\n", name, keylen);
}
//void create_secretbox_key(const char *filebase, const char *varname) {
// unsigned char key[crypto_secretbox_KEYBYTES];
//
// crypto_secretbox_keygen(key);
//
// char key_filename[4096];
// char key_varname[4096];
//
// sprintf(key_filename, "%s_secretbox", filebase);
// sprintf(key_varname, "%s_key", varname);
//
// write_key_files(key_filename, key_varname, key, crypto_secretbox_KEYBYTES);
//}
void
dump_key_as_exim_config(FILE *f, const char *name, unsigned char *key,
unsigned int keylen)
{
unsigned int keylen) {
// write a comment with C variable declaration
fprintf(f, "# const unsigned char %s[%d] = { ", name, keylen);
for (unsigned int i = 0; i < keylen; i++) {
@ -75,6 +79,9 @@ write_key_files(const char *filebase, const char *varname,
sprintf(exim_filename, "%s_exim.conf", filebase);
sprintf(raw_filename, "%s.raw", filebase);
// set restrictive umask (access to user only)
mode_t original_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
// open exim config snippet file
f = fopen(exim_filename, "w+");
if (f == NULL) {
@ -101,6 +108,8 @@ write_key_files(const char *filebase, const char *varname,
// close raw file
fclose(f);
// restore original umask
umask(original_umask);
}
void create_cryptobox_keys(const char *filebase, const char *varname)
@ -127,20 +136,6 @@ void create_cryptobox_keys(const char *filebase, const char *varname)
}
void create_secretbox_key(const char *filebase, const char *varname) {
unsigned char key[crypto_secretbox_KEYBYTES];
crypto_secretbox_keygen(key);
char key_filename[4096];
char key_varname[4096];
sprintf(key_filename, "%s_secretbox", filebase);
sprintf(key_varname, "%s_key", varname);
write_key_files(key_filename, key_varname, key, crypto_secretbox_KEYBYTES);
}
int main(void) {
if (sodium_init() < 0) {
fputs("Unable to initialize libsodium", stderr);

View File

@ -64,7 +64,7 @@ int sodium_crypto_secretbox_encrypt_password(uschar **yield, int argc, uschar *a
/*
* Derive a key from the password using a generic hash.
* This operations needs to be fast (exim holds no state, this might be called multiple times per email).
* This operation needs to be fast (exim holds no state, this might be called multiple times per email).
* Collisions avoidance or brute force attacks are not a concern here.
*/
unsigned char keybytes[crypto_secretbox_KEYBYTES];

View File

@ -1,6 +1,14 @@
configure_file(output: 'config.h', configuration: conf_data)
executable('generate_encryption_keys', 'generate_encryption_keys.c',
executable('libexim-encrypt-dlfunc-genkeys', 'libexim-encrypt-dlfunc-genkeys.c',
dependencies : [ sodium_deps ],
install: true)
executable('libexim-encrypt-dlfunc-decrypt-sealedbox', 'libexim-encrypt-dlfunc-decrypt-sealedbox.c',
dependencies : [ sodium_deps ],
install: true)
executable('libexim-encrypt-dlfunc-decrypt-secretbox', 'libexim-encrypt-dlfunc-decrypt-secretbox.c',
dependencies : [ sodium_deps ],
install: true)
@ -8,7 +16,14 @@ shared_library('exim-encrypt-dlfunc', 'libexim-encrypt-dlfunc.c',
dependencies : [ sodium_deps ],
install: true)
test('libexim-encrypt-dlfunc',
find_program('test_libexim-encrypt-dlfunc.sh'),
protocol: 'tap')
simple_exim_test = find_program('simple_exim_test.sh')
test('simple test', simple_exim_test)
test('decrypt-secretbox',
find_program('test_libexim-encrypt-dlfunc-decrypt-secretbox.sh'),
protocol: 'tap')
test('decrypt-sealedbox',
find_program('test_libexim-encrypt-dlfunc-decrypt-sealedbox.sh'),
protocol: 'tap')

View File

@ -1,37 +0,0 @@
#!/bin/bash
set -e
PATH=/sbin:/usr/sbin:$PATH
# copy to /tmp to keep call to exim under 256 chars (prevent problems on Ubuntu)
install -t /tmp src/libexim-encrypt-dlfunc.so
LIB=/tmp/libexim-encrypt-dlfunc.so
CLEARTEXT="127.88.99.23" # keep short; see above
PASSWORD="`openssl rand -base64 32`"
CIPHERTEXT=$(exim -be "\${dlfunc{${LIB}}{sodium_crypto_secretbox_encrypt_password}{${PASSWORD}}{${CLEARTEXT}}}")
DECRYPTED=$(exim -be "\${dlfunc{${LIB}}{sodium_crypto_secretbox_decrypt_password}{${PASSWORD}}{${CIPHERTEXT}}}")
if [ "${CLEARTEXT}" == "${DECRYPTED}" ] ; then
echo "secretbox test successful"
else
echo "secretbox test unsuccessful"
exit 127
fi
# { 0xb6, 0x01, 0x45, 0x20, 0x9f, 0x55, 0x06, 0x74, 0x29, 0x71, 0x7b, 0x5e, 0xa9, 0x68, 0x60, 0x5e, 0x81, 0x1a, 0x54, 0x6b, 0xc9, 0x80, 0x97, 0x78, 0x41, 0xc6, 0x20, 0xae, 0x66, 0x9f, 0xd9, 0x53 };
PK="tgFFIJ9VBnQpcXteqWhgXoEaVGvJgJd4QcYgrmaf2VM="
# { 0x95, 0x8d, 0x45, 0xef, 0x45, 0x6a, 0xc1, 0xef, 0xae, 0x0a, 0x7e, 0x1c, 0xcc, 0x67, 0x70, 0xc8, 0x67, 0x6b, 0xd1, 0x62, 0xd4, 0x59, 0xd9, 0x23, 0xfc, 0x6a, 0xb7, 0xf6, 0x6d, 0xa4, 0xdc, 0xfd };
SK="lY1F70Vqwe+uCn4czGdwyGdr0WLUWdkj/Gq39m2k3P0="
CIPHERTEXT=$(exim -be "\${dlfunc{${LIB}}{sodium_crypto_box_seal}{${PK}}{${CLEARTEXT}}}")
DECRYPTED=$(exim -be "\${dlfunc{${LIB}}{sodium_crypto_box_seal_open}{${SK}}{${PK}}{${CIPHERTEXT}}}")
if [ "${CLEARTEXT}" == "${DECRYPTED}" ] ; then
echo "sealed_box test successful"
else
echo "sealed_box test unsuccessful"
exit 128
fi

View File

@ -0,0 +1,46 @@
#!/bin/bash
# shellcheck disable=SC2034
# this script implements the TAP protocol (https://testanything.org)
echo 1..3
TEST_PUBLICKEY='z7+GlUWgoiXJ4VK6cdLikCF7M6Mj9i4eXNE6Jh1m9yw='
TEST_SECRETKEY='h5C/V2nzhILRmJ6UZrNK/6G8Xc4KWzLq/Qr8Xj42jus='
TEST_CLEARTEXT='There is nothing in the middle of the road but a yellow stripe and dead armadillos. ~ Jim Hightower'
TEST_CIPHERTEXT='+UgXgarCuX3dUobgt8rRnjnxPNWHpsw98GjLZy8+2m5e/v9K/acMq+0UsFW7lwAZIRqj1F55n78y73Y6XCBEVSt8G6nntV8WuDYlr1BHcBIXNr5toUbE+CxtLoGqfD3c3nw1NkJDO1NYGzK/cG43TEEBLrQJCRLRBXOZmxG6ugFo4FtYl297/B1xNtkd9IR4TY5C'
CIPHERTEXT_FILE="$(mktemp)"
TEST_PUBLICKEY_FILE="$(mktemp)"
TEST_SECRETKEY_FILE="$(mktemp)"
echo -n "${TEST_CIPHERTEXT}" > "${CIPHERTEXT_FILE}"
echo -n "${TEST_PUBLICKEY}" | base64 -d > "${TEST_PUBLICKEY_FILE}"
echo -n "${TEST_SECRETKEY}" | base64 -d > "${TEST_SECRETKEY_FILE}"
cleanup() {
rm -rf "${CIPHERTEXT_FILE}" "${TEST_PUBLICKEY_FILE}" "${TEST_SECRETKEY_FILE}"
}
trap cleanup EXIT INT TERM
export LIBEXIM_PUBLICKEY="${TEST_PUBLICKEY}"
export LIBEXIM_SECRETKEY="${TEST_SECRETKEY}"
DECRYPTED="$(src/libexim-encrypt-dlfunc-decrypt-sealedbox "${TEST_CIPHERTEXT}")"
if [ "${DECRYPTED}" == "${TEST_CLEARTEXT}" ] ; then
echo "ok 1 - decrypt commandline argument with keys from environment successful"
else
echo "not ok 1 - decrypt commandline argument with keys from environment unsuccessful"
fi
export -n LIBEXIM_PUBLICKEY LIBEXIM_SECRETKEY
DECRYPTED="$(src/libexim-encrypt-dlfunc-decrypt-sealedbox --secret-key "${TEST_SECRETKEY}" --public-key "${TEST_PUBLICKEY}" --infile "${CIPHERTEXT_FILE}")"
if [ "${DECRYPTED}" == "${TEST_CLEARTEXT}" ] ; then
echo "ok 2 - decrypt file contents with keys from commandline"
else
echo "not ok 2 - decrypt file contents with keys from commandline"
fi
DECRYPTED="$(src/libexim-encrypt-dlfunc-decrypt-sealedbox --secret-key-file "${TEST_SECRETKEY_FILE}" --public-key-file "${TEST_PUBLICKEY_FILE}" --infile - < "${CIPHERTEXT_FILE}")"
if [ "${DECRYPTED}" == "${TEST_CLEARTEXT}" ] ; then
echo "ok 3 - decrypt stdin contents with keys from files"
else
echo "not ok 3 - decrypt stdin contents with keys from files"
fi

View File

@ -0,0 +1,57 @@
#!/bin/bash
# shellcheck disable=SC2034
# this script implements the TAP protocol (https://testanything.org)
echo 1..5
TEST_PASSWORD='be6rahqu3bukee3Aengohgoopheeyis5'
TEST_CLEARTEXT='The great thing about attackers is that there are so many to choose from! - Daniel J. Bernstein'
TEST_CIPHERTEXT01='K+TOzrbkni7wydNvF1gMRwZWQPNnNIXRG9iQgkFhszBu8ImqIrAK4wWWP02UmclITi8DZbr3sg/EVWurDzAYK+pjkcDAa78glz4qXIqrPbYvEIEHPEExFzCtwi5hqOR+KF7tsqbPvdAOIqwf/2KBomX0GS1I/1CxQMrbJd1VgXc51M4hI0I8'
TEST_CIPHERTEXT02='lAod5UhfW6fQCxd4PSktnrzwyWzcw05Svio5XqPOr/p/Ts4Pr0eEjj2TgmT2K85T2xrxCiqmE/OUcODRldEWeSqBSxx0Z6PzXqOzz5ZL6Iq1tggjihMydGz9mNS4jRF9f52k5t2i7xFrMMCRrfq/rer/ngp1h3pposCds+OmX0u+1f4Urj0b'
CIPHERTEXT_FILE01="$(mktemp)"
CIPHERTEXT_FILE02="$(mktemp)"
echo -n "${TEST_CIPHERTEXT01}" > "${CIPHERTEXT_FILE01}"
echo -n "${TEST_CIPHERTEXT02}" > "${CIPHERTEXT_FILE02}"
cleanup() {
rm -rf "${CIPHERTEXT_FILE01}" "${CIPHERTEXT_FILE02}"
}
trap cleanup EXIT INT TERM
export LIBEXIM_PASSWORD="${TEST_PASSWORD}"
DECRYPTED01="$(src/libexim-encrypt-dlfunc-decrypt-secretbox ${TEST_CIPHERTEXT01})"
if [ "${DECRYPTED01}" == "${TEST_CLEARTEXT}" ] ; then
echo "ok 1 - decrypt commandline argument with password from environment successful"
else
echo "not ok 1 - decrypt commandline argument with password from environment unsuccessful"
fi
DECRYPTED02="$(src/libexim-encrypt-dlfunc-decrypt-secretbox --infile ${CIPHERTEXT_FILE01})"
if [ "${DECRYPTED02}" == "${TEST_CLEARTEXT}" ] ; then
echo "ok 2 - decrypt file contents with password from environment successful"
else
echo "not ok 2 - decrypt file contents with password from environment unsuccessful"
fi
DECRYPTED03="$(echo -n ${TEST_CIPHERTEXT01} | src/libexim-encrypt-dlfunc-decrypt-secretbox --infile -)"
if [ "${DECRYPTED03}" == "${TEST_CLEARTEXT}" ] ; then
echo "ok 3 - decrypt stdin contents with password from environment successful"
else
echo "not ok 3 - decrypt stdin file contents with password from environment unsuccessful"
fi
export -n LIBEXIM_PASSWORD
DECRYPTED04="$(src/libexim-encrypt-dlfunc-decrypt-secretbox -p ${TEST_PASSWORD} ${TEST_CIPHERTEXT02})"
if [ "${DECRYPTED04}" == "${TEST_CLEARTEXT}" ] ; then
echo "ok 4 - decrypt commandline argument with password from commandline successful"
else
echo "not ok 4 - decrypt commandline argument with password from commandline unsuccessful"
fi
DECRYPTED05="$(src/libexim-encrypt-dlfunc-decrypt-secretbox -p ${TEST_PASSWORD} --infile ${CIPHERTEXT_FILE02})"
if [ "${DECRYPTED05}" == "${TEST_CLEARTEXT}" ] ; then
echo "ok 5 - decrypt file contents with password from commandline successful"
else
echo "not ok 5 - decrypt file contents with password from commandline unsuccessful"
fi

View File

@ -0,0 +1,99 @@
#!/bin/bash
# shellcheck disable=SC2164
PATH=/sbin:/usr/sbin:$PATH
# this script implements the TAP protocol (https://testanything.org)
echo 1..6
# copy to /tmp to keep commandline arguments to exim calls under 256 chars (prevent problems on Ubuntu)
install -t /tmp src/libexim-encrypt-dlfunc.so
LIB=/tmp/libexim-encrypt-dlfunc.so
CLEARTEXT="127.88.99.23" # keep short; see above
PASSWORD="$(openssl rand -base64 32)"
CIPHERTEXT=$(exim -C /dev/null -be "\${dlfunc{${LIB}}{sodium_crypto_secretbox_encrypt_password}{${PASSWORD}}{${CLEARTEXT}}}")
DECRYPTED=$(exim -C /dev/null -be "\${dlfunc{${LIB}}{sodium_crypto_secretbox_decrypt_password}{${PASSWORD}}{${CIPHERTEXT}}}")
if [ "${CLEARTEXT}" == "${DECRYPTED}" ] ; then
echo "ok 1 - secretbox test successful"
else
echo "not ok 1 - secretbox test unsuccessful"
fi
PK="tgFFIJ9VBnQpcXteqWhgXoEaVGvJgJd4QcYgrmaf2VM="
SK="lY1F70Vqwe+uCn4czGdwyGdr0WLUWdkj/Gq39m2k3P0="
CIPHERTEXT=$(exim -C /dev/null -be "\${dlfunc{${LIB}}{sodium_crypto_box_seal}{${PK}}{${CLEARTEXT}}}")
DECRYPTED=$(exim -C /dev/null -be "\${dlfunc{${LIB}}{sodium_crypto_box_seal_open}{${SK}}{${PK}}{${CIPHERTEXT}}}")
if [ "${CLEARTEXT}" == "${DECRYPTED}" ] ; then
echo "ok 2 - sealed_box test with pre-generated key pair successful"
else
echo "not ok 2 - sealed_box test with pre-generated key pair unsuccessful"
fi
# skip test on Ubuntu
#[ "$(lsb_release --id --short)" == "Ubuntu" ] && echo "not ok 3 # skip Ubuntu has patches against long commandline arguments, bailing out"
### Test libexim-encrypt-dlfunc-genkeys
TEMPDIR01="$(mktemp --directory --quiet)"
TEMPDIR02="$(mktemp --directory --quiet)"
cleanup() {
rm -rf "${TEMPDIR01}" "${TEMPDIR02}"
}
trap cleanup EXIT INT TERM
CURDIR="$(pwd)"
pushd "${TEMPDIR01}" > /dev/null
"${CURDIR}/src/libexim-encrypt-dlfunc-genkeys" 2> /dev/null # TAP parser seems to hate the output
PK="$(base64 cryptobox_recipient_pk.raw)"
SK="$(base64 cryptobox_recipient_sk.raw)"
popd > /dev/null
CIPHERTEXT=$(exim -C /dev/null -be "\${dlfunc{${LIB}}{sodium_crypto_box_seal}{${PK}}{${CLEARTEXT}}}")
DECRYPTED=$(exim -C /dev/null -be "\${dlfunc{${LIB}}{sodium_crypto_box_seal_open}{${SK}}{${PK}}{${CIPHERTEXT}}}")
if [ "${CLEARTEXT}" == "${DECRYPTED}" ] ; then
echo "ok 3 - sealed_box test with newly generated key pair successful"
else
echo "not ok 3 - sealed_box test with newly generated key pair unsuccessful"
fi
### Check if --help works
if src/libexim-encrypt-dlfunc-decrypt-secretbox --help > /dev/null ; then
echo "ok 4 - secretbox --help argument works"
else
echo "not ok 4 - secretbox --help argument does not work"
fi
if src/libexim-encrypt-dlfunc-decrypt-sealedbox --help > /dev/null ; then
echo "ok 5 - sealedbox --help argument works"
else
echo "not ok 5 - sealedbox --help argument does not work"
fi
### Code coverage for genkeys file access failures
pushd "${TEMPDIR02}" > /dev/null
KEYFILES=(cryptobox_recipient_pk.raw cryptobox_recipient_pk_exim.conf cryptobox_recipient_sk.raw cryptobox_recipient_sk_exim.conf)
for KF in "${KEYFILES[@]}"; do
rm -f "${KF}"
touch "${KF}"
done
FS_ACCESS_FAILURE=0
for KF in "${KEYFILES[@]}"; do
su -s /bin/bash -c "${CURDIR}/src/libexim-encrypt-dlfunc-genkeys" - nobody 2> /dev/null && FS_ACCESS_FAILURE=1
rm -f "${KF}"
touch "${KF}"
chown nobody: "${KF}"
done
if [ ${FS_ACCESS_FAILURE} -eq 0 ]; then
echo "ok 6 - genkeys should fail without filesystem access"
else
echo "not ok 6 - genkeys should fail without filesystem access"
fi
popd > /dev/null