Commit | Line | Data |
---|---|---|
80a47a2c TK |
1 | /* |
2 | * PDKIM - a RFC4871 (DKIM) implementation | |
3 | * | |
f444c2c7 | 4 | * Copyright (C) 2009 - 2016 Tom Kistner <tom@duncanthrax.net> |
d4e5e70b | 5 | * Copyright (C) 2016 - 2017 Jeremy Harris <jgh@exim.org> |
80a47a2c TK |
6 | * |
7 | * http://duncanthrax.net/pdkim/ | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License along | |
20 | * with this program; if not, write to the Free Software Foundation, Inc., | |
21 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
22 | */ | |
23 | ||
0d04a285 | 24 | #include "../exim.h" |
80a47a2c | 25 | |
f444c2c7 JH |
26 | |
27 | #ifndef DISABLE_DKIM /* entire file */ | |
28 | ||
29 | #ifndef SUPPORT_TLS | |
30 | # error Need SUPPORT_TLS for DKIM | |
31 | #endif | |
32 | ||
cb224393 | 33 | #include "crypt_ver.h" |
f444c2c7 | 34 | |
d73e45df | 35 | #ifdef SIGN_OPENSSL |
cb224393 JH |
36 | # include <openssl/rsa.h> |
37 | # include <openssl/ssl.h> | |
38 | # include <openssl/err.h> | |
d73e45df | 39 | #elif defined(SIGN_GNUTLS) |
f444c2c7 JH |
40 | # include <gnutls/gnutls.h> |
41 | # include <gnutls/x509.h> | |
f444c2c7 JH |
42 | #endif |
43 | ||
44 | #include "pdkim.h" | |
9b2583c4 | 45 | #include "signing.h" |
80a47a2c TK |
46 | |
47 | #define PDKIM_SIGNATURE_VERSION "1" | |
e2e3255a | 48 | #define PDKIM_PUB_RECORD_VERSION US "DKIM1" |
80a47a2c TK |
49 | |
50 | #define PDKIM_MAX_HEADER_LEN 65536 | |
51 | #define PDKIM_MAX_HEADERS 512 | |
52 | #define PDKIM_MAX_BODY_LINE_LEN 16384 | |
53 | #define PDKIM_DNS_TXT_MAX_NAMELEN 1024 | |
80a47a2c TK |
54 | |
55 | /* -------------------------------------------------------------------------- */ | |
56 | struct pdkim_stringlist { | |
e2e3255a JH |
57 | uschar * value; |
58 | int tag; | |
59 | void * next; | |
80a47a2c TK |
60 | }; |
61 | ||
80a47a2c TK |
62 | /* -------------------------------------------------------------------------- */ |
63 | /* A bunch of list constants */ | |
e2e3255a JH |
64 | const uschar * pdkim_querymethods[] = { |
65 | US"dns/txt", | |
80a47a2c TK |
66 | NULL |
67 | }; | |
e2e3255a JH |
68 | const uschar * pdkim_canons[] = { |
69 | US"simple", | |
70 | US"relaxed", | |
80a47a2c TK |
71 | NULL |
72 | }; | |
d73e45df JH |
73 | |
74 | typedef struct { | |
75 | const uschar * dkim_hashname; | |
76 | hashmethod exim_hashmethod; | |
77 | } pdkim_hashtype; | |
78 | static const pdkim_hashtype pdkim_hashes[] = { | |
79 | { US"sha1", HASH_SHA1 }, | |
80 | { US"sha256", HASH_SHA2_256 }, | |
81 | { US"sha512", HASH_SHA2_512 } | |
80a47a2c | 82 | }; |
d73e45df | 83 | |
e2e3255a | 84 | const uschar * pdkim_keytypes[] = { |
286b9d5f JH |
85 | [KEYTYPE_RSA] = US"rsa", |
86 | #ifdef SIGN_HAVE_ED25519 | |
87 | [KEYTYPE_ED25519] = US"ed25519", /* Works for 3.6.0 GnuTLS */ | |
88 | #endif | |
89 | ||
90 | #ifdef notyet_EC_dkim_extensions /* https://tools.ietf.org/html/draft-srose-dkim-ecc-00 */ | |
91 | US"eccp256", | |
92 | US"eccp348", | |
93 | US"ed448", | |
94 | #endif | |
80a47a2c TK |
95 | }; |
96 | ||
97 | typedef struct pdkim_combined_canon_entry { | |
286b9d5f JH |
98 | const uschar * str; |
99 | int canon_headers; | |
100 | int canon_body; | |
80a47a2c | 101 | } pdkim_combined_canon_entry; |
f444c2c7 | 102 | |
80a47a2c | 103 | pdkim_combined_canon_entry pdkim_combined_canons[] = { |
e2e3255a JH |
104 | { US"simple/simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE }, |
105 | { US"simple/relaxed", PDKIM_CANON_SIMPLE, PDKIM_CANON_RELAXED }, | |
106 | { US"relaxed/simple", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE }, | |
107 | { US"relaxed/relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_RELAXED }, | |
108 | { US"simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE }, | |
109 | { US"relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE }, | |
110 | { NULL, 0, 0 } | |
80a47a2c TK |
111 | }; |
112 | ||
113 | ||
9e70917d JH |
114 | static blob lineending = {.data = US"\r\n", .len = 2}; |
115 | ||
f444c2c7 | 116 | /* -------------------------------------------------------------------------- */ |
d73e45df | 117 | uschar * |
9e70917d | 118 | dkim_sig_to_a_tag(const pdkim_signature * sig) |
d73e45df JH |
119 | { |
120 | if ( sig->keytype < 0 || sig->keytype > nelem(pdkim_keytypes) | |
121 | || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes)) | |
122 | return US"err"; | |
123 | return string_sprintf("%s-%s", | |
124 | pdkim_keytypes[sig->keytype], pdkim_hashes[sig->hashtype].dkim_hashname); | |
125 | } | |
126 | ||
127 | ||
f444c2c7 JH |
128 | |
129 | const char * | |
130 | pdkim_verify_status_str(int status) | |
131 | { | |
f7302073 JH |
132 | switch(status) |
133 | { | |
134 | case PDKIM_VERIFY_NONE: return "PDKIM_VERIFY_NONE"; | |
135 | case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID"; | |
136 | case PDKIM_VERIFY_FAIL: return "PDKIM_VERIFY_FAIL"; | |
137 | case PDKIM_VERIFY_PASS: return "PDKIM_VERIFY_PASS"; | |
138 | default: return "PDKIM_VERIFY_UNKNOWN"; | |
ff7ddfd7 TK |
139 | } |
140 | } | |
f444c2c7 JH |
141 | |
142 | const char * | |
143 | pdkim_verify_ext_status_str(int ext_status) | |
144 | { | |
f7302073 JH |
145 | switch(ext_status) |
146 | { | |
147 | case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY"; | |
148 | case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE"; | |
135e9496 | 149 | case PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH: return "PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH"; |
f7302073 JH |
150 | case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE"; |
151 | case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE"; | |
152 | case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD"; | |
153 | case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT"; | |
154 | case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: return "PDKIM_VERIFY_INVALID_SIGNATURE_ERROR"; | |
155 | case PDKIM_VERIFY_INVALID_DKIM_VERSION: return "PDKIM_VERIFY_INVALID_DKIM_VERSION"; | |
156 | default: return "PDKIM_VERIFY_UNKNOWN"; | |
157 | } | |
158 | } | |
159 | ||
b9df1829 | 160 | const uschar * |
f7302073 JH |
161 | pdkim_errstr(int status) |
162 | { | |
163 | switch(status) | |
164 | { | |
b9df1829 JH |
165 | case PDKIM_OK: return US"OK"; |
166 | case PDKIM_FAIL: return US"FAIL"; | |
286b9d5f JH |
167 | case PDKIM_ERR_RSA_PRIVKEY: return US"PRIVKEY"; |
168 | case PDKIM_ERR_RSA_SIGNING: return US"SIGNING"; | |
169 | case PDKIM_ERR_LONG_LINE: return US"LONG_LINE"; | |
b9df1829 JH |
170 | case PDKIM_ERR_BUFFER_TOO_SMALL: return US"BUFFER_TOO_SMALL"; |
171 | case PDKIM_SIGN_PRIVKEY_WRAP: return US"PRIVKEY_WRAP"; | |
172 | case PDKIM_SIGN_PRIVKEY_B64D: return US"PRIVKEY_B64D"; | |
ef698bf6 | 173 | default: return US"(unknown)"; |
ff7ddfd7 TK |
174 | } |
175 | } | |
176 | ||
177 | ||
80a47a2c TK |
178 | /* -------------------------------------------------------------------------- */ |
179 | /* Print debugging functions */ | |
2592e6c0 | 180 | static void |
b78006ac | 181 | pdkim_quoteprint(const uschar *data, int len) |
3045f050 JH |
182 | { |
183 | int i; | |
0d04a285 | 184 | for (i = 0; i < len; i++) |
3045f050 | 185 | { |
b78006ac | 186 | const int c = data[i]; |
3045f050 JH |
187 | switch (c) |
188 | { | |
0d04a285 JH |
189 | case ' ' : debug_printf("{SP}"); break; |
190 | case '\t': debug_printf("{TB}"); break; | |
191 | case '\r': debug_printf("{CR}"); break; | |
192 | case '\n': debug_printf("{LF}"); break; | |
193 | case '{' : debug_printf("{BO}"); break; | |
194 | case '}' : debug_printf("{BC}"); break; | |
3045f050 JH |
195 | default: |
196 | if ( (c < 32) || (c > 127) ) | |
0d04a285 | 197 | debug_printf("{%02x}", c); |
3045f050 | 198 | else |
0d04a285 | 199 | debug_printf("%c", c); |
80a47a2c TK |
200 | break; |
201 | } | |
202 | } | |
2592e6c0 | 203 | debug_printf("\n"); |
80a47a2c | 204 | } |
80a47a2c | 205 | |
2592e6c0 | 206 | static void |
b78006ac | 207 | pdkim_hexprint(const uschar *data, int len) |
3045f050 JH |
208 | { |
209 | int i; | |
02c4f8fb JH |
210 | if (data) for (i = 0 ; i < len; i++) debug_printf("%02x", data[i]); |
211 | else debug_printf("<NULL>"); | |
2592e6c0 | 212 | debug_printf("\n"); |
80a47a2c | 213 | } |
80a47a2c TK |
214 | |
215 | ||
f444c2c7 | 216 | |
f444c2c7 | 217 | static pdkim_stringlist * |
ca9cb170 | 218 | pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str) |
3045f050 | 219 | { |
ca9cb170 | 220 | pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist)); |
3045f050 | 221 | |
abe1010c | 222 | memset(new_entry, 0, sizeof(pdkim_stringlist)); |
ca9cb170 | 223 | new_entry->value = string_copy(str); |
ab9152ff JH |
224 | if (base) new_entry->next = base; |
225 | return new_entry; | |
6ab02e3f | 226 | } |
80a47a2c TK |
227 | |
228 | ||
8ef02a06 JH |
229 | |
230 | /* Trim whitespace fore & aft */ | |
231 | ||
ca9cb170 | 232 | static void |
acec9514 | 233 | pdkim_strtrim(gstring * str) |
3045f050 | 234 | { |
acec9514 | 235 | uschar * p = str->s; |
b2bcdd35 | 236 | uschar * q; |
acec9514 JH |
237 | |
238 | while (*p == '\t' || *p == ' ') /* dump the leading whitespace */ | |
239 | { str->size--; str->ptr--; str->s++; } | |
240 | ||
241 | while ( str->ptr > 0 | |
81147e20 | 242 | && ((q = str->s + str->ptr - 1), (*q == '\t' || *q == ' ')) |
acec9514 JH |
243 | ) |
244 | str->ptr--; /* dump trailing whitespace */ | |
245 | ||
246 | (void) string_from_gstring(str); | |
8ef02a06 JH |
247 | } |
248 | ||
249 | ||
80a47a2c | 250 | |
80a47a2c | 251 | /* -------------------------------------------------------------------------- */ |
3045f050 JH |
252 | |
253 | DLLEXPORT void | |
254 | pdkim_free_ctx(pdkim_ctx *ctx) | |
255 | { | |
6ab02e3f | 256 | } |
80a47a2c TK |
257 | |
258 | ||
259 | /* -------------------------------------------------------------------------- */ | |
260 | /* Matches the name of the passed raw "header" against | |
8ef02a06 | 261 | the passed colon-separated "tick", and invalidates |
484cc1a9 JH |
262 | the entry in tick. Entries can be prefixed for multi- or over-signing, |
263 | in which case do not invalidate. | |
264 | ||
265 | Returns OK for a match, or fail-code | |
266 | */ | |
3045f050 | 267 | |
f444c2c7 | 268 | static int |
e2e3255a | 269 | header_name_match(const uschar * header, uschar * tick) |
3045f050 | 270 | { |
484cc1a9 JH |
271 | const uschar * ticklist = tick; |
272 | int sep = ':'; | |
273 | BOOL multisign; | |
274 | uschar * hname, * p, * ele; | |
ca9cb170 | 275 | uschar * hcolon = Ustrchr(header, ':'); /* Get header name */ |
3045f050 | 276 | |
ca9cb170 JH |
277 | if (!hcolon) |
278 | return PDKIM_FAIL; /* This isn't a header */ | |
3045f050 | 279 | |
ca9cb170 JH |
280 | /* if we had strncmpic() we wouldn't need this copy */ |
281 | hname = string_copyn(header, hcolon-header); | |
3045f050 | 282 | |
484cc1a9 | 283 | while (p = US ticklist, ele = string_nextinlist(&ticklist, &sep, NULL, 0)) |
3045f050 | 284 | { |
484cc1a9 JH |
285 | switch (*ele) |
286 | { | |
287 | case '=': case '+': multisign = TRUE; ele++; break; | |
288 | default: multisign = FALSE; break; | |
80a47a2c TK |
289 | } |
290 | ||
484cc1a9 JH |
291 | if (strcmpic(ele, hname) == 0) |
292 | { | |
293 | if (!multisign) | |
294 | *p = '_'; /* Invalidate this header name instance in tick-off list */ | |
295 | return PDKIM_OK; | |
296 | } | |
297 | } | |
ca9cb170 | 298 | return PDKIM_FAIL; |
80a47a2c TK |
299 | } |
300 | ||
301 | ||
302 | /* -------------------------------------------------------------------------- */ | |
ca9cb170 | 303 | /* Performs "relaxed" canonicalization of a header. */ |
3045f050 | 304 | |
ca9cb170 | 305 | static uschar * |
ea18931d | 306 | pdkim_relax_header(const uschar * header, BOOL append_crlf) |
3045f050 JH |
307 | { |
308 | BOOL past_field_name = FALSE; | |
309 | BOOL seen_wsp = FALSE; | |
ca9cb170 JH |
310 | const uschar * p; |
311 | uschar * relaxed = store_get(Ustrlen(header)+3); | |
312 | uschar * q = relaxed; | |
3045f050 | 313 | |
ca9cb170 | 314 | for (p = header; *p; p++) |
3045f050 | 315 | { |
ca9cb170 | 316 | uschar c = *p; |
ea18931d JH |
317 | |
318 | if (c == '\r' || c == '\n') /* Ignore CR & LF */ | |
3045f050 JH |
319 | continue; |
320 | if (c == '\t' || c == ' ') | |
321 | { | |
322 | if (seen_wsp) | |
80a47a2c | 323 | continue; |
3045f050 JH |
324 | c = ' '; /* Turns WSP into SP */ |
325 | seen_wsp = TRUE; | |
80a47a2c | 326 | } |
3045f050 JH |
327 | else |
328 | if (!past_field_name && c == ':') | |
329 | { | |
ea18931d JH |
330 | if (seen_wsp) q--; /* This removes WSP immediately before the colon */ |
331 | seen_wsp = TRUE; /* This removes WSP immediately after the colon */ | |
3045f050 | 332 | past_field_name = TRUE; |
80a47a2c | 333 | } |
3045f050 JH |
334 | else |
335 | seen_wsp = FALSE; | |
336 | ||
337 | /* Lowercase header name */ | |
338 | if (!past_field_name) c = tolower(c); | |
339 | *q++ = c; | |
80a47a2c | 340 | } |
3045f050 JH |
341 | |
342 | if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */ | |
3045f050 | 343 | |
ea18931d | 344 | if (append_crlf) { *q++ = '\r'; *q++ = '\n'; } |
ca9cb170 | 345 | *q = '\0'; |
3045f050 | 346 | return relaxed; |
6ab02e3f | 347 | } |
80a47a2c TK |
348 | |
349 | ||
350 | /* -------------------------------------------------------------------------- */ | |
351 | #define PDKIM_QP_ERROR_DECODE -1 | |
3045f050 | 352 | |
35cf75e9 JH |
353 | static const uschar * |
354 | pdkim_decode_qp_char(const uschar *qp_p, int *c) | |
3045f050 | 355 | { |
35cf75e9 | 356 | const uschar *initial_pos = qp_p; |
3045f050 JH |
357 | |
358 | /* Advance one char */ | |
359 | qp_p++; | |
360 | ||
361 | /* Check for two hex digits and decode them */ | |
362 | if (isxdigit(*qp_p) && isxdigit(qp_p[1])) | |
363 | { | |
364 | /* Do hex conversion */ | |
365 | *c = (isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10) << 4; | |
a5840e10 | 366 | *c |= isdigit(qp_p[1]) ? qp_p[1] - '0' : toupper(qp_p[1]) - 'A' + 10; |
3045f050 | 367 | return qp_p + 2; |
6ab02e3f | 368 | } |
80a47a2c | 369 | |
3045f050 JH |
370 | /* Illegal char here */ |
371 | *c = PDKIM_QP_ERROR_DECODE; | |
372 | return initial_pos; | |
80a47a2c TK |
373 | } |
374 | ||
375 | ||
376 | /* -------------------------------------------------------------------------- */ | |
3045f050 | 377 | |
e2e3255a | 378 | static uschar * |
35cf75e9 | 379 | pdkim_decode_qp(const uschar * str) |
3045f050 JH |
380 | { |
381 | int nchar = 0; | |
ca9cb170 | 382 | uschar * q; |
35cf75e9 | 383 | const uschar * p = str; |
ca9cb170 | 384 | uschar * n = store_get(Ustrlen(str)+1); |
3045f050 JH |
385 | |
386 | *n = '\0'; | |
387 | q = n; | |
ca9cb170 | 388 | while (*p) |
3045f050 JH |
389 | { |
390 | if (*p == '=') | |
391 | { | |
392 | p = pdkim_decode_qp_char(p, &nchar); | |
393 | if (nchar >= 0) | |
394 | { | |
395 | *q++ = nchar; | |
396 | continue; | |
80a47a2c TK |
397 | } |
398 | } | |
3045f050 JH |
399 | else |
400 | *q++ = *p; | |
401 | p++; | |
80a47a2c | 402 | } |
3045f050 JH |
403 | *q = '\0'; |
404 | return n; | |
80a47a2c TK |
405 | } |
406 | ||
407 | ||
408 | /* -------------------------------------------------------------------------- */ | |
3045f050 | 409 | |
2592e6c0 | 410 | static void |
35cf75e9 | 411 | pdkim_decode_base64(const uschar * str, blob * b) |
3045f050 | 412 | { |
2592e6c0 | 413 | int dlen; |
2592e6c0 JH |
414 | dlen = b64decode(str, &b->data); |
415 | if (dlen < 0) b->data = NULL; | |
416 | b->len = dlen; | |
80a47a2c TK |
417 | } |
418 | ||
ca9cb170 | 419 | static uschar * |
2592e6c0 | 420 | pdkim_encode_base64(blob * b) |
3045f050 | 421 | { |
ca9cb170 | 422 | return b64encode(b->data, b->len); |
80a47a2c TK |
423 | } |
424 | ||
425 | ||
426 | /* -------------------------------------------------------------------------- */ | |
427 | #define PDKIM_HDR_LIMBO 0 | |
428 | #define PDKIM_HDR_TAG 1 | |
429 | #define PDKIM_HDR_VALUE 2 | |
3045f050 | 430 | |
f444c2c7 | 431 | static pdkim_signature * |
9e70917d | 432 | pdkim_parse_sig_header(pdkim_ctx * ctx, uschar * raw_hdr) |
3045f050 | 433 | { |
2c0f3ea1 | 434 | pdkim_signature * sig; |
ca9cb170 | 435 | uschar *p, *q; |
acec9514 JH |
436 | gstring * cur_tag = NULL; |
437 | gstring * cur_val = NULL; | |
3045f050 JH |
438 | BOOL past_hname = FALSE; |
439 | BOOL in_b_val = FALSE; | |
440 | int where = PDKIM_HDR_LIMBO; | |
441 | int i; | |
442 | ||
ca9cb170 | 443 | sig = store_get(sizeof(pdkim_signature)); |
abe1010c | 444 | memset(sig, 0, sizeof(pdkim_signature)); |
3045f050 JH |
445 | sig->bodylength = -1; |
446 | ||
07eeb4df | 447 | /* Set so invalid/missing data error display is accurate */ |
07eeb4df | 448 | sig->version = 0; |
d73e45df JH |
449 | sig->keytype = -1; |
450 | sig->hashtype = -1; | |
07eeb4df | 451 | |
ca9cb170 | 452 | q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1); |
80a47a2c | 453 | |
3045f050 JH |
454 | for (p = raw_hdr; ; p++) |
455 | { | |
456 | char c = *p; | |
80a47a2c | 457 | |
3045f050 JH |
458 | /* Ignore FWS */ |
459 | if (c == '\r' || c == '\n') | |
460 | goto NEXT_CHAR; | |
80a47a2c | 461 | |
3045f050 JH |
462 | /* Fast-forward through header name */ |
463 | if (!past_hname) | |
464 | { | |
465 | if (c == ':') past_hname = TRUE; | |
466 | goto NEXT_CHAR; | |
80a47a2c TK |
467 | } |
468 | ||
3045f050 JH |
469 | if (where == PDKIM_HDR_LIMBO) |
470 | { | |
471 | /* In limbo, just wait for a tag-char to appear */ | |
472 | if (!(c >= 'a' && c <= 'z')) | |
473 | goto NEXT_CHAR; | |
80a47a2c | 474 | |
3045f050 | 475 | where = PDKIM_HDR_TAG; |
80a47a2c TK |
476 | } |
477 | ||
3045f050 JH |
478 | if (where == PDKIM_HDR_TAG) |
479 | { | |
3045f050 | 480 | if (c >= 'a' && c <= 'z') |
acec9514 | 481 | cur_tag = string_catn(cur_tag, p, 1); |
80a47a2c | 482 | |
3045f050 JH |
483 | if (c == '=') |
484 | { | |
acec9514 | 485 | if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0) |
3045f050 | 486 | { |
ca9cb170 | 487 | *q++ = '='; |
3045f050 JH |
488 | in_b_val = TRUE; |
489 | } | |
490 | where = PDKIM_HDR_VALUE; | |
491 | goto NEXT_CHAR; | |
80a47a2c TK |
492 | } |
493 | } | |
494 | ||
3045f050 JH |
495 | if (where == PDKIM_HDR_VALUE) |
496 | { | |
3045f050 JH |
497 | if (c == '\r' || c == '\n' || c == ' ' || c == '\t') |
498 | goto NEXT_CHAR; | |
499 | ||
500 | if (c == ';' || c == '\0') | |
501 | { | |
a05d3e34 JH |
502 | /* We must have both tag and value, and tags must be one char except |
503 | for the possibility of "bh". */ | |
504 | ||
505 | if ( cur_tag && cur_val | |
506 | && (cur_tag->ptr == 1 || *cur_tag->s == 'b') | |
507 | ) | |
3045f050 | 508 | { |
acec9514 | 509 | (void) string_from_gstring(cur_val); |
3045f050 JH |
510 | pdkim_strtrim(cur_val); |
511 | ||
acec9514 | 512 | DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s); |
3045f050 | 513 | |
acec9514 | 514 | switch (*cur_tag->s) |
3045f050 | 515 | { |
286b9d5f | 516 | case 'b': /* sig-data or body-hash */ |
a05d3e34 JH |
517 | switch (cur_tag->s[1]) |
518 | { | |
519 | case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break; | |
520 | case 'h': if (cur_tag->ptr == 2) | |
521 | pdkim_decode_base64(cur_val->s, &sig->bodyhash); | |
522 | break; | |
523 | default: break; | |
524 | } | |
3045f050 | 525 | break; |
286b9d5f | 526 | case 'v': /* version */ |
3045f050 JH |
527 | /* We only support version 1, and that is currently the |
528 | only version there is. */ | |
07eeb4df | 529 | sig->version = |
acec9514 | 530 | Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1; |
3045f050 | 531 | break; |
286b9d5f | 532 | case 'a': /* algorithm */ |
d73e45df | 533 | { |
acec9514 | 534 | uschar * s = Ustrchr(cur_val->s, '-'); |
d73e45df JH |
535 | |
536 | for(i = 0; i < nelem(pdkim_keytypes); i++) | |
acec9514 | 537 | if (Ustrncmp(cur_val->s, pdkim_keytypes[i], s - cur_val->s) == 0) |
d73e45df | 538 | { sig->keytype = i; break; } |
286b9d5f JH |
539 | if (sig->keytype < 0) |
540 | log_write(0, LOG_MAIN, | |
541 | "DKIM: ignoring signature due to nonhandled keytype in a=%s", | |
542 | cur_val->s); | |
543 | ||
d73e45df JH |
544 | for (++s, i = 0; i < nelem(pdkim_hashes); i++) |
545 | if (Ustrcmp(s, pdkim_hashes[i].dkim_hashname) == 0) | |
546 | { sig->hashtype = i; break; } | |
286b9d5f JH |
547 | if (sig->hashtype < 0) |
548 | log_write(0, LOG_MAIN, | |
549 | "DKIM: ignoring signature due to nonhandled hashtype in a=%s", | |
550 | cur_val); | |
3045f050 | 551 | break; |
d73e45df JH |
552 | } |
553 | ||
286b9d5f | 554 | case 'c': /* canonicalization */ |
3045f050 | 555 | for (i = 0; pdkim_combined_canons[i].str; i++) |
acec9514 | 556 | if (Ustrcmp(cur_val->s, pdkim_combined_canons[i].str) == 0) |
3045f050 JH |
557 | { |
558 | sig->canon_headers = pdkim_combined_canons[i].canon_headers; | |
559 | sig->canon_body = pdkim_combined_canons[i].canon_body; | |
560 | break; | |
561 | } | |
562 | break; | |
286b9d5f | 563 | case 'q': /* Query method (for pubkey)*/ |
3045f050 | 564 | for (i = 0; pdkim_querymethods[i]; i++) |
acec9514 | 565 | if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0) |
3045f050 | 566 | { |
286b9d5f | 567 | sig->querymethod = i; /* we never actually use this */ |
3045f050 JH |
568 | break; |
569 | } | |
570 | break; | |
286b9d5f | 571 | case 's': /* Selector */ |
acec9514 | 572 | sig->selector = string_copyn(cur_val->s, cur_val->ptr); break; |
286b9d5f | 573 | case 'd': /* SDID */ |
acec9514 | 574 | sig->domain = string_copyn(cur_val->s, cur_val->ptr); break; |
286b9d5f | 575 | case 'i': /* AUID */ |
acec9514 | 576 | sig->identity = pdkim_decode_qp(cur_val->s); break; |
286b9d5f | 577 | case 't': /* Timestamp */ |
acec9514 | 578 | sig->created = strtoul(CS cur_val->s, NULL, 10); break; |
286b9d5f | 579 | case 'x': /* Expiration */ |
acec9514 | 580 | sig->expires = strtoul(CS cur_val->s, NULL, 10); break; |
286b9d5f | 581 | case 'l': /* Body length count */ |
acec9514 | 582 | sig->bodylength = strtol(CS cur_val->s, NULL, 10); break; |
286b9d5f | 583 | case 'h': /* signed header fields */ |
acec9514 | 584 | sig->headernames = string_copyn(cur_val->s, cur_val->ptr); break; |
286b9d5f | 585 | case 'z': /* Copied headfields */ |
acec9514 | 586 | sig->copiedheaders = pdkim_decode_qp(cur_val->s); break; |
286b9d5f JH |
587 | /*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support |
588 | for rsafp signatures. But later discussion is dropping those. */ | |
3045f050 | 589 | default: |
0d04a285 | 590 | DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); |
3045f050 JH |
591 | break; |
592 | } | |
593 | } | |
acec9514 | 594 | cur_tag = cur_val = NULL; |
3045f050 JH |
595 | in_b_val = FALSE; |
596 | where = PDKIM_HDR_LIMBO; | |
80a47a2c | 597 | } |
3045f050 | 598 | else |
acec9514 | 599 | cur_val = string_catn(cur_val, p, 1); |
80a47a2c TK |
600 | } |
601 | ||
3045f050 JH |
602 | NEXT_CHAR: |
603 | if (c == '\0') | |
604 | break; | |
80a47a2c | 605 | |
3045f050 JH |
606 | if (!in_b_val) |
607 | *q++ = c; | |
80a47a2c TK |
608 | } |
609 | ||
286b9d5f JH |
610 | if (sig->keytype < 0 || sig->hashtype < 0) /* Cannot verify this signature */ |
611 | return NULL; | |
612 | ||
3045f050 JH |
613 | *q = '\0'; |
614 | /* Chomp raw header. The final newline must not be added to the signature. */ | |
37f3dc43 JH |
615 | while (--q > sig->rawsig_no_b_val && (*q == '\r' || *q == '\n')) |
616 | *q = '\0'; | |
80a47a2c | 617 | |
0d04a285 | 618 | DEBUG(D_acl) |
3045f050 | 619 | { |
0d04a285 | 620 | debug_printf( |
3045f050 | 621 | "PDKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); |
e2e3255a | 622 | pdkim_quoteprint(US sig->rawsig_no_b_val, Ustrlen(sig->rawsig_no_b_val)); |
0d04a285 | 623 | debug_printf( |
dcd03763 | 624 | "PDKIM >> Sig size: %4u bits\n", (unsigned) sig->sighash.len*8); |
0d04a285 | 625 | debug_printf( |
3045f050 | 626 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); |
80a47a2c | 627 | } |
80a47a2c | 628 | |
cf1cce5e | 629 | if (!pdkim_set_bodyhash(ctx, sig)) |
7b83389d | 630 | return NULL; |
cf1cce5e | 631 | |
3045f050 | 632 | return sig; |
80a47a2c TK |
633 | } |
634 | ||
635 | ||
636 | /* -------------------------------------------------------------------------- */ | |
80a47a2c | 637 | |
f444c2c7 | 638 | static pdkim_pubkey * |
e2e3255a | 639 | pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record) |
3045f050 | 640 | { |
35cf75e9 JH |
641 | const uschar * ele; |
642 | int sep = ';'; | |
643 | pdkim_pubkey * pub; | |
80a47a2c | 644 | |
ca9cb170 | 645 | pub = store_get(sizeof(pdkim_pubkey)); |
abe1010c | 646 | memset(pub, 0, sizeof(pdkim_pubkey)); |
80a47a2c | 647 | |
35cf75e9 JH |
648 | while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0))) |
649 | { | |
650 | const uschar * val; | |
651 | ||
652 | if ((val = Ustrchr(ele, '='))) | |
3045f050 | 653 | { |
35cf75e9 | 654 | int taglen = val++ - ele; |
80a47a2c | 655 | |
35cf75e9 JH |
656 | DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val); |
657 | switch (ele[0]) | |
3045f050 | 658 | { |
35cf75e9 JH |
659 | case 'v': pub->version = val; break; |
660 | case 'h': pub->hashes = val; break; | |
286b9d5f | 661 | case 'k': pub->keytype = val; break; |
35cf75e9 JH |
662 | case 'g': pub->granularity = val; break; |
663 | case 'n': pub->notes = pdkim_decode_qp(val); break; | |
664 | case 'p': pdkim_decode_base64(val, &pub->key); break; | |
665 | case 's': pub->srvtype = val; break; | |
666 | case 't': if (Ustrchr(val, 'y')) pub->testing = 1; | |
667 | if (Ustrchr(val, 's')) pub->no_subdomaining = 1; | |
e21a4d00 | 668 | break; |
35cf75e9 | 669 | default: DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); break; |
80a47a2c | 670 | } |
e21a4d00 | 671 | } |
35cf75e9 | 672 | } |
80a47a2c | 673 | |
3045f050 | 674 | /* Set fallback defaults */ |
fc6fb551 JH |
675 | if (!pub->version) |
676 | pub->version = string_copy(PDKIM_PUB_RECORD_VERSION); | |
135e9496 JH |
677 | else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0) |
678 | { | |
679 | DEBUG(D_acl) debug_printf(" Bad v= field\n"); | |
680 | return NULL; | |
681 | } | |
e21a4d00 | 682 | |
35cf75e9 | 683 | if (!pub->granularity) pub->granularity = US"*"; |
35cf75e9 | 684 | if (!pub->keytype ) pub->keytype = US"rsa"; |
35cf75e9 | 685 | if (!pub->srvtype ) pub->srvtype = US"*"; |
80a47a2c | 686 | |
3045f050 | 687 | /* p= is required */ |
2592e6c0 | 688 | if (pub->key.data) |
80a47a2c | 689 | return pub; |
3045f050 | 690 | |
135e9496 | 691 | DEBUG(D_acl) debug_printf(" Missing p= field\n"); |
3045f050 | 692 | return NULL; |
80a47a2c TK |
693 | } |
694 | ||
695 | ||
696 | /* -------------------------------------------------------------------------- */ | |
3045f050 | 697 | |
cf1cce5e | 698 | /* Update one bodyhash with some additional data. |
9e70917d JH |
699 | If we have to relax the data for this sig, return our copy of it. */ |
700 | ||
9e70917d | 701 | static blob * |
cf1cce5e | 702 | pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data) |
3045f050 | 703 | { |
9e70917d JH |
704 | blob * canon_data = orig_data; |
705 | /* Defaults to simple canon (no further treatment necessary) */ | |
3045f050 | 706 | |
cf1cce5e | 707 | if (b->canon_method == PDKIM_CANON_RELAXED) |
3045f050 | 708 | { |
9e70917d JH |
709 | /* Relax the line if not done already */ |
710 | if (!relaxed_data) | |
3045f050 | 711 | { |
9e70917d | 712 | BOOL seen_wsp = FALSE; |
744976d4 | 713 | const uschar * p, * r; |
9e70917d | 714 | int q = 0; |
3045f050 | 715 | |
9e70917d JH |
716 | /* We want to be able to free this else we allocate |
717 | for the entire message which could be many MB. Since | |
718 | we don't know what allocations the SHA routines might | |
719 | do, not safe to use store_get()/store_reset(). */ | |
d5bccfc8 | 720 | |
9e70917d JH |
721 | relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1); |
722 | relaxed_data->data = US (relaxed_data+1); | |
3045f050 | 723 | |
744976d4 | 724 | for (p = orig_data->data, r = p + orig_data->len; p < r; p++) |
9e70917d JH |
725 | { |
726 | char c = *p; | |
727 | if (c == '\r') | |
728 | { | |
729 | if (q > 0 && relaxed_data->data[q-1] == ' ') | |
730 | q--; | |
731 | } | |
732 | else if (c == '\t' || c == ' ') | |
733 | { | |
734 | c = ' '; /* Turns WSP into SP */ | |
735 | if (seen_wsp) | |
736 | continue; | |
737 | seen_wsp = TRUE; | |
3045f050 | 738 | } |
9e70917d JH |
739 | else |
740 | seen_wsp = FALSE; | |
741 | relaxed_data->data[q++] = c; | |
80a47a2c | 742 | } |
9e70917d JH |
743 | relaxed_data->data[q] = '\0'; |
744 | relaxed_data->len = q; | |
80a47a2c | 745 | } |
9e70917d JH |
746 | canon_data = relaxed_data; |
747 | } | |
80a47a2c | 748 | |
9e70917d | 749 | /* Make sure we don't exceed the to-be-signed body length */ |
cf1cce5e JH |
750 | if ( b->bodylength >= 0 |
751 | && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength | |
9e70917d | 752 | ) |
cf1cce5e | 753 | canon_data->len = b->bodylength - b->signed_body_bytes; |
80a47a2c | 754 | |
9e70917d JH |
755 | if (canon_data->len > 0) |
756 | { | |
cf1cce5e JH |
757 | exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len); |
758 | b->signed_body_bytes += canon_data->len; | |
9e70917d | 759 | DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len); |
80a47a2c TK |
760 | } |
761 | ||
9e70917d | 762 | return relaxed_data; |
6ab02e3f | 763 | } |
80a47a2c TK |
764 | |
765 | ||
766 | /* -------------------------------------------------------------------------- */ | |
80a47a2c | 767 | |
ca9cb170 | 768 | static void |
9e70917d | 769 | pdkim_finish_bodyhash(pdkim_ctx * ctx) |
3045f050 | 770 | { |
cf1cce5e | 771 | pdkim_bodyhash * b; |
9e70917d | 772 | pdkim_signature * sig; |
80a47a2c | 773 | |
cf1cce5e JH |
774 | for (b = ctx->bodyhash; b; b = b->next) /* Finish hashes */ |
775 | exim_sha_finish(&b->body_hash_ctx, &b->bh); | |
776 | ||
3045f050 | 777 | /* Traverse all signatures */ |
f4d091fb | 778 | for (sig = ctx->sig; sig; sig = sig->next) |
cf1cce5e JH |
779 | { |
780 | b = sig->calc_body_hash; | |
3045f050 | 781 | |
0d04a285 | 782 | DEBUG(D_acl) |
3045f050 | 783 | { |
0d04a285 | 784 | debug_printf("PDKIM [%s] Body bytes hashed: %lu\n" |
9e70917d | 785 | "PDKIM [%s] Body %s computed: ", |
cf1cce5e | 786 | sig->domain, b->signed_body_bytes, |
9e70917d | 787 | sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname); |
cf1cce5e | 788 | pdkim_hexprint(CUS b->bh.data, b->bh.len); |
80a47a2c | 789 | } |
3045f050 JH |
790 | |
791 | /* SIGNING -------------------------------------------------------------- */ | |
e983e85a | 792 | if (ctx->flags & PDKIM_MODE_SIGN) |
3045f050 | 793 | { |
3045f050 JH |
794 | /* If bodylength limit is set, and we have received less bytes |
795 | than the requested amount, effectively remove the limit tag. */ | |
cf1cce5e | 796 | if (b->signed_body_bytes < sig->bodylength) |
3045f050 | 797 | sig->bodylength = -1; |
80a47a2c | 798 | } |
3045f050 | 799 | |
3045f050 | 800 | else |
02c4f8fb JH |
801 | /* VERIFICATION --------------------------------------------------------- */ |
802 | /* Be careful that the header sig included a bodyash */ | |
803 | ||
cf1cce5e JH |
804 | if ( sig->bodyhash.data |
805 | && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0) | |
3045f050 | 806 | { |
0d04a285 | 807 | DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash verified OK\n", sig->domain); |
80a47a2c | 808 | } |
3045f050 JH |
809 | else |
810 | { | |
0d04a285 | 811 | DEBUG(D_acl) |
3045f050 | 812 | { |
e21a4d00 | 813 | debug_printf("PDKIM [%s] Body hash signature from headers: ", sig->domain); |
dcd03763 | 814 | pdkim_hexprint(sig->bodyhash.data, sig->bodyhash.len); |
0d04a285 | 815 | debug_printf("PDKIM [%s] Body hash did NOT verify\n", sig->domain); |
3045f050 | 816 | } |
3045f050 JH |
817 | sig->verify_status = PDKIM_VERIFY_FAIL; |
818 | sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY; | |
80a47a2c | 819 | } |
80a47a2c | 820 | } |
6ab02e3f | 821 | } |
80a47a2c TK |
822 | |
823 | ||
824 | ||
9e70917d | 825 | static void |
e983e85a JH |
826 | pdkim_body_complete(pdkim_ctx * ctx) |
827 | { | |
cf1cce5e | 828 | pdkim_bodyhash * b; |
e983e85a JH |
829 | |
830 | /* In simple body mode, if any empty lines were buffered, | |
831 | replace with one. rfc 4871 3.4.3 */ | |
832 | /*XXX checking the signed-body-bytes is a gross hack; I think | |
833 | it indicates that all linebreaks should be buffered, including | |
834 | the one terminating a text line */ | |
835 | ||
cf1cce5e JH |
836 | for (b = ctx->bodyhash; b; b = b->next) |
837 | if ( b->canon_method == PDKIM_CANON_SIMPLE | |
838 | && b->signed_body_bytes == 0 | |
839 | && b->num_buffered_blanklines > 0 | |
9e70917d | 840 | ) |
cf1cce5e | 841 | (void) pdkim_update_ctx_bodyhash(b, &lineending, NULL); |
e983e85a JH |
842 | |
843 | ctx->flags |= PDKIM_SEEN_EOD; | |
844 | ctx->linebuf_offset = 0; | |
e983e85a JH |
845 | } |
846 | ||
847 | ||
848 | ||
80a47a2c | 849 | /* -------------------------------------------------------------------------- */ |
e983e85a | 850 | /* Call from pdkim_feed below for processing complete body lines */ |
744976d4 | 851 | /* NOTE: the line is not NUL-terminated; but we have a count */ |
3045f050 | 852 | |
9e70917d JH |
853 | static void |
854 | pdkim_bodyline_complete(pdkim_ctx * ctx) | |
3045f050 | 855 | { |
9e70917d | 856 | blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset}; |
cf1cce5e | 857 | pdkim_bodyhash * b; |
9e70917d JH |
858 | blob * rnl = NULL; |
859 | blob * rline = NULL; | |
3045f050 JH |
860 | |
861 | /* Ignore extra data if we've seen the end-of-data marker */ | |
9e70917d | 862 | if (ctx->flags & PDKIM_SEEN_EOD) goto all_skip; |
3045f050 JH |
863 | |
864 | /* We've always got one extra byte to stuff a zero ... */ | |
9e70917d | 865 | ctx->linebuf[line.len] = '\0'; |
3045f050 | 866 | |
0d04a285 | 867 | /* Terminate on EOD marker */ |
e983e85a | 868 | if (ctx->flags & PDKIM_DOT_TERM) |
3045f050 | 869 | { |
9e70917d JH |
870 | if (memcmp(line.data, ".\r\n", 3) == 0) |
871 | { pdkim_body_complete(ctx); return; } | |
0d04a285 | 872 | |
e983e85a | 873 | /* Unstuff dots */ |
9e70917d JH |
874 | if (memcmp(line.data, "..", 2) == 0) |
875 | { line.data++; line.len--; } | |
80a47a2c TK |
876 | } |
877 | ||
3045f050 | 878 | /* Empty lines need to be buffered until we find a non-empty line */ |
9e70917d | 879 | if (memcmp(line.data, "\r\n", 2) == 0) |
3045f050 | 880 | { |
cf1cce5e | 881 | for (b = ctx->bodyhash; b; b = b->next) b->num_buffered_blanklines++; |
9e70917d | 882 | goto all_skip; |
80a47a2c TK |
883 | } |
884 | ||
cf1cce5e JH |
885 | /* Process line for each bodyhash separately */ |
886 | for (b = ctx->bodyhash; b; b = b->next) | |
3045f050 | 887 | { |
cf1cce5e | 888 | if (b->canon_method == PDKIM_CANON_RELAXED) |
3045f050 | 889 | { |
9e70917d | 890 | /* Lines with just spaces need to be buffered too */ |
7845dbb3 | 891 | uschar * cp = line.data; |
9e70917d | 892 | char c; |
6a11a9e6 | 893 | |
9e70917d JH |
894 | while ((c = *cp)) |
895 | { | |
896 | if (c == '\r' && cp[1] == '\n') break; | |
cf1cce5e | 897 | if (c != ' ' && c != '\t') goto hash_process; |
9e70917d JH |
898 | cp++; |
899 | } | |
900 | ||
cf1cce5e JH |
901 | b->num_buffered_blanklines++; |
902 | goto hash_skip; | |
6a11a9e6 JH |
903 | } |
904 | ||
cf1cce5e | 905 | hash_process: |
9e70917d | 906 | /* At this point, we have a non-empty line, so release the buffered ones. */ |
6a11a9e6 | 907 | |
cf1cce5e | 908 | while (b->num_buffered_blanklines) |
9e70917d | 909 | { |
cf1cce5e JH |
910 | rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl); |
911 | b->num_buffered_blanklines--; | |
9e70917d JH |
912 | } |
913 | ||
cf1cce5e JH |
914 | rline = pdkim_update_ctx_bodyhash(b, &line, rline); |
915 | hash_skip: ; | |
80a47a2c TK |
916 | } |
917 | ||
9e70917d JH |
918 | if (rnl) store_free(rnl); |
919 | if (rline) store_free(rline); | |
920 | ||
921 | all_skip: | |
80a47a2c | 922 | |
3045f050 | 923 | ctx->linebuf_offset = 0; |
9e70917d | 924 | return; |
80a47a2c TK |
925 | } |
926 | ||
927 | ||
928 | /* -------------------------------------------------------------------------- */ | |
929 | /* Callback from pdkim_feed below for processing complete headers */ | |
930 | #define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:" | |
3045f050 | 931 | |
f444c2c7 | 932 | static int |
2c0f3ea1 | 933 | pdkim_header_complete(pdkim_ctx * ctx) |
3045f050 | 934 | { |
2c0f3ea1 JH |
935 | pdkim_signature * sig, * last_sig; |
936 | ||
3045f050 | 937 | /* Special case: The last header can have an extra \r appended */ |
acec9514 JH |
938 | if ( (ctx->cur_header->ptr > 1) && |
939 | (ctx->cur_header->s[ctx->cur_header->ptr-1] == '\r') ) | |
940 | --ctx->cur_header->ptr; | |
941 | (void) string_from_gstring(ctx->cur_header); | |
80a47a2c | 942 | |
2c0f3ea1 | 943 | if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL; |
80a47a2c | 944 | |
3045f050 | 945 | /* SIGNING -------------------------------------------------------------- */ |
e983e85a | 946 | if (ctx->flags & PDKIM_MODE_SIGN) |
0d04a285 | 947 | for (sig = ctx->sig; sig; sig = sig->next) /* Traverse all signatures */ |
80a47a2c | 948 | |
ab9152ff | 949 | /* Add header to the signed headers list (in reverse order) */ |
acec9514 | 950 | sig->headers = pdkim_prepend_stringlist(sig->headers, ctx->cur_header->s); |
94431adb | 951 | |
0d04a285 | 952 | /* VERIFICATION ----------------------------------------------------------- */ |
3045f050 | 953 | /* DKIM-Signature: headers are added to the verification list */ |
e983e85a | 954 | else |
3045f050 | 955 | { |
f6ee24a2 | 956 | #ifdef notdef |
bd8fbe36 JH |
957 | DEBUG(D_acl) |
958 | { | |
959 | debug_printf("PDKIM >> raw hdr: "); | |
acec9514 | 960 | pdkim_quoteprint(CUS ctx->cur_header->s, ctx->cur_header->ptr); |
bd8fbe36 | 961 | } |
f6ee24a2 | 962 | #endif |
acec9514 | 963 | if (strncasecmp(CCS ctx->cur_header->s, |
3045f050 | 964 | DKIM_SIGNATURE_HEADERNAME, |
e2e3255a | 965 | Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0) |
3045f050 | 966 | { |
02c4f8fb JH |
967 | /* Create and chain new signature block. We could error-check for all |
968 | required tags here, but prefer to create the internal sig and expicitly | |
969 | fail verification of it later. */ | |
80a47a2c | 970 | |
0d04a285 | 971 | DEBUG(D_acl) debug_printf( |
3045f050 | 972 | "PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); |
3045f050 | 973 | |
acec9514 | 974 | sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s); |
02c4f8fb JH |
975 | |
976 | if (!(last_sig = ctx->sig)) | |
2c0f3ea1 | 977 | ctx->sig = sig; |
02c4f8fb | 978 | else |
3045f050 | 979 | { |
02c4f8fb | 980 | while (last_sig->next) last_sig = last_sig->next; |
2c0f3ea1 | 981 | last_sig->next = sig; |
80a47a2c TK |
982 | } |
983 | } | |
37f8b554 | 984 | |
eea19017 | 985 | /* all headers are stored for signature verification */ |
acec9514 | 986 | ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s); |
80a47a2c TK |
987 | } |
988 | ||
3045f050 | 989 | BAIL: |
acec9514 | 990 | ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0'; /* leave buffer for reuse */ |
3045f050 | 991 | return PDKIM_OK; |
6ab02e3f | 992 | } |
80a47a2c TK |
993 | |
994 | ||
995 | ||
996 | /* -------------------------------------------------------------------------- */ | |
997 | #define HEADER_BUFFER_FRAG_SIZE 256 | |
3045f050 JH |
998 | |
999 | DLLEXPORT int | |
ef698bf6 | 1000 | pdkim_feed(pdkim_ctx * ctx, uschar * data, int len) |
3045f050 | 1001 | { |
02c4f8fb | 1002 | int p, rc; |
3045f050 | 1003 | |
e983e85a JH |
1004 | /* Alternate EOD signal, used in non-dotstuffing mode */ |
1005 | if (!data) | |
1006 | pdkim_body_complete(ctx); | |
1007 | ||
10c50704 | 1008 | else for (p = 0; p<len; p++) |
3045f050 | 1009 | { |
ca9cb170 | 1010 | uschar c = data[p]; |
3045f050 | 1011 | |
e983e85a | 1012 | if (ctx->flags & PDKIM_PAST_HDRS) |
3045f050 | 1013 | { |
b895f4b2 JH |
1014 | if (c == '\n' && !(ctx->flags & PDKIM_SEEN_CR)) /* emulate the CR */ |
1015 | { | |
1016 | ctx->linebuf[ctx->linebuf_offset++] = '\r'; | |
1017 | if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1) | |
1018 | return PDKIM_ERR_LONG_LINE; | |
1019 | } | |
1020 | ||
3045f050 | 1021 | /* Processing body byte */ |
0d04a285 | 1022 | ctx->linebuf[ctx->linebuf_offset++] = c; |
b895f4b2 JH |
1023 | if (c == '\r') |
1024 | ctx->flags |= PDKIM_SEEN_CR; | |
1025 | else if (c == '\n') | |
3045f050 | 1026 | { |
b895f4b2 | 1027 | ctx->flags &= ~PDKIM_SEEN_CR; |
9e70917d | 1028 | pdkim_bodyline_complete(ctx); |
80a47a2c | 1029 | } |
b895f4b2 JH |
1030 | |
1031 | if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1) | |
3045f050 | 1032 | return PDKIM_ERR_LONG_LINE; |
80a47a2c | 1033 | } |
3045f050 JH |
1034 | else |
1035 | { | |
1036 | /* Processing header byte */ | |
b895f4b2 JH |
1037 | if (c == '\r') |
1038 | ctx->flags |= PDKIM_SEEN_CR; | |
1039 | else if (c == '\n') | |
3045f050 | 1040 | { |
b895f4b2 | 1041 | if (!(ctx->flags & PDKIM_SEEN_CR)) /* emulate the CR */ |
acec9514 | 1042 | ctx->cur_header = string_catn(ctx->cur_header, CUS "\r", 1); |
b895f4b2 | 1043 | |
02c4f8fb | 1044 | if (ctx->flags & PDKIM_SEEN_LF) /* Seen last header line */ |
b895f4b2 | 1045 | { |
02c4f8fb JH |
1046 | if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK) |
1047 | return rc; | |
b895f4b2 | 1048 | |
863bd541 | 1049 | ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS; |
b895f4b2 | 1050 | DEBUG(D_acl) debug_printf( |
02c4f8fb | 1051 | "PDKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); |
b895f4b2 | 1052 | continue; |
3045f050 | 1053 | } |
b895f4b2 | 1054 | else |
863bd541 | 1055 | ctx->flags = (ctx->flags & ~PDKIM_SEEN_CR) | PDKIM_SEEN_LF; |
b895f4b2 JH |
1056 | } |
1057 | else if (ctx->flags & PDKIM_SEEN_LF) | |
1058 | { | |
02c4f8fb JH |
1059 | if (!(c == '\t' || c == ' ')) /* End of header */ |
1060 | if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK) | |
1061 | return rc; | |
b895f4b2 | 1062 | ctx->flags &= ~PDKIM_SEEN_LF; |
80a47a2c | 1063 | } |
3045f050 | 1064 | |
acec9514 JH |
1065 | if (!ctx->cur_header || ctx->cur_header->ptr < PDKIM_MAX_HEADER_LEN) |
1066 | ctx->cur_header = string_catn(ctx->cur_header, CUS &data[p], 1); | |
80a47a2c TK |
1067 | } |
1068 | } | |
3045f050 | 1069 | return PDKIM_OK; |
6ab02e3f | 1070 | } |
80a47a2c | 1071 | |
ca9cb170 JH |
1072 | |
1073 | ||
acec9514 JH |
1074 | /* Extend a growing header with a continuation-linebreak */ |
1075 | static gstring * | |
1076 | pdkim_hdr_cont(gstring * str, int * col) | |
ca9cb170 JH |
1077 | { |
1078 | *col = 1; | |
acec9514 | 1079 | return string_catn(str, US"\r\n\t", 3); |
ca9cb170 JH |
1080 | } |
1081 | ||
1082 | ||
1083 | ||
05b7d6de JB |
1084 | /* |
1085 | * RFC 5322 specifies that header line length SHOULD be no more than 78 | |
1086 | * lets make it so! | |
1087 | * pdkim_headcat | |
ca9cb170 JH |
1088 | * |
1089 | * returns uschar * (not nul-terminated) | |
05b7d6de JB |
1090 | * |
1091 | * col: this int holds and receives column number (octets since last '\n') | |
1092 | * str: partial string to append to | |
94431adb | 1093 | * pad: padding, split line or space after before or after eg: ";" |
05b7d6de JB |
1094 | * intro: - must join to payload eg "h=", usually the tag name |
1095 | * payload: eg base64 data - long data can be split arbitrarily. | |
1096 | * | |
1097 | * this code doesn't fold the header in some of the places that RFC4871 | |
1098 | * allows: As per RFC5322(2.2.3) it only folds before or after tag-value | |
1099 | * pairs and inside long values. it also always spaces or breaks after the | |
94431adb | 1100 | * "pad" |
05b7d6de JB |
1101 | * |
1102 | * no guarantees are made for output given out-of range input. like tag | |
f444c2c7 | 1103 | * names longer than 78, or bogus col. Input is assumed to be free of line breaks. |
05b7d6de JB |
1104 | */ |
1105 | ||
acec9514 JH |
1106 | static gstring * |
1107 | pdkim_headcat(int * col, gstring * str, | |
ca9cb170 | 1108 | const uschar * pad, const uschar * intro, const uschar * payload) |
3045f050 JH |
1109 | { |
1110 | size_t l; | |
1111 | ||
1112 | if (pad) | |
05b7d6de | 1113 | { |
ca9cb170 | 1114 | l = Ustrlen(pad); |
3045f050 | 1115 | if (*col + l > 78) |
acec9514 JH |
1116 | str = pdkim_hdr_cont(str, col); |
1117 | str = string_catn(str, pad, l); | |
3045f050 | 1118 | *col += l; |
05b7d6de JB |
1119 | } |
1120 | ||
ca9cb170 | 1121 | l = (pad?1:0) + (intro?Ustrlen(intro):0); |
05b7d6de | 1122 | |
3045f050 | 1123 | if (*col + l > 78) |
05b7d6de | 1124 | { /*can't fit intro - start a new line to make room.*/ |
acec9514 | 1125 | str = pdkim_hdr_cont(str, col); |
ca9cb170 | 1126 | l = intro?Ustrlen(intro):0; |
05b7d6de JB |
1127 | } |
1128 | ||
ca9cb170 | 1129 | l += payload ? Ustrlen(payload):0 ; |
05b7d6de | 1130 | |
3045f050 | 1131 | while (l>77) |
05b7d6de | 1132 | { /* this fragment will not fit on a single line */ |
3045f050 | 1133 | if (pad) |
05b7d6de | 1134 | { |
acec9514 | 1135 | str = string_catn(str, US" ", 1); |
3045f050 JH |
1136 | *col += 1; |
1137 | pad = NULL; /* only want this once */ | |
1138 | l--; | |
05b7d6de | 1139 | } |
3045f050 JH |
1140 | |
1141 | if (intro) | |
05b7d6de | 1142 | { |
ca9cb170 | 1143 | size_t sl = Ustrlen(intro); |
3045f050 | 1144 | |
acec9514 | 1145 | str = string_catn(str, intro, sl); |
3045f050 JH |
1146 | *col += sl; |
1147 | l -= sl; | |
1148 | intro = NULL; /* only want this once */ | |
05b7d6de | 1149 | } |
3045f050 JH |
1150 | |
1151 | if (payload) | |
05b7d6de | 1152 | { |
ca9cb170 | 1153 | size_t sl = Ustrlen(payload); |
3045f050 JH |
1154 | size_t chomp = *col+sl < 77 ? sl : 78-*col; |
1155 | ||
acec9514 | 1156 | str = string_catn(str, payload, chomp); |
3045f050 JH |
1157 | *col += chomp; |
1158 | payload += chomp; | |
1159 | l -= chomp-1; | |
05b7d6de | 1160 | } |
3045f050 JH |
1161 | |
1162 | /* the while precondition tells us it didn't fit. */ | |
acec9514 | 1163 | str = pdkim_hdr_cont(str, col); |
05b7d6de | 1164 | } |
3045f050 JH |
1165 | |
1166 | if (*col + l > 78) | |
05b7d6de | 1167 | { |
acec9514 | 1168 | str = pdkim_hdr_cont(str, col); |
3045f050 | 1169 | pad = NULL; |
05b7d6de JB |
1170 | } |
1171 | ||
3045f050 | 1172 | if (pad) |
05b7d6de | 1173 | { |
acec9514 | 1174 | str = string_catn(str, US" ", 1); |
3045f050 JH |
1175 | *col += 1; |
1176 | pad = NULL; | |
05b7d6de JB |
1177 | } |
1178 | ||
3045f050 | 1179 | if (intro) |
05b7d6de | 1180 | { |
ca9cb170 | 1181 | size_t sl = Ustrlen(intro); |
3045f050 | 1182 | |
acec9514 | 1183 | str = string_catn(str, intro, sl); |
3045f050 JH |
1184 | *col += sl; |
1185 | l -= sl; | |
1186 | intro = NULL; | |
05b7d6de | 1187 | } |
3045f050 JH |
1188 | |
1189 | if (payload) | |
05b7d6de | 1190 | { |
ca9cb170 | 1191 | size_t sl = Ustrlen(payload); |
3045f050 | 1192 | |
acec9514 | 1193 | str = string_catn(str, payload, sl); |
3045f050 | 1194 | *col += sl; |
05b7d6de JB |
1195 | } |
1196 | ||
ca9cb170 | 1197 | return str; |
05b7d6de | 1198 | } |
80a47a2c | 1199 | |
3045f050 | 1200 | |
80a47a2c | 1201 | /* -------------------------------------------------------------------------- */ |
3045f050 | 1202 | |
cf1cce5e JH |
1203 | /* Signing: create signature header |
1204 | */ | |
ca9cb170 | 1205 | static uschar * |
9e70917d | 1206 | pdkim_create_header(pdkim_signature * sig, BOOL final) |
3045f050 | 1207 | { |
ca9cb170 JH |
1208 | uschar * base64_bh; |
1209 | uschar * base64_b; | |
3045f050 | 1210 | int col = 0; |
acec9514 JH |
1211 | gstring * hdr; |
1212 | gstring * canon_all; | |
3045f050 | 1213 | |
acec9514 JH |
1214 | canon_all = string_cat (NULL, pdkim_canons[sig->canon_headers]); |
1215 | canon_all = string_catn(canon_all, US"/", 1); | |
1216 | canon_all = string_cat (canon_all, pdkim_canons[sig->canon_body]); | |
1217 | (void) string_from_gstring(canon_all); | |
3045f050 | 1218 | |
acec9514 JH |
1219 | hdr = string_cat(NULL, US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION); |
1220 | col = hdr->ptr; | |
3045f050 JH |
1221 | |
1222 | /* Required and static bits */ | |
acec9514 JH |
1223 | hdr = pdkim_headcat(&col, hdr, US";", US"a=", dkim_sig_to_a_tag(sig)); |
1224 | hdr = pdkim_headcat(&col, hdr, US";", US"q=", pdkim_querymethods[sig->querymethod]); | |
1225 | hdr = pdkim_headcat(&col, hdr, US";", US"c=", canon_all->s); | |
1226 | hdr = pdkim_headcat(&col, hdr, US";", US"d=", sig->domain); | |
1227 | hdr = pdkim_headcat(&col, hdr, US";", US"s=", sig->selector); | |
ca9cb170 JH |
1228 | |
1229 | /* list of header names can be split between items. */ | |
3045f050 | 1230 | { |
e2e3255a | 1231 | uschar * n = string_copy(sig->headernames); |
ca9cb170 JH |
1232 | uschar * i = US"h="; |
1233 | uschar * s = US";"; | |
1234 | ||
1235 | while (*n) | |
05b7d6de | 1236 | { |
ca9cb170 | 1237 | uschar * c = Ustrchr(n, ':'); |
3045f050 | 1238 | |
ca9cb170 | 1239 | if (c) *c ='\0'; |
05b7d6de | 1240 | |
ca9cb170 | 1241 | if (!i) |
acec9514 | 1242 | hdr = pdkim_headcat(&col, hdr, NULL, NULL, US":"); |
05b7d6de | 1243 | |
acec9514 | 1244 | hdr = pdkim_headcat(&col, hdr, s, i, n); |
3045f050 | 1245 | |
ca9cb170 JH |
1246 | if (!c) |
1247 | break; | |
3045f050 | 1248 | |
ca9cb170 JH |
1249 | n = c+1; |
1250 | s = NULL; | |
1251 | i = NULL; | |
80a47a2c | 1252 | } |
ca9cb170 | 1253 | } |
05b7d6de | 1254 | |
cf1cce5e | 1255 | base64_bh = pdkim_encode_base64(&sig->calc_body_hash->bh); |
acec9514 | 1256 | hdr = pdkim_headcat(&col, hdr, US";", US"bh=", base64_bh); |
3045f050 | 1257 | |
ca9cb170 JH |
1258 | /* Optional bits */ |
1259 | if (sig->identity) | |
acec9514 | 1260 | hdr = pdkim_headcat(&col, hdr, US";", US"i=", sig->identity); |
3045f050 | 1261 | |
ca9cb170 JH |
1262 | if (sig->created > 0) |
1263 | { | |
e2e3255a | 1264 | uschar minibuf[20]; |
3045f050 | 1265 | |
e2e3255a | 1266 | snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created); |
acec9514 | 1267 | hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf); |
ca9cb170 | 1268 | } |
3045f050 | 1269 | |
ca9cb170 JH |
1270 | if (sig->expires > 0) |
1271 | { | |
e2e3255a | 1272 | uschar minibuf[20]; |
3045f050 | 1273 | |
e2e3255a | 1274 | snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires); |
acec9514 | 1275 | hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf); |
ca9cb170 | 1276 | } |
3045f050 | 1277 | |
ca9cb170 JH |
1278 | if (sig->bodylength >= 0) |
1279 | { | |
e2e3255a | 1280 | uschar minibuf[20]; |
80a47a2c | 1281 | |
e2e3255a | 1282 | snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength); |
acec9514 | 1283 | hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf); |
3045f050 | 1284 | } |
05b7d6de | 1285 | |
ca9cb170 | 1286 | /* Preliminary or final version? */ |
ea18931d JH |
1287 | if (final) |
1288 | { | |
1289 | base64_b = pdkim_encode_base64(&sig->sighash); | |
acec9514 | 1290 | hdr = pdkim_headcat(&col, hdr, US";", US"b=", base64_b); |
80a47a2c | 1291 | |
ea18931d | 1292 | /* add trailing semicolon: I'm not sure if this is actually needed */ |
acec9514 | 1293 | hdr = pdkim_headcat(&col, hdr, NULL, US";", US""); |
ea18931d JH |
1294 | } |
1295 | else | |
1296 | { | |
1297 | /* To satisfy the rule "all surrounding whitespace [...] deleted" | |
1298 | ( RFC 6376 section 3.7 ) we ensure there is no whitespace here. Otherwise | |
1299 | the headcat routine could insert a linebreak which the relaxer would reduce | |
1300 | to a single space preceding the terminating semicolon, resulting in an | |
1301 | incorrect header-hash. */ | |
acec9514 | 1302 | hdr = pdkim_headcat(&col, hdr, US";", US"b=;", US""); |
ea18931d | 1303 | } |
80a47a2c | 1304 | |
acec9514 | 1305 | return string_from_gstring(hdr); |
80a47a2c TK |
1306 | } |
1307 | ||
1308 | ||
cd1a5fe0 JH |
1309 | /* -------------------------------------------------------------------------- */ |
1310 | ||
1311 | static pdkim_pubkey * | |
b9df1829 JH |
1312 | pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx, |
1313 | const uschar ** errstr) | |
cd1a5fe0 JH |
1314 | { |
1315 | uschar * dns_txt_name, * dns_txt_reply; | |
1316 | pdkim_pubkey * p; | |
cd1a5fe0 JH |
1317 | |
1318 | /* Fetch public key for signing domain, from DNS */ | |
1319 | ||
1320 | dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain); | |
1321 | ||
1eedc10f | 1322 | if ( !(dns_txt_reply = ctx->dns_txt_callback(CS dns_txt_name)) |
cd1a5fe0 JH |
1323 | || dns_txt_reply[0] == '\0' |
1324 | ) | |
1325 | { | |
1326 | sig->verify_status = PDKIM_VERIFY_INVALID; | |
1327 | sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE; | |
1328 | return NULL; | |
1329 | } | |
1330 | ||
1331 | DEBUG(D_acl) | |
1332 | { | |
1333 | debug_printf( | |
1334 | "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" | |
9e70917d JH |
1335 | " %s\n" |
1336 | " Raw record: ", | |
1337 | dns_txt_name); | |
cd1a5fe0 JH |
1338 | pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply)); |
1339 | } | |
1340 | ||
1341 | if ( !(p = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply)) | |
1342 | || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0) | |
1343 | ) | |
1344 | { | |
1345 | sig->verify_status = PDKIM_VERIFY_INVALID; | |
1346 | sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD; | |
1347 | ||
1348 | DEBUG(D_acl) | |
1349 | { | |
1350 | if (p) | |
1351 | debug_printf(" Invalid public key service type '%s'\n", p->srvtype); | |
1352 | else | |
1353 | debug_printf(" Error while parsing public key record\n"); | |
1354 | debug_printf( | |
1355 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); | |
1356 | } | |
1357 | return NULL; | |
1358 | } | |
1359 | ||
1360 | DEBUG(D_acl) debug_printf( | |
1361 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); | |
1362 | ||
1363 | /* Import public key */ | |
286b9d5f JH |
1364 | |
1365 | if ((*errstr = exim_dkim_verify_init(&p->key, | |
1366 | sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER, | |
1367 | vctx))) | |
cd1a5fe0 | 1368 | { |
b9df1829 | 1369 | DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr); |
cd1a5fe0 JH |
1370 | sig->verify_status = PDKIM_VERIFY_INVALID; |
1371 | sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT; | |
1372 | return NULL; | |
1373 | } | |
1374 | ||
286b9d5f | 1375 | vctx->keytype = sig->keytype; |
cd1a5fe0 JH |
1376 | return p; |
1377 | } | |
1378 | ||
1379 | ||
80a47a2c | 1380 | /* -------------------------------------------------------------------------- */ |
3045f050 JH |
1381 | |
1382 | DLLEXPORT int | |
b9df1829 JH |
1383 | pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures, |
1384 | const uschar ** err) | |
3045f050 | 1385 | { |
cf1cce5e | 1386 | pdkim_bodyhash * b; |
9e70917d | 1387 | pdkim_signature * sig; |
286b9d5f JH |
1388 | BOOL verify_pass = FALSE; |
1389 | es_ctx sctx; | |
3045f050 JH |
1390 | |
1391 | /* Check if we must still flush a (partial) header. If that is the | |
1392 | case, the message has no body, and we must compute a body hash | |
1393 | out of '<CR><LF>' */ | |
acec9514 | 1394 | if (ctx->cur_header && ctx->cur_header->ptr > 0) |
3045f050 | 1395 | { |
9e70917d JH |
1396 | blob * rnl = NULL; |
1397 | int rc; | |
1398 | ||
1399 | if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK) | |
1400 | return rc; | |
1401 | ||
cf1cce5e JH |
1402 | for (b = ctx->bodyhash; b; b = b->next) |
1403 | rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl); | |
9e70917d | 1404 | if (rnl) store_free(rnl); |
80a47a2c | 1405 | } |
3045f050 | 1406 | else |
0d04a285 | 1407 | DEBUG(D_acl) debug_printf( |
3045f050 | 1408 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); |
80a47a2c | 1409 | |
286b9d5f JH |
1410 | if (!ctx->sig) |
1411 | { | |
1412 | DEBUG(D_acl) debug_printf("PDKIM: no signatures\n"); | |
1413 | return PDKIM_OK; | |
1414 | } | |
1415 | ||
3045f050 | 1416 | /* Build (and/or evaluate) body hash */ |
ca9cb170 | 1417 | pdkim_finish_bodyhash(ctx); |
80a47a2c | 1418 | |
9e70917d | 1419 | for (sig = ctx->sig; sig; sig = sig->next) |
3045f050 | 1420 | { |
2592e6c0 | 1421 | hctx hhash_ctx; |
ad6f5499 | 1422 | uschar * sig_hdr = US""; |
2592e6c0 | 1423 | blob hhash; |
acec9514 | 1424 | gstring * hdata = NULL; |
286b9d5f JH |
1425 | es_ctx sctx; |
1426 | ||
1427 | /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA | |
1428 | suging only, as it happens) and for either GnuTLS and OpenSSL when we are | |
1429 | signing with EC (specifically, Ed25519). The former is because the GCrypt | |
1430 | signing operation is pure (does not do its own hash) so we must hash. The | |
1431 | latter is because we (stupidly, but this is what the IETF draft is saying) | |
1432 | must hash with the declared hash method, then pass the result to the library | |
1433 | hash-and-sign routine (because that's all the libraries are providing. And | |
1434 | we're stuck with whatever that hidden hash method is, too). We may as well | |
1435 | do this hash incrementally. | |
1436 | We don't need the hash we're calculating here for the GnuTLS and OpenSSL | |
1437 | cases of RSA signing, since those library routines can do hash-and-sign. | |
1438 | ||
1439 | Some time in the future we could easily avoid doing the hash here for those | |
1440 | cases (which will be common for a long while. We could also change from | |
1441 | the current copy-all-the-headers-into-one-block, then call the hash-and-sign | |
1442 | implementation - to a proper incremental one. Unfortunately, GnuTLS just | |
1443 | cannot do incremental - either signing or verification. Unsure about GCrypt. | |
1444 | */ | |
1445 | ||
1446 | /*XXX The header hash is also used (so far) by the verify operation */ | |
2592e6c0 | 1447 | |
d73e45df | 1448 | if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod)) |
7b83389d | 1449 | { |
286b9d5f JH |
1450 | log_write(0, LOG_MAIN|LOG_PANIC, |
1451 | "PDKIM: hash setup error, possibly nonhandled hashtype"); | |
7b83389d JH |
1452 | break; |
1453 | } | |
80a47a2c | 1454 | |
9e70917d JH |
1455 | if (ctx->flags & PDKIM_MODE_SIGN) |
1456 | DEBUG(D_acl) debug_printf( | |
1457 | "PDKIM >> Headers to be signed: >>>>>>>>>>>>\n" | |
1458 | " %s\n", | |
1459 | sig->sign_headers); | |
1460 | ||
0d04a285 | 1461 | DEBUG(D_acl) debug_printf( |
328c5688 | 1462 | "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>\n"); |
3045f050 | 1463 | |
9e70917d | 1464 | |
3045f050 JH |
1465 | /* SIGNING ---------------------------------------------------------------- */ |
1466 | /* When signing, walk through our header list and add them to the hash. As we | |
8ef02a06 JH |
1467 | go, construct a list of the header's names to use for the h= parameter. |
1468 | Then append to that list any remaining header names for which there was no | |
1469 | header to sign. */ | |
3045f050 | 1470 | |
e983e85a | 1471 | if (ctx->flags & PDKIM_MODE_SIGN) |
3045f050 | 1472 | { |
acec9514 | 1473 | gstring * g = NULL; |
3045f050 | 1474 | pdkim_stringlist *p; |
8ef02a06 JH |
1475 | const uschar * l; |
1476 | uschar * s; | |
1477 | int sep = 0; | |
3045f050 | 1478 | |
286b9d5f JH |
1479 | /* Import private key, including the keytype which we need for building |
1480 | the signature header */ | |
1481 | ||
1482 | /*XXX extend for non-RSA algos */ | |
1483 | if ((*err = exim_dkim_signing_init(US sig->privkey, &sctx))) | |
1484 | { | |
1485 | log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err); | |
1486 | return PDKIM_ERR_RSA_PRIVKEY; | |
1487 | } | |
1488 | sig->keytype = sctx.keytype; | |
9e70917d | 1489 | |
286b9d5f JH |
1490 | for (sig->headernames = NULL, /* Collected signed header names */ |
1491 | p = sig->headers; p; p = p->next) | |
9e70917d JH |
1492 | { |
1493 | uschar * rh = p->value; | |
1494 | ||
1495 | if (header_name_match(rh, sig->sign_headers) == PDKIM_OK) | |
ab9152ff | 1496 | { |
ab9152ff | 1497 | /* Collect header names (Note: colon presence is guaranteed here) */ |
acec9514 | 1498 | g = string_append_listele_n(g, ':', rh, Ustrchr(rh, ':') - rh); |
3045f050 | 1499 | |
9e70917d JH |
1500 | if (sig->canon_headers == PDKIM_CANON_RELAXED) |
1501 | rh = pdkim_relax_header(rh, TRUE); /* cook header for relaxed canon */ | |
3045f050 | 1502 | |
ab9152ff | 1503 | /* Feed header to the hash algorithm */ |
e2e3255a | 1504 | exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh)); |
f444c2c7 | 1505 | |
ab9152ff | 1506 | /* Remember headers block for signing (when the library cannot do incremental) */ |
286b9d5f | 1507 | /*XXX we could avoid doing this for all but the GnuTLS/RSA case */ |
acec9514 | 1508 | hdata = exim_dkim_data_append(hdata, rh); |
3045f050 | 1509 | |
ab9152ff JH |
1510 | DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh)); |
1511 | } | |
9e70917d | 1512 | } |
8ef02a06 | 1513 | |
484cc1a9 JH |
1514 | /* Any headers we wanted to sign but were not present must also be listed. |
1515 | Ignore elements that have been ticked-off or are marked as never-oversign. */ | |
1516 | ||
ca9cb170 | 1517 | l = sig->sign_headers; |
8ef02a06 | 1518 | while((s = string_nextinlist(&l, &sep, NULL, 0))) |
484cc1a9 JH |
1519 | { |
1520 | if (*s == '+') /* skip oversigning marker */ | |
1521 | s++; | |
1522 | if (*s != '_' && *s != '=') | |
acec9514 | 1523 | g = string_append_listele(g, ':', s); |
484cc1a9 | 1524 | } |
acec9514 | 1525 | sig->headernames = string_from_gstring(g); |
ca9cb170 JH |
1526 | |
1527 | /* Create signature header with b= omitted */ | |
1528 | sig_hdr = pdkim_create_header(sig, FALSE); | |
80a47a2c | 1529 | } |
37f8b554 | 1530 | |
3045f050 JH |
1531 | /* VERIFICATION ----------------------------------------------------------- */ |
1532 | /* When verifying, walk through the header name list in the h= parameter and | |
1533 | add the headers to the hash in that order. */ | |
1534 | else | |
1535 | { | |
10c50704 | 1536 | uschar * p = sig->headernames; |
2592e6c0 JH |
1537 | uschar * q; |
1538 | pdkim_stringlist * hdrs; | |
3045f050 | 1539 | |
10c50704 | 1540 | if (p) |
3045f050 | 1541 | { |
10c50704 | 1542 | /* clear tags */ |
3045f050 | 1543 | for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next) |
10c50704 | 1544 | hdrs->tag = 0; |
3045f050 | 1545 | |
10c50704 JH |
1546 | p = string_copy(p); |
1547 | while(1) | |
1548 | { | |
1549 | if ((q = Ustrchr(p, ':'))) | |
1550 | *q = '\0'; | |
1551 | ||
1552 | /*XXX walk the list of headers in same order as received. */ | |
1553 | for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next) | |
1554 | if ( hdrs->tag == 0 | |
4dc2379a | 1555 | && strncasecmp(CCS hdrs->value, CCS p, Ustrlen(p)) == 0 |
10c50704 JH |
1556 | && (hdrs->value)[Ustrlen(p)] == ':' |
1557 | ) | |
1558 | { | |
1559 | /* cook header for relaxed canon, or just copy it for simple */ | |
1560 | ||
1561 | uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED | |
ea18931d | 1562 | ? pdkim_relax_header(hdrs->value, TRUE) |
10c50704 JH |
1563 | : string_copy(CUS hdrs->value); |
1564 | ||
1565 | /* Feed header to the hash algorithm */ | |
1566 | exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh)); | |
1567 | ||
1568 | DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh)); | |
1569 | hdrs->tag = 1; | |
1570 | break; | |
1571 | } | |
3045f050 | 1572 | |
10c50704 JH |
1573 | if (!q) break; |
1574 | p = q+1; | |
1575 | } | |
3045f050 | 1576 | |
10c50704 | 1577 | sig_hdr = string_copy(sig->rawsig_no_b_val); |
80a47a2c | 1578 | } |
80a47a2c TK |
1579 | } |
1580 | ||
0d04a285 | 1581 | DEBUG(D_acl) debug_printf( |
3045f050 | 1582 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); |
80a47a2c | 1583 | |
9e70917d JH |
1584 | DEBUG(D_acl) |
1585 | { | |
1586 | debug_printf( | |
1587 | "PDKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n"); | |
1588 | pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr)); | |
1589 | debug_printf( | |
1590 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); | |
1591 | } | |
1592 | ||
3045f050 JH |
1593 | /* Relax header if necessary */ |
1594 | if (sig->canon_headers == PDKIM_CANON_RELAXED) | |
ea18931d | 1595 | sig_hdr = pdkim_relax_header(sig_hdr, FALSE); |
80a47a2c | 1596 | |
0d04a285 | 1597 | DEBUG(D_acl) |
3045f050 | 1598 | { |
0d04a285 | 1599 | debug_printf( |
3045f050 | 1600 | "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n"); |
e2e3255a | 1601 | pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr)); |
0d04a285 | 1602 | debug_printf( |
3045f050 | 1603 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); |
80a47a2c | 1604 | } |
3045f050 JH |
1605 | |
1606 | /* Finalize header hash */ | |
e2e3255a | 1607 | exim_sha_update(&hhash_ctx, CUS sig_hdr, Ustrlen(sig_hdr)); |
2592e6c0 | 1608 | exim_sha_finish(&hhash_ctx, &hhash); |
3045f050 | 1609 | |
f444c2c7 JH |
1610 | DEBUG(D_acl) |
1611 | { | |
9e70917d JH |
1612 | debug_printf("PDKIM [%s] Header %s computed: ", |
1613 | sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname); | |
2592e6c0 | 1614 | pdkim_hexprint(hhash.data, hhash.len); |
80a47a2c TK |
1615 | } |
1616 | ||
9e70917d JH |
1617 | /* Remember headers block for signing (when the signing library cannot do |
1618 | incremental) */ | |
e983e85a | 1619 | if (ctx->flags & PDKIM_MODE_SIGN) |
acec9514 | 1620 | hdata = exim_dkim_data_append(hdata, US sig_hdr); |
f444c2c7 | 1621 | |
3045f050 | 1622 | /* SIGNING ---------------------------------------------------------------- */ |
e983e85a | 1623 | if (ctx->flags & PDKIM_MODE_SIGN) |
3045f050 | 1624 | { |
286b9d5f JH |
1625 | hashmethod hm = sig->keytype == KEYTYPE_ED25519 |
1626 | ? HASH_SHA2_512 : pdkim_hashes[sig->hashtype].exim_hashmethod; | |
f444c2c7 | 1627 | |
286b9d5f JH |
1628 | #ifdef SIGN_HAVE_ED25519 |
1629 | /* For GCrypt, and for EC, we pass the hash-of-headers to the signing | |
1630 | routine. For anything else we just pass the headers. */ | |
f444c2c7 | 1631 | |
286b9d5f | 1632 | if (sig->keytype != KEYTYPE_ED25519) |
f444c2c7 | 1633 | #endif |
286b9d5f JH |
1634 | { |
1635 | hhash.data = hdata->s; | |
1636 | hhash.len = hdata->ptr; | |
1637 | } | |
f444c2c7 | 1638 | |
9b2583c4 | 1639 | /*XXX extend for non-RSA algos */ |
286b9d5f JH |
1640 | /*- done for GnuTLS */ |
1641 | if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash))) | |
f444c2c7 | 1642 | { |
286b9d5f | 1643 | log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err); |
f444c2c7 JH |
1644 | return PDKIM_ERR_RSA_SIGNING; |
1645 | } | |
80a47a2c | 1646 | |
0d04a285 | 1647 | DEBUG(D_acl) |
3045f050 | 1648 | { |
0d04a285 | 1649 | debug_printf( "PDKIM [%s] b computed: ", sig->domain); |
dcd03763 | 1650 | pdkim_hexprint(sig->sighash.data, sig->sighash.len); |
80a47a2c | 1651 | } |
80a47a2c | 1652 | |
ca9cb170 | 1653 | sig->signature_header = pdkim_create_header(sig, TRUE); |
80a47a2c | 1654 | } |
80a47a2c | 1655 | |
3045f050 JH |
1656 | /* VERIFICATION ----------------------------------------------------------- */ |
1657 | else | |
1658 | { | |
2592e6c0 | 1659 | ev_ctx vctx; |
3045f050 | 1660 | |
07eeb4df | 1661 | /* Make sure we have all required signature tags */ |
1662 | if (!( sig->domain && *sig->domain | |
1663 | && sig->selector && *sig->selector | |
1664 | && sig->headernames && *sig->headernames | |
1665 | && sig->bodyhash.data | |
dcd03763 | 1666 | && sig->sighash.data |
d73e45df JH |
1667 | && sig->keytype >= 0 |
1668 | && sig->hashtype >= 0 | |
07eeb4df | 1669 | && sig->version |
1670 | ) ) | |
1671 | { | |
1672 | sig->verify_status = PDKIM_VERIFY_INVALID; | |
1673 | sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR; | |
1674 | ||
1675 | DEBUG(D_acl) debug_printf( | |
1676 | " Error in DKIM-Signature header: tags missing or invalid\n" | |
1677 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); | |
1678 | goto NEXT_VERIFY; | |
1679 | } | |
1680 | ||
1681 | /* Make sure sig uses supported DKIM version (only v1) */ | |
1682 | if (sig->version != 1) | |
1683 | { | |
1684 | sig->verify_status = PDKIM_VERIFY_INVALID; | |
1685 | sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION; | |
1686 | ||
1687 | DEBUG(D_acl) debug_printf( | |
1688 | " Error in DKIM-Signature header: unsupported DKIM version\n" | |
1689 | "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); | |
1690 | goto NEXT_VERIFY; | |
1691 | } | |
1692 | ||
9e70917d JH |
1693 | DEBUG(D_acl) |
1694 | { | |
1695 | debug_printf( "PDKIM [%s] b from mail: ", sig->domain); | |
1696 | pdkim_hexprint(sig->sighash.data, sig->sighash.len); | |
1697 | } | |
1698 | ||
b9df1829 | 1699 | if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err))) |
286b9d5f JH |
1700 | { |
1701 | log_write(0, LOG_MAIN, "PDKIM: %s%s %s%s [failed key import]", | |
1702 | sig->domain ? "d=" : "", sig->domain ? sig->domain : US"", | |
1703 | sig->selector ? "s=" : "", sig->selector ? sig->selector : US""); | |
3045f050 | 1704 | goto NEXT_VERIFY; |
286b9d5f | 1705 | } |
80a47a2c | 1706 | |
135e9496 JH |
1707 | /* If the pubkey limits to a list of specific hashes, ignore sigs that |
1708 | do not have the hash part of the sig algorithm matching */ | |
1709 | ||
1710 | if (sig->pubkey->hashes) | |
1711 | { | |
1712 | const uschar * list = sig->pubkey->hashes, * ele; | |
1713 | int sep = ':'; | |
1714 | while ((ele = string_nextinlist(&list, &sep, NULL, 0))) | |
d73e45df | 1715 | if (Ustrcmp(ele, pdkim_hashes[sig->hashtype].dkim_hashname) == 0) break; |
135e9496 JH |
1716 | if (!ele) |
1717 | { | |
d73e45df JH |
1718 | DEBUG(D_acl) debug_printf("pubkey h=%s vs. sig a=%s_%s\n", |
1719 | sig->pubkey->hashes, | |
1720 | pdkim_keytypes[sig->keytype], | |
1721 | pdkim_hashes[sig->hashtype].dkim_hashname); | |
135e9496 JH |
1722 | sig->verify_status = PDKIM_VERIFY_FAIL; |
1723 | sig->verify_ext_status = PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH; | |
1724 | goto NEXT_VERIFY; | |
1725 | } | |
1726 | } | |
1727 | ||
3045f050 | 1728 | /* Check the signature */ |
286b9d5f JH |
1729 | /*XXX extend for non-RSA algos */ |
1730 | /*- done for GnuTLS */ | |
d73e45df | 1731 | if ((*err = exim_dkim_verify(&vctx, |
286b9d5f JH |
1732 | pdkim_hashes[sig->hashtype].exim_hashmethod, |
1733 | &hhash, &sig->sighash))) | |
3045f050 | 1734 | { |
b9df1829 | 1735 | DEBUG(D_acl) debug_printf("headers verify: %s\n", *err); |
3045f050 JH |
1736 | sig->verify_status = PDKIM_VERIFY_FAIL; |
1737 | sig->verify_ext_status = PDKIM_VERIFY_FAIL_MESSAGE; | |
1738 | goto NEXT_VERIFY; | |
ff7ddfd7 TK |
1739 | } |
1740 | ||
2592e6c0 | 1741 | |
4c04137d | 1742 | /* We have a winner! (if bodyhash was correct earlier) */ |
3045f050 | 1743 | if (sig->verify_status == PDKIM_VERIFY_NONE) |
286b9d5f | 1744 | { |
3045f050 | 1745 | sig->verify_status = PDKIM_VERIFY_PASS; |
286b9d5f JH |
1746 | verify_pass = TRUE; |
1747 | } | |
3045f050 JH |
1748 | |
1749 | NEXT_VERIFY: | |
1750 | ||
0d04a285 | 1751 | DEBUG(D_acl) |
3045f050 | 1752 | { |
286b9d5f JH |
1753 | debug_printf("PDKIM [%s] %s signature status: %s", |
1754 | sig->domain, dkim_sig_to_a_tag(sig), | |
1755 | pdkim_verify_status_str(sig->verify_status)); | |
3045f050 | 1756 | if (sig->verify_ext_status > 0) |
0d04a285 | 1757 | debug_printf(" (%s)\n", |
3045f050 JH |
1758 | pdkim_verify_ext_status_str(sig->verify_ext_status)); |
1759 | else | |
0d04a285 | 1760 | debug_printf("\n"); |
80a47a2c | 1761 | } |
80a47a2c | 1762 | } |
80a47a2c TK |
1763 | } |
1764 | ||
3045f050 JH |
1765 | /* If requested, set return pointer to signature(s) */ |
1766 | if (return_signatures) | |
1767 | *return_signatures = ctx->sig; | |
80a47a2c | 1768 | |
286b9d5f JH |
1769 | return ctx->flags & PDKIM_MODE_SIGN || verify_pass |
1770 | ? PDKIM_OK : PDKIM_FAIL; | |
80a47a2c TK |
1771 | } |
1772 | ||
1773 | ||
1774 | /* -------------------------------------------------------------------------- */ | |
3045f050 JH |
1775 | |
1776 | DLLEXPORT pdkim_ctx * | |
1eedc10f | 1777 | pdkim_init_verify(uschar * (*dns_txt_callback)(char *), BOOL dot_stuffing) |
3045f050 | 1778 | { |
ca9cb170 | 1779 | pdkim_ctx * ctx; |
3045f050 | 1780 | |
ca9cb170 | 1781 | ctx = store_get(sizeof(pdkim_ctx)); |
abe1010c | 1782 | memset(ctx, 0, sizeof(pdkim_ctx)); |
3045f050 | 1783 | |
e983e85a | 1784 | if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM; |
ca9cb170 | 1785 | ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN); |
3045f050 | 1786 | ctx->dns_txt_callback = dns_txt_callback; |
80a47a2c | 1787 | |
3045f050 | 1788 | return ctx; |
80a47a2c TK |
1789 | } |
1790 | ||
1791 | ||
1792 | /* -------------------------------------------------------------------------- */ | |
80a47a2c | 1793 | |
9e70917d JH |
1794 | DLLEXPORT pdkim_signature * |
1795 | pdkim_init_sign(pdkim_ctx * ctx, | |
1796 | uschar * domain, uschar * selector, uschar * privkey, | |
1797 | uschar * hashname, const uschar ** errstr) | |
3045f050 | 1798 | { |
d73e45df | 1799 | int hashtype; |
cd1a5fe0 | 1800 | pdkim_signature * sig; |
80a47a2c | 1801 | |
d73e45df | 1802 | if (!domain || !selector || !privkey) |
3045f050 | 1803 | return NULL; |
80a47a2c | 1804 | |
9e70917d | 1805 | /* Allocate & init one signature struct */ |
80a47a2c | 1806 | |
9e70917d | 1807 | sig = store_get(sizeof(pdkim_signature)); |
abe1010c | 1808 | memset(sig, 0, sizeof(pdkim_signature)); |
80a47a2c | 1809 | |
3045f050 | 1810 | sig->bodylength = -1; |
80a47a2c | 1811 | |
e2e3255a JH |
1812 | sig->domain = string_copy(US domain); |
1813 | sig->selector = string_copy(US selector); | |
d73e45df | 1814 | sig->privkey = string_copy(US privkey); |
286b9d5f | 1815 | sig->keytype = -1; |
cb224393 | 1816 | |
d73e45df JH |
1817 | for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++) |
1818 | if (Ustrcmp(hashname, pdkim_hashes[hashtype].dkim_hashname) == 0) | |
1819 | { sig->hashtype = hashtype; break; } | |
1820 | if (hashtype >= nelem(pdkim_hashes)) | |
7b83389d | 1821 | { |
286b9d5f JH |
1822 | log_write(0, LOG_MAIN|LOG_PANIC, |
1823 | "PDKIM: unrecognised hashname '%s'", hashname); | |
d73e45df JH |
1824 | return NULL; |
1825 | } | |
1826 | ||
cd1a5fe0 JH |
1827 | DEBUG(D_acl) |
1828 | { | |
1829 | pdkim_signature s = *sig; | |
1830 | ev_ctx vctx; | |
1831 | ||
328c5688 | 1832 | debug_printf("PDKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); |
b9df1829 | 1833 | if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr)) |
cd1a5fe0 | 1834 | debug_printf("WARNING: bad dkim key in dns\n"); |
328c5688 | 1835 | debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); |
cd1a5fe0 | 1836 | } |
9e70917d | 1837 | return sig; |
6ab02e3f | 1838 | } |
80a47a2c | 1839 | |
f444c2c7 | 1840 | |
80a47a2c | 1841 | /* -------------------------------------------------------------------------- */ |
3045f050 | 1842 | |
9e70917d JH |
1843 | DLLEXPORT void |
1844 | pdkim_set_optional(pdkim_signature * sig, | |
1845 | char * sign_headers, | |
1846 | char * identity, | |
80a47a2c TK |
1847 | int canon_headers, |
1848 | int canon_body, | |
1849 | long bodylength, | |
80a47a2c | 1850 | unsigned long created, |
3045f050 JH |
1851 | unsigned long expires) |
1852 | { | |
3045f050 | 1853 | if (identity) |
e2e3255a | 1854 | sig->identity = string_copy(US identity); |
80a47a2c | 1855 | |
ca9cb170 JH |
1856 | sig->sign_headers = string_copy(sign_headers |
1857 | ? US sign_headers : US PDKIM_DEFAULT_SIGN_HEADERS); | |
80a47a2c | 1858 | |
8ef02a06 JH |
1859 | sig->canon_headers = canon_headers; |
1860 | sig->canon_body = canon_body; | |
1861 | sig->bodylength = bodylength; | |
1862 | sig->created = created; | |
1863 | sig->expires = expires; | |
80a47a2c | 1864 | |
9e70917d JH |
1865 | return; |
1866 | } | |
1867 | ||
1868 | ||
1869 | ||
cf1cce5e JH |
1870 | /* Set up a blob for calculating the bodyhash according to the |
1871 | needs of this signature. Use an existing one if possible, or | |
1872 | create a new one. | |
1873 | ||
1874 | Return: hashblob pointer, or NULL on error (only used as a boolean). | |
1875 | */ | |
1876 | pdkim_bodyhash * | |
1877 | pdkim_set_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig) | |
1878 | { | |
1879 | pdkim_bodyhash * b; | |
1880 | ||
1881 | for (b = ctx->bodyhash; b; b = b->next) | |
1882 | if ( sig->hashtype == b->hashtype | |
1883 | && sig->canon_body == b->canon_method | |
1884 | && sig->bodylength == b->bodylength) | |
1885 | goto old; | |
1886 | ||
1887 | b = store_get(sizeof(pdkim_bodyhash)); | |
1888 | b->next = ctx->bodyhash; | |
1889 | b->hashtype = sig->hashtype; | |
1890 | b->canon_method = sig->canon_body; | |
1891 | b->bodylength = sig->bodylength; | |
1892 | if (!exim_sha_init(&b->body_hash_ctx, /*XXX hash method: extend for sha512 */ | |
1893 | pdkim_hashes[sig->hashtype].exim_hashmethod)) | |
1894 | { | |
1895 | DEBUG(D_acl) | |
1896 | debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n"); | |
1897 | return NULL; | |
1898 | } | |
1899 | b->signed_body_bytes = 0; | |
1900 | b->num_buffered_blanklines = 0; | |
1901 | ctx->bodyhash = b; | |
1902 | ||
1903 | old: | |
1904 | sig->calc_body_hash = b; | |
1905 | return b; | |
1906 | } | |
1907 | ||
1908 | ||
1909 | /* -------------------------------------------------------------------------- */ | |
1910 | ||
1911 | ||
9e70917d JH |
1912 | void |
1913 | pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed, | |
1eedc10f | 1914 | uschar * (*dns_txt_callback)(char *)) |
9e70917d JH |
1915 | { |
1916 | memset(ctx, 0, sizeof(pdkim_ctx)); | |
1917 | ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN; | |
1918 | ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN); | |
1919 | DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback; | |
6ab02e3f | 1920 | } |
3045f050 | 1921 | |
3045f050 | 1922 | |
2592e6c0 JH |
1923 | void |
1924 | pdkim_init(void) | |
1925 | { | |
9b2583c4 | 1926 | exim_dkim_init(); |
2592e6c0 JH |
1927 | } |
1928 | ||
1929 | ||
1930 | ||
f444c2c7 | 1931 | #endif /*DISABLE_DKIM*/ |