1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2003 Bruce M. Simpson <bms@spc.org>
5 * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *   notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *   notice, this list of conditions and the following disclaimer in the
15 *   documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote products
17 *   derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/* TCP MD5 Signature Option (RFC2385) */
32#include <sys/cdefs.h>
33#include "opt_inet.h"
34#include "opt_inet6.h"
35#include "opt_ipsec.h"
36
37#include <sys/param.h>
38#include <sys/systm.h>
39#include <sys/mbuf.h>
40#include <sys/lock.h>
41#include <sys/md5.h>
42#include <sys/rmlock.h>
43#include <sys/socket.h>
44#include <sys/sockopt.h>
45#include <sys/kernel.h>
46#include <sys/module.h>
47#include <sys/protosw.h>
48
49#include <netinet/in.h>
50#include <netinet/in_pcb.h>
51#include <netinet/in_systm.h>
52#include <netinet/ip.h>
53#include <netinet/ip_var.h>
54#include <netinet/tcp.h>
55#include <netinet/tcp_var.h>
56#include <netinet/udp.h>
57
58#include <net/vnet.h>
59
60#include <netipsec/ipsec.h>
61#include <netipsec/ipsec_support.h>
62#include <netipsec/xform.h>
63
64#ifdef INET6
65#include <netinet/ip6.h>
66#include <netipsec/ipsec6.h>
67#endif
68
69#include <netipsec/key.h>
70#include <netipsec/key_debug.h>
71
72#define	TCP_SIGLEN	16	/* length of computed digest in bytes */
73#define	TCP_KEYLEN_MIN	1	/* minimum length of TCP-MD5 key */
74#define	TCP_KEYLEN_MAX	80	/* maximum length of TCP-MD5 key */
75
76static int
77tcp_ipsec_pcbctl(struct inpcb *inp, struct sockopt *sopt)
78{
79	struct tcpcb *tp;
80	int error, optval;
81
82	if (sopt->sopt_name != TCP_MD5SIG) {
83		return (ENOPROTOOPT);
84	}
85
86	if (sopt->sopt_dir == SOPT_GET) {
87		INP_RLOCK(inp);
88		if (inp->inp_flags & INP_DROPPED) {
89			INP_RUNLOCK(inp);
90			return (ECONNRESET);
91		}
92		tp = intotcpcb(inp);
93		optval = (tp->t_flags & TF_SIGNATURE) ? 1 : 0;
94		INP_RUNLOCK(inp);
95
96		/* On success return with released INP_WLOCK */
97		return (sooptcopyout(sopt, &optval, sizeof(optval)));
98	}
99
100	error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval));
101	if (error != 0)
102		return (error);
103
104	/* INP_WLOCK_RECHECK */
105	INP_WLOCK(inp);
106	if (inp->inp_flags & INP_DROPPED) {
107		INP_WUNLOCK(inp);
108		return (ECONNRESET);
109	}
110	tp = intotcpcb(inp);
111	if (optval > 0)
112		tp->t_flags |= TF_SIGNATURE;
113	else
114		tp->t_flags &= ~TF_SIGNATURE;
115
116	INP_WUNLOCK(inp);
117	return (error);
118}
119
120/*
121 * Callback function invoked by m_apply() to digest TCP segment data
122 * contained within an mbuf chain.
123 */
124static int
125tcp_signature_apply(void *fstate, void *data, u_int len)
126{
127
128	MD5Update(fstate, (u_char *)data, len);
129	return (0);
130}
131
132#ifdef INET
133static int
134ip_pseudo_compute(struct mbuf *m, MD5_CTX *ctx)
135{
136	struct ippseudo ipp;
137	struct ip *ip;
138	int hdr_len;
139
140	ip = mtod(m, struct ip *);
141	ipp.ippseudo_src.s_addr = ip->ip_src.s_addr;
142	ipp.ippseudo_dst.s_addr = ip->ip_dst.s_addr;
143	ipp.ippseudo_p = IPPROTO_TCP;
144	ipp.ippseudo_pad = 0;
145	hdr_len = ip->ip_hl << 2;
146	if (ip->ip_p == IPPROTO_UDP)
147		/* TCP over UDP */
148		hdr_len += sizeof(struct udphdr);
149	ipp.ippseudo_len = htons(m->m_pkthdr.len - hdr_len);
150	MD5Update(ctx, (char *)&ipp, sizeof(ipp));
151	return (hdr_len);
152}
153#endif
154
155#ifdef INET6
156static int
157ip6_pseudo_compute(struct mbuf *m, MD5_CTX *ctx)
158{
159	struct ip6_pseudo {
160		struct in6_addr src, dst;
161		uint32_t len;
162		uint32_t nxt;
163	} ip6p __aligned(4);
164	struct ip6_hdr *ip6;
165	int hdr_len;
166
167	ip6 = mtod(m, struct ip6_hdr *);
168	ip6p.src = ip6->ip6_src;
169	ip6p.dst = ip6->ip6_dst;
170	hdr_len = sizeof(struct ip6_hdr);
171	if (ip6->ip6_nxt == IPPROTO_UDP)
172		/* TCP over UDP */
173		hdr_len += sizeof(struct udphdr);
174	/* XXX: ext headers */
175	ip6p.len = htonl(m->m_pkthdr.len - hdr_len);
176	ip6p.nxt = htonl(IPPROTO_TCP);
177	MD5Update(ctx, (char *)&ip6p, sizeof(ip6p));
178	return (hdr_len);
179}
180#endif
181
182static int
183tcp_signature_compute(struct mbuf *m, struct tcphdr *th,
184    struct secasvar *sav, u_char *buf)
185{
186	MD5_CTX ctx;
187	int len;
188	u_short csum;
189
190	MD5Init(&ctx);
191	 /* Step 1: Update MD5 hash with IP(v6) pseudo-header. */
192	switch (sav->sah->saidx.dst.sa.sa_family) {
193#ifdef INET
194	case AF_INET:
195		len = ip_pseudo_compute(m, &ctx);
196		break;
197#endif
198#ifdef INET6
199	case AF_INET6:
200		len = ip6_pseudo_compute(m, &ctx);
201		break;
202#endif
203	default:
204		return (EAFNOSUPPORT);
205	}
206	/*
207	 * Step 2: Update MD5 hash with TCP header, excluding options.
208	 * The TCP checksum must be set to zero.
209	 */
210	csum = th->th_sum;
211	th->th_sum = 0;
212	MD5Update(&ctx, (char *)th, sizeof(struct tcphdr));
213	th->th_sum = csum;
214	/*
215	 * Step 3: Update MD5 hash with TCP segment data.
216	 * Use m_apply() to avoid an early m_pullup().
217	 */
218	len += (th->th_off << 2);
219	if (m->m_pkthdr.len - len > 0)
220		m_apply(m, len, m->m_pkthdr.len - len,
221		    tcp_signature_apply, &ctx);
222	/*
223	 * Step 4: Update MD5 hash with shared secret.
224	 */
225	MD5Update(&ctx, sav->key_auth->key_data, _KEYLEN(sav->key_auth));
226	MD5Final(buf, &ctx);
227	key_sa_recordxfer(sav, m);
228	return (0);
229}
230
231static void
232setsockaddrs(const struct mbuf *m, union sockaddr_union *src,
233    union sockaddr_union *dst)
234{
235	struct ip *ip;
236
237	IPSEC_ASSERT(m->m_len >= sizeof(*ip), ("unexpected mbuf len"));
238
239	ip = mtod(m, struct ip *);
240	switch (ip->ip_v) {
241#ifdef INET
242	case IPVERSION:
243		ipsec4_setsockaddrs(m, src, dst);
244		break;
245#endif
246#ifdef INET6
247	case (IPV6_VERSION >> 4):
248		ipsec6_setsockaddrs(m, src, dst);
249		break;
250#endif
251	default:
252		bzero(src, sizeof(*src));
253		bzero(dst, sizeof(*dst));
254	}
255}
256
257/*
258 * Compute TCP-MD5 hash of an *INBOUND* TCP segment.
259 * Parameters:
260 * m		pointer to head of mbuf chain
261 * th		pointer to TCP header
262 * buf		pointer to storage for computed MD5 digest
263 *
264 * Return 0 if successful, otherwise return error code.
265 */
266static int
267tcp_ipsec_input(struct mbuf *m, struct tcphdr *th, u_char *buf)
268{
269	char tmpdigest[TCP_SIGLEN];
270	struct secasindex saidx;
271	struct secasvar *sav;
272
273	setsockaddrs(m, &saidx.src, &saidx.dst);
274	saidx.proto = IPPROTO_TCP;
275	saidx.mode = IPSEC_MODE_TCPMD5;
276	saidx.reqid = 0;
277	sav = key_allocsa_tcpmd5(&saidx);
278	if (sav == NULL) {
279		KMOD_TCPSTAT_INC(tcps_sig_err_buildsig);
280		return (ENOENT);
281	}
282	if (buf == NULL) {
283		key_freesav(&sav);
284		KMOD_TCPSTAT_INC(tcps_sig_err_nosigopt);
285		return (EACCES);
286	}
287	/*
288	 * tcp_input() operates with TCP header fields in host
289	 * byte order. We expect them in network byte order.
290	 */
291	tcp_fields_to_net(th);
292	tcp_signature_compute(m, th, sav, tmpdigest);
293	tcp_fields_to_host(th);
294	key_freesav(&sav);
295	if (bcmp(buf, tmpdigest, TCP_SIGLEN) != 0) {
296		KMOD_TCPSTAT_INC(tcps_sig_rcvbadsig);
297		return (EACCES);
298	}
299	KMOD_TCPSTAT_INC(tcps_sig_rcvgoodsig);
300	return (0);
301}
302
303/*
304 * Compute TCP-MD5 hash of an *OUTBOUND* TCP segment.
305 * Parameters:
306 * m		pointer to head of mbuf chain
307 * th		pointer to TCP header
308 * buf		pointer to storage for computed MD5 digest
309 *
310 * Return 0 if successful, otherwise return error code.
311 */
312static int
313tcp_ipsec_output(struct mbuf *m, struct tcphdr *th, u_char *buf)
314{
315	struct secasindex saidx;
316	struct secasvar *sav;
317
318	setsockaddrs(m, &saidx.src, &saidx.dst);
319	saidx.proto = IPPROTO_TCP;
320	saidx.mode = IPSEC_MODE_TCPMD5;
321	saidx.reqid = 0;
322	sav = key_allocsa_tcpmd5(&saidx);
323	if (sav == NULL) {
324		KMOD_TCPSTAT_INC(tcps_sig_err_buildsig);
325		return (ENOENT);
326	}
327	tcp_signature_compute(m, th, sav, buf);
328	key_freesav(&sav);
329	return (0);
330}
331
332/*
333 * Initialize a TCP-MD5 SA. Called when the SA is being set up.
334 *
335 * We don't need to set up the tdb prefixed fields, as we don't use the
336 * opencrypto code; we just perform a key length check.
337 *
338 * XXX: Currently we have used single 'magic' SPI and need to still
339 * support this.
340 *
341 * This allows per-host granularity without affecting the userland
342 * interface, which is a simple socket option toggle switch,
343 * TCP_SIGNATURE_ENABLE.
344 *
345 * To allow per-service granularity requires that we have a means
346 * of mapping port to SPI. The mandated way of doing this is to
347 * use SPD entries to specify packet flows which get the TCP-MD5
348 * treatment, however the code to do this is currently unstable
349 * and unsuitable for production use.
350 *
351 * Therefore we use this compromise in the meantime.
352 */
353static int
354tcpsignature_init(struct secasvar *sav, struct xformsw *xsp)
355{
356	int keylen;
357
358	if (sav->alg_auth != SADB_X_AALG_TCP_MD5) {
359		DPRINTF(("%s: unsupported authentication algorithm %u\n",
360		    __func__, sav->alg_auth));
361		return (EINVAL);
362	}
363	if (sav->key_auth == NULL) {
364		DPRINTF(("%s: no authentication key present\n", __func__));
365		return (EINVAL);
366	}
367	keylen = _KEYLEN(sav->key_auth);
368	if ((keylen < TCP_KEYLEN_MIN) || (keylen > TCP_KEYLEN_MAX)) {
369		DPRINTF(("%s: invalid key length %u\n", __func__, keylen));
370		return (EINVAL);
371	}
372	sav->tdb_xform = xsp;
373	return (0);
374}
375
376/*
377 * Called when the SA is deleted.
378 */
379static void
380tcpsignature_cleanup(struct secasvar *sav)
381{
382}
383
384static struct xformsw tcpsignature_xformsw = {
385	.xf_type =	XF_TCPSIGNATURE,
386	.xf_name =	"TCP-MD5",
387	.xf_init =	tcpsignature_init,
388	.xf_cleanup =	tcpsignature_cleanup,
389};
390
391static const struct tcpmd5_methods tcpmd5_methods = {
392	.input = tcp_ipsec_input,
393	.output = tcp_ipsec_output,
394	.pcbctl = tcp_ipsec_pcbctl,
395};
396
397#ifndef KLD_MODULE
398/* TCP-MD5 support is build in the kernel */
399static const struct tcpmd5_support tcpmd5_ipsec = {
400	.enabled = IPSEC_MODULE_ENABLED,
401	.methods = &tcpmd5_methods
402};
403const struct tcpmd5_support * const tcp_ipsec_support = &tcpmd5_ipsec;
404#endif /* !KLD_MODULE */
405
406static int
407tcpmd5_modevent(module_t mod, int type, void *data)
408{
409
410	switch (type) {
411	case MOD_LOAD:
412		xform_attach(&tcpsignature_xformsw);
413#ifdef KLD_MODULE
414		tcpmd5_support_enable(&tcpmd5_methods);
415#endif
416		break;
417	case MOD_UNLOAD:
418#ifdef KLD_MODULE
419		tcpmd5_support_disable();
420#endif
421		xform_detach(&tcpsignature_xformsw);
422		break;
423	default:
424		return (EOPNOTSUPP);
425	}
426	return (0);
427}
428
429static moduledata_t tcpmd5_mod = {
430	"tcpmd5",
431	tcpmd5_modevent,
432	0
433};
434
435DECLARE_MODULE(tcpmd5, tcpmd5_mod, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY);
436MODULE_VERSION(tcpmd5, 1);
437#ifdef KLD_MODULE
438MODULE_DEPEND(tcpmd5, ipsec_support, 1, 1, 1);
439#endif
440