SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / tlscert-gnu.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2021 - 2022 */
6 /* Copyright (c) Jeremy Harris 2014 - 2018 */
7 /* SPDX-License-Identifier: GPL-2.0-only */
8
9 /* This file provides TLS/SSL support for Exim using the GnuTLS library,
10 one of the available supported implementations.  This file is #included into
11 tls.c when USE_GNUTLS has been set.
12 */
13
14 #include <gnutls/gnutls.h>
15 /* needed for cert checks in verification and DN extraction: */
16 #include <gnutls/x509.h>
17 /* needed to disable PKCS11 autoload unless requested */
18 #if GNUTLS_VERSION_NUMBER >= 0x020c00
19 # include <gnutls/pkcs11.h>
20 #endif
21
22
23 /*****************************************************
24 *  Export/import a certificate, binary/printable
25 ******************************************************
26 Return: boolean success
27 */
28
29 BOOL
30 tls_export_cert(uschar * buf, size_t buflen, void * cert)
31 {
32 size_t sz = buflen;
33 rmark reset_point = store_mark();
34 BOOL fail;
35 const uschar * cp;
36
37 if ((fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
38     GNUTLS_X509_FMT_PEM, buf, &sz)))
39   {
40   log_write(0, LOG_MAIN, "TLS error in certificate export: %s",
41     gnutls_strerror(fail));
42   return FALSE;
43   }
44 if ((cp = string_printing(buf)) != buf)
45   {
46   Ustrncpy(buf, cp, buflen);
47   if (buf[buflen-1])
48     fail = 1;
49   }
50 store_reset(reset_point);
51 return !fail;
52 }
53
54 /* On error, NULL out the destination */
55 BOOL
56 tls_import_cert(const uschar * buf, void ** cert)
57 {
58 rmark reset_point = store_mark();
59 gnutls_datum_t datum;
60 gnutls_x509_crt_t crt = *(gnutls_x509_crt_t *)cert;
61 int rc;
62
63 if (crt)
64   gnutls_x509_crt_deinit(crt);
65 else
66   gnutls_global_init();
67
68 gnutls_x509_crt_init(&crt);
69
70 datum.data = string_unprinting(US buf);
71 datum.size = Ustrlen(datum.data);
72 if ((rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM)))
73   {
74   log_write(0, LOG_MAIN, "TLS error in certificate import: %s",
75     gnutls_strerror(rc));
76   crt = NULL;
77   }
78 *cert = (void *)crt;
79 store_reset(reset_point);
80 return rc != 0;
81 }
82
83 void
84 tls_free_cert(void ** cert)
85 {
86 gnutls_x509_crt_t crt = *(gnutls_x509_crt_t *)cert;
87 if (crt)
88   {
89   gnutls_x509_crt_deinit(crt);
90   gnutls_global_deinit();
91   *cert = NULL;
92   }
93 }
94
95 /*****************************************************
96 *  Certificate field extraction routines
97 *****************************************************/
98
99 /* First, some internal service functions */
100
101 static uschar *
102 g_err(const char * tag, const char * from, int gnutls_err)
103 {
104 expand_string_message = string_sprintf("%s: %s fail: %s\n",
105   from, tag, gnutls_strerror(gnutls_err));
106 return NULL;
107 }
108
109
110 static uschar *
111 time_copy(time_t t, uschar * mod)
112 {
113 uschar * cp;
114 size_t len = 32;
115
116 if (mod && Ustrcmp(mod, "int") == 0)
117   return string_sprintf("%u", (unsigned)t);
118
119 cp = store_get(len, GET_UNTAINTED);
120 if (f.timestamps_utc)
121   {
122   uschar * tz = to_tz(US"GMT0");
123   len = strftime(CS cp, len, "%b %e %T %Y %Z", gmtime(&t));
124   restore_tz(tz);
125   }
126 else
127   len = strftime(CS cp, len, "%b %e %T %Y %Z", localtime(&t));
128 return len > 0 ? cp : NULL;
129 }
130
131
132 /**/
133 /* Now the extractors, called from expand.c
134 Arguments:
135   cert          The certificate
136   mod           Optional modifiers for the operator
137
138 Return:
139   Allocated string with extracted value
140 */
141
142 uschar *
143 tls_cert_issuer(void * cert, uschar * mod)
144 {
145 uschar * cp = NULL;
146 int ret;
147 size_t siz = 0;
148
149 if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz))
150     != GNUTLS_E_SHORT_MEMORY_BUFFER)
151   return g_err("gi0", __FUNCTION__, ret);
152
153 cp = store_get(siz, GET_TAINTED);
154 if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz)) < 0)
155   return g_err("gi1", __FUNCTION__, ret);
156
157 return mod ? tls_field_from_dn(cp, mod) : cp;
158 }
159
160 uschar *
161 tls_cert_not_after(void * cert, uschar * mod)
162 {
163 return time_copy(
164   gnutls_x509_crt_get_expiration_time((gnutls_x509_crt_t)cert),
165   mod);
166 }
167
168 uschar *
169 tls_cert_not_before(void * cert, uschar * mod)
170 {
171 return time_copy(
172   gnutls_x509_crt_get_activation_time((gnutls_x509_crt_t)cert),
173   mod);
174 }
175
176 uschar *
177 tls_cert_serial_number(void * cert, uschar * mod)
178 {
179 uschar bin[50], txt[150];
180 uschar * sp = bin;
181 size_t sz = sizeof(bin);
182 int ret;
183
184 if ((ret = gnutls_x509_crt_get_serial((gnutls_x509_crt_t)cert,
185     bin, &sz)))
186   return g_err("gs0", __FUNCTION__, ret);
187
188 for(uschar * dp = txt; sz; sz--)
189   dp += sprintf(CS dp, "%.2x", *sp++);
190 for(sp = txt; sp[0]=='0' && sp[1]; ) sp++;      /* leading zeroes */
191 return string_copy(sp);
192 }
193
194 uschar *
195 tls_cert_signature(void * cert, uschar * mod)
196 {
197 uschar * cp1 = NULL;
198 uschar * cp2;
199 uschar * cp3;
200 size_t len = 0;
201 int ret;
202
203 if ((ret = gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, CS cp1, &len))
204     != GNUTLS_E_SHORT_MEMORY_BUFFER)
205   return g_err("gs0", __FUNCTION__, ret);
206
207 cp1 = store_get(len*4+1, GET_TAINTED);
208 if (gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, CS cp1, &len) != 0)
209   return g_err("gs1", __FUNCTION__, ret);
210
211 for(cp3 = cp2 = cp1+len; cp1 < cp2; cp1++)
212   cp3 += sprintf(CS cp3, "%.2x ", *cp1);
213 cp3[-1]= '\0';
214
215 return cp2;
216 }
217
218 uschar *
219 tls_cert_signature_algorithm(void * cert, uschar * mod)
220 {
221 gnutls_sign_algorithm_t algo =
222   gnutls_x509_crt_get_signature_algorithm((gnutls_x509_crt_t)cert);
223 return algo < 0 ? NULL : string_copy(US gnutls_sign_get_name(algo));
224 }
225
226 uschar *
227 tls_cert_subject(void * cert, uschar * mod)
228 {
229 uschar * cp = NULL;
230 int ret;
231 size_t siz = 0;
232
233 if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz))
234     != GNUTLS_E_SHORT_MEMORY_BUFFER)
235   return g_err("gs0", __FUNCTION__, ret);
236
237 cp = store_get(siz, GET_TAINTED);
238 if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz)) < 0)
239   return g_err("gs1", __FUNCTION__, ret);
240
241 return mod ? tls_field_from_dn(cp, mod) : cp;
242 }
243
244 uschar *
245 tls_cert_version(void * cert, uschar * mod)
246 {
247 return string_sprintf("%d", gnutls_x509_crt_get_version(cert));
248 }
249
250 uschar *
251 tls_cert_ext_by_oid(void * cert, uschar * oid, int idx)
252 {
253 uschar * cp1 = NULL;
254 uschar * cp2;
255 uschar * cp3;
256 size_t siz = 0;
257 unsigned int crit;
258 int ret;
259
260 ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
261   CS oid, idx, CS cp1, &siz, &crit);
262 if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER)
263   return g_err("ge0", __FUNCTION__, ret);
264
265 cp1 = store_get(siz*4 + 1, GET_TAINTED);
266
267 ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
268   CS oid, idx, CS cp1, &siz, &crit);
269 if (ret < 0)
270   return g_err("ge1", __FUNCTION__, ret);
271
272 /* binary data, DER encoded */
273
274 /* just dump for now */
275 for(cp3 = cp2 = cp1+siz; cp1 < cp2; cp1++)
276   cp3 += sprintf(CS cp3, "%.2x ", *cp1);
277 cp3[-1]= '\0';
278
279 return cp2;
280 }
281
282 uschar *
283 tls_cert_subject_altname(void * cert, uschar * mod)
284 {
285 gstring * list = NULL;
286 size_t siz;
287 int ret;
288 uschar sep = '\n';
289 uschar * tag = US"";
290 uschar * ele;
291 int match = -1;
292
293 if (mod) while (*mod)
294   {
295   if (*mod == '>' && *++mod) sep = *mod++;
296   else if (Ustrncmp(mod, "dns", 3)==0) { match = GNUTLS_SAN_DNSNAME; mod += 3; }
297   else if (Ustrncmp(mod, "uri", 3)==0) { match = GNUTLS_SAN_URI; mod += 3; }
298   else if (Ustrncmp(mod, "mail", 4)==0) { match = GNUTLS_SAN_RFC822NAME; mod += 4; }
299   else break;
300
301   if (*mod++ != ',')
302     break;
303   }
304
305 for (int index = 0;; index++)
306   {
307   siz = 0;
308   switch(ret = gnutls_x509_crt_get_subject_alt_name(
309       (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL))
310     {
311     case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
312       return string_from_gstring(list); /* no more elements; normal exit */
313
314     case GNUTLS_E_SHORT_MEMORY_BUFFER:
315       break;
316
317     default:
318       return g_err("gs0", __FUNCTION__, ret);
319     }
320
321   ele = store_get(siz+1, GET_TAINTED);
322   if ((ret = gnutls_x509_crt_get_subject_alt_name(
323     (gnutls_x509_crt_t)cert, index, ele, &siz, NULL)) < 0)
324     return g_err("gs1", __FUNCTION__, ret);
325   ele[siz] = '\0';
326
327   if (  match != -1 && match != ret     /* wrong type of SAN */
328      || Ustrlen(ele) != siz)            /* contains a NUL */
329     continue;
330   switch (ret)
331     {
332     case GNUTLS_SAN_DNSNAME:    tag = US"DNS";  break;
333     case GNUTLS_SAN_URI:        tag = US"URI";  break;
334     case GNUTLS_SAN_RFC822NAME: tag = US"MAIL"; break;
335     default: continue;        /* ignore unrecognised types */
336     }
337   list = string_append_listele(list, sep,
338           match == -1 ? string_sprintf("%s=%s", tag, ele) : ele);
339   }
340 /*NOTREACHED*/
341 }
342
343 uschar *
344 tls_cert_ocsp_uri(void * cert, uschar * mod)
345 {
346 #if GNUTLS_VERSION_NUMBER >= 0x030000
347 gnutls_datum_t uri;
348 int ret;
349 uschar sep = '\n';
350 gstring * list = NULL;
351
352 if (mod)
353   if (*mod == '>' && *++mod) sep = *mod++;
354
355 for (int index = 0;; index++)
356   {
357   ret = gnutls_x509_crt_get_authority_info_access((gnutls_x509_crt_t)cert,
358           index, GNUTLS_IA_OCSP_URI, &uri, NULL);
359
360   if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
361     return string_from_gstring(list);
362   if (ret < 0)
363     return g_err("gai", __FUNCTION__, ret);
364
365   list = string_append_listele_n(list, sep, uri.data, uri.size);
366   }
367 /*NOTREACHED*/
368
369 #else
370
371 expand_string_message =
372   string_sprintf("%s: OCSP support with GnuTLS requires version 3.0.0\n",
373     __FUNCTION__);
374 return NULL;
375
376 #endif
377 }
378
379 uschar *
380 tls_cert_crl_uri(void * cert, uschar * mod)
381 {
382 int ret;
383 uschar sep = '\n';
384 gstring * list = NULL;
385 uschar * ele;
386
387 if (mod)
388   if (*mod == '>' && *++mod) sep = *mod++;
389
390 for (int index = 0;; index++)
391   {
392   size_t siz = 0;
393   switch(ret = gnutls_x509_crt_get_crl_dist_points(
394     (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL, NULL))
395     {
396     case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
397       return string_from_gstring(list);
398     case GNUTLS_E_SHORT_MEMORY_BUFFER:
399       break;
400     default:
401       return g_err("gc0", __FUNCTION__, ret);
402     }
403
404   ele = store_get(siz, GET_TAINTED);
405   if ((ret = gnutls_x509_crt_get_crl_dist_points(
406       (gnutls_x509_crt_t)cert, index, ele, &siz, NULL, NULL)) < 0)
407     return g_err("gc1", __FUNCTION__, ret);
408
409   list = string_append_listele_n(list, sep, ele, siz);
410   }
411 /*NOTREACHED*/
412 }
413
414
415 /*****************************************************
416 *  Certificate operator routines
417 *****************************************************/
418 uschar *
419 tls_cert_der_b64(void * cert)
420 {
421 size_t len = 0;
422 uschar * cp = NULL;
423 int fail;
424
425 if (  (fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
426         GNUTLS_X509_FMT_DER, cp, &len)) != GNUTLS_E_SHORT_MEMORY_BUFFER
427    || !(cp = store_get((int)len, GET_TAINTED), TRUE)    /* tainted */
428    || (fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
429         GNUTLS_X509_FMT_DER, cp, &len))
430    )
431   {
432   log_write(0, LOG_MAIN, "TLS error in certificate export: %s",
433     gnutls_strerror(fail));
434   return NULL;
435   }
436 return b64encode(CUS cp, (int)len);
437 }
438
439
440 static uschar *
441 fingerprint(gnutls_x509_crt_t cert, gnutls_digest_algorithm_t algo)
442 {
443 int ret;
444 size_t siz = 0;
445 uschar * cp;
446 uschar * cp2;
447
448 if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, NULL, &siz))
449     != GNUTLS_E_SHORT_MEMORY_BUFFER)
450   return g_err("gf0", __FUNCTION__, ret);
451
452 cp = store_get(siz*3+1, GET_TAINTED);
453 if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, cp, &siz)) < 0)
454   return g_err("gf1", __FUNCTION__, ret);
455
456 for (uschar * cp3 = cp2 = cp+siz; cp < cp2; cp++)
457   cp3 += sprintf(CS cp3, "%02X", *cp);
458 return cp2;
459 }
460
461
462 uschar *
463 tls_cert_fprt_md5(void * cert)
464 {
465 return fingerprint((gnutls_x509_crt_t)cert, GNUTLS_DIG_MD5);
466 }
467
468 uschar *
469 tls_cert_fprt_sha1(void * cert)
470 {
471 return fingerprint((gnutls_x509_crt_t)cert, GNUTLS_DIG_SHA1);
472 }
473
474 uschar *
475 tls_cert_fprt_sha256(void * cert)
476 {
477 return fingerprint((gnutls_x509_crt_t)cert, GNUTLS_DIG_SHA256);
478 }
479
480
481 /* vi: aw ai sw=2
482 */
483 /* End of tlscert-gnu.c */