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