From f3b3f2c4f944c98a341f406abd2e4a6b7b69da36 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Sun, 20 Sep 2026 15:41:55 +0200 Subject: settings: encrypted secrets and SMTP settings --- src/secret.c | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/secret.c (limited to 'src/secret.c') diff --git a/src/secret.c b/src/secret.c new file mode 100644 index 0000000..7222ddd --- /dev/null +++ b/src/secret.c @@ -0,0 +1,194 @@ +#include "secret.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "util.h" + +#define SECRET_PREFIX "enc:v1:" +#define SECRET_KEY_LEN 32 +#define SECRET_NONCE_LEN 12 +#define SECRET_TAG_LEN 16 + +static int hex_val(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +static int secret_key(unsigned char out[SECRET_KEY_LEN]) +{ + const char *s = getenv("BOKFD_SECRET_KEY"); + if (!s || !*s) + return -1; + size_t n = strlen(s); + if (n == 2 * SECRET_KEY_LEN) { + int hex = 1; + for (size_t i = 0; i < n && hex; i++) + if (hex_val((unsigned char)s[i]) < 0) + hex = 0; + if (hex) { + for (size_t i = 0; i < SECRET_KEY_LEN; i++) + out[i] = (unsigned char)((hex_val(s[2 * i]) << 4) | + hex_val(s[2 * i + 1])); + return 0; + } + } + if (n % 4 == 1 || n > 172) + return -1; + char padded[176]; + memcpy(padded, s, n); + while (n % 4) + padded[n++] = '='; + padded[n] = '\0'; + unsigned char *raw = NULL; + size_t raw_n = 0; + if (util_b64_decode(padded, n, &raw, &raw_n) != 0) + return -1; + int ok = raw_n == SECRET_KEY_LEN; + if (ok) + memcpy(out, raw, SECRET_KEY_LEN); + free(raw); + return ok ? 0 : -1; +} + +int secret_available(void) +{ + unsigned char key[SECRET_KEY_LEN] = { 0 }; + int ok = secret_key(key) == 0; + OPENSSL_cleanse(key, sizeof key); + return ok; +} + +int secret_encrypt(const char *plain, char **out) +{ + if (out) + *out = NULL; + if (!plain || !out) + return -1; + size_t plen = strlen(plain); + if (plen > (size_t)INT_MAX) + return -1; + unsigned char key[SECRET_KEY_LEN], nonce[SECRET_NONCE_LEN]; + if (secret_key(key) != 0) + return -1; + if (RAND_bytes(nonce, SECRET_NONCE_LEN) != 1) { + OPENSSL_cleanse(key, sizeof key); + return -1; + } + size_t clen = plen + SECRET_TAG_LEN; + unsigned char *ct = xmalloc(clen); + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int len = 0, total = 0, ok = 0; + if (ctx && + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) == 1 && + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, SECRET_NONCE_LEN, + NULL) == 1 && + EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce) == 1 && + EVP_EncryptUpdate(ctx, ct, &len, (const unsigned char *)plain, + (int)plen) == 1) { + total = len; + ok = EVP_EncryptFinal_ex(ctx, ct + total, &len) == 1; + total += len; + } + if (ok) + ok = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, SECRET_TAG_LEN, + ct + total) == 1; + EVP_CIPHER_CTX_free(ctx); + OPENSSL_cleanse(key, sizeof key); + if (!ok || total != (int)plen) { + free(ct); + return -1; + } + total += SECRET_TAG_LEN; + char *nonce_b64 = util_b64(nonce, sizeof nonce); + char *ct_b64 = util_b64(ct, (size_t)total); + free(ct); + size_t olen = + strlen(SECRET_PREFIX) + strlen(nonce_b64) + 1 + strlen(ct_b64) + 1; + char *s = xmalloc(olen); + snprintf(s, olen, "%s%s:%s", SECRET_PREFIX, nonce_b64, ct_b64); + free(nonce_b64); + free(ct_b64); + *out = s; + return 0; +} + +int secret_decrypt(const char *stored, char **out) +{ + if (out) + *out = NULL; + if (!stored || !out) + return -1; + if (strncmp(stored, SECRET_PREFIX, strlen(SECRET_PREFIX)) != 0) + return -1; + const char *p = stored + strlen(SECRET_PREFIX); + const char *sep = strchr(p, ':'); + if (!sep || sep == p || !sep[1]) + return -1; + unsigned char key[SECRET_KEY_LEN]; + if (secret_key(key) != 0) + return -1; + unsigned char *nonce = NULL, *ct = NULL; + size_t nonce_n = 0, ct_n = 0; + if (util_b64_decode(p, (size_t)(sep - p), &nonce, &nonce_n) != 0) { + OPENSSL_cleanse(key, sizeof key); + return -1; + } + if (util_b64_decode(sep + 1, strlen(sep + 1), &ct, &ct_n) != 0) { + free(nonce); + OPENSSL_cleanse(key, sizeof key); + return -1; + } + if (nonce_n != SECRET_NONCE_LEN || ct_n < SECRET_TAG_LEN || + ct_n - SECRET_TAG_LEN > (size_t)INT_MAX) { + free(nonce); + free(ct); + OPENSSL_cleanse(key, sizeof key); + return -1; + } + size_t plen = ct_n - SECRET_TAG_LEN; + unsigned char *pt = xmalloc(plen + 1); + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int len = 0, total = 0, ok = 0; + if (ctx && + EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) == 1 && + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, SECRET_NONCE_LEN, + NULL) == 1 && + EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce) == 1 && + EVP_DecryptUpdate(ctx, pt, &len, ct, (int)plen) == 1) { + total = len; + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, SECRET_TAG_LEN, + ct + plen) == 1) + ok = EVP_DecryptFinal_ex(ctx, pt + total, &len) == 1; + total += len; + } + EVP_CIPHER_CTX_free(ctx); + OPENSSL_cleanse(key, sizeof key); + free(nonce); + free(ct); + if (!ok || total != (int)plen) { + OPENSSL_cleanse(pt, plen + 1); + free(pt); + return -1; + } + pt[total] = '\0'; + *out = (char *)pt; + return 0; +} + +int secret_key_is_secret(const char *key) +{ + return key && strcmp(key, "smtp_password") == 0; +} -- cgit v1.3