summaryrefslogtreecommitdiffstats
path: root/src/tls/tls_fprint.c
blob: 802157045be81ef4c118294fba6da256467eaf49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
/*++
/* NAME
/*	tls_fprint 3
/* SUMMARY
/*	Digests fingerprints and all that.
/* SYNOPSIS
/*	#include <tls.h>
/*
/*	EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr)
/*	const char *mdalg;
/*	EVP_MD_CTX **mdctxPtr;
/*
/*	char	*tls_serverid_digest(TLScontext, props, ciphers)
/*	TLS_SESS_STATE *TLScontext;
/*	const TLS_CLIENT_START_PROPS *props;
/*	const char *ciphers;
/*
/*	char	*tls_digest_encode(md_buf, md_len)
/*	const unsigned char *md_buf;
/*	const char *md_len;
/*
/*	char	*tls_cert_fprint(peercert, mdalg)
/*	X509	*peercert;
/*	const char *mdalg;
/*
/*	char	*tls_pkey_fprint(peercert, mdalg)
/*	X509	*peercert;
/*	const char *mdalg;
/* DESCRIPTION
/*	tls_digest_byname() constructs, and optionally returns, an EVP_MD_CTX
/*	handle for performing digest operations with the algorithm named by the
/*	mdalg parameter.  The return value is non-null on success, and holds a
/*	digest algorithm handle.  If the mdctxPtr argument is non-null the
/*	created context is returned to the caller, who is then responsible for
/*	deleting it by calling EVP_MD_ctx_free() once it is no longer needed.
/*
/*	tls_digest_encode() converts a binary message digest to a hex ASCII
/*	format with ':' separators between each pair of hex digits.
/*	The return value is dynamically allocated with mymalloc(),
/*	and the caller must eventually free it with myfree().
/*
/*	tls_cert_fprint() returns a fingerprint of the given
/*	certificate using the requested message digest, formatted
/*	with tls_digest_encode(). Panics if the
/*	(previously verified) digest algorithm is not found. The return
/*	value is dynamically allocated with mymalloc(), and the caller
/*	must eventually free it with myfree().
/*
/*	tls_pkey_fprint() returns a public-key fingerprint; in all
/*	other respects the function behaves as tls_cert_fprint().
/*	The var_tls_bc_pkey_fprint variable enables an incorrect
/*	algorithm that was used in Postfix versions 2.9.[0-5].
/*	The return value is dynamically allocated with mymalloc(),
/*	and the caller must eventually free it with myfree().
/*
/*	tls_serverid_digest() suffixes props->serverid computed by the SMTP
/*	client with "&" plus a digest of additional parameters needed to ensure
/*	that re-used sessions are more likely to be reused and that they will
/*	satisfy all protocol and security requirements.  The return value is
/*	dynamically allocated with mymalloc(), and the caller must eventually
/*	free it with myfree().
/*
/*	Arguments:
/* .IP mdalg
/*	A digest algorithm name, such as "sha256".
/* .IP peercert
/*	Server or client X.509 certificate.
/* .IP md_buf
/*	The raw binary digest.
/* .IP md_len
/*	The digest length in bytes.
/* .IP mdalg
/*	Name of a message digest algorithm suitable for computing secure
/*	(1st pre-image resistant) message digests of certificates. For now,
/*	md5, sha1, or member of SHA-2 family if supported by OpenSSL.
/* .IP mdctxPtr
/*	Pointer to an (EVP_MD_CTX *) handle, or NULL if only probing for
/*	algorithm support without immediate use in mind.
/* .IP buf
/*	Input data for the message digest algorithm mdalg.
/* .IP len
/*	The length of the input data.
/* .IP props
/*	The client start properties for the session, which contains the
/*	initial serverid from the SMTP client and the DANE verification
/*	parameters.
/* .IP protomask
/*	The mask of protocol exclusions.
/* .IP ciphers
/*	The SSL client cipherlist.
/* LICENSE
/* .ad
/* .fi
/*	This software is free. You can do with it whatever you want.
/*	The original author kindly requests that you acknowledge
/*	the use of his software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Viktor Dukhovni
/*--*/

/* System library. */

#include <sys_defs.h>
#include <ctype.h>

#ifdef USE_TLS
#include <string.h>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>

/* Global library. */

#include <mail_params.h>

/* TLS library. */

#define TLS_INTERNAL
#include <tls.h>

/* Application-specific. */

static const char hexcodes[] = "0123456789ABCDEF";

#define checkok(stillok) (ok = ok && (stillok))
#define digest_object(p) digest_data((unsigned char *)(p), sizeof(*(p)))
#define digest_data(p, l) checkok(digest_bytes(mdctx, (p), (l)))
#define digest_string(s) checkok(digest_chars(mdctx, (s)))
#define digest_dane(tlsa) checkok(tls_digest_tlsa(mdctx, tlsa))

/* digest_bytes - hash octet string of given length */

static int digest_bytes(EVP_MD_CTX *ctx, const unsigned char *buf, size_t len)
{
    return (EVP_DigestUpdate(ctx, buf, len));
}

