aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/crypto.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/crypto.c')
-rw-r--r--smtpd/crypto.c400
1 files changed, 400 insertions, 0 deletions
diff --git a/smtpd/crypto.c b/smtpd/crypto.c
new file mode 100644
index 00000000..20a422cd
--- /dev/null
+++ b/smtpd/crypto.c
@@ -0,0 +1,400 @@
+/* $OpenBSD: crypto.c,v 1.8 2019/06/28 13:32:50 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2013 Gilles Chehade <gilles@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/evp.h>
+
+
+#define CRYPTO_BUFFER_SIZE 16384
+
+#define GCM_TAG_SIZE 16
+#define IV_SIZE 12
+#define KEY_SIZE 32
+
+/* bump if we ever switch from aes-256-gcm to anything else */
+#define API_VERSION 1
+
+
+int crypto_setup(const char *, size_t);
+int crypto_encrypt_file(FILE *, FILE *);
+int crypto_decrypt_file(FILE *, FILE *);
+size_t crypto_encrypt_buffer(const char *, size_t, char *, size_t);
+size_t crypto_decrypt_buffer(const char *, size_t, char *, size_t);
+
+static struct crypto_ctx {
+ unsigned char key[KEY_SIZE];
+} cp;
+
+int
+crypto_setup(const char *key, size_t len)
+{
+ if (len != KEY_SIZE)
+ return 0;
+
+ memset(&cp, 0, sizeof cp);
+
+ /* openssl rand -hex 16 */
+ memcpy(cp.key, key, sizeof cp.key);
+
+ return 1;
+}
+
+int
+crypto_encrypt_file(FILE * in, FILE * out)
+{
+ EVP_CIPHER_CTX *ctx;
+ uint8_t ibuf[CRYPTO_BUFFER_SIZE];
+ uint8_t obuf[CRYPTO_BUFFER_SIZE];
+ uint8_t iv[IV_SIZE];
+ uint8_t tag[GCM_TAG_SIZE];
+ uint8_t version = API_VERSION;
+ size_t r, w;
+ int len;
+ int ret = 0;
+ struct stat sb;
+
+ /* XXX - Do NOT encrypt files bigger than 64GB */
+ if (fstat(fileno(in), &sb) == -1)
+ return 0;
+ if (sb.st_size >= 0x1000000000LL)
+ return 0;
+
+ /* prepend version byte*/
+ if ((w = fwrite(&version, 1, sizeof version, out)) != sizeof version)
+ return 0;
+
+ /* generate and prepend IV */
+ memset(iv, 0, sizeof iv);
+ arc4random_buf(iv, sizeof iv);
+ if ((w = fwrite(iv, 1, sizeof iv, out)) != sizeof iv)
+ return 0;
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL)
+ return 0;
+
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv);
+
+ /* encrypt until end of file */
+ while ((r = fread(ibuf, 1, CRYPTO_BUFFER_SIZE, in)) != 0) {
+ if (!EVP_EncryptUpdate(ctx, obuf, &len, ibuf, r))
+ goto end;
+ if (len && (w = fwrite(obuf, len, 1, out)) != 1)
+ goto end;
+ }
+ if (!feof(in))
+ goto end;
+
+ /* finalize and write last chunk if any */
+ if (!EVP_EncryptFinal_ex(ctx, obuf, &len))
+ goto end;
+ if (len && (w = fwrite(obuf, len, 1, out)) != 1)
+ goto end;
+
+ /* get and append tag */
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag);
+ if ((w = fwrite(tag, sizeof tag, 1, out)) != 1)
+ goto end;
+
+ fflush(out);
+ ret = 1;
+
+end:
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+int
+crypto_decrypt_file(FILE * in, FILE * out)
+{
+ EVP_CIPHER_CTX *ctx;
+ uint8_t ibuf[CRYPTO_BUFFER_SIZE];
+ uint8_t obuf[CRYPTO_BUFFER_SIZE];
+ uint8_t iv[IV_SIZE];
+ uint8_t tag[GCM_TAG_SIZE];
+ uint8_t version;
+ size_t r, w;
+ off_t sz;
+ int len;
+ int ret = 0;
+ struct stat sb;
+
+ /* input file too small to be an encrypted file */
+ if (fstat(fileno(in), &sb) == -1)
+ return 0;
+ if (sb.st_size <= (off_t) (sizeof version + sizeof tag + sizeof iv))
+ return 0;
+ sz = sb.st_size;
+
+ /* extract tag */
+ if (fseek(in, -sizeof(tag), SEEK_END) == -1)
+ return 0;
+ if ((r = fread(tag, 1, sizeof tag, in)) != sizeof tag)
+ return 0;
+
+ if (fseek(in, 0, SEEK_SET) == -1)
+ return 0;
+
+ /* extract version */
+ if ((r = fread(&version, 1, sizeof version, in)) != sizeof version)
+ return 0;
+ if (version != API_VERSION)
+ return 0;
+
+ /* extract IV */
+ memset(iv, 0, sizeof iv);
+ if ((r = fread(iv, 1, sizeof iv, in)) != sizeof iv)
+ return 0;
+
+ /* real ciphertext length */
+ sz -= sizeof version;
+ sz -= sizeof iv;
+ sz -= sizeof tag;
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL)
+ return 0;
+
+ EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv);
+
+ /* set expected tag */
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof tag, tag);
+
+ /* decrypt until end of ciphertext */
+ while (sz) {
+ if (sz > CRYPTO_BUFFER_SIZE)
+ r = fread(ibuf, 1, CRYPTO_BUFFER_SIZE, in);
+ else
+ r = fread(ibuf, 1, sz, in);
+ if (!r)
+ break;
+ if (!EVP_DecryptUpdate(ctx, obuf, &len, ibuf, r))
+ goto end;
+ if (len && (w = fwrite(obuf, len, 1, out)) != 1)
+ goto end;
+ sz -= r;
+ }
+ if (ferror(in))
+ goto end;
+
+ /* finalize, write last chunk if any and perform authentication check */
+ if (!EVP_DecryptFinal_ex(ctx, obuf, &len))
+ goto end;
+ if (len && (w = fwrite(obuf, len, 1, out)) != 1)
+ goto end;
+
+ fflush(out);
+ ret = 1;
+
+end:
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+size_t
+crypto_encrypt_buffer(const char *in, size_t inlen, char *out, size_t outlen)
+{
+ EVP_CIPHER_CTX *ctx;
+ uint8_t iv[IV_SIZE];
+ uint8_t tag[GCM_TAG_SIZE];
+ uint8_t version = API_VERSION;
+ off_t sz;
+ int olen;
+ int len = 0;
+ int ret = 0;
+
+ /* output buffer does not have enough room */
+ if (outlen < inlen + sizeof version + sizeof tag + sizeof iv)
+ return 0;
+
+ /* input should not exceed 64GB */
+ sz = inlen;
+ if (sz >= 0x1000000000LL)
+ return 0;
+
+ /* prepend version */
+ *out = version;
+ len++;
+
+ /* generate IV */
+ memset(iv, 0, sizeof iv);
+ arc4random_buf(iv, sizeof iv);
+ memcpy(out + len, iv, sizeof iv);
+ len += sizeof iv;
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL)
+ return 0;
+
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv);
+
+ /* encrypt buffer */
+ if (!EVP_EncryptUpdate(ctx, out + len, &olen, in, inlen))
+ goto end;
+ len += olen;
+
+ /* finalize and write last chunk if any */
+ if (!EVP_EncryptFinal_ex(ctx, out + len, &olen))
+ goto end;
+ len += olen;
+
+ /* get and append tag */
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag);
+ memcpy(out + len, tag, sizeof tag);
+ ret = len + sizeof tag;
+
+end:
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+size_t
+crypto_decrypt_buffer(const char *in, size_t inlen, char *out, size_t outlen)
+{
+ EVP_CIPHER_CTX *ctx;
+ uint8_t iv[IV_SIZE];
+ uint8_t tag[GCM_TAG_SIZE];
+ int olen;
+ int len = 0;
+ int ret = 0;
+
+ /* out does not have enough room */
+ if (outlen < inlen - sizeof tag + sizeof iv)
+ return 0;
+
+ /* extract tag */
+ memcpy(tag, in + inlen - sizeof tag, sizeof tag);
+ inlen -= sizeof tag;
+
+ /* check version */
+ if (*in != API_VERSION)
+ return 0;
+ in++;
+ inlen--;
+
+ /* extract IV */
+ memset(iv, 0, sizeof iv);
+ memcpy(iv, in, sizeof iv);
+ inlen -= sizeof iv;
+ in += sizeof iv;
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL)
+ return 0;
+
+ EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv);
+
+ /* set expected tag */
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof tag, tag);
+
+ /* decrypt buffer */
+ if (!EVP_DecryptUpdate(ctx, out, &olen, in, inlen))
+ goto end;
+ len += olen;
+
+ /* finalize, write last chunk if any and perform authentication check */
+ if (!EVP_DecryptFinal_ex(ctx, out + len, &olen))
+ goto end;
+ ret = len + olen;
+
+end:
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+#if 0
+int
+main(int argc, char *argv[])
+{
+ if (argc != 3) {
+ printf("usage: crypto <key> <buffer>\n");
+ return 1;
+ }
+
+ if (!crypto_setup(argv[1], strlen(argv[1]))) {
+ printf("crypto_setup failed\n");
+ return 1;
+ }
+
+ {
+ char encbuffer[4096];
+ size_t enclen;
+ char decbuffer[4096];
+ size_t declen;
+
+ printf("encrypt/decrypt buffer: ");
+ enclen = crypto_encrypt_buffer(argv[2], strlen(argv[2]),
+ encbuffer, sizeof encbuffer);
+
+ /* uncomment below to provoke integrity check failure */
+ /*
+ * encbuffer[13] = 0x42;
+ * encbuffer[14] = 0x42;
+ * encbuffer[15] = 0x42;
+ * encbuffer[16] = 0x42;
+ */
+
+ declen = crypto_decrypt_buffer(encbuffer, enclen,
+ decbuffer, sizeof decbuffer);
+ if (declen != 0 && !strncmp(argv[2], decbuffer, declen))
+ printf("ok\n");
+ else
+ printf("nope\n");
+ }
+
+ {
+ FILE *fpin;
+ FILE *fpout;
+ printf("encrypt/decrypt file: ");
+
+ fpin = fopen("/etc/passwd", "r");
+ fpout = fopen("/tmp/passwd.enc", "w");
+ if (!crypto_encrypt_file(fpin, fpout)) {
+ printf("encryption failed\n");
+ return 1;
+ }
+ fclose(fpin);
+ fclose(fpout);
+
+ /* uncomment below to provoke integrity check failure */
+ /*
+ * fpin = fopen("/tmp/passwd.enc", "a");
+ * fprintf(fpin, "borken");
+ * fclose(fpin);
+ */
+ fpin = fopen("/tmp/passwd.enc", "r");
+ fpout = fopen("/tmp/passwd.dec", "w");
+ if (!crypto_decrypt_file(fpin, fpout))
+ printf("nope\n");
+ else
+ printf("ok\n");
+ fclose(fpin);
+ fclose(fpout);
+ }
+
+
+ return 0;
+}
+#endif