|
|
@@ -21,6 +21,7 @@ |
|
|
|
#include "utlist.h" |
|
|
|
#include "unix-std.h" |
|
|
|
#include "mempool_vars_internal.h" |
|
|
|
#include "libcryptobox/ed25519/ed25519.h" |
|
|
|
|
|
|
|
#include <openssl/evp.h> |
|
|
|
#include <openssl/rsa.h> |
|
|
@@ -29,6 +30,10 @@ |
|
|
|
/* special DNS tokens */ |
|
|
|
#define DKIM_DNSKEYNAME "_domainkey" |
|
|
|
|
|
|
|
/* ed25519 key lengths */ |
|
|
|
#define ED25519_B64_BYTES 45 |
|
|
|
#define ED25519_BYTES 32 |
|
|
|
|
|
|
|
/* Canonization methods */ |
|
|
|
#define DKIM_CANON_UNKNOWN (-1) /* unknown method */ |
|
|
|
#define DKIM_CANON_SIMPLE 0 /* as specified in DKIM spec */ |
|
|
@@ -78,6 +83,10 @@ enum rspamd_dkim_param_type { |
|
|
|
rspamd_dkim_log_id, "dkim", ctx->pool->tag.uid, \ |
|
|
|
G_STRFUNC, \ |
|
|
|
__VA_ARGS__) |
|
|
|
#define msg_debug2_dkim(...) rspamd_conditional_debug_fast (NULL, NULL, \ |
|
|
|
rspamd_dkim_log_id, "dkim", "", \ |
|
|
|
G_STRFUNC, \ |
|
|
|
__VA_ARGS__) |
|
|
|
|
|
|
|
INIT_LOG_MODULE(dkim) |
|
|
|
|
|
|
@@ -155,10 +164,14 @@ struct rspamd_dkim_sign_context_s { |
|
|
|
}; |
|
|
|
|
|
|
|
struct rspamd_dkim_sign_key_s { |
|
|
|
enum rspamd_dkim_sign_key_type type; |
|
|
|
enum rspamd_dkim_key_type type; |
|
|
|
guint8 *keydata; |
|
|
|
gpointer map; |
|
|
|
gsize keylen; |
|
|
|
RSA *key_rsa; |
|
|
|
union { |
|
|
|
RSA *key_rsa; |
|
|
|
guchar *key_eddsa; |
|
|
|
} key; |
|
|
|
BIO *key_bio; |
|
|
|
EVP_PKEY *key_evp; |
|
|
|
time_t mtime; |
|
|
@@ -1342,8 +1355,10 @@ rspamd_dkim_sign_key_free (rspamd_dkim_sign_key_t *key) |
|
|
|
if (key->key_evp) { |
|
|
|
EVP_PKEY_free (key->key_evp); |
|
|
|
} |
|
|
|
if (key->key_rsa) { |
|
|
|
RSA_free (key->key_rsa); |
|
|
|
if (key->type == RSPAMD_DKIM_KEY_RSA) { |
|
|
|
if (key->key.key_rsa) { |
|
|
|
RSA_free (key->key.key_rsa); |
|
|
|
} |
|
|
|
} |
|
|
|
if (key->key_bio) { |
|
|
|
BIO_free (key->key_bio); |
|
|
@@ -1351,10 +1366,11 @@ rspamd_dkim_sign_key_free (rspamd_dkim_sign_key_t *key) |
|
|
|
|
|
|
|
if (key->keydata && key->keylen > 0) { |
|
|
|
|
|
|
|
if (key->type == RSPAMD_DKIM_SIGN_KEY_FILE) { |
|
|
|
munmap (key->keydata, key->keylen); |
|
|
|
if (key->map) { |
|
|
|
munmap (key->map, key->keylen); |
|
|
|
} |
|
|
|
else { |
|
|
|
rspamd_explicit_memzero (key->keydata, key->keylen); |
|
|
|
g_free (key->keydata); |
|
|
|
} |
|
|
|
} |
|
|
@@ -2652,6 +2668,7 @@ rspamd_dkim_sign_key_load (const gchar *what, gsize len, |
|
|
|
switch (type) { |
|
|
|
case RSPAMD_DKIM_SIGN_KEY_FILE: |
|
|
|
(void)mlock (map, len); |
|
|
|
nkey->map = map; |
|
|
|
nkey->keydata = map; |
|
|
|
nkey->keylen = map_len; |
|
|
|
break; |
|
|
@@ -2668,57 +2685,85 @@ rspamd_dkim_sign_key_load (const gchar *what, gsize len, |
|
|
|
nkey->keylen = len; |
|
|
|
} |
|
|
|
|
|
|
|
msg_debug2_dkim("got public key with length %d and type %d", nkey->keylen, type); |
|
|
|
(void)mlock (nkey->keydata, nkey->keylen); |
|
|
|
nkey->key_bio = BIO_new_mem_buf (nkey->keydata, nkey->keylen); |
|
|
|
if (type == RSPAMD_DKIM_SIGN_KEY_FILE && nkey->keylen == ED25519_B64_BYTES) { |
|
|
|
unsigned char seed[32]; |
|
|
|
unsigned char pk[32]; |
|
|
|
nkey->type = RSPAMD_DKIM_KEY_EDDSA; |
|
|
|
nkey->keydata = g_malloc (rspamd_cryptobox_sk_sig_bytes (RSPAMD_CRYPTOBOX_MODE_25519)); |
|
|
|
rspamd_cryptobox_base64_decode (nkey->map, ED25519_B64_BYTES, seed, &nkey->keylen); |
|
|
|
ed25519_seed_keypair (pk, nkey->keydata, seed); |
|
|
|
nkey->key.key_eddsa = nkey->keydata; |
|
|
|
nkey->keylen = rspamd_cryptobox_sk_sig_bytes (RSPAMD_CRYPTOBOX_MODE_25519); |
|
|
|
rspamd_explicit_memzero (seed, 32); |
|
|
|
munmap (nkey->map, ED25519_B64_BYTES); |
|
|
|
nkey->map = NULL; |
|
|
|
} |
|
|
|
else if (type == RSPAMD_DKIM_SIGN_KEY_BASE64 && nkey->keylen == ED25519_BYTES) { |
|
|
|
unsigned char pk[32]; |
|
|
|
nkey->type = RSPAMD_DKIM_KEY_EDDSA; |
|
|
|
nkey->key.key_eddsa = |
|
|
|
g_malloc (rspamd_cryptobox_sk_sig_bytes (RSPAMD_CRYPTOBOX_MODE_25519)); |
|
|
|
ed25519_seed_keypair (pk, nkey->key.key_eddsa, nkey->keydata); |
|
|
|
rspamd_explicit_memzero (nkey->keydata, nkey->keylen); |
|
|
|
g_free (nkey->keydata); |
|
|
|
nkey->keydata = nkey->key.key_eddsa; |
|
|
|
nkey->keylen = rspamd_cryptobox_sk_sig_bytes (RSPAMD_CRYPTOBOX_MODE_25519); |
|
|
|
} |
|
|
|
else { |
|
|
|
nkey->key_bio = BIO_new_mem_buf (nkey->keydata, nkey->keylen); |
|
|
|
|
|
|
|
if (type == RSPAMD_DKIM_SIGN_KEY_DER || type == RSPAMD_DKIM_SIGN_KEY_BASE64) { |
|
|
|
if (d2i_PrivateKey_bio (nkey->key_bio, &nkey->key_evp) == NULL) { |
|
|
|
if (type == RSPAMD_DKIM_SIGN_KEY_FILE) { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot read private key from %*s: %s", |
|
|
|
(gint)len, what, |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
} |
|
|
|
else { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot read private key from string: %s", |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
} |
|
|
|
|
|
|
|
if (type == RSPAMD_DKIM_SIGN_KEY_DER || type == RSPAMD_DKIM_SIGN_KEY_BASE64) { |
|
|
|
if (d2i_PrivateKey_bio (nkey->key_bio, &nkey->key_evp) == NULL) { |
|
|
|
if (type == RSPAMD_DKIM_SIGN_KEY_FILE) { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot read private key from %*s: %s", |
|
|
|
(gint)len, what, |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
} |
|
|
|
else { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot read private key from string: %s", |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
rspamd_dkim_sign_key_free (nkey); |
|
|
|
|
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
if (!PEM_read_bio_PrivateKey (nkey->key_bio, &nkey->key_evp, NULL, NULL)) { |
|
|
|
if (type == RSPAMD_DKIM_SIGN_KEY_FILE) { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot read private key from %*s: %s", |
|
|
|
(gint)len, what, |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
} |
|
|
|
else { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot read private key from string: %s", |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
} |
|
|
|
|
|
|
|
rspamd_dkim_sign_key_free (nkey); |
|
|
|
rspamd_dkim_sign_key_free (nkey); |
|
|
|
|
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
if (!PEM_read_bio_PrivateKey (nkey->key_bio, &nkey->key_evp, NULL, NULL)) { |
|
|
|
if (type == RSPAMD_DKIM_SIGN_KEY_FILE) { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot read private key from %*s: %s", |
|
|
|
(gint)len, what, |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
} |
|
|
|
else { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot read private key from string: %s", |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
nkey->key.key_rsa = EVP_PKEY_get1_RSA (nkey->key_evp); |
|
|
|
if (nkey->key.key_rsa == NULL) { |
|
|
|
g_set_error (err, |
|
|
|
DKIM_ERROR, |
|
|
|
DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot extract rsa key from evp key"); |
|
|
|
rspamd_dkim_sign_key_free (nkey); |
|
|
|
|
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
nkey->key_rsa = EVP_PKEY_get1_RSA (nkey->key_evp); |
|
|
|
if (nkey->key_rsa == NULL) { |
|
|
|
g_set_error (err, |
|
|
|
DKIM_ERROR, |
|
|
|
DKIM_SIGERROR_KEYFAIL, |
|
|
|
"cannot extract rsa key from evp key"); |
|
|
|
rspamd_dkim_sign_key_free (nkey); |
|
|
|
|
|
|
|
return NULL; |
|
|
|
nkey->type = RSPAMD_DKIM_KEY_RSA; |
|
|
|
} |
|
|
|
|
|
|
|
REF_INIT_RETAIN (nkey, rspamd_dkim_sign_key_free); |
|
|
@@ -2784,7 +2829,7 @@ rspamd_create_dkim_sign_context (struct rspamd_task *task, |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
if (!priv_key || !priv_key->key_rsa) { |
|
|
|
if (!priv_key || (!priv_key->key.key_rsa && !priv_key->key.key_eddsa)) { |
|
|
|
g_set_error (err, |
|
|
|
DKIM_ERROR, |
|
|
|
DKIM_SIGERROR_KEYFAIL, |
|
|
@@ -2852,8 +2897,8 @@ rspamd_dkim_sign (struct rspamd_task *task, const gchar *selector, |
|
|
|
gsize dlen = 0; |
|
|
|
guint i, j; |
|
|
|
gchar *b64_data; |
|
|
|
guchar *rsa_buf; |
|
|
|
guint rsa_len; |
|
|
|
guchar *sig_buf; |
|
|
|
guint sig_len; |
|
|
|
guint headers_len = 0, cur_len = 0; |
|
|
|
union rspamd_dkim_header_stat hstat; |
|
|
|
|
|
|
@@ -2889,7 +2934,9 @@ rspamd_dkim_sign (struct rspamd_task *task, const gchar *selector, |
|
|
|
hdr = g_string_sized_new (255); |
|
|
|
|
|
|
|
if (ctx->common.type == RSPAMD_DKIM_NORMAL) { |
|
|
|
rspamd_printf_gstring (hdr, "v=1; a=rsa-sha256; c=%s/%s; d=%s; s=%s; ", |
|
|
|
rspamd_printf_gstring (hdr, "v=1; a=%s; c=%s/%s; d=%s; s=%s; ", |
|
|
|
ctx->key->type == RSPAMD_DKIM_KEY_RSA ? |
|
|
|
"rsa-sha256" : "ed25519-sha256", |
|
|
|
ctx->common.header_canon_type == DKIM_CANON_RELAXED ? |
|
|
|
"relaxed" : "simple", |
|
|
|
ctx->common.body_canon_type == DKIM_CANON_RELAXED ? |
|
|
@@ -2897,8 +2944,10 @@ rspamd_dkim_sign (struct rspamd_task *task, const gchar *selector, |
|
|
|
domain, selector); |
|
|
|
} |
|
|
|
else if (ctx->common.type == RSPAMD_DKIM_ARC_SIG) { |
|
|
|
rspamd_printf_gstring (hdr, "i=%d; a=rsa-sha256; c=%s/%s; d=%s; s=%s; ", |
|
|
|
rspamd_printf_gstring (hdr, "i=%d; a=%s; c=%s/%s; d=%s; s=%s; ", |
|
|
|
idx, |
|
|
|
ctx->key->type == RSPAMD_DKIM_KEY_RSA ? |
|
|
|
"rsa-sha256" : "ed25519-sha256", |
|
|
|
ctx->common.header_canon_type == DKIM_CANON_RELAXED ? |
|
|
|
"relaxed" : "simple", |
|
|
|
ctx->common.body_canon_type == DKIM_CANON_RELAXED ? |
|
|
@@ -2907,8 +2956,10 @@ rspamd_dkim_sign (struct rspamd_task *task, const gchar *selector, |
|
|
|
} |
|
|
|
else { |
|
|
|
g_assert (arc_cv != NULL); |
|
|
|
rspamd_printf_gstring (hdr, "i=%d; a=rsa-sha256; c=%s/%s; d=%s; s=%s; cv=%s; ", |
|
|
|
rspamd_printf_gstring (hdr, "i=%d; a=%s; c=%s/%s; d=%s; s=%s; cv=%s; ", |
|
|
|
arc_cv, |
|
|
|
ctx->key->type == RSPAMD_DKIM_KEY_RSA ? |
|
|
|
"rsa-sha256" : "ed25519-sha256", |
|
|
|
idx, |
|
|
|
domain, selector); |
|
|
|
} |
|
|
@@ -3040,24 +3091,37 @@ rspamd_dkim_sign (struct rspamd_task *task, const gchar *selector, |
|
|
|
|
|
|
|
dlen = EVP_MD_CTX_size (ctx->common.headers_hash); |
|
|
|
EVP_DigestFinal_ex (ctx->common.headers_hash, raw_digest, NULL); |
|
|
|
rsa_len = RSA_size (ctx->key->key_rsa); |
|
|
|
rsa_buf = g_alloca (rsa_len); |
|
|
|
if (ctx->key->type == RSPAMD_DKIM_KEY_RSA) { |
|
|
|
sig_len = RSA_size (ctx->key->key.key_rsa); |
|
|
|
sig_buf = g_alloca (sig_len); |
|
|
|
|
|
|
|
if (RSA_sign (NID_sha256, raw_digest, dlen, sig_buf, &sig_len, |
|
|
|
ctx->key->key.key_rsa) != 1) { |
|
|
|
g_string_free (hdr, TRUE); |
|
|
|
msg_err_task ("rsa sign error: %s", |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
|
|
|
|
if (RSA_sign (NID_sha256, raw_digest, dlen, rsa_buf, &rsa_len, |
|
|
|
ctx->key->key_rsa) != 1) { |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} else if (ctx->key->type == RSPAMD_DKIM_KEY_EDDSA) { |
|
|
|
sig_len = rspamd_cryptobox_signature_bytes (RSPAMD_CRYPTOBOX_MODE_25519); |
|
|
|
sig_buf = g_alloca (sig_len); |
|
|
|
|
|
|
|
rspamd_cryptobox_sign (sig_buf, NULL, raw_digest, dlen, |
|
|
|
ctx->key->key.key_eddsa, RSPAMD_CRYPTOBOX_MODE_25519); |
|
|
|
} else { |
|
|
|
g_string_free (hdr, TRUE); |
|
|
|
msg_err_task ("rsa sign error: %s", |
|
|
|
ERR_error_string (ERR_get_error (), NULL)); |
|
|
|
msg_err_task ("unsupported key type for signing"); |
|
|
|
|
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
if (task->flags & RSPAMD_TASK_FLAG_MILTER) { |
|
|
|
b64_data = rspamd_encode_base64_fold (rsa_buf, rsa_len, 70, NULL, |
|
|
|
b64_data = rspamd_encode_base64_fold (sig_buf, sig_len, 70, NULL, |
|
|
|
RSPAMD_TASK_NEWLINES_LF); |
|
|
|
} |
|
|
|
else { |
|
|
|
b64_data = rspamd_encode_base64_fold (rsa_buf, rsa_len, 70, NULL, |
|
|
|
b64_data = rspamd_encode_base64_fold (sig_buf, sig_len, 70, NULL, |
|
|
|
task->nlines_type); |
|
|
|
} |
|
|
|
|
|
|
@@ -3072,33 +3136,30 @@ rspamd_dkim_match_keys (rspamd_dkim_key_t *pk, |
|
|
|
rspamd_dkim_sign_key_t *sk, |
|
|
|
GError **err) |
|
|
|
{ |
|
|
|
const BIGNUM *n1, *n2; |
|
|
|
|
|
|
|
if (pk == NULL || sk == NULL) { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"missing public or private key"); |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
if (pk->type != RSPAMD_DKIM_KEY_RSA) { |
|
|
|
if (pk->type != sk->type) { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, |
|
|
|
"pubkey is not RSA key"); |
|
|
|
"public and private key types do not match"); |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) |
|
|
|
RSA_get0_key (pk->key.key_rsa, &n1, NULL, NULL); |
|
|
|
RSA_get0_key (sk->key_rsa, &n2, NULL, NULL); |
|
|
|
#else |
|
|
|
n1 = pk->key.key_rsa->n; |
|
|
|
n2 = sk->key_rsa->n; |
|
|
|
#endif |
|
|
|
if (pk->type == RSPAMD_DKIM_KEY_EDDSA) { |
|
|
|
if (memcmp(sk->key.key_eddsa + 32, pk->key.key_eddsa, 32) != 0) { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYHASHMISMATCH, |
|
|
|
"pubkey does not match private key"); |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
if (BN_cmp (n1, n2) != 0) { |
|
|
|
} |
|
|
|
else if (EVP_PKEY_cmp (pk->key_evp, sk->key_evp) != 1) { |
|
|
|
g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYHASHMISMATCH, |
|
|
|
"pubkey does not match private key"); |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
return TRUE; |
|
|
|
} |
|
|
|
} |