/* $OpenBSD: ip_esp.c,v 1.196 2024/06/07 13:15:25 jsg Exp $ */ /* * The authors of this code are John Ioannidis (ji@tla.org), * Angelos D. Keromytis (kermit@csd.uch.gr) and * Niels Provos (provos@physnet.uni-hamburg.de). * * The original version of this code was written by John Ioannidis * for BSD/OS in Athens, Greece, in November 1995. * * Ported to OpenBSD and NetBSD, with additional transforms, in December 1996, * by Angelos D. Keromytis. * * Additional transforms and features in 1997 and 1998 by Angelos D. Keromytis * and Niels Provos. * * Additional features in 1999 by Angelos D. Keromytis. * * Copyright (C) 1995, 1996, 1997, 1998, 1999 by John Ioannidis, * Angelos D. Keromytis and Niels Provos. * Copyright (c) 2001 Angelos D. Keromytis. * * Permission to use, copy, and modify this software with or without fee * is hereby granted, provided that this entire notice is included in * all copies of any software which is or includes a copy or * modification of this software. * You may use this code under the GNU public license if you so wish. Please * contribute changes back to the authors under this freer than GPL license * so that we may further the use of strong encryption without limitations to * all. * * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR * PURPOSE. */ #include "pfsync.h" #include #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #endif /* INET6 */ #include #include #include #include #if NPFSYNC > 0 #include #include #endif /* NPFSYNC > 0 */ #include #include #include "bpfilter.h" #ifdef ENCDEBUG #define DPRINTF(fmt, args...) \ do { \ if (encdebug) \ printf("%s: " fmt "\n", __func__, ## args); \ } while (0) #else #define DPRINTF(fmt, args...) \ do { } while (0) #endif /* * esp_attach() is called from the transformation initialization code. */ int esp_attach(void) { return 0; } /* * esp_init() is called when an SPI is being set up. */ int esp_init(struct tdb *tdbp, const struct xformsw *xsp, struct ipsecinit *ii) { const struct enc_xform *txform = NULL; const struct auth_hash *thash = NULL; struct cryptoini cria, crie, crin; int error; if (!ii->ii_encalg && !ii->ii_authalg) { DPRINTF("neither authentication nor encryption algorithm " "given"); return EINVAL; } if (ii->ii_encalg) { switch (ii->ii_encalg) { case SADB_EALG_NULL: txform = &enc_xform_null; break; case SADB_EALG_3DESCBC: txform = &enc_xform_3des; break; case SADB_X_EALG_AES: txform = &enc_xform_aes; break; case SADB_X_EALG_AESCTR: txform = &enc_xform_aes_ctr; break; case SADB_X_EALG_AESGCM16: txform = &enc_xform_aes_gcm; break; case SADB_X_EALG_AESGMAC: txform = &enc_xform_aes_gmac; break; case SADB_X_EALG_CHACHA20POLY1305: txform = &enc_xform_chacha20_poly1305; break; case SADB_X_EALG_BLF: txform = &enc_xform_blf; break; case SADB_X_EALG_CAST: txform = &enc_xform_cast5; break; default: DPRINTF("unsupported encryption algorithm %d " "specified", ii->ii_encalg); return EINVAL; } if (ii->ii_enckeylen < txform->minkey) { DPRINTF("keylength %d too small (min length is %d) " "for algorithm %s", ii->ii_enckeylen, txform->minkey, txform->name); return EINVAL; } if (ii->ii_enckeylen > txform->maxkey) { DPRINTF("keylength %d too large (max length is %d) " "for algorithm %s", ii->ii_enckeylen, txform->maxkey, txform->name); return EINVAL; } if (ii->ii_encalg == SADB_X_EALG_AESGCM16 || ii->ii_encalg == SADB_X_EALG_AESGMAC) { switch (ii->ii_enckeylen) { case 20: ii->ii_authalg = SADB_X_AALG_AES128GMAC; break; case 28: ii->ii_authalg = SADB_X_AALG_AES192GMAC; break; case 36: ii->ii_authalg = SADB_X_AALG_AES256GMAC; break; } ii->ii_authkeylen = ii->ii_enckeylen; ii->ii_authkey = ii->ii_enckey; } else if (ii->ii_encalg == SADB_X_EALG_CHACHA20POLY1305) { ii->ii_authalg = SADB_X_AALG_CHACHA20POLY1305; ii->ii_authkeylen = ii->ii_enckeylen; ii->ii_authkey = ii->ii_enckey; } tdbp->tdb_encalgxform = txform; DPRINTF("initialized TDB with enc algorithm %s", txform->name); tdbp->tdb_ivlen = txform->ivsize; } if (ii->ii_authalg) { switch (ii->ii_authalg) { case SADB_AALG_MD5HMAC: thash = &auth_hash_hmac_md5_96; break; case SADB_AALG_SHA1HMAC: thash = &auth_hash_hmac_sha1_96; break; case SADB_X_AALG_RIPEMD160HMAC: thash = &auth_hash_hmac_ripemd_160_96; break; case SADB_X_AALG_SHA2_256: thash = &auth_hash_hmac_sha2_256_128; break; case SADB_X_AALG_SHA2_384: thash = &auth_hash_hmac_sha2_384_192; break; case SADB_X_AALG_SHA2_512: thash = &auth_hash_hmac_sha2_512_256; break; case SADB_X_AALG_AES128GMAC: thash = &auth_hash_gmac_aes_128; break; case SADB_X_AALG_AES192GMAC: thash = &auth_hash_gmac_aes_192; break; case SADB_X_AALG_AES256GMAC: thash = &auth_hash_gmac_aes_256; break; case SADB_X_AALG_CHACHA20POLY1305: thash = &auth_hash_chacha20_poly1305; break; default: DPRINTF("unsupported authentication algorithm %d " "specified", ii->ii_authalg); return EINVAL; } if (ii->ii_authkeylen != thash->keysize) { DPRINTF("keylength %d doesn't match algorithm %s " "keysize (%d)", ii->ii_authkeylen, thash->name, thash->keysize); return EINVAL; } tdbp->tdb_authalgxform = thash; DPRINTF("initialized TDB with hash algorithm %s", thash->name); } tdbp->tdb_xform = xsp; tdbp->tdb_rpl = AH_HMAC_INITIAL_RPL; /* Initialize crypto session */ if (tdbp->tdb_encalgxform) { /* Save the raw keys */ tdbp->tdb_emxkeylen = ii->ii_enckeylen; tdbp->tdb_emxkey = malloc(tdbp->tdb_emxkeylen, M_XDATA, M_WAITOK); memcpy(tdbp->tdb_emxkey, ii->ii_enckey, tdbp->tdb_emxkeylen); memset(&crie, 0, sizeof(crie)); crie.cri_alg = tdbp->tdb_encalgxform->type; if (tdbp->tdb_authalgxform) crie.cri_next = &cria; else crie.cri_next = NULL; crie.cri_klen = ii->ii_enckeylen * 8; crie.cri_key = ii->ii_enckey; /* XXX Rounds ? */ } if (tdbp->tdb_authalgxform) { /* Save the raw keys */ tdbp->tdb_amxkeylen = ii->ii_authkeylen; tdbp->tdb_amxkey = malloc(tdbp->tdb_amxkeylen, M_XDATA, M_WAITOK); memcpy(tdbp->tdb_amxkey, ii->ii_authkey, tdbp->tdb_amxkeylen); memset(&cria, 0, sizeof(cria)); cria.cri_alg = tdbp->tdb_authalgxform->type; if ((tdbp->tdb_wnd > 0) && (tdbp->tdb_flags & TDBF_ESN)) { memset(&crin, 0, sizeof(crin)); crin.cri_alg = CRYPTO_ESN; cria.cri_next = &crin; } cria.cri_klen = ii->ii_authkeylen * 8; cria.cri_key = ii->ii_authkey; } KERNEL_LOCK(); error = crypto_newsession(&tdbp->tdb_cryptoid, (tdbp->tdb_encalgxform ? &crie : &cria), 0); KERNEL_UNLOCK(); return error; } /* * Paranoia. */ int esp_zeroize(struct tdb *tdbp) { int error; if (tdbp->tdb_amxkey) { explicit_bzero(tdbp->tdb_amxkey, tdbp->tdb_amxkeylen); free(tdbp->tdb_amxkey, M_XDATA, tdbp->tdb_amxkeylen); tdbp->tdb_amxkey = NULL; } if (tdbp->tdb_emxkey) { explicit_bzero(tdbp->tdb_emxkey, tdbp->tdb_emxkeylen); free(tdbp->tdb_emxkey, M_XDATA, tdbp->tdb_emxkeylen); tdbp->tdb_emxkey = NULL; } KERNEL_LOCK(); error = crypto_freesession(tdbp->tdb_cryptoid); KERNEL_UNLOCK(); tdbp->tdb_cryptoid = 0; return error; } /* * ESP input processing, called (eventually) through the protocol switch. */ int esp_input(struct mbuf **mp, struct tdb *tdb, int skip, int protoff) { const struct auth_hash *esph = tdb->tdb_authalgxform; const struct enc_xform *espx = tdb->tdb_encalgxform; struct mbuf *m = *mp, *m1, *mo; struct cryptodesc *crde = NULL, *crda = NULL; struct cryptop *crp = NULL; int plen, alen, hlen, error, roff; uint32_t btsx, esn; #ifdef ENCDEBUG char buf[INET6_ADDRSTRLEN]; #endif uint8_t abuf[AH_HMAC_MAX_HASHLEN]; uint8_t lastthree[3], aalg[AH_HMAC_MAX_HASHLEN]; /* Determine the ESP header length */ hlen = 2 * sizeof(u_int32_t) + tdb->tdb_ivlen; /* "new" ESP */ alen = esph ? esph->authsize : 0; plen = m->m_pkthdr.len - (skip + hlen + alen); if (plen <= 0) { DPRINTF("invalid payload length"); espstat_inc(esps_badilen); goto drop; } if (espx) { /* * Verify payload length is multiple of encryption algorithm * block size. */ if (plen & (espx->blocksize - 1)) { DPRINTF("payload of %d octets not a multiple " "of %d octets, SA %s/%08x", plen, espx->blocksize, ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_badilen); goto drop; } } /* Replay window checking, if appropriate -- no value commitment. */ if (tdb->tdb_wnd > 0) { m_copydata(m, skip + sizeof(u_int32_t), sizeof(u_int32_t), &btsx); btsx = ntohl(btsx); switch (checkreplaywindow(tdb, tdb->tdb_rpl, btsx, &esn, 0)) { case 0: /* All's well */ break; case 1: DPRINTF("replay counter wrapped for SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_wrap); goto drop; case 2: DPRINTF("old packet received in SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_replay); goto drop; case 3: DPRINTF("duplicate packet received in SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_replay); goto drop; default: DPRINTF("bogus value from checkreplaywindow() " "in SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_replay); goto drop; } } /* Update the counters */ tdb->tdb_cur_bytes += plen; tdbstat_add(tdb, tdb_ibytes, plen); espstat_add(esps_ibytes, plen); /* Hard expiration */ if ((tdb->tdb_flags & TDBF_BYTES) && (tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes)) { ipsecstat_inc(ipsec_exctdb); pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD); tdb_delete(tdb); goto drop; } /* Notify on soft expiration */ mtx_enter(&tdb->tdb_mtx); if ((tdb->tdb_flags & TDBF_SOFT_BYTES) && (tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes)) { tdb->tdb_flags &= ~TDBF_SOFT_BYTES; /* Turn off checking */ mtx_leave(&tdb->tdb_mtx); /* may sleep in solock() for the pfkey socket */ pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT); } else mtx_leave(&tdb->tdb_mtx); /* Get crypto descriptors */ crp = crypto_getreq(esph && espx ? 2 : 1); if (crp == NULL) { DPRINTF("failed to acquire crypto descriptors"); espstat_inc(esps_crypto); goto drop; } if (esph) { crda = &crp->crp_desc[0]; crde = &crp->crp_desc[1]; /* Authentication descriptor */ crda->crd_skip = skip; crda->crd_inject = m->m_pkthdr.len - alen; crda->crd_alg = esph->type; crda->crd_key = tdb->tdb_amxkey; crda->crd_klen = tdb->tdb_amxkeylen * 8; if ((tdb->tdb_wnd > 0) && (tdb->tdb_flags & TDBF_ESN)) { esn = htonl(esn); memcpy(crda->crd_esn, &esn, 4); crda->crd_flags |= CRD_F_ESN; } if (espx && (espx->type == CRYPTO_AES_GCM_16 || espx->type == CRYPTO_CHACHA20_POLY1305)) crda->crd_len = hlen - tdb->tdb_ivlen; else crda->crd_len = m->m_pkthdr.len - (skip + alen); /* Copy the authenticator */ m_copydata(m, m->m_pkthdr.len - alen, alen, abuf); } else crde = &crp->crp_desc[0]; /* Crypto operation descriptor */ crp->crp_ilen = m->m_pkthdr.len; /* Total input length */ crp->crp_flags = CRYPTO_F_IMBUF; crp->crp_buf = (caddr_t)m; crp->crp_sid = tdb->tdb_cryptoid; /* Decryption descriptor */ if (espx) { crde->crd_skip = skip + hlen; crde->crd_inject = skip + hlen - tdb->tdb_ivlen; crde->crd_alg = espx->type; crde->crd_key = tdb->tdb_emxkey; crde->crd_klen = tdb->tdb_emxkeylen * 8; /* XXX Rounds ? */ if (crde->crd_alg == CRYPTO_AES_GMAC) crde->crd_len = 0; else crde->crd_len = plen; } while ((error = crypto_invoke(crp)) == EAGAIN) { /* Reset the session ID */ if (tdb->tdb_cryptoid != 0) tdb->tdb_cryptoid = crp->crp_sid; } if (error) { DPRINTF("crypto error %d", error); ipsecstat_inc(ipsec_noxform); goto drop; } /* Release the crypto descriptors */ crypto_freereq(crp); crp = NULL; /* If authentication was performed, check now. */ if (esph != NULL) { /* Copy the authenticator from the packet */ m_copydata(m, m->m_pkthdr.len - esph->authsize, esph->authsize, aalg); /* Verify authenticator */ if (timingsafe_bcmp(abuf, aalg, esph->authsize)) { DPRINTF("authentication failed for packet " "in SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_badauth); goto drop; } /* Remove trailing authenticator */ m_adj(m, -(esph->authsize)); } /* Replay window checking, if appropriate */ if (tdb->tdb_wnd > 0) { m_copydata(m, skip + sizeof(u_int32_t), sizeof(u_int32_t), &btsx); btsx = ntohl(btsx); switch (checkreplaywindow(tdb, tdb->tdb_rpl, btsx, &esn, 1)) { case 0: /* All's well */ #if NPFSYNC > 0 pfsync_update_tdb(tdb,0); #endif break; case 1: DPRINTF("replay counter wrapped for SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_wrap); goto drop; case 2: DPRINTF("old packet received in SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_replay); goto drop; case 3: DPRINTF("duplicate packet received in SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_replay); goto drop; default: DPRINTF("bogus value from checkreplaywindow() " "in SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_replay); goto drop; } } /* Find beginning of ESP header */ m1 = m_getptr(m, skip, &roff); if (m1 == NULL) { DPRINTF("bad mbuf chain, SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_hdrops); goto drop; } /* Remove the ESP header and IV from the mbuf. */ if (roff == 0) { /* The ESP header was conveniently at the beginning of the mbuf */ m_adj(m1, hlen); /* * If m1 is the first mbuf, it has set M_PKTHDR and m_adj() * has already adjusted the packet header length for us. */ if (m1 != m) m->m_pkthdr.len -= hlen; } else if (roff + hlen >= m1->m_len) { int adjlen; /* * Part or all of the ESP header is at the end of this mbuf, so * first let's remove the remainder of the ESP header from the * beginning of the remainder of the mbuf chain, if any. */ if (roff + hlen > m1->m_len) { adjlen = roff + hlen - m1->m_len; /* Adjust the next mbuf by the remainder */ m_adj(m1->m_next, adjlen); /* The second mbuf is guaranteed not to have a pkthdr */ m->m_pkthdr.len -= adjlen; } /* Now, let's unlink the mbuf chain for a second...*/ mo = m1->m_next; m1->m_next = NULL; /* ...and trim the end of the first part of the chain...sick */ adjlen = m1->m_len - roff; m_adj(m1, -adjlen); /* * If m1 is the first mbuf, it has set M_PKTHDR and m_adj() * has already adjusted the packet header length for us. */ if (m1 != m) m->m_pkthdr.len -= adjlen; /* Finally, let's relink */ m1->m_next = mo; } else { /* * The ESP header lies in the "middle" of the mbuf...do an * overlapping copy of the remainder of the mbuf over the ESP * header. */ memmove(mtod(m1, u_char *) + roff, mtod(m1, u_char *) + roff + hlen, m1->m_len - (roff + hlen)); m1->m_len -= hlen; m->m_pkthdr.len -= hlen; } /* Save the last three bytes of decrypted data */ m_copydata(m, m->m_pkthdr.len - 3, 3, lastthree); /* Verify pad length */ if (lastthree[1] + 2 > m->m_pkthdr.len - skip) { DPRINTF("invalid padding length %d for packet in SA %s/%08x", lastthree[1], ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_badilen); goto drop; } /* Verify correct decryption by checking the last padding bytes */ if ((lastthree[1] != lastthree[0]) && (lastthree[1] != 0)) { DPRINTF("decryption failed for packet in SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_badenc); goto drop; } /* Trim the mbuf chain to remove the padding */ m_adj(m, -(lastthree[1] + 2)); /* Restore the Next Protocol field */ m_copyback(m, protoff, sizeof(u_int8_t), lastthree + 2, M_NOWAIT); /* Back to generic IPsec input processing */ return ipsec_common_input_cb(mp, tdb, skip, protoff); drop: m_freemp(mp); crypto_freereq(crp); return IPPROTO_DONE; } /* * ESP output routine, called by ipsp_process_packet(). */ int esp_output(struct mbuf *m, struct tdb *tdb, int skip, int protoff) { const struct enc_xform *espx = tdb->tdb_encalgxform; const struct auth_hash *esph = tdb->tdb_authalgxform; int ilen, hlen, rlen, padding, blks, alen, roff, error; uint64_t replay64; uint32_t replay; struct mbuf *mi, *mo = (struct mbuf *) NULL; unsigned char *pad; uint8_t prot; #ifdef ENCDEBUG char buf[INET6_ADDRSTRLEN]; #endif struct cryptodesc *crde = NULL, *crda = NULL; struct cryptop *crp = NULL; #if NBPFILTER > 0 struct ifnet *encif; if ((encif = enc_getif(tdb->tdb_rdomain, tdb->tdb_tap)) != NULL) { encif->if_opackets++; encif->if_obytes += m->m_pkthdr.len; if (encif->if_bpf) { struct enchdr hdr; memset(&hdr, 0, sizeof(hdr)); hdr.af = tdb->tdb_dst.sa.sa_family; hdr.spi = tdb->tdb_spi; if (espx) hdr.flags |= M_CONF; if (esph) hdr.flags |= M_AUTH; bpf_mtap_hdr(encif->if_bpf, (char *)&hdr, ENC_HDRLEN, m, BPF_DIRECTION_OUT); } } #endif hlen = 2 * sizeof(u_int32_t) + tdb->tdb_ivlen; rlen = m->m_pkthdr.len - skip; /* Raw payload length. */ if (espx) blks = MAX(espx->blocksize, 4); else blks = 4; /* If no encryption, we have to be 4-byte aligned. */ padding = ((blks - ((rlen + 2) % blks)) % blks) + 2; alen = esph ? esph->authsize : 0; espstat_inc(esps_output); switch (tdb->tdb_dst.sa.sa_family) { case AF_INET: /* Check for IP maximum packet size violations. */ if (skip + hlen + rlen + padding + alen > IP_MAXPACKET) { DPRINTF("packet in SA %s/%08x got too big", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_toobig); error = EMSGSIZE; goto drop; } break; #ifdef INET6 case AF_INET6: /* Check for IPv6 maximum packet size violations. */ if (skip + hlen + rlen + padding + alen > IPV6_MAXPACKET) { DPRINTF("acket in SA %s/%08x got too big", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_toobig); error = EMSGSIZE; goto drop; } break; #endif /* INET6 */ default: DPRINTF("unknown/unsupported protocol family %d, SA %s/%08x", tdb->tdb_dst.sa.sa_family, ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_nopf); error = EPFNOSUPPORT; goto drop; } /* Update the counters. */ tdb->tdb_cur_bytes += m->m_pkthdr.len - skip; espstat_add(esps_obytes, m->m_pkthdr.len - skip); /* Hard byte expiration. */ if ((tdb->tdb_flags & TDBF_BYTES) && (tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes)) { ipsecstat_inc(ipsec_exctdb); pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD); tdb_delete(tdb); error = EINVAL; goto drop; } /* Soft byte expiration. */ mtx_enter(&tdb->tdb_mtx); if ((tdb->tdb_flags & TDBF_SOFT_BYTES) && (tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes)) { tdb->tdb_flags &= ~TDBF_SOFT_BYTES; /* Turn off checking */ mtx_leave(&tdb->tdb_mtx); /* may sleep in solock() for the pfkey socket */ pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT); } else mtx_leave(&tdb->tdb_mtx); /* * Loop through mbuf chain; if we find a readonly mbuf, * copy the packet. */ mi = m; while (mi != NULL && !M_READONLY(mi)) mi = mi->m_next; if (mi != NULL) { struct mbuf *n = m_dup_pkt(m, 0, M_DONTWAIT); if (n == NULL) { DPRINTF("bad mbuf chain, SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_hdrops); error = ENOBUFS; goto drop; } m_freem(m); m = n; } /* Inject ESP header. */ mo = m_makespace(m, skip, hlen, &roff); if (mo == NULL) { DPRINTF("failed to inject ESP header for SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_hdrops); error = ENOBUFS; goto drop; } /* Initialize ESP header. */ memcpy(mtod(mo, caddr_t) + roff, (caddr_t) &tdb->tdb_spi, sizeof(u_int32_t)); replay64 = tdb->tdb_rpl++; /* used for both header and ESN */ replay = htonl((u_int32_t)replay64); memcpy(mtod(mo, caddr_t) + roff + sizeof(u_int32_t), (caddr_t) &replay, sizeof(u_int32_t)); #if NPFSYNC > 0 pfsync_update_tdb(tdb,1); #endif /* * Add padding -- better to do it ourselves than use the crypto engine, * although if/when we support compression, we'd have to do that. */ mo = m_makespace(m, m->m_pkthdr.len, padding + alen, &roff); if (mo == NULL) { DPRINTF("m_makespace() failed for SA %s/%08x", ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)), ntohl(tdb->tdb_spi)); espstat_inc(esps_hdrops); error = ENOBUFS; goto drop; } pad = mtod(mo, caddr_t) + roff; /* Apply self-describing padding */ for (ilen = 0; ilen < padding - 2; ilen++) pad[ilen] = ilen + 1; /* Fix padding length and Next Protocol in padding itself. */ pad[padding - 2] = padding - 2; m_copydata(m, protoff, sizeof(u_int8_t), pad + padding - 1); /* Fix Next Protocol in IPv4/IPv6 header. */ prot = IPPROTO_ESP; m_copyback(m, protoff, sizeof(u_int8_t), &prot, M_NOWAIT); /* Get crypto descriptors. */ crp = crypto_getreq(esph && espx ? 2 : 1); if (crp == NULL) { DPRINTF("failed to acquire crypto descriptors"); espstat_inc(esps_crypto); error = ENOBUFS; goto drop; } if (espx) { crde = &crp->crp_desc[0]; crda = &crp->crp_desc[1]; /* Encryption descriptor. */ crde->crd_skip = skip + hlen; crde->crd_flags = CRD_F_ENCRYPT | CRD_F_IV_EXPLICIT; crde->crd_inject = skip + hlen - tdb->tdb_ivlen; /* Encryption operation. */ crde->crd_alg = espx->type; crde->crd_key = tdb->tdb_emxkey; crde->crd_klen = tdb->tdb_emxkeylen * 8; /* XXX Rounds ? */ if (crde->crd_alg == CRYPTO_AES_GMAC) crde->crd_len = 0; else crde->crd_len = m->m_pkthdr.len - (skip + hlen + alen); /* GCM & friends just require a NONCE (non-repeating!) */ if (espx->type == CRYPTO_AES_CTR || espx->type == CRYPTO_AES_GCM_16 || espx->type == CRYPTO_CHACHA20_POLY1305) bcopy(&replay64, crde->crd_iv, sizeof(replay64)); else arc4random_buf(crde->crd_iv, espx->ivsize); } else crda = &crp->crp_desc[0]; /* Crypto operation descriptor. */ crp->crp_ilen = m->m_pkthdr.len; /* Total input length. */ crp->crp_flags = CRYPTO_F_IMBUF; crp->crp_buf = (caddr_t)m; crp->crp_sid = tdb->tdb_cryptoid; if (esph) { /* Authentication descriptor. */ crda->crd_skip = skip; crda->crd_inject = m->m_pkthdr.len - alen; /* Authentication operation. */ crda->crd_alg = esph->type; crda->crd_key = tdb->tdb_amxkey; crda->crd_klen = tdb->tdb_amxkeylen * 8; if ((tdb->tdb_wnd > 0) && (tdb->tdb_flags & TDBF_ESN)) { u_int32_t esn; esn = htonl((u_int32_t)(replay64 >> 32)); memcpy(crda->crd_esn, &esn, 4); crda->crd_flags |= CRD_F_ESN; } if (espx && (espx->type == CRYPTO_AES_GCM_16 || espx->type == CRYPTO_CHACHA20_POLY1305)) crda->crd_len = hlen - tdb->tdb_ivlen; else crda->crd_len = m->m_pkthdr.len - (skip + alen); } while ((error = crypto_invoke(crp)) == EAGAIN) { /* Reset the session ID */ if (tdb->tdb_cryptoid != 0) tdb->tdb_cryptoid = crp->crp_sid; } if (error) { DPRINTF("crypto error %d", error); ipsecstat_inc(ipsec_noxform); goto drop; } /* Release the crypto descriptors */ crypto_freereq(crp); /* Call the IPsec input callback. */ error = ipsp_process_done(m, tdb); if (error) espstat_inc(esps_outfail); return (error); drop: m_freem(m); crypto_freereq(crp); return error; } #define SEEN_SIZE howmany(TDB_REPLAYMAX, 32) /* * return 0 on success * return 1 for counter == 0 * return 2 for very old packet * return 3 for packet within current window but already received */ int checkreplaywindow(struct tdb *tdb, u_int64_t t, u_int32_t seq, u_int32_t *seqh, int commit) { u_int32_t tl, th, wl; u_int32_t packet, window = TDB_REPLAYMAX - TDB_REPLAYWASTE; int idx, esn = tdb->tdb_flags & TDBF_ESN; tl = (u_int32_t)t; th = (u_int32_t)(t >> 32); /* Zero SN is not allowed */ if ((esn && seq == 0 && tl <= AH_HMAC_INITIAL_RPL && th == 0) || (!esn && seq == 0)) return (1); if (th == 0 && tl < window) window = tl; /* Current replay window starts here */ wl = tl - window + 1; idx = (seq % TDB_REPLAYMAX) / 32; packet = 1U << (31 - (seq & 31)); /* * We keep the high part intact when: * 1) the SN is within [wl, 0xffffffff] and the whole window is * within one subspace; * 2) the SN is within [0, wl) and window spans two subspaces. */ if ((tl >= window - 1 && seq >= wl) || (tl < window - 1 && seq < wl)) { *seqh = th; if (seq > tl) { if (commit) { if (seq - tl > window) memset(tdb->tdb_seen, 0, sizeof(tdb->tdb_seen)); else { int i = (tl % TDB_REPLAYMAX) / 32; while (i != idx) { i = (i + 1) % SEEN_SIZE; tdb->tdb_seen[i] = 0; } } tdb->tdb_seen[idx] |= packet; tdb->tdb_rpl = ((u_int64_t)*seqh << 32) | seq; } } else { if (tl - seq >= window) return (2); if (tdb->tdb_seen[idx] & packet) return (3); if (commit) tdb->tdb_seen[idx] |= packet; } return (0); } /* Can't wrap if not doing ESN */ if (!esn) return (2); /* * (3) SN is within [wl, 0xffffffff] and wl is within * (0xffffffff-window+1, 0xffffffff]. * This means we got a SN which is within our replay window, * but in the previous subspace. */ if (tl < window - 1 && seq >= wl) { if (tdb->tdb_seen[idx] & packet) return (3); *seqh = th - 1; if (commit) tdb->tdb_seen[idx] |= packet; return (0); } /* * (4) SN has wrapped and the last authenticated SN is in the old * subspace. */ *seqh = th + 1; if (*seqh == 0) /* Don't let high bit to wrap */ return (1); if (commit) { if (seq - tl > window) memset(tdb->tdb_seen, 0, sizeof(tdb->tdb_seen)); else { int i = (tl % TDB_REPLAYMAX) / 32; while (i != idx) { i = (i + 1) % SEEN_SIZE; tdb->tdb_seen[i] = 0; } } tdb->tdb_seen[idx] |= packet; tdb->tdb_rpl = ((u_int64_t)*seqh << 32) | seq; } return (0); }