diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2020-05-26 01:58:50 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2020-05-26 18:35:08 +0300 |
commit | a42c94f08bda3fba17b8d2d46d6675aea063eeed (patch) | |
tree | 180383c8a273097bd29673535b5465b5c07cd9c1 /src | |
parent | 25bb240e22f66276efc75a461c3261c999054b6f (diff) | |
download | mailfromd-a42c94f08bda3fba17b8d2d46d6675aea063eeed.tar.gz mailfromd-a42c94f08bda3fba17b8d2d46d6675aea063eeed.tar.bz2 |
Implementation of DKIM signing
* configure.ac: Detect presence of nettle libraries.
* src/Makefile.am: Add new sources.
* src/dkim-canonicalize.c: New file.
* src/dkim.c: New file.
* src/dkim.h: New file.
* src/builtin/Makefile.am: Add new sources.
* src/builtin/body.bi (current_message): Rewrite as a simple
wrapper over bi_get_current_message.
* src/builtin/dkim.bi: New file.
* src/builtin/msg.bi (bi_get_current_message): Optionally return
the message itself.
* src/builtin/msg.h (bi_get_current_message(: Change signature.
* src/builtin/snarf.m4 (env_get_stream): Fix quoting.
* NEWS: Document changes.
* doc/functions.texi: Document the dkim_sign function
* doc/mailfromd.texi: Minor changes.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 10 | ||||
-rw-r--r-- | src/builtin/.gitignore | 1 | ||||
-rw-r--r-- | src/builtin/Makefile.am | 4 | ||||
-rw-r--r-- | src/builtin/body.bi | 15 | ||||
-rw-r--r-- | src/builtin/dkim.bi | 93 | ||||
-rw-r--r-- | src/builtin/msg.bi | 26 | ||||
-rw-r--r-- | src/builtin/msg.h | 2 | ||||
-rw-r--r-- | src/builtin/snarf.m4 | 2 | ||||
-rw-r--r-- | src/dkim-canonicalize.c | 344 | ||||
-rw-r--r-- | src/dkim.c | 816 | ||||
-rw-r--r-- | src/dkim.h | 62 | ||||
-rw-r--r-- | src/srvcfg.c | 1 |
12 files changed, 1356 insertions, 20 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 7170b9d3..963f63ed 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -41,6 +41,13 @@ mailfromd_SOURCES = \ stack.c\ symbols.c +if NETTLE_COND + mailfromd_SOURCES += \ + dkim.c\ + dkim.h\ + dkim-canonicalize.c +endif + mailfromd_LDADD = \ ./libcallout.a\ builtin/libbuiltin.a\ @@ -49,7 +56,8 @@ mailfromd_LDADD = \ $(MAILUTILS_LIBS)\ $(MILTER)\ $(GEOIP_LIBS)\ - $(DSPAM_LIBS) + $(DSPAM_LIBS)\ + $(NETTLE_LIBS) noinst_HEADERS = \ bitmask.h\ diff --git a/src/builtin/.gitignore b/src/builtin/.gitignore index 6f73f451..c78ef1f5 100644 --- a/src/builtin/.gitignore +++ b/src/builtin/.gitignore @@ -6,6 +6,7 @@ ctype.c curhdr.c db.c debug.c +dkim.c dns.c dspam.c email.c diff --git a/src/builtin/Makefile.am b/src/builtin/Makefile.am index 32bcedc9..d8c4fe30 100644 --- a/src/builtin/Makefile.am +++ b/src/builtin/Makefile.am @@ -57,6 +57,10 @@ BI_FILES=\ vars.bi\ qrnt.bi +if NETTLE_COND + BI_FILES += dkim.bi +endif + libbuiltin_a_SOURCES = builtin.c $(BI_FILES:.bi=.c) EXTRA_DIST = \ diff --git a/src/builtin/body.bi b/src/builtin/body.bi index f8466833..09616f8b 100644 --- a/src/builtin/body.bi +++ b/src/builtin/body.bi @@ -38,21 +38,10 @@ END /* number current_message() */ MF_STATE(eom) -MF_CAPTURE(mstr) +MF_CAPTURE MF_DEFUN(current_message, NUMBER) { - mu_message_t msg; - int rc; - - rc = bi_get_current_message(env); - if (rc < 0) { - msg = MF_STREAM_TO_MESSAGE(mstr); - rc = bi_message_register(env, NULL, msg, MF_MSG_CURRENT); - MF_ASSERT(rc >= 0, - mfe_failure, - _("no more message slots available")); - } - MF_RETURN(rc); + MF_RETURN(bi_get_current_message(env, NULL)); } END diff --git a/src/builtin/dkim.bi b/src/builtin/dkim.bi new file mode 100644 index 00000000..b30c4b97 --- /dev/null +++ b/src/builtin/dkim.bi @@ -0,0 +1,93 @@ +/* This file is part of Mailfromd. -*- c -*- + 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 <http://www.gnu.org/licenses/>. */ + +MF_BUILTIN_MODULE + +#include "dkim.h" +#include "msg.h" + +MF_STATE(eom) +MF_CAPTURE +MF_DEFUN(dkim_sign, VOID, STRING d, STRING s, STRING keyfile, +OPTIONAL, +STRING canon_h, STRING canon_b, STRING headers) +{ + struct dkim_signature sig = { + .d = d, + .s = s, + .canon = { DKIM_CANON_SIMPLE, DKIM_CANON_SIMPLE } + }; + static char default_headers[] = + "From:From:" + "Reply-To:Reply-To:" + "Subject:Subject:" + "Date:Date:" + "To:" + "Cc:" + "Resent-Date:" + "Resent-From:" + "Resent-To:" + "Resent-Cc:" + "In-Reply-To:" + "References:" + "List-Id:" + "List-Help:" + "List-Unsubscribe:" + "List-Subscribe:" + "List-Post:" + "List-Owner:" + "List-Archive"; + mu_message_t msg; + int rc; + char *sighdr, *p; + struct mu_locus_range locus; + + env_get_locus(env, &locus); + + if (MF_DEFINED(canon_h)) { + sig.canon[0] = dkim_str_to_canon_type(canon_h); + if (sig.canon[0] == DKIM_CANON_ERR) + MF_THROW(mfe_failure, + _("bad canonicalization type: %s"), canon_h); + } + if (MF_DEFINED(canon_b)) { + sig.canon[1] = dkim_str_to_canon_type(canon_b); + if (sig.canon[1] == DKIM_CANON_ERR) + MF_THROW(mfe_failure, + _("bad canonicalization type: %s"), canon_b); + } + + sig.h = MF_OPTVAL(headers, default_headers); + bi_get_current_message(env, &msg); + rc = mfd_dkim_sign(msg, &sig, keyfile, &sighdr); + MF_ASSERT(rc == 0, + mfe_failure, + _("DKIM failed")); + + p = strchr(sighdr, ':'); + *p++ = 0; + while (mu_isblank(*p)) + p++; + + trace("%s%s:%u: %s \"%s: %s\"", + mailfromd_msgid(env_get_context(env)), + locus.beg.mu_file, locus.beg.mu_line, + msgmod_opcode_str(header_add), + sighdr, p); + env_msgmod(env, header_add, sighdr, p, 1); + free(sighdr); +} +END diff --git a/src/builtin/msg.bi b/src/builtin/msg.bi index 14e74245..a87b1916 100644 --- a/src/builtin/msg.bi +++ b/src/builtin/msg.bi @@ -109,14 +109,32 @@ bi_message_register(eval_environ_t env, } int -bi_get_current_message(eval_environ_t env) +bi_get_current_message(eval_environ_t env, mu_message_t *ret_msg) { int i; struct mf_message *tab = MF_GET_DATA; + mu_stream_t mstr; + mu_message_t msg; + int rc; + for (i = 0; i < nmsgs; i++) - if (tab[i].msg && tab[i].type == MF_MSG_CURRENT) - return i; - return -1; + if (tab[i].msg && tab[i].type == MF_MSG_CURRENT) + break; + if (i == nmsgs) { + rc = env_get_stream(env, &mstr); + MF_ASSERT(rc >= 0, + mfe_failure, + _("cannot obtain capture stream reference: %s"), + mu_strerror(rc)); + msg = MF_STREAM_TO_MESSAGE(mstr); + i = bi_message_register(env, NULL, msg, MF_MSG_CURRENT); + MF_ASSERT(i >= 0, + mfe_failure, + _("no more message slots available")); + } + if (ret_msg) + *ret_msg = tab[i].msg; + return i; } diff --git a/src/builtin/msg.h b/src/builtin/msg.h index c2bd919b..8c8aa803 100644 --- a/src/builtin/msg.h +++ b/src/builtin/msg.h @@ -42,7 +42,7 @@ struct mf_message { void bi_close_message(struct mf_message *msg); int bi_message_register(eval_environ_t env, mu_list_t list, mu_message_t msg, int type); -int bi_get_current_message(eval_environ_t env); +int bi_get_current_message(eval_environ_t env, mu_message_t *msg); mu_message_t bi_message_from_descr(eval_environ_t env, int md); int _bi_io_fd(eval_environ_t env, int fd, int what); diff --git a/src/builtin/snarf.m4 b/src/builtin/snarf.m4 index 1cd41da1..951a6f00 100644 --- a/src/builtin/snarf.m4 +++ b/src/builtin/snarf.m4 @@ -424,7 +424,7 @@ m4_define([<env_get_stream>],m4_dnl [<m4_errprint(m4___file__:m4___line__: [<env_get_stream is illegal here>] ) m4_define([<__mf_error_code>],1)>],[<[<env_get_stream>]($@)>])>],m4_dnl -[<env_get_stream>])>]) +[<[<env_get_stream>]($@)>])>]) /* mf_optcount(ARGS...) * -------------------- diff --git a/src/dkim-canonicalize.c b/src/dkim-canonicalize.c new file mode 100644 index 00000000..42e01413 --- /dev/null +++ b/src/dkim-canonicalize.c @@ -0,0 +1,344 @@ +/* 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <mailutils/errno.h> +#include <mailutils/filter.h> +#include <mailutils/cctype.h> + +/* + * This file defines a mailutils filter implementing the two DKIM + * canonicalization algrorithms: "simple" and "relaxed" (see + * RFC 6376, 3.4. "Canonicalization" (page 13). + * + * The filter reads a LF-delimited message stream from its input + * and outpus a canonicalized message, with header and body parts + * processed using the selected algorithm. + * + * For further processing the output stream must be passed through a + * CRLF encoder. + * + * The filter is governed by the following structure: + */ + +struct encoder_state { + int canon[2]; /* Canonicalization algorithm. 0 - for headers, + 1 - for body. See the DKIM_CANON_ constants + in dkim.h */ + int state; /* Encoder state (see below) */ + size_t nlcount; /* Number of contiguous empty lines for "relaxed" + body algorithm. */ + +}; + +/* Enoder states: */ +enum { + /* Header "simple" canonicalization states */ + HS_INIT, + HS_NL, + /* Header "relaxed" canonicalization states */ + HR_NAME, + HR_SPACE, + HR_COLON, + HR_VALUE, + HR_WS, + HR_NL, + /* Delimiter */ + ST_DELIM, + /* Body "simple" canonicalization states */ + BS_INIT, + BS_NL, + /* Body "relaxed" canonicalization states */ + BR_INIT, + BR_WS, + BR_NL +}; + +/* Initial states states */ +static int init_state[][2] = { + { HS_INIT, HR_NAME }, + { BS_INIT, BR_INIT } +}; + +static enum mu_filter_result +dkim_canonicalizer(void *xd, + enum mu_filter_command cmd, + struct mu_filter_io *iobuf) +{ + struct encoder_state *encoder = xd; + const char *iptr, *iendptr; + char *optr, *oendptr; + + switch (cmd) { + case mu_filter_init: + encoder->state = init_state[0][encoder->canon[0]]; + case mu_filter_done: + return mu_filter_ok; + default: + break; + } + + iptr = iobuf->input; + iendptr = iptr + iobuf->isize; + optr = iobuf->output; + oendptr = optr + iobuf->osize; + + while (iptr < iendptr && optr < oendptr) { + switch (encoder->state) { + /* Header "simple" canonicalization */ + case HS_INIT: + if (*iptr == '\n') + encoder->state = HS_NL; + else + *optr++ = *iptr; + iptr++; + break; + + case HS_NL: + if (*iptr == '\n') { + encoder->state = ST_DELIM; + } else { + *optr++ = '\n'; + encoder->state = HS_INIT; + } + break; + + /* Header "relaxed" canonicalization */ + case HR_NAME: + if (mu_isblank(*iptr)) { + iptr++; + encoder->state = HR_SPACE; + } else if (*iptr == ':') { + *optr++ = *iptr++; + encoder->state = HR_COLON; + } else if (mu_isheadr(*iptr)) { + *optr++ = mu_tolower(*iptr++); + } else { + iobuf->errcode = MU_ERR_USER0; + return mu_filter_failure; + } + break; + + case HR_SPACE: + if (mu_isblank(*iptr)) + iptr++; + else if (*iptr == ':') { + *optr++ = *iptr++; + encoder->state = HR_COLON; + } else { + iobuf->errcode = MU_ERR_USER0; + return mu_filter_failure; + } + break; + + case HR_COLON: + if (mu_isblank(*iptr)) + iptr++; + else + encoder->state = HR_VALUE; + break; + + case HR_VALUE: + if (mu_isblank(*iptr)) { + iptr++; + encoder->state = HR_WS; + } else if (*iptr == '\n') { + iptr++; + encoder->state = HR_NL; + } else + *optr++ = *iptr++; + break; + + case HR_WS: + if (mu_isblank(*iptr)) + iptr++; + else if (*iptr == '\n') { + iptr++; + encoder->state = HR_NL; + } else { + *optr++ = ' '; + encoder->state = HR_VALUE; + } + break; + + case HR_NL: + if (*iptr == '\n') { + encoder->state = ST_DELIM; + } else if (mu_isblank(*iptr)) { + iptr++; + encoder->state = HR_WS; + } else { + *optr++ = '\n'; + encoder->state = HR_NAME; + } + break; + + /* Delimiter between header and body */ + case ST_DELIM: + if (oendptr - optr < 2) + goto end; + iptr++; + *optr++ = '\n'; + *optr++ = '\n'; + encoder->state = init_state[1][encoder->canon[1]]; + break; + + /* Body "simple" canonicalization */ + case BS_INIT: + if (*iptr == '\n') { + iptr++; + encoder->nlcount++; + encoder->state = BS_NL; + } else + *optr++ = *iptr++; + break; + + case BS_NL: + if (*iptr == '\n') { + iptr++; + encoder->nlcount++; + } else { + for (; encoder->nlcount; encoder->nlcount--) { + if (optr == oendptr) + goto end; + *optr++ = '\n'; + } + encoder->state = BS_INIT; + } + break; + + /* Body "relaxed" canonicalization */ + case BR_INIT: + if (*iptr == '\n') { + encoder->nlcount++; + encoder->state = BR_NL; + } else if (mu_isblank (*iptr)) + encoder->state = BR_WS; + else + *optr++ = *iptr; + iptr++; + break; + + case BR_WS: + if (!mu_isblank(*iptr)) { + *optr++ = ' '; + encoder->state = BR_INIT; + } else { + if (*iptr == '\n') { + encoder->nlcount++; + encoder->state = BR_NL; + } + iptr++; + } + break; + + case BR_NL: + if (*iptr == '\n') { + iptr++; + encoder->nlcount++; + } else { + for (; encoder->nlcount; encoder->nlcount--) { + if (optr == oendptr) + goto end; + *optr++ = '\n'; + } + encoder->state = BR_INIT; + } + break; + } + } + + if (cmd == mu_filter_lastbuf && iptr == iendptr && + encoder->state >= BS_INIT) { + if (oendptr == optr) { + iobuf->osize++; + return mu_filter_moreoutput; + } + *optr++ = '\n'; + } + +end: + iobuf->isize = iptr - iobuf->input; + iobuf->osize = optr - iobuf->output; + + return mu_filter_ok; +} + +/* + * Create a message canonicalizer. Arguments: + * + * pstream return pointer + * stream input stream + * canon_header header canonicalization algorithm + * canon_body body canonicalization algorithm + * + * Return value: mailutils error code. + */ +int +dkim_canonicalizer_create(mu_stream_t *pstream, + mu_stream_t stream, + int canon_header, + int canon_body, + int flags) +{ + struct encoder_state *encoder = malloc(sizeof (*encoder)); + if (!encoder) + return ENOMEM; + memset(encoder, 0, sizeof(*encoder)); + encoder->canon[0] = canon_header; + encoder->canon[1] = canon_body; + return mu_filter_stream_create(pstream, stream, + MU_FILTER_ENCODE, + dkim_canonicalizer, encoder, flags); +} + +#if 0 +/* + * The canonicalizer can be registered as a regular mailutils filter. + * If such approach is ever needed, uncomment this block. + */ + +static int +alloc_state(void **pret, int mode, int argc, const char **argv) +{ + struct encoder_state *encoder; + switch (mode) { + case MU_FILTER_ENCODE: + encoder = malloc(sizeof(*encoder)); + if (!encoder) + return ENOMEM; + memset(encoder, 0, sizeof(*encoder)); + *pret = encoder; + break; + + case MU_FILTER_DECODE:; + } + return 0; +} + +static struct _mu_filter_record dkim_canonicalize_filter_s = { + "DKIM", + alloc_state, + dkim_canonicalizer, + NULL +}; + +mu_filter_record_t dkim_canonicalize_filter = &dkim_canonicalize_filter_s; +#endif diff --git a/src/dkim.c b/src/dkim.c new file mode 100644 index 00000000..b4ab9105 --- /dev/null +++ b/src/dkim.c @@ -0,0 +1,816 @@ +/* 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <nettle/sha2.h> +#include <nettle/buffer.h> +#include <nettle/rsa.h> +#include <nettle/base64.h> +#include "mailfromd.h" +#include <mailutils/imaputil.h> +#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; +} + +/* + * SHA256 hashing functions. + */ + +#define BUF_SIZE 1000 + +/* 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) { + 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); + 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, uint8_t *outbuf) +{ + struct sha256_ctx ctx; + uint8_t body_digest[SHA256_DIGEST_SIZE]; + + sha256_init(&ctx); + hash_stream(str, &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 }; + +int +dkim_str_to_canon_type(char const *str) +{ + int i; + + for (i = 0; dkim_canon_string[i]; i++) + if (strcmp(str, dkim_canon_string[i]) == 0) + return i; + return DKIM_CANON_ERR; +} + +/* Format a struct dkim_signature as a DKIM-Signature header. */ +int +dkim_signature_format(struct dkim_signature *sig, char **result) +{ + mu_opool_t op; + int rc; + + rc = mu_opool_create(&op, MU_OPOOL_DEFAULT); + if (rc) + return rc; + mu_opool_appendz(op, "DKIM-Signature: "); + mu_opool_appendz(op, "v=1; a=rsa-sha256; d="); + mu_opool_appendz(op, sig->d); + mu_opool_appendz(op, "; s="); + mu_opool_appendz(op, sig->s); + mu_opool_appendz(op, "; "); + if (!(sig->canon[0] == DKIM_CANON_SIMPLE && + sig->canon[0] == sig->canon[1])) { + mu_opool_appendz(op, "c="); + mu_opool_appendz(op, dkim_canon_string[sig->canon[0]]); + mu_opool_append(op, "/", 1); + mu_opool_appendz(op, dkim_canon_string[sig->canon[1]]); + mu_opool_appendz(op, "; "); + } + mu_opool_appendz(op, "q=dns/txt; "); + if (sig->i) { + mu_opool_appendz(op, "i="); + mu_opool_appendz(op, sig->i); + mu_opool_appendz(op, "; "); + } + mu_opool_appendz(op, "h="); + mu_opool_appendz(op, sig->h); + mu_opool_appendz(op, "; "); + if (sig->t) { + char tbuf[80]; + snprintf(tbuf, sizeof(tbuf), "%lu", (long unsigned) sig->t); + mu_opool_appendz(op, "t="); + mu_opool_appendz(op, tbuf); + mu_opool_appendz(op, "; "); + } + if (sig->x) { + char tbuf[80]; + snprintf(tbuf, sizeof(tbuf), "%lu", (long unsigned) sig->x); + mu_opool_appendz(op, "x="); + mu_opool_appendz(op, tbuf); + mu_opool_appendz(op, "; "); + } + mu_opool_appendz(op, "bh="); + mu_opool_appendz(op, (char*)sig->bh); + mu_opool_appendz(op, "; "); + mu_opool_appendz(op, "b="); + if (sig->b) + mu_opool_appendz(op, (char*)sig->b); + mu_opool_append_char(op, 0); + + *result = mu_opool_detach(op, NULL); + //FIXME: Add line wrapping + mu_opool_destroy(&op); + return 0; +} + +/* 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( |