/* digest_chars - hash string including trailing NUL */

static int digest_chars(EVP_MD_CTX *ctx, const char *s)
{
    return (EVP_DigestUpdate(ctx, s, strlen(s) + 1));
}

/* tlsa_cmp - compare TLSA RRs for sorting to canonical order */

static int tlsa_cmp(const void *a, const void *b)
{
    TLS_TLSA *p = *(TLS_TLSA **) a;
    TLS_TLSA *q = *(TLS_TLSA **) b;
    int     d;

    if ((d = (int) p->usage - (int) q->usage) != 0)
	return d;
    if ((d = (int) p->selector - (int) q->selector) != 0)
	return d;
    if ((d = (int) p->mtype - (int) q->mtype) != 0)
	return d;
    if ((d = (int) p->length - (int) q->length) != 0)
	return d;
    return (memcmp(p->data, q->data, p->length));
}

/* tls_digest_tlsa - fold in digest of TLSA records */

static int tls_digest_tlsa(EVP_MD_CTX *mdctx, TLS_TLSA *tlsa)
{
    TLS_TLSA *p;
    TLS_TLSA **arr;
    int     ok = 1;
    int     n;
    int     i;

    for (n = 0, p = tlsa; p != 0; p = p->next)
	++n;
    arr = (TLS_TLSA **) mymalloc(n * sizeof(*arr));
    for (i = 0, p = tlsa; p; p = p->next)
	arr[i++] = (void *) p;
    qsort(arr, n, sizeof(arr[0]), tlsa_cmp);

    digest_object(&n);
    for (i = 0; i < n; ++i) {
	digest_object(&arr[i]->usage);
	digest_object(&arr[i]->selector);
	digest_object(&arr[i]->mtype);
	digest_object(&arr[i]->length);
	digest_data(arr[i]->data, arr[i]->length);
    }
    myfree((void *) arr);
    return (ok);
}

/* tls_digest_byname - test availability or prepare to use digest */

const EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr)
{
    const EVP_MD *md;
    EVP_MD_CTX *mdctx = NULL;
    int     ok = 1;

    /*
     * In OpenSSL 3.0, because of dynamically variable algorithm providers,
     * there is a time-of-check/time-of-use issue that means that abstract
     * algorithm handles returned by EVP_get_digestbyname() can (and not
     * infrequently do) return ultimately unusable algorithms, to check for
     * actual availability, one needs to use the new EVP_MD_fetch() API, or
     * indirectly check usability by creating a concrete context. We take the
     * latter approach here (works for 1.1.1 without #ifdef).
     * 
     * Note that EVP_MD_CTX_{create,destroy} were renamed to, respectively,
     * EVP_MD_CTX_{new,free} in OpenSSL 1.1.0.
     */
    checkok(md = EVP_get_digestbyname(mdalg));

    /*
     * Sanity check: Newer shared libraries could (hypothetical ABI break)
     * allow larger digests, we avoid such poison algorithms.
     */
    checkok(EVP_MD_size(md) <= EVP_MAX_MD_SIZE);
    checkok(mdctx = EVP_MD_CTX_new());
    checkok(EVP_DigestInit_ex(mdctx, md, NULL));


    if (ok && mdctxPtr != 0)
	*mdctxPtr = mdctx;
    else
	EVP_MD_CTX_free(mdctx);
    return (ok ? md : 0);
}

/* tls_serverid_digest - suffix props->serverid with parameter digest */

