/* This file is part of Mailfromd.
Copyright (C) 2020 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mailfromd.h"
#include
#include "dkim.h"
/*
* Read public and private rsa keys from file.
* Matching code derived from pkcs1-conv.c in Nettle.
*/
static const uint8_t pem_start_pattern[] = "-----BEGIN ";
static int pem_start_pattern_length = sizeof(pem_start_pattern) - 1;
static const uint8_t pem_end_pattern[] = "-----END ";
static int pem_end_pattern_length = sizeof(pem_end_pattern) - 1;
static const uint8_t pem_trailer_pattern[] = "-----";
static int pem_trailer_pattern_length = sizeof(pem_trailer_pattern) - 1;
enum {
READ_PEM_OK,
READ_PEM_ERROR,
READ_PEM_EOF
};
/* Returns READ_PEM_OK on match. */
static int
match_pem_start(size_t length, const uint8_t *line,
size_t *marker_start,
size_t *marker_length)
{
while (length > 0 && mu_isspace(line[length - 1]))
length--;
if (length > (pem_start_pattern_length + pem_trailer_pattern_length)
&& memcmp(line, pem_start_pattern, pem_start_pattern_length) == 0
&& memcmp(line + length - pem_trailer_pattern_length,
pem_trailer_pattern, pem_trailer_pattern_length) == 0) {
*marker_start = pem_start_pattern_length;
*marker_length = length -
(pem_start_pattern_length + pem_trailer_pattern_length);
return READ_PEM_OK;
}
return READ_PEM_ERROR;
}
/* Returns READ_PEM_OK on match, READ_PEM_EOF if the line is of the right
form except for the marker, otherwise READ_PEM_ERROR. */
static int
match_pem_end(size_t length, const uint8_t *line,
size_t marker_length,
const uint8_t *marker)
{
while (length > 0 && mu_isspace(line[length - 1]))
length--;
if (length > (pem_end_pattern_length + pem_trailer_pattern_length)
&& memcmp(line, pem_end_pattern, pem_end_pattern_length) == 0
&& memcmp(line + length - pem_trailer_pattern_length,
pem_trailer_pattern, pem_trailer_pattern_length) == 0) {
if (length == marker_length +
(pem_end_pattern_length + pem_trailer_pattern_length)
&& memcmp(line + pem_end_pattern_length, marker,
marker_length) == 0)
return READ_PEM_OK;
else
return READ_PEM_EOF;
}
return READ_PEM_ERROR;
}
struct pem_info {
size_t marker_start;
size_t marker_length;
size_t data_start;
size_t data_length;
};
/* Read a single line from file into buffer. */
static int
read_line(FILE *fp, struct nettle_buffer *buffer)
{
int c;
while ((c = getc(fp)) != EOF) {
if (!NETTLE_BUFFER_PUTC(buffer, c))
return READ_PEM_ERROR;
if (c == '\n')
return READ_PEM_OK;
}
if (ferror(fp))
return READ_PEM_ERROR;
return READ_PEM_EOF;
}
/* Read PEM file into buffer and parse it. Arguments:
*
* fp input file.
* buffer buffer to read PEM into.
* info fill this structure with information about PEM structure.
*/
static int
read_pem(FILE *fp, struct nettle_buffer *buffer, struct pem_info *info)
{
int rc;
/* Find start line */
for (;;) {
nettle_buffer_reset(buffer);
rc = read_line(fp, buffer);
if (rc != READ_PEM_OK)
return rc;
if (match_pem_start(buffer->size, buffer->contents,
&info->marker_start,
&info->marker_length) == READ_PEM_OK)
break;
}
buffer->contents[info->marker_start + info->marker_length] = 0;
info->data_start = buffer->size;
for (;;) {
size_t line_start = buffer->size;
if ((rc = read_line(fp, buffer)) != READ_PEM_OK)
return rc;
switch (match_pem_end(buffer->size - line_start,
buffer->contents + line_start,
info->marker_length,
buffer->contents + info->marker_start)) {
case READ_PEM_OK:
info->data_length = line_start - info->data_start;
return READ_PEM_OK;
case READ_PEM_ERROR:
break;
case READ_PEM_EOF:
return READ_PEM_EOF;
}
}
return READ_PEM_ERROR;
}
static inline int
base64_decode_in_place (struct base64_decode_ctx *ctx, size_t *dst_length,
size_t length, uint8_t *data)
{
return base64_decode_update(ctx, dst_length,
data, length,
(const uint8_t *) data);
}
static int
decode_base64(struct nettle_buffer *buffer, size_t start, size_t *length)
{
struct base64_decode_ctx ctx;
base64_decode_init(&ctx);
/* Decode in place */
if (base64_decode_in_place(&ctx, length, *length,
buffer->contents + start)
&& base64_decode_final(&ctx))
return READ_PEM_OK;
return READ_PEM_ERROR;
}
static int
convert_rsa_private_key(uint8_t *buffer, size_t size,
struct rsa_public_key *pub,
struct rsa_private_key *priv)
{
rsa_public_key_init(pub);
rsa_private_key_init(priv);
return rsa_keypair_from_der(pub, priv, 0, size, buffer)
? READ_PEM_OK : READ_PEM_ERROR;
}
/* Read a private key PEM file and return RSA key pair. */
static int
read_keys(FILE *fp, struct rsa_public_key *pub, struct rsa_private_key *priv)
{
struct nettle_buffer buffer;
struct pem_info info;
static char privkey_marker[] = "RSA PRIVATE KEY";
static size_t privkey_marker_length = sizeof(privkey_marker) - 1;
int rc;
nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);//FIXME
rc = read_pem(fp, &buffer, &info);
if (info.marker_length == privkey_marker_length
&& memcmp(buffer.contents + info.marker_start, privkey_marker,
privkey_marker_length) == 0
&& decode_base64(&buffer, info.data_start,
&info.data_length) == READ_PEM_OK
&& convert_rsa_private_key(buffer.contents + info.data_start,
info.data_length,
pub, priv) == READ_PEM_OK)
rc = READ_PEM_OK;
else
rc = READ_PEM_ERROR;
nettle_buffer_clear(&buffer);
return rc;
}
static int
pubkey_from_base64(struct rsa_public_key *pub, const char *str)
{
struct nettle_buffer buffer;
size_t length = strlen(str);
struct asn1_der_iterator i, j;
int result = READ_PEM_ERROR;
nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);
nettle_buffer_write(&buffer, strlen(str), (const uint8_t*) str);
if (decode_base64(&buffer, 0, &length) == READ_PEM_OK
/* SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters OPTIONAL
}
*/
&& asn1_der_iterator_first(&i, length, buffer.contents) == ASN1_ITERATOR_CONSTRUCTED
&& i.type == ASN1_SEQUENCE
&& asn1_der_decode_constructed_last(&i) == ASN1_ITERATOR_CONSTRUCTED
&& i.type == ASN1_SEQUENCE
/* Use the j iterator to parse the algorithm identifier */
&& asn1_der_decode_constructed(&i, &j) == ASN1_ITERATOR_PRIMITIVE
&& j.type == ASN1_IDENTIFIER
&& asn1_der_iterator_next(&i) == ASN1_ITERATOR_PRIMITIVE
&& i.type == ASN1_BITSTRING
/* Use i to parse the object wrapped in the bit string.*/
&& asn1_der_decode_bitstring_last(&i)) {
/* pkcs-1 {
iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1)
pkcs-1(1) modules(0) pkcs-1(1)
}
--
-- When rsaEncryption is used in an AlgorithmIdentifier the
-- parameters MUST be present and MUST be NULL.
--
rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 }
*/
static const uint8_t id_rsaEncryption[9] =
{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
if (j.length == sizeof(id_rsaEncryption)
&& memcmp(j.data, id_rsaEncryption,
sizeof(id_rsaEncryption)) == 0
&& asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE
&& j.type == ASN1_NULL
&& j.length == 0
&& asn1_der_iterator_next(&j) == ASN1_ITERATOR_END) {
rsa_public_key_init(pub);
if (rsa_public_key_from_der_iterator(pub, 0, &i))
result = READ_PEM_OK;
}
}
nettle_buffer_clear(&buffer);
return result;
}
/* State of iteratiom over a colon-separated list of headers. */
struct h_list_buf {
char const *ptr; /* Current position in the header list. */
char *base; /* Return memory pointer. */
size_t size; /* Size of memory allocated for base. */
};
/*
* dkim_header_list_next(SAVE)
* ---------------------------
* Free the state buffer allocated by dkim_header_list_first.
*/
void
dkim_header_list_end(void *save)
{
struct h_list_buf *hbuf = save;
free(hbuf->base);
free(hbuf);
}
/*
* Return next header from the header list, or NULL if the list is exhausted.
* SAVE is the iteration state pointer returned by dkim_header_list_first.
*/
static char *
dkim_header_list_next(void *save)
{
struct h_list_buf *hbuf = save;
char const *hp = hbuf->ptr;
while (*hp && (*hp == ' ' || *hp == '\t' || *hp == ':'))
hp++;
if (*hp) {
size_t len = strcspn(hp, " \t:");
if (len + 1 > hbuf->size) {
hbuf->base = mu_realloc(hbuf->base, len + 1);
hbuf->size = len + 1;
}
memcpy(hbuf->base, hp, len);
hbuf->base[len] = 0;
hbuf->ptr = hp + len;
} else {
hbuf->ptr = hp;
return NULL;
}
return hbuf->base;
}
/*
* Start iteration over a list of header names (H_LIST), delimited by
* colons with optional whitespace around them. Return first header
* name and save the state in the memory location pointed to by SAVE.
* No matter the return value, dkim_header_list_end must be called to
* reclaim the allocated memory.
*/
char *
dkim_header_list_first(char const *h_list, void *save)
{
struct h_list_buf *hbuf, **hbuf_ptr = save;
hbuf = mu_alloc(sizeof(hbuf[0]));
hbuf->ptr = h_list;
hbuf->base = NULL;
hbuf->size = 0;
*hbuf_ptr = hbuf;
return dkim_header_list_next(hbuf);
}
int
dkim_header_list_match(char const *h_list, char const *h)
{
size_t len = strlen (h);
while (*h_list) {
size_t n;
while (*h_list && (*h_list == ' ' || *h_list == '\t'))
h_list++;
if (*h_list == 0)
break;
n = strcspn(h_list, " \t:");
if (n == len && mu_c_strncasecmp (h_list, h, len) == 0)
return 1;
h_list += n;
while (*h_list && (*h_list == ' ' || *h_list == '\t'))
h_list++;
if (*h_list != ':')
break;
++h_list;
}
return 0;
}
/*
* SHA256 hashing functions.
*/
#define BUF_SIZE 1024
/* Hash the contents read from the mailutils stream STR starting from
* the current position and up to the end of file.
*/
static int
hash_stream(mu_stream_t str, struct sha256_ctx *ctx)
{
uint8_t buffer[BUF_SIZE];
size_t count;
int rc;
while ((rc = mu_stream_read(str, buffer, sizeof(buffer), &count)) == 0
&& count > 0) {
// mu_error("READ %*.*s", (int)count, (int)count, buffer);
sha256_update(ctx, count, buffer);
}
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL, rc);
return -1;
}
return 0;
}
/* Hash LEN bytes from the current position in stream STR. */
static int
hash_stream_segment(mu_stream_t str, size_t len, struct sha256_ctx *ctx)
{
while (len) {
uint8_t buffer[BUF_SIZE];
int rc;
size_t n = sizeof(buffer), count;
if (n > len)
n = len;
rc = mu_stream_read(str, buffer, n, &count);
//printf("HASH %*.*s\n",(int)count,(int)count,buffer);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL,
rc);
return -1;
}
if (count == 0) {
mu_error(_("unexpected end of file in canonical stream"));
return -1;
}
sha256_update(ctx, count, buffer);
len -= count;
}
return 0;
}
/* Hash the remaining content of the stream from the current position
* and store a nul-terminated base64 encoded result in outbuf.
* Outbuf must have at least BASE64_ENCODE_RAW_LENGTH(SHA256_DIGEST_SIZE) + 1
* bytes of capacity,
*/
static int
dkim_body_hash(mu_stream_t str, size_t len, uint8_t *outbuf)
{
struct sha256_ctx ctx;
uint8_t body_digest[SHA256_DIGEST_SIZE];
sha256_init(&ctx);
if (len == DKIM_LENGTH_ALL)
hash_stream(str, &ctx);
else
hash_stream_segment(str, len, &ctx);
sha256_digest(&ctx, sizeof(body_digest), body_digest);
base64_encode_raw(outbuf, sizeof(body_digest), body_digest);
return 0;
}
/*
* RSA SHA256 digest.
*/
/* Given the RSA private key and a pointer to SHA256 context, create the
* RSA digest in base64. Return value is stored in RET_B64 (malloced).
*/
int
dkim_rsa_sha256_sign(struct rsa_private_key *priv, struct sha256_ctx *ctx,
uint8_t **ret_b64)
{
int rc;
mpz_t sig;
size_t i;
struct nettle_buffer buffer;
uint8_t *outbuf;
mpz_init(sig);
rc = rsa_sha256_sign(priv, ctx, sig);
if (!rc)
return -1;
nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);//FIXME
for (i = mpz_size(sig); i > 0; i--) {
mp_limb_t limb = mpz_getlimbn(sig, i - 1);
size_t j;
uint8_t *p = nettle_buffer_space(&buffer, sizeof(mp_limb_t))
+ sizeof(mp_limb_t) - 1;
for (j = 0; j < sizeof(mp_limb_t); j++) {
*p-- = limb & 0xff;
limb >>= 8;
}
}
mpz_clear(sig);
outbuf = malloc(BASE64_ENCODE_RAW_LENGTH(buffer.size) + 1);
if (!outbuf) {
nettle_buffer_clear(&buffer);
return -1;
}
base64_encode_raw(outbuf, buffer.size, buffer.contents);
outbuf[BASE64_ENCODE_RAW_LENGTH(buffer.size)] = 0;
nettle_buffer_clear(&buffer);
*ret_b64 = outbuf;
return 0;
}
/*
* Canonicalization names
* re. "DKIM-Signature Canonicalization Header" IANA registry.
*/
static char const *dkim_canon_string[] = { "simple", "relaxed", NULL };
#define DKIM_CANON_STRING_MAX (sizeof(dkim_canon_string[DKIM_CANON_RELAXED])-1)
/*
* Convert STR to a DKIM_CANON_* constant (return DKIM_CANON_ERR on error).
* If ENDP is NULL, STR must be one of the strings from dkim_canon_string.
* Otherwise, STR may be followed by '\0' or '/'. The pointer to that
* character will be returned in the memory location pointed to by ENDP.
*/
int
dkim_str_to_canon_type(char const *str, char **endp)
{
int i;
size_t len = strcspn(str, "/");
if (endp || str[len] == 0) {
for (i = 0; dkim_canon_string[i]; i++)
if (len == strlen(dkim_canon_string[i]) &&
memcmp(str, dkim_canon_string[i], len) == 0) {
if (endp)
*endp = (char*)(str + len);
return i;
}
}
return DKIM_CANON_ERR;
}
/* Format a struct dkim_signature as a DKIM-Signature header. */
#define MAX_LINE_LEN 78
struct dkim_format_buf {
mu_stream_t str;
mu_stream_stat_buffer stat;
int crlf;
};
static inline void
dkim_format_nl(struct dkim_format_buf *fb)
{
if (fb->crlf)
mu_stream_write(fb->str, "\r\n ", 3, NULL);
else
mu_stream_write(fb->str, "\n ", 2, NULL);
fb->stat[MU_STREAM_STAT_OUT] = 0;
}
static inline int
dkim_format_wrap(struct dkim_format_buf *fb, size_t len)
{
if (fb->stat[MU_STREAM_STAT_OUT]
&& fb->stat[MU_STREAM_STAT_OUT] + len > MAX_LINE_LEN) {
dkim_format_nl(fb);
return 1;
}
return 0;
}
static void
dkim_format_tag(struct dkim_format_buf *fb, char const *tag, char const *val)
{
if (!val)
return;
if (!dkim_format_wrap(fb, strlen(tag) + strlen(val) + 2))
dkim_format_wrap(fb, strlen(tag) + 1);
mu_stream_write(fb->str, tag, strlen(tag), NULL);
mu_stream_write(fb->str, "=", 1, NULL);
dkim_format_wrap(fb, strlen(val) + 1);
mu_stream_write(fb->str, val, strlen(val), NULL);
mu_stream_write(fb->str, ";", 1, NULL);
}
static void
dkim_format_tag_base64(struct dkim_format_buf *fb, char const *tag,
char const *val)
{
size_t len = strlen(val);
if (!dkim_format_wrap(fb, strlen(tag) + strlen(val) + 2))
dkim_format_wrap(fb, strlen(tag) + 1);
mu_stream_write(fb->str, tag, strlen(tag), NULL);
mu_stream_write(fb->str, "=", 1, NULL);
while (len > 0) {
size_t n = MAX_LINE_LEN - fb->stat[MU_STREAM_STAT_OUT];
if (len < n)
n = len;
mu_stream_write(fb->str, val, n, NULL);
dkim_format_wrap(fb, 1);
val += n;
len -= n;
}
mu_stream_write(fb->str, ";", 1, NULL);
}
void
dkim_signature_free(struct dkim_signature *sig)
{
free(sig->a);
free(sig->b);
free(sig->bh);
free(sig->d);
free(sig->s);
free(sig->h);
free(sig->i);
free(sig->q);
free(sig->v);
}
/*
* Auxiliary functions for parsing and formatting dkim_signature fields.
*
* Each parser is declared as
* int P(char const *value, void *data)
* Its arguments are:
* value - actual tag value obtained from the header.
* data - pointer to the member of struct dkim_signature.
* The parser returns 0 on success and non-zero on error. It is not supposed
* to emit any diagnostic messages.
*
* Each formatter is declared as
* int F(struct dkim_format_buf *fb, char const *tag, void *data)
* Its arguments are:
* fb - formatting buffer,
* tag - the tag name,
* value - pointer to the member of struct dkim_signature.
*/
/* General purpose parser for char* (or uint8_t*) fields */
static int
dkim_tag_char_parser(char const *value, void *data)
{
char **cptr = data;
*cptr = mu_strdup(value);
return 0;
}
/* General purpose formatter for char* (or uint8_t*) fields */
static void
dkim_tag_char_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
char **cptr = data;
dkim_format_tag(fb, tag, *cptr);
}
/* Formatter for the a= tag */
static void
dkim_tag_a_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
char **cptr = data;
dkim_format_tag(fb, tag, *cptr ? *cptr : DKIM_ALGORITHM);
}
/* Formatter for the q= tag */
static void
dkim_tag_q_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
char **cptr = data;
dkim_format_tag(fb, tag, *cptr ? *cptr : DKIM_QUERY_METHOD);
}
/* Special formatter for the h= tag, that ensures proper wrapping. */
static void
dkim_tag_h_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
char **cptr = data;
void *save;
char const *hval;
if (!dkim_format_wrap(fb, 2))
dkim_format_wrap(fb, strlen(*cptr) + 3);
mu_stream_write(fb->str, "h=", 2, NULL);
if ((hval = dkim_header_list_first(*cptr, &save)) != NULL) {
dkim_format_wrap(fb, strlen(hval));
mu_stream_write(fb->str, hval, strlen(hval), NULL);
while ((hval = dkim_header_list_next(save)) != NULL) {
dkim_format_wrap(fb, strlen(hval) + 1);
mu_stream_write(fb->str, ":", 1, NULL);
mu_stream_write(fb->str, hval, strlen(hval), NULL);
}
}
dkim_header_list_end(save);
dkim_format_wrap(fb, 1);
mu_stream_write(fb->str, ";", 1, NULL);
}
/* Parser and formatter for the c= tag. */
static int
dkim_tag_c_parser(char const *value, void *data)
{
int *canon = data;
char *s;
if ((canon[0] = dkim_str_to_canon_type(value, &s)) == DKIM_CANON_ERR)
return -1;
if (*s == 0)
canon[1] = canon[0];
else if (*s != '/' ||
(canon[1] = dkim_str_to_canon_type(s + 1, NULL)) == DKIM_CANON_ERR)
return -1;
return 0;
}
static void
dkim_tag_c_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
int *canon = data;
char v[(DKIM_CANON_STRING_MAX+1)*2];
strcpy(v, dkim_canon_string[canon[0]]);
strcat(v, "/");
strcat(v, dkim_canon_string[canon[1]]);
dkim_format_tag(fb, tag, v);
}
/* General-purpose parser and formatter for time_t members (t and x) */
static int
dkim_tag_time_parser(char const *value, void *data)
{
time_t *tptr = data;
unsigned long n;
char *p;
errno = 0;
n = strtoul(value, &p, 10);
if (errno || *p)
return -1;
*tptr = n;
return 0;
}
static void
dkim_tag_time_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
time_t *tptr = data;
if (*tptr) {
char tbuf[80];
snprintf(tbuf, sizeof(tbuf), "%lu", (long unsigned) *tptr);
dkim_format_tag(fb, tag, tbuf);
}
}
/* Parser and formatter for the l= tag */
static int
dkim_tag_l_parser(char const *value, void *data)
{
size_t *sptr = data;
unsigned long n;
char *endp;
errno = 0;
n = strtoul(value, &endp, 10);
if (errno || *endp)
return -1;
*sptr = n;
return 0;
}
static void
dkim_tag_l_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
size_t *sptr = data;
char tbuf[80];
if (*sptr != DKIM_LENGTH_ALL) {
snprintf(tbuf, sizeof(tbuf), "%zu", *sptr);
dkim_format_tag(fb, tag, tbuf);
}
}
/* Formatter for the bh= tag. */
static void
dkim_tag_bh_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
char **sptr = data;
dkim_format_tag_base64(fb, tag, *sptr);
}
/* Formatter for the b= tag. */
static void
dkim_tag_b_formatter(struct dkim_format_buf *fb,
char const *tag,
void *data)
{
char **sptr = data;
dkim_format_nl(fb);
dkim_format_tag_base64(fb, tag, *sptr ? *sptr : "");
}
/* Tag definition structure */
struct dkim_tag_descr {
char *tag; /* Tag name. */
size_t off; /* Field offset in struct dkim_signature. */
int (*parser)(char const *, void *);
/* Parser function. */
void (*formatter)(struct dkim_format_buf *, char const *, void *);
/* Formatter function. */
};
/* Order of entries in this array defines the order in which tags are
formatted.
*/
static struct dkim_tag_descr tag_descr[] = {
{
"v",
offsetof(struct dkim_signature, v),
dkim_tag_char_parser,
dkim_tag_char_formatter,
},
{
"a",
offsetof(struct dkim_signature, a),
dkim_tag_char_parser,
dkim_tag_a_formatter
},
{
"d",
offsetof(struct dkim_signature, d),
dkim_tag_char_parser,
dkim_tag_char_formatter,
},
{
"s",
offsetof(struct dkim_signature, s),
dkim_tag_char_parser,
dkim_tag_char_formatter,
},
{
"c",
offsetof(struct dkim_signature, canon),
dkim_tag_c_parser,
dkim_tag_c_formatter
},
{
"q",
offsetof(struct dkim_signature, q),
dkim_tag_char_parser,
dkim_tag_q_formatter
},
{
"h",
offsetof(struct dkim_signature, h),
dkim_tag_char_parser,
dkim_tag_h_formatter,
},
{
"i",
offsetof(struct dkim_signature, i),
dkim_tag_char_parser,
dkim_tag_char_formatter,
},
{
"l",
offsetof(struct dkim_signature, l),
dkim_tag_l_parser,
dkim_tag_l_formatter
},
{
"t",
offsetof(struct dkim_signature, t),
dkim_tag_time_parser,
dkim_tag_time_formatter
},
{
"x",
offsetof(struct dkim_signature, x),
dkim_tag_time_parser,
dkim_tag_time_formatter
},
{
"bh",
offsetof(struct dkim_signature, bh),
dkim_tag_char_parser,
dkim_tag_bh_formatter
},
{
"b",
offsetof(struct dkim_signature, b),
dkim_tag_char_parser,
dkim_tag_b_formatter
},
{ NULL }
};
/* Table-driven DKIM-Signature parser. */
int
dkim_signature_parse(char *str, struct dkim_signature *ret_sig)
{
struct mu_wordsplit ws;
int i;
int rc = 0;
struct dkim_signature sig;
/*
* tag-list = tag-spec *( ";" tag-spec ) [ ";" ]
* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS]
*
* The mu_wordsplit call splits the value on ';' and removes
* whitespace at both sides of each token. Internal FWS (around
* the equals sign) is removed later.
*/
ws.ws_delim = ";";
rc = mu_wordsplit(str,
&ws,
MU_WRDSF_DELIM |
MU_WRDSF_NOVAR |
MU_WRDSF_WS |
MU_WRDSF_NOCMD);
if (rc) {
mu_wordsplit_free(&ws);
return rc;
}
memset(&sig, 0, sizeof(sig));
sig.canon[0] = sig.canon[1] = DKIM_CANON_SIMPLE;
sig.l = DKIM_LENGTH_ALL;
for (i = 0; i < ws.ws_wordc; i++) {
struct dkim_tag_descr *tg;
char *k = ws.ws_wordv[i];
char *p = strchr(k, '=');
if (!p) {
rc = -1;
goto end;
}
*p++ = 0;
/* Remove internal FWS */
mu_rtrim_class(k, MU_CTYPE_BLANK);
mu_ltrim_class(p, MU_CTYPE_BLANK);
/* Handle known tags */
for (tg = tag_descr; tg->tag; tg++) {
if (strcmp(k, tg->tag) == 0) {
rc = tg->parser(p, (char*)&sig + tg->off);
if (rc)
goto end;
break;
}
}
}
end:
mu_wordsplit_free(&ws);
if (rc)
dkim_signature_free(&sig);
else
*ret_sig = sig;
return rc;
}
/*
* Table-driven DKIM-Signature formatter.
* The CRLF parameter defines what delimiter to use for wrapping.
*/
int
dkim_signature_format(struct dkim_signature *sig, int crlf, char **result)
{
struct dkim_format_buf fb;
int rc;
static char header_field[] = DKIM_SIGNATURE_HEADER ": ";
mu_off_t off;
size_t size;
char *text;
struct dkim_tag_descr *tg;
/* Initialize format buffer */
fb.crlf = crlf;
rc = mu_memory_stream_create(&fb.str, MU_STREAM_RDWR);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR,
"mu_memory_stream_create",
NULL, rc);
return rc;
}
mu_stream_set_stat(fb.str,
MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT),
fb.stat);
mu_stream_write(fb.str, header_field, strlen(header_field), NULL);
for (tg = tag_descr; tg->tag; tg++) {
tg->formatter(&fb, tg->tag, ((char*)sig + tg->off));
}
if (mu_stream_err(fb.str)) {
rc = mu_stream_last_error(fb.str);
mu_diag_funcall(MU_DIAG_ERROR,
"dkim_signature_format", NULL, rc);
} else {
rc = 0;
mu_stream_seek(fb.str, 0, MU_SEEK_CUR, &off);
//FIXME: assert(size < (size_t)~0)
size = off;
text = mu_alloc(size + 1);
mu_stream_seek(fb.str, 0, MU_SEEK_SET, NULL);
mu_stream_read(fb.str, text, size, NULL);
text[size] = 0;
*result = text;
}
mu_stream_destroy(&fb.str);
return rc;
}
/* Canonicalize the message into a stream.
* Arguments:
*
* msg input message.
* canon canonicalization algorithms for header and body.
* canon_str output stream.
*/
int
canonicalize(mu_message_t msg, int canon[2], mu_stream_t *canon_str)
{
mu_stream_t mstr, flt, in;
int rc;
rc = mu_temp_file_stream_create(&mstr, NULL, 0);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR,
"mu_temp_file_stream_create",
NULL, rc);
return -1;
}
rc = mu_message_get_streamref(msg, &in);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR,
"mu_message_get_streamref",
NULL, rc);
mu_stream_destroy(&mstr);
return -1;
}
rc = dkim_canonicalizer_create(&flt, in,
canon[0],
canon[1],
MU_STREAM_READ);
mu_stream_unref(in);
if (rc) {
mu_error("dkim_canonicalizer_create: %s",
mu_strerror(rc));
mu_stream_destroy(&mstr);
return -1;
}
in = flt;
rc = mu_filter_create(&flt, in, "CRLF",
MU_FILTER_ENCODE, MU_STREAM_READ);
mu_stream_unref(in);
if (rc) {
mu_error("mu_filter_stream_create: %s", mu_strerror(rc));
mu_stream_destroy(&mstr);
return -1;
}
rc = mu_stream_copy(mstr, flt, 0, NULL);
mu_stream_unref(flt);
if (rc) {
mu_error("mu_stream_copy: %s", mu_strerror(rc));
mu_stream_destroy(&mstr);
return -1;
}
mu_stream_seek(mstr, 0, MU_SEEK_SET, NULL);
*canon_str = mstr;
return 0;
}
/*
* Header maps.
*
* Message headers form a doubly-linked list of struct header_map
* pointers. The list itself is referenced by its head structure,
* which is a regular header_map where only prev and next pointers
* are valid. In a header, next points to the first and prev to the
* last element in the list.
*/
struct header_map {
struct header_map *prev, *next; /* List of elements */
char *header; /* Header name */
mu_off_t start; /* Header start offset */
mu_off_t end; /* Header end offset (points past the
final CRLF) */
};
/* Header_map is initialized using this macro. */
#define HEADER_MAP_INITIALIZER(h) { &(h), &(h) }
/* Remove the header_map from the list */
static inline void
header_map_remove(struct header_map *hp)
{
hp->prev->next = hp->next;
hp->next->prev = hp->prev;
}
/* Insert header_map B to the list after A. */
static inline void
header_map_insert_after(struct header_map *a, struct header_map *b)
{
a->next->prev = b;
b->next = a->next;
a->next = b;
b->prev = a;
}
/* Append header_map to the end of the list. */
static inline void
header_map_append(struct header_map *link, struct header_map *hp)
{
header_map_insert_after(link->prev, hp);
}
/* Free the list. List head is not freed. */
static inline void
header_map_free(struct header_map *link)
{
struct header_map *hmap = link->next;
while (hmap != link) {
header_map_remove(hmap);
free(hmap);
hmap = link->next;
}
}
/*
* Two iterators.
*
* Arguments:
* link the head element of the list.
* h iteration variable
*/
/* Iterate over the list in natural direction. */
#define HEADER_MAP_FOREACH(h,link) \
for (h = (link)->next; h != (link); h = (h)->next)
/* Iterate over the list in reverse direction. */
#define HEADER_MAP_FOREACH_REV(h,link) \
for (h = (link)->prev; h != (link); h = (h)->prev)
/* Return true if a message can have multiple instances of the named header.
*
* RFC 6376 mandates that such headers be processed in reverse order.
* Refer to subsection 5.4.2. "Signatures Involving Multiple Instances of
* a Field" on page 41 for details.
*/
static int
is_rev_header(char const *name)
{
static char *rev_headers[] = {
"Received",
DKIM_SIGNATURE_HEADER,
"Resent-*",
NULL
};
int i;
for (i = 0; rev_headers[i]; i++) {
if (mu_imap_wildmatch_ci(rev_headers[i], name, ':') == 0)
return 1;
}
return 0;
}
static int
dkim_tag_find(char const *sigstr, char const *tag, size_t *ret_len)
{
int i;
size_t tag_len = strlen(tag);
size_t sig_len = strlen(sigstr);
for (i = 0; i + tag_len + 1 < sig_len; i++) {
if (!(sigstr[i] == ' ' || sigstr[i] == '\t' ||
sigstr[i] == '\r' || sigstr[i] == '\n')) {
size_t n = strcspn(sigstr + i, ";");
if (memcmp(sigstr + i, tag, tag_len) == 0 &&
sigstr[i + tag_len] == '=') {
*ret_len = n - tag_len - 1;
return i;
}
i += n;
}
}
return -1;
}
enum {
DKIM_HASH_ERR = -1,
DKIM_HASH_OK,
DKIM_HASH_DIFF
};
#define BH_SIZE (BASE64_ENCODE_RAW_LENGTH(SHA256_DIGEST_SIZE))
/*
* dkim_hash(MSG, SIG, SIGSTR, CTX)
* --------------------------------
* Compute a message hash of MSG as per RFC 6376 section 3.7.
*
* Parameters:
* MSG - (input) message
* SIG - (input/output) parsed out DKIM signature
* SIGSTR - (input) original value of the DKIM signature header.
* CTX - (output) SHA256 context to leave the hash in.
*
* The function is used both for message signing and verification.
*
* When signing, SIGSTR is NULL. Before return, the malloced copy
* of the computed body hash is left in SIG->bh. The caller is
* responsible for freeing it when no longer needed. In this mode,
* the function returns DKIM_HASH_OK on success and DKIM_HASH_ERR
* on error.
*
* When verifying, SIGSTR is not NULL and SIG is the validated broken
* out DKIM signature obtained from SIGSTR. In this case, the function
* does not modify SIG in any way. Instead, it checks whether the computed
* body hash matches SIG->bh and returns DKIM_HASH_DIFF if it does not.
*/
static int
dkim_hash(mu_message_t msg, struct dkim_signature *sig, char const *sigstr,
struct sha256_ctx *ctx)
{
mu_stream_t canon_stream = NULL;
char *hp;
void *hstate;
struct header_map h_all = HEADER_MAP_INITIALIZER(h_all);
struct header_map h_sel = HEADER_MAP_INITIALIZER(h_sel);
struct header_map *hmap;
uint8_t bh[BH_SIZE];
mu_opool_t op = NULL;
char c;
int rc;
int result = DKIM_HASH_ERR;
size_t count;
enum { H_INIT, H_HEADER, H_CR1, H_CR2, H_NL } state = H_INIT;
mu_stream_t sigcanon, str;
char *sig_str_buf;
/* Create a canonical representation of the message */
if (canonicalize(msg, sig->canon, &canon_stream))
goto err;
/*
* Scan the header part of the canonicalized stream and record
* the headers in the h_all list.
*
* Header names its elements refer to are stored in the object
* pool.
*/
mu_opool_create(&op, MU_OPOOL_DEFAULT);
hmap = NULL;
while ((rc = mu_stream_read(canon_stream, &c, 1, &count)) == 0 &&
count == 1) {
switch (state) {
case H_INIT:
if (c == ':') {
c = 0;
mu_opool_append(op, &c, 1);
hmap = calloc(1, sizeof(hmap[0]));
hmap->header = mu_opool_finish(op, NULL);
mu_strlower(hmap->header);
mu_stream_seek(canon_stream, 0, MU_SEEK_CUR,
&hmap->start);
hmap->start -= strlen(hmap->header) + 1;
header_map_append(&h_all, hmap);
state = H_HEADER;
} else
mu_opool_append(op, &c, 1);
break;
case H_HEADER:
if (c == '\r')
state = H_CR1;
break;
case H_CR1:
if (c == '\n')
state = H_NL;
break;
case H_NL:
if (mu_isblank(c))
state = H_HEADER;
else {
mu_stream_seek(canon_stream, 0, MU_SEEK_CUR,
&hmap->end);
hmap->end--;
if (c == '\r')
state = H_CR2;
else {
state = H_INIT;
mu_opool_append(op, &c, 1);
}
}
break;
case H_CR2:
if (c != '\n') {
goto badstream;
}
goto end;
}
}
/* Error exit from the above loop */
if (rc)
mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL, rc);
badstream:
mu_error(_("malformed canonical stream"));
goto err;
end:
/*
* Scanning terminated successfully. Current position in the
* canon_stream is left at the start of the body, which will come
* handy later.
*
* Select headers to hash according to the h= tag. Selected headers
* are removed from the h_all and added to the h_sel list.
*/
for (hp = dkim_header_list_first(sig->h, &hstate); hp;
hp = dkim_header_list_next(hstate)) {
if (is_rev_header(hp)) {
HEADER_MAP_FOREACH_REV(hmap, &h_all) {
if (mu_c_strcasecmp(hmap->header, hp) == 0) {
header_map_remove(hmap);
header_map_append(&h_sel, hmap);
break;
}
}
} else {
HEADER_MAP_FOREACH(hmap, &h_all) {
if (mu_c_strcasecmp(hmap->header, hp) == 0) {
header_map_remove(hmap);
header_map_append(&h_sel, hmap);
break;
}
}
}
}
dkim_header_list_end(hstate);
/* Hash the body */
if (dkim_body_hash(canon_stream, sig->l, bh))
goto err;
if (sig->bh) {
if (memcmp(sig->bh, bh, BH_SIZE)) {
result = DKIM_HASH_DIFF;
goto err;
}
} else {
sig->bh = malloc(BH_SIZE + 1);
if (!sig->bh)
goto err;
memcpy(sig->bh, bh, BH_SIZE);
sig->bh[BH_SIZE] = 0;
}
/* Hash the selected headers */
HEADER_MAP_FOREACH(hmap, &h_sel) {
mu_stream_seek(canon_stream, hmap->start, MU_SEEK_SET, NULL);
hash_stream_segment(canon_stream, hmap->end - hmap->start,
ctx);
}
/* Add to the hash the DKIM-Signature header with empty b= tag. */
if (sigstr) {
size_t len, blen;
char *vp;
int n = dkim_tag_find(sigstr, "b", &blen);
if (n == -1)
goto err;
len = strlen(sigstr);
sig_str_buf = malloc(sizeof(DKIM_SIGNATURE_HEADER) + 1
+ len - blen + 1);
if (!sig_str_buf)
goto err;
strcpy(sig_str_buf, DKIM_SIGNATURE_HEADER ": ");
vp = sig_str_buf + sizeof(DKIM_SIGNATURE_HEADER) + 1;
memcpy(vp, sigstr, n);
memcpy(vp + n, "b=", 2);
strcpy(vp + n + 2, sigstr + n + 2 + blen);
} else {
dkim_signature_format(sig, 0, &sig_str_buf);
}
mu_fixed_memory_stream_create(&str, sig_str_buf, strlen(sig_str_buf),
MU_STREAM_RDWR|MU_STREAM_SEEK);
rc = dkim_canonicalizer_create(&sigcanon, str,
sig->canon[0], sig->canon[1],
MU_STREAM_READ);
mu_stream_unref(str);
if (rc) {
mu_error("dkim_canonicalizer_create: %s", mu_strerror(rc));
goto err;
}
rc = mu_filter_create(&str, sigcanon, "CRLF", MU_FILTER_ENCODE,
MU_STREAM_READ);
mu_stream_unref(sigcanon);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR,
"mu_filter_stream_create", NULL, c);
goto err;
}
sigcanon = str;
hash_stream(sigcanon, ctx);
mu_stream_unref(sigcanon);
free(sig_str_buf);
result = DKIM_HASH_OK;
err:
/* Reclaim the allocated memory. */
mu_opool_destroy(&op);
header_map_free(&h_all);
header_map_free(&h_sel);
mu_stream_destroy(&canon_stream);
return result;
}
/* Sign the message. Arguments:
*
* msg message to sign.
* sig initialized struct dkim_signature.
* priv_file name of a disk file with the RSA private key in PEM format.
* ret_sighdr return pointer.
*
* On success, a malloced copy of DKIM-Signature header line will be stored
* in ret_sighdr and 0 will be returned.
*
* Side effects: sig->bh is filled with SHA256 digest of the message body.
*/
int
mfd_dkim_sign(mu_message_t msg, struct dkim_signature *sig,
char *priv_file,
char **ret_sighdr)
{
int rc;
struct rsa_private_key priv;
struct rsa_public_key pub;
FILE *fp;
struct sha256_ctx ctx;
int result = -1;
fp = fopen(priv_file, "r");
if (!fp) {
mu_error(_("can't open %s: %s"), priv_file, strerror(errno));
return -1;
}
rc = read_keys(fp, &pub, &priv);
fclose(fp);
if (rc != READ_PEM_OK) {
mu_error(_("can't read private key from %s: %s"),
priv_file, strerror(errno));
return -1;
}
rsa_public_key_clear(&pub);
sha256_init(&ctx);
if (dkim_hash(msg, sig, NULL, &ctx) == DKIM_HASH_OK) {
/* Create the RSA-SHA256 signature in b */
dkim_rsa_sha256_sign(&priv, &ctx, &sig->b);
/* Create the header */
dkim_signature_format(sig, 1, ret_sighdr);
result = 0;
}
/* Reclaim the allocated memory. */
free(sig->b);
sig->b = NULL;
free(sig->bh);
sig->bh = NULL;
rsa_private_key_clear(&priv);
return result;
}
char const *dkim_explanation_str[] = {
[DKIM_EXPL_OK] = "DKIM verification passed",
[DKIM_EXPL_NO_SIG] = "No DKIM signature",
[DKIM_EXPL_INTERNAL_ERROR] = "internal error",
[DKIM_EXPL_SIG_SYNTAX] = "signature syntax error",
[DKIM_EXPL_SIG_MISS] = "signature is missing required tag",
[DKIM_EXPL_DOMAIN_MISMATCH] = "domain mismatch",
[DKIM_EXPL_BAD_VERSION] = "incompatible version",
[DKIM_EXPL_BAD_ALGORITHM] = "unsupported algorithm",
[DKIM_EXPL_BAD_QUERY] = "unsupported query method",
[DKIM_EXPL_FROM] = "From field not signed",
[DKIM_EXPL_EXPIRED] = "signature expired",
[DKIM_EXPL_DNS_UNAVAIL] = "public key unavailable",
[DKIM_EXPL_DNS_NOTFOUND] = "public key not found",
[DKIM_EXPL_KEY_SYNTAX] = "key syntax error",
[DKIM_EXPL_KEY_REVOKED] = "key revoked",
[DKIM_EXPL_BAD_BODY] = "body hash did not verify",
[DKIM_EXPL_BAD_BASE64] = "can't decode b= tag",
[DKIM_EXPL_BAD_SIG] = "signature did not verify",
};
int dkim_result_trans[] = {
[DKIM_EXPL_OK] = DKIM_VERIFY_OK,
[DKIM_EXPL_BAD_ALGORITHM] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_BAD_BASE64] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_BAD_BODY] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_BAD_SIG] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_BAD_QUERY] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_BAD_VERSION] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_DNS_NOTFOUND] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_DOMAIN_MISMATCH] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_EXPIRED] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_FROM] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_KEY_REVOKED] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_KEY_SYNTAX] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_SIG_MISS] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_SIG_SYNTAX] = DKIM_VERIFY_PERMFAIL,
[DKIM_EXPL_NO_SIG] = DKIM_VERIFY_TEMPFAIL,
[DKIM_EXPL_DNS_UNAVAIL] = DKIM_VERIFY_TEMPFAIL,
[DKIM_EXPL_INTERNAL_ERROR] = DKIM_VERIFY_TEMPFAIL,
};
static int
dkim_sig_validate(struct dkim_signature *sig)
{
if (!sig->a
|| !sig->b
|| !sig->bh
|| !sig->d
|| !sig->h
|| !sig->s
|| !sig->v) {
return DKIM_EXPL_SIG_MISS;
}
if (strcmp(sig->v, DKIM_VERSION))
return DKIM_EXPL_BAD_VERSION;
if (strcmp(sig->a, DKIM_ALGORITHM))
return DKIM_EXPL_BAD_ALGORITHM;
if (!sig->q)
sig->q = mu_strdup(DKIM_QUERY_METHOD);
else if (strcmp(sig->q, DKIM_QUERY_METHOD))
return DKIM_EXPL_BAD_QUERY;
if (sig->i) {
char *p = strchr(sig->i, '@');
size_t ilen, dlen;
if (!p)
return DKIM_EXPL_SIG_SYNTAX;
p++;
ilen = strlen(p);
dlen = strlen(sig->d);
if (!(dlen <= ilen &&
mu_c_strcasecmp(sig->d, p + ilen - dlen) == 0 &&
(p[ilen - dlen - 1] == '.' || p[ilen - dlen - 1] == '@')))
return DKIM_EXPL_DOMAIN_MISMATCH;
}
if (!dkim_header_list_match(sig->h, MU_HEADER_FROM))
return DKIM_EXPL_FROM;
if (sig->x && time(NULL) > sig->x)
return DKIM_EXPL_EXPIRED;
return DKIM_EXPL_OK;
}
static int
dnsrec_parse(char *rec, mu_assoc_t *pa)
{
mu_assoc_t a;
struct mu_wordsplit ws;
int result;
if (mu_assoc_create (&a, 0))
mu_alloc_die ();
mu_assoc_set_destroy_item (a, mu_list_free_item);
ws.ws_delim = ";";
if (mu_wordsplit(rec,
&ws,
MU_WRDSF_DELIM |
MU_WRDSF_NOVAR |
MU_WRDSF_WS |
MU_WRDSF_NOCMD)) {
result = 1;
} else {
size_t i;
for (i = 0; i < ws.ws_wordc; i++) {
char *p = strchr(ws.ws_wordv[i], '=');
char **slot;
int rc;
if (!p) {
result = 1;
break;
}
*p++ = 0;
mu_rtrim_class(ws.ws_wordv[i], MU_CTYPE_BLANK);
mu_ltrim_class(p, MU_CTYPE_BLANK);
rc = mu_assoc_install_ref(a, ws.ws_wordv[i], &slot);
if (rc == ENOMEM)
mu_alloc_die ();
else if (rc) {
result = 1;
break;
} else
result = 0;
*slot = mu_strdup(p);
}
}
mu_wordsplit_free(&ws);
if (result)
mu_assoc_destroy (&a);
else
*pa = a;
return result;
}
static int
pubkey_validate(mu_assoc_t a, struct dkim_signature const *sig)
{
char *s;
size_t n;
if ((s = mu_assoc_get(a, "v")) != NULL &&
strcmp(s, DKIM_KEYRECORD_VERSION))
return DKIM_EXPL_KEY_SYNTAX;
if ((s = mu_assoc_get(a, "p")) == NULL)
return DKIM_EXPL_KEY_SYNTAX;
if (s[0] == 0)
return DKIM_EXPL_KEY_REVOKED;
n = strcspn(sig->a, "-");
if ((s = mu_assoc_get(a, "k")) != NULL &&
!(n == strlen(s) && memcmp(s, sig->a, n) == 0))
return DKIM_EXPL_BAD_ALGORITHM;
if ((s = mu_assoc_get(a, "h")) != NULL &&
!dkim_header_list_match(s, sig->a + n + 1))
return DKIM_EXPL_BAD_ALGORITHM;
return DKIM_EXPL_OK;
}
static int
dkim_sig_key_verify(mu_message_t msg, struct dkim_signature *sig,
struct rsa_public_key *pub)
{
struct sha256_ctx ctx;
mpz_t bs;
int rc;
struct nettle_buffer buffer;
size_t length;
mu_header_t hdr;
char const *sigstr;
rc = mu_message_get_header(msg, &hdr);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR,
"mu_message_get_header", NULL, rc);
return DKIM_EXPL_INTERNAL_ERROR;
}
rc = mu_header_sget_value(hdr, DKIM_SIGNATURE_HEADER, &sigstr);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR,
"mu_header_sget_value",
DKIM_SIGNATURE_HEADER, rc);
return DKIM_EXPL_INTERNAL_ERROR;
}
sha256_init(&ctx);
switch (dkim_hash(msg, sig, sigstr, &ctx)) {
case DKIM_HASH_OK:
break;
case DKIM_HASH_DIFF:
return DKIM_EXPL_BAD_BODY;
case DKIM_HASH_ERR:
return DKIM_EXPL_INTERNAL_ERROR;
}
length = strlen((char*)sig->b);
nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);
nettle_buffer_write(&buffer, length, sig->b);
if (decode_base64(&buffer, 0, &length) != READ_PEM_OK)
return DKIM_EXPL_BAD_BASE64;
nettle_mpz_init_set_str_256_u(bs, length, buffer.contents);
rc = rsa_sha256_verify (pub, &ctx, bs);
nettle_buffer_clear(&buffer);
mpz_clear(bs);
return rc ? DKIM_EXPL_OK : DKIM_EXPL_BAD_SIG;
}
static int
dkim_sig_verify(mu_message_t msg, struct dkim_signature *sig)
{
char **dnsrec;
int i;
int result;
/* Get the DKIM DNS record */
switch (dkim_lookup(sig->d, sig->s, &dnsrec)) {
case dns_success:
break;
case dns_not_found:
return DKIM_EXPL_DNS_NOTFOUND;
default:
return DKIM_EXPL_DNS_UNAVAIL;
}
for (i = 0; dnsrec[i]; i++) {
mu_assoc_t a;
struct rsa_public_key pub;
int rc;
if (dnsrec_parse(dnsrec[i], &a))
continue;
if ((rc = pubkey_validate(a, sig)) != DKIM_EXPL_OK) {
result = rc;
} else if (pubkey_from_base64(&pub, mu_assoc_get(a, "p"))
!= READ_PEM_OK) {
result = DKIM_EXPL_KEY_SYNTAX;
} else {
result = dkim_sig_key_verify(msg, sig, &pub);
rsa_public_key_clear(&pub);
}
mu_assoc_destroy(&a);
if (result == DKIM_EXPL_OK)
break;
}
for (i = 0; dnsrec[i]; i++)
free(dnsrec[i]);
free(dnsrec);
return result;
}
static void
wselim(char *s)
{
char *p = s; /* current destination pointer */
char *q = s; /* q - current source pointer */
while (*q) {
size_t len = strcspn(q, " \t");
if (p != q)
memmove(p, q, len);
p += len;
q += len;
q += strspn(q, " \t");
}
*p = 0;
}
int
mfd_dkim_verify(mu_message_t msg, char **ret_sig)
{
mu_header_t hdr;
int rc;
int result = DKIM_EXPL_NO_SIG;
size_t i;
/* Get the DKIM-Signature header from the message */
rc = mu_message_get_header(msg, &hdr);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR,
"mu_message_get_header", NULL, rc);
return DKIM_EXPL_INTERNAL_ERROR;
}
for (i = 1; result != DKIM_EXPL_OK; i++) {
struct dkim_signature sig;
char *sig_str;
rc = mu_header_aget_value_unfold_n(hdr,
DKIM_SIGNATURE_HEADER,
i,
&sig_str);
if (rc == MU_ERR_NOENT)
break;
else if (rc) {
mu_diag_funcall(MU_DIAG_ERROR,
"mu_header_aget_value_unfold",
NULL, rc);
result = DKIM_EXPL_INTERNAL_ERROR;
break;
}
wselim(sig_str);
/* Parse the DKIM signature */
if (dkim_signature_parse(sig_str, &sig)) {
result = DKIM_EXPL_SIG_SYNTAX;
} else {
/* Validate the signature */
result = dkim_sig_validate(&sig);
if (result == DKIM_EXPL_OK)
result = dkim_sig_verify(msg, &sig);
if (result == DKIM_EXPL_OK && ret_sig) {
*ret_sig = sig_str;
sig_str = NULL;
}
dkim_signature_free(&sig);
}
free(sig_str);
}
return result;
}