aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2020-05-26 01:58:50 +0300
committerSergey Poznyakoff <gray@gnu.org>2020-05-26 18:35:08 +0300
commita42c94f08bda3fba17b8d2d46d6675aea063eeed (patch)
tree180383c8a273097bd29673535b5465b5c07cd9c1 /src
parent25bb240e22f66276efc75a461c3261c999054b6f (diff)
downloadmailfromd-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.am10
-rw-r--r--src/builtin/.gitignore1
-rw-r--r--src/builtin/Makefile.am4
-rw-r--r--src/builtin/body.bi15
-rw-r--r--src/builtin/dkim.bi93
-rw-r--r--src/builtin/msg.bi26
-rw-r--r--src/builtin/msg.h2
-rw-r--r--src/builtin/snarf.m42
-rw-r--r--src/dkim-canonicalize.c344
-rw-r--r--src/dkim.c816
-rw-r--r--src/dkim.h62
-rw-r--r--src/srvcfg.c1
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(