diff --git a/.gitignore b/.gitignore index a774788..24bfb13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /build +debian/*.ex diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0837428..8a25446 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,8 +21,10 @@ stages: - cd .. artifacts: paths: - - build/src/generate_encryption_keys - 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-sealedtbox .debian-package: stage: debian-package @@ -37,31 +39,23 @@ stages: - ./*.changes - ./*.buildinfo -build:buster: - extends: - - .build - - .image-buster - needs: [] - build:bullseye: extends: - .image-bullseye - .build - needs: [] + needs: [ ] build:focal: extends: - .image-focal - .build - needs: [] + needs: [ ] -debian-package:buster: +build:buster: extends: + - .build - .image-buster - - .debian-package - dependencies: - - build:buster - needs: ["build:buster"] + needs: [ ] debian-package:bullseye: extends: @@ -69,7 +63,7 @@ debian-package:bullseye: - .debian-package dependencies: - build:bullseye - needs: ["build:bullseye"] + needs: [ "build:bullseye" ] debian-package:focal: extends: @@ -77,4 +71,12 @@ debian-package:focal: - .debian-package dependencies: - build:focal - needs: ["build:focal"] + needs: [ "build:focal" ] + +debian-package:buster: + extends: + - .image-buster + - .debian-package + dependencies: + - build:buster + needs: [ "build:buster" ] diff --git a/README.md b/README.md index f18db1f..a8fa8af 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,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 images used in continous integration for this project. + +5. Copy to final destination (feel free to pick another place than `/usr/lib/x86_64-linux-gnu/`): ```shell meson install -C build @@ -54,9 +57,18 @@ 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. The Debian package [`exim4-daemon-heavy`](https://packages.debian.org/exim4-daemon-heavy) + 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. + ## Usage There are currently two pairs of complementary functions: @@ -74,11 +86,11 @@ Public key encryption that uses a key pair which needs to be created beforehand: * `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 +`libexim-encrypt-dlfunc-genkeys` utility is for. Simply run it once to generate a pair. 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« @@ -107,11 +119,11 @@ original header is usually provided in these cases). Add this snippet to your DA ``` 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 +132,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 the [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. +``` \ No newline at end of file diff --git a/ci_container/README.md b/ci_container/README.md index 6f54f9c..3782fb6 100644 --- a/ci_container/README.md +++ b/ci_container/README.md @@ -3,7 +3,7 @@ ## Prerequisites * [buildah](https://buildah.io/) -* {podman](https://podman.io/) +* [podman](https://podman.io/) ## Build and upload diff --git a/ci_container/build.sh b/ci_container/build.sh index da63c15..10ce816 100755 --- a/ci_container/build.sh +++ b/ci_container/build.sh @@ -61,11 +61,14 @@ for i in "${images[@]}"; do openssl; \ DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get install -y \ debhelper \ - dh-make \ - devscripts \ - git-buildpackage \ debsigs \ - gpgv1; \ + devscripts \ + dh-make \ + git-buildpackage \ + gpgv1 \ + meson; \ + DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get install -y \ + vim; \ rm -rf /var/lib/apt/lists/*;' buildah run "$ctr" /bin/sh -c \ 'pip3 install meson ninja; \ diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..559b8cd --- /dev/null +++ b/src/common.c @@ -0,0 +1,89 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include "common.h" + +//#define MIN_KEY_SIZE (crypto_box_SECRETKEYBYTES < crypto_box_PUBLICKEYBYTES ? crypto_box_SECRETKEYBYTES : crypto_box_PUBLICKEYBYTES) +//#define MAX_KEY_SIZE (crypto_box_SECRETKEYBYTES > crypto_box_PUBLICKEYBYTES ? crypto_box_SECRETKEYBYTES : crypto_box_PUBLICKEYBYTES) + +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); +} \ No newline at end of file diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..7e0b6ff --- /dev/null +++ b/src/common.h @@ -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 diff --git a/src/debug_helpers.c b/src/debug_helpers.c index 329fd64..e596ac8 100644 --- a/src/debug_helpers.c +++ b/src/debug_helpers.c @@ -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. */ \ No newline at end of file diff --git a/src/libexim-encrypt-dlfunc-decrypt-sealedbox.c b/src/libexim-encrypt-dlfunc-decrypt-sealedbox.c new file mode 100644 index 0000000..9a099ea --- /dev/null +++ b/src/libexim-encrypt-dlfunc-decrypt-sealedbox.c @@ -0,0 +1,203 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#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 = 0; + 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); + break; + } + } + + // read first non-option argument as ciphertext if present + if (optind < argc) { + size_t b64cipherstring_len = strlen(argv[optind]); + b64cipherstring = malloc(b64cipherstring_len); + sodium_memzero(b64cipherstring, b64cipherstring_len); + 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); + } + // 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); + } +} diff --git a/src/libexim-encrypt-dlfunc-decrypt-secretbox.c b/src/libexim-encrypt-dlfunc-decrypt-secretbox.c new file mode 100644 index 0000000..28d17e5 --- /dev/null +++ b/src/libexim-encrypt-dlfunc-decrypt-secretbox.c @@ -0,0 +1,161 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#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); + break; + } + } + + // 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); + } +} \ No newline at end of file diff --git a/src/generate_encryption_keys.c b/src/libexim-encrypt-dlfunc-genkeys.c similarity index 100% rename from src/generate_encryption_keys.c rename to src/libexim-encrypt-dlfunc-genkeys.c diff --git a/src/meson.build b/src/meson.build index 81ccf72..58fa10d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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') \ No newline at end of file diff --git a/src/test_libexim-encrypt-dlfunc-decrypt-sealedbox.sh b/src/test_libexim-encrypt-dlfunc-decrypt-sealedbox.sh new file mode 100755 index 0000000..b9aa055 --- /dev/null +++ b/src/test_libexim-encrypt-dlfunc-decrypt-sealedbox.sh @@ -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 diff --git a/src/test_libexim-encrypt-dlfunc-decrypt-secretbox.sh b/src/test_libexim-encrypt-dlfunc-decrypt-secretbox.sh new file mode 100755 index 0000000..ac675ed --- /dev/null +++ b/src/test_libexim-encrypt-dlfunc-decrypt-secretbox.sh @@ -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 diff --git a/src/simple_exim_test.sh b/src/test_libexim-encrypt-dlfunc.sh similarity index 81% rename from src/simple_exim_test.sh rename to src/test_libexim-encrypt-dlfunc.sh index cf5acff..1177e98 100755 --- a/src/simple_exim_test.sh +++ b/src/test_libexim-encrypt-dlfunc.sh @@ -1,24 +1,24 @@ #!/bin/bash -set -e - PATH=/sbin:/usr/sbin:$PATH +# this script implements the TAP protocol (https://testanything.org) +echo 1..2 + # 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`" +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" + echo "ok 1 - secretbox test successful" else - echo "secretbox test unsuccessful" - exit 127 + echo "not ok 1 - secretbox test unsuccessful" 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 }; @@ -30,8 +30,7 @@ CIPHERTEXT=$(exim -be "\${dlfunc{${LIB}}{sodium_crypto_box_seal}{${PK}}{${CLEART DECRYPTED=$(exim -be "\${dlfunc{${LIB}}{sodium_crypto_box_seal_open}{${SK}}{${PK}}{${CIPHERTEXT}}}") if [ "${CLEARTEXT}" == "${DECRYPTED}" ] ; then - echo "sealed_box test successful" + echo "ok 2 - sealed_box test successful" else - echo "sealed_box test unsuccessful" - exit 128 + echo "ok 2 - sealed_box test unsuccessful" fi