char   *tls_serverid_digest(TLS_SESS_STATE *TLScontext,
			            const TLS_CLIENT_START_PROPS *props,
			            const char *ciphers)
{
    EVP_MD_CTX *mdctx;
    const char *mdalg;
    unsigned char md_buf[EVP_MAX_MD_SIZE];
    unsigned int md_len;
    int     ok = 1;
    int     i;
    long    sslversion;
    VSTRING *result;

    /*
     * Try to use sha256: our serverid choice should be strong enough to
     * resist 2nd-preimage attacks with a difficulty comparable to that of
     * DANE TLSA digests.  Failing that, we compute serverid digests with the
     * default digest, but DANE requires sha256 and sha512, so if we must
     * fall back to our default digest, DANE support won't be available.  We
     * panic if the fallback algorithm is not available, as it was verified
     * available in tls_client_init() and must not simply vanish.  Our
     * provider set is not expected to change once the OpenSSL library is
     * initialized.
     */
    if (tls_digest_byname(mdalg = LN_sha256, &mdctx) == 0
	&& tls_digest_byname(mdalg = props->mdalg, &mdctx) == 0)
	msg_panic("digest algorithm \"%s\" not found", props->mdalg);

    /* Salt the session lookup key with the OpenSSL runtime version. */
    sslversion = OpenSSL_version_num();

    digest_string(props->helo ? props->helo : "");
    digest_object(&sslversion);
    digest_string(props->protocols);
    digest_string(ciphers);

    /*
     * Ensure separation of caches for sessions where DANE trust
     * configuration succeeded from those where it did not.  The latter
     * should always see a certificate validation failure, both on initial
     * handshake and on resumption.
     */
    digest_object(&TLScontext->must_fail);

    /*
     * DNS-based or synthetic DANE trust settings are potentially used at all
     * levels above "encrypt".
     */
    if (TLScontext->level > TLS_LEV_ENCRYPT
	&& props->dane && props->dane->tlsa) {
	digest_dane(props->dane->tlsa);
    } else {
	int     none = 0;		/* Record a TLSA RR count of zero */

	digest_object(&none);
    }

    /*
     * Include the chosen SNI name, which can affect server certificate
     * selection.
     */
    if (TLScontext->level > TLS_LEV_ENCRYPT && TLScontext->peer_sni)
	digest_string(TLScontext->peer_sni);
    else
	digest_string("");

    checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
    EVP_MD_CTX_destroy(mdctx);
    if (!ok)
	msg_fatal("error computing %s message digest", mdalg);

    /* Check for OpenSSL contract violation */
    if (md_len > EVP_MAX_MD_SIZE)
	msg_panic("unexpectedly large %s digest size: %u", mdalg, md_len);

    /*
     * Append the digest to the serverid.  We don't compare this digest to
     * any user-specified fingerprints.  Therefore, we don't need to use a
     * colon-separated format, which saves space in the TLS session cache and
     * makes logging of session cache lookup keys more readable.
     * 
     * This does however duplicate a few lines of code from the digest encoder
     * for colon-separated cert and pkey fingerprints. If that is a
     * compelling reason to consolidate, we could use that and append the
     * result.
     */
    result = vstring_alloc(strlen(props->serverid) + 1 + 2 * md_len);
    vstring_strcpy(result, props->serverid);
    VSTRING_ADDCH(result, '&');
    for (i = 0; i < md_len; i++) {
	VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0xf0) >> 4U]);
	VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0x0f)]);
    }
    VSTRING_TERMINATE(result);
    return (vstring_export(result));
}

/* tls_digest_encode - encode message digest binary blob as xx:xx:... */

char   *tls_digest_encode(const unsigned char *md_buf, int md_len)
{
    int     i;
    char   *result = mymalloc(md_len * 3);

    /* Check for contract violation */
    if (md_len > EVP_MAX_MD_SIZE || md_len >= INT_MAX / 3)
	msg_panic("unexpectedly large message digest size: %u", md_len);

    /* No risk of overruns, len is bounded by OpenSSL digest length */
    for (i = 0; i < md_len; i++) {
	result[i * 3] = hexcodes[(md_buf[i] & 0xf0) >> 4U];
	result[(i * 3) + 1] = hexcodes[(md_buf[i] & 0x0f)];
	result[(i * 3) + 2] = (i + 1 != md_len) ? ':' : '\0';
    }
    return (result);
}

/* tls_data_fprint - compute and encode digest of binary object */

static char *tls_data_fprint(const unsigned char *buf, int len, const char *mdalg)
{
    EVP_MD_CTX *mdctx = NULL;
    unsigned char md_buf[EVP_MAX_MD_SIZE];
    unsigned int md_len;
    int     ok = 1;

    /* Previously available in "init" routine. */
    if (tls_digest_byname(mdalg, &mdctx) == 0)
	msg_panic("digest algorithm \"%s\" not found", mdalg);

    digest_data(buf, len);
    checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
    EVP_MD_CTX_destroy(mdctx);
    if (!ok)
	msg_fatal("error computing %s message digest", mdalg);

    return (tls_digest_encode(md_buf, md_len));
}

/* tls_cert_fprint - extract certificate fingerprint */

char   *tls_cert_fprint(X509 *peercert, const char *mdalg)
{
    int     len;
    unsigned char *buf;
    unsigned char *buf2;
    char   *result;

    len = i2d_X509(peercert, NULL);
    buf2 = buf = mymalloc(len);
    i2d_X509(peercert, &buf2);
    if (buf2 - buf != len)
	msg_panic("i2d_X509 invalid result length");

    result = tls_data_fprint(buf, len, mdalg);
    myfree(buf);

    return (result);
}

/* tls_pkey_fprint - extract public key fingerprint from certificate */

char   *tls_pkey_fprint(X509 *peercert, const char *mdalg)
{
    if (var_tls_bc_pkey_fprint) {
	const char *myname = "tls_pkey_fprint";
	ASN1_BIT_STRING *key;
	char   *result;

	key = X509_get0_pubkey_bitstr(peercert);
	if (key == 0)
	    msg_fatal("%s: error extracting legacy public-key fingerprint: %m",
		      myname);

	result = tls_data_fprint(key->data, key->length, mdalg);
	return (result);
    } else {
	int     len;
	unsigned char *buf;
	unsigned char *buf2;
	char   *result;

	len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), NULL);
	buf2 = buf = mymalloc(len);
	i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), &buf2);
	if (buf2 - buf != len)
	    msg_panic("i2d_X509_PUBKEY invalid result length");

	result = tls_data_fprint(buf, len, mdalg);
	myfree(buf);
	return (result);
    }
}

#endif