From b218a28f61e1e9a93c3a4f2e180590f85df62e79 Mon Sep 17 00:00:00 2001 From: Toke Høiland-Jørgensen Date: Sat, 17 Apr 2021 15:04:16 +0200 Subject: Babel: Add MAC authentication support This implements support for MAC authentication in the Babel protocol, as specified by RFC 8967. The implementation seeks to follow the RFC as close as possible, with the only deliberate deviation being the addition of support for all the HMAC algorithms already supported by Bird, as well as the Blake2b variant of the Blake algorithm. For description of applicability, assumptions and security properties, see RFC 8967 sections 1.1 and 1.2. --- proto/babel/packets.c | 496 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 490 insertions(+), 6 deletions(-) (limited to 'proto/babel/packets.c') diff --git a/proto/babel/packets.c b/proto/babel/packets.c index 1d2f5f5b..703d4026 100644 --- a/proto/babel/packets.c +++ b/proto/babel/packets.c @@ -11,7 +11,7 @@ */ #include "babel.h" - +#include "lib/mac.h" struct babel_pkt_header { u8 magic; @@ -112,6 +112,31 @@ struct babel_subtlv_source_prefix { u8 addr[0]; } PACKED; +struct babel_tlv_pc { + u8 type; + u8 length; + u32 pc; + u8 index[0]; +} PACKED; + +struct babel_tlv_mac { + u8 type; + u8 length; + u8 mac[0]; +} PACKED; + +struct babel_tlv_challenge { + u8 type; + u8 length; + u8 nonce[0]; +} PACKED; + +struct babel_mac_pseudohdr { + u8 src_addr[16]; + u16 src_port; + u8 dst_addr[16]; + u16 dst_port; +} PACKED; /* Hello flags */ #define BABEL_HF_UNICAST 0x8000 @@ -146,6 +171,9 @@ struct babel_parse_state { u8 def_ip4_prefix_seen; /* def_ip4_prefix is valid */ u8 current_tlv_endpos; /* End of self-terminating TLVs (offset from start) */ u8 sadr_enabled; + u8 is_unicast; + + struct babel_msg_auth auth; }; enum parse_result { @@ -168,6 +196,10 @@ struct babel_write_state { #define DROP1(DSC) do { err_dsc = DSC; goto drop; } while(0) #define LOG_PKT(msg, args...) \ log_rl(&p->log_pkt_tbf, L_REMOTE "%s: " msg, p->p.name, args) +#define LOG_WARN(msg, args...) \ + log_rl(&p->log_pkt_tbf, L_WARN "%s: " msg, p->p.name, args) +#define LOG_PKT_AUTH(msg, args...) \ + log_rl(&p->log_pkt_tbf, L_AUTH "%s: " msg, p->p.name, args) #define FIRST_TLV(p) ((struct babel_tlv *) (((struct babel_pkt_header *) p) + 1)) #define NEXT_TLV(t) ((struct babel_tlv *) (((byte *) t) + TLV_LENGTH(t))) @@ -274,6 +306,17 @@ put_ip6_ll(void *p, ip6_addr addr) put_u32(p+4, _I3(addr)); } +/* + * Authentication-related functions + */ +uint babel_auth_write_challenge(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len); +int babel_auth_add_tlvs(struct babel_iface *ifa, struct babel_tlv *tlv, int max_len); +int babel_auth_sign(struct babel_iface *ifa, ip_addr dest); +int babel_auth_check(struct babel_iface *ifa, + ip_addr saddr, u16 sport, + ip_addr daddr, u16 dport, + struct babel_pkt_header *pkt, + byte *start, uint len); /* * TLV read/write functions @@ -352,6 +395,16 @@ static const struct babel_tlv_data tlv_data[BABEL_TLV_MAX] = { babel_write_seqno_request, babel_handle_seqno_request }, + [BABEL_TLV_CHALLENGE_REQ] = { + sizeof(struct babel_tlv), + NULL, + babel_auth_write_challenge, + }, + [BABEL_TLV_CHALLENGE_REPLY] = { + sizeof(struct babel_tlv), + NULL, + babel_auth_write_challenge, + }, }; static const struct babel_tlv_data *get_packet_tlv_data(u8 type) @@ -1225,7 +1278,6 @@ babel_write_tlv(struct babel_tlv *hdr, return tlv_data[msg->type].write_tlv(hdr, msg, state, max_len); } - /* * Packet RX/TX functions */ @@ -1237,6 +1289,8 @@ babel_send_to(struct babel_iface *ifa, ip_addr dest) struct babel_pkt_header *hdr = (void *) sk->tbuf; int len = get_u16(&hdr->length) + sizeof(struct babel_pkt_header); + len += babel_auth_sign(ifa, dest); + DBG("Babel: Sending %d bytes to %I\n", len, dest); return sk_send_to(sk, len, dest, 0); } @@ -1289,6 +1343,8 @@ babel_write_queue(struct babel_iface *ifa, list *queue) sl_free(p->msg_slab, msg); } + pos += babel_auth_add_tlvs(ifa, (struct babel_tlv *) pos, end-pos); + uint plen = pos - (byte *) pkt; put_u16(&pkt->length, plen - sizeof(struct babel_pkt_header)); @@ -1367,10 +1423,13 @@ babel_enqueue(union babel_msg *msg, struct babel_iface *ifa) /** * babel_process_packet - process incoming data packet + * @ifa: Interface packet was received on. * @pkt: Pointer to the packet data * @len: Length of received packet * @saddr: Address of packet sender - * @ifa: Interface packet was received on. + * @sport: Packet source port + * @daddr: Destination address of packet + * @dport: Packet destination port * * This function is the main processing hook of incoming Babel packets. It * checks that the packet header is well-formed, then processes the TLVs @@ -1382,8 +1441,10 @@ babel_enqueue(union babel_msg *msg, struct babel_iface *ifa) * order. */ static void -babel_process_packet(struct babel_pkt_header *pkt, int len, - ip_addr saddr, struct babel_iface *ifa) +babel_process_packet(struct babel_iface *ifa, + struct babel_pkt_header *pkt, int len, + ip_addr saddr, u16 sport, + ip_addr daddr, u16 dport) { u8 frame_err UNUSED = 0; struct babel_proto *p = ifa->proto; @@ -1422,6 +1483,9 @@ babel_process_packet(struct babel_pkt_header *pkt, int len, TRACE(D_PACKETS, "Packet received from %I via %s", saddr, ifa->iface->name); + if (babel_auth_check(ifa, saddr, sport, daddr, dport, pkt, end, len-plen)) + return; + init_list(&msgs); /* First pass through the packet TLV by TLV, parsing each into internal data @@ -1512,7 +1576,10 @@ babel_rx_hook(sock *sk, uint len) if (sk->flags & SKF_TRUNCATED) DROP("truncated", len); - babel_process_packet((struct babel_pkt_header *) sk->rbuf, len, sk->faddr, ifa); + babel_process_packet(ifa, + (struct babel_pkt_header *) sk->rbuf, len, + sk->faddr, sk->fport, + sk->laddr, sk->dport); return 1; drop: @@ -1562,3 +1629,420 @@ err: rfree(sk); return 0; } + + +/* Authentication checks */ +static int +babel_read_pc(struct babel_tlv *hdr, union babel_msg *m UNUSED, + struct babel_parse_state *state) +{ + struct babel_tlv_pc *tlv = (void *) hdr; + + if (!state->auth.pc_seen) + { + state->auth.pc_seen = 1; + state->auth.pc = get_u32(&tlv->pc); + state->auth.index_len = TLV_OPT_LENGTH(tlv); + state->auth.index = tlv->index; + } + + return PARSE_IGNORE; +} + +static const struct babel_tlv_data pc_tlv_data = { + .min_length = sizeof(struct babel_tlv_pc), + .read_tlv = &babel_read_pc +}; + +static int +babel_read_challenge_req(struct babel_tlv *hdr, union babel_msg *m UNUSED, + struct babel_parse_state *state) +{ + struct babel_tlv_challenge *tlv = (void *) hdr; + + if (!state->is_unicast) + { + DBG("Ignoring non-unicast challenge request from %I\n", state->saddr); + return PARSE_IGNORE; + } + + if (tlv->length > BABEL_AUTH_MAX_NONCE_LEN) + return PARSE_IGNORE; + + state->auth.challenge_len = tlv->length; + if (state->auth.challenge_len) + memcpy(state->auth.challenge, tlv->nonce, state->auth.challenge_len); + state->auth.challenge_seen = 1; + + return PARSE_IGNORE; +} + +static const struct babel_tlv_data challenge_req_tlv_data = { + .min_length = sizeof(struct babel_tlv_challenge), + .read_tlv = &babel_read_challenge_req, +}; + +static int +babel_read_challenge_reply(struct babel_tlv *hdr, union babel_msg *m UNUSED, + struct babel_parse_state *state) +{ + struct babel_tlv_challenge *tlv = (void *) hdr; + + if (tlv->length != BABEL_AUTH_NONCE_LEN || state->auth.challenge_reply_seen) + return PARSE_IGNORE; + + state->auth.challenge_reply_seen = 1; + memcpy(state->auth.challenge_reply, tlv->nonce, BABEL_AUTH_NONCE_LEN); + + return PARSE_IGNORE; +} + +static const struct babel_tlv_data challenge_reply_tlv_data = { + .min_length = sizeof(struct babel_tlv_challenge), + .read_tlv = &babel_read_challenge_reply, +}; + +static const struct babel_tlv_data * +get_auth_tlv_data(u8 type) +{ + switch(type) + { + case BABEL_TLV_PC: + return &pc_tlv_data; + case BABEL_TLV_CHALLENGE_REQ: + return &challenge_req_tlv_data; + case BABEL_TLV_CHALLENGE_REPLY: + return &challenge_reply_tlv_data; + default: + return NULL; + } +} + +uint +babel_auth_write_challenge(struct babel_tlv *hdr, union babel_msg *m, + struct babel_write_state *state UNUSED,uint max_len) +{ + struct babel_tlv_challenge *tlv = (void *) hdr; + struct babel_msg_challenge *msg = &m->challenge; + + uint len = sizeof(struct babel_tlv_challenge) + msg->nonce_len; + + if (len > max_len) + return 0; + + TLV_HDR(tlv, msg->type, len); + memcpy(tlv->nonce, msg->nonce, msg->nonce_len); + + return len; +} + +static int +babel_mac_hash(struct password_item *pass, + struct babel_mac_pseudohdr *phdr, + byte *pkt, uint pkt_len, + byte *buf, uint *buf_len) +{ + struct mac_context ctx; + + if (mac_type_length(pass->alg) > *buf_len) + return 1; + + mac_init(&ctx, pass->alg, pass->password, pass->length); + mac_update(&ctx, (byte *)phdr, sizeof(*phdr)); + mac_update(&ctx, (byte *)pkt, pkt_len); + + *buf_len = mac_get_length(&ctx); + memcpy(buf, mac_final(&ctx), *buf_len); + + mac_cleanup(&ctx); + + return 0; +} + +static void +babel_mac_build_phdr(struct babel_mac_pseudohdr *phdr, + ip_addr saddr, u16 sport, + ip_addr daddr, u16 dport) +{ + memset(phdr, 0, sizeof(*phdr)); + put_ip6(phdr->src_addr, saddr); + put_u16(&phdr->src_port, sport); + put_ip6(phdr->dst_addr, daddr); + put_u16(&phdr->dst_port, dport); + DBG("MAC pseudo-header: %I %d %I %d\n", saddr, sport, daddr, dport); +} + +static int +babel_auth_check_mac(struct babel_iface *ifa, byte *pkt, + byte *trailer, uint trailer_len, + ip_addr saddr, u16 sport, + ip_addr daddr, u16 dport) +{ + uint hash_len = (uint)(trailer - pkt); + struct babel_proto *p = ifa->proto; + byte *end = trailer + trailer_len; + btime now_ = current_real_time(); + struct babel_mac_pseudohdr phdr; + struct password_item *pass; + struct babel_tlv *tlv; + + if (trailer_len < sizeof(*tlv)) + { + LOG_PKT_AUTH("No MAC signature on packet from %I on %s", + saddr, ifa->ifname); + return 1; + } + + babel_mac_build_phdr(&phdr, saddr, sport, daddr, dport); + + WALK_LIST(pass, *ifa->cf->passwords) + { + byte mac_res[MAX_HASH_SIZE]; + uint mac_len = MAX_HASH_SIZE; + u8 frame_err = 0; + + if (pass->accfrom > now_ || pass->accto < now_) + continue; + + if (babel_mac_hash(pass, &phdr, + pkt, hash_len, + mac_res, &mac_len)) + continue; + + WALK_TLVS((void *)trailer, end, tlv, frame_err, saddr, ifa->ifname) + { + struct babel_tlv_mac *mac = (void *)tlv; + + if (tlv->type != BABEL_TLV_MAC) + continue; + + if (tlv->length == mac_len && !memcmp(mac->mac, mac_res, mac_len)) + return 0; + + DBG("MAC mismatch key id %d pos %d len %d/%d\n", + pass->id, (byte *)tlv - (byte *)pkt, mac_len, tlv->length); + } + WALK_TLVS_END; + + if (frame_err) { + DBG("MAC trailer TLV framing error\n"); + return 1; + } + } + + LOG_PKT_AUTH("No MAC key matching packet from %I found on %s", + saddr, ifa->ifname); + return 1; +} + +/** + * babel_auth_check - Check authentication for a packet + * @ifa: Interface holding the transmission buffer + * @saddr: Source address the packet was received from + * @sport: Source port the packet was received from + * @daddr: Destination address the packet was sent to + * @dport: Destination port the packet was sent to + * @pkt: Pointer to start of the packet data + * @trailer: Pointer to the packet trailer + * @trailer_len: Length of the packet trailer + * + * This function performs any necessary authentication checks on a packet and + * returns 0 if the packet should be accepted (either because it has been + * successfully authenticated or because authentication is disabled or + * configured in permissive mode), or 1 if the packet should be dropped without + * further processing. + */ +int +babel_auth_check(struct babel_iface *ifa, + ip_addr saddr, u16 sport, + ip_addr daddr, u16 dport, + struct babel_pkt_header *pkt, + byte *trailer, uint trailer_len) +{ + u8 frame_err UNUSED = 0; + struct babel_proto *p = ifa->proto; + struct babel_tlv *tlv; + + struct babel_parse_state state = { + .get_tlv_data = &get_auth_tlv_data, + .proto = p, + .ifa = ifa, + .saddr = saddr, + .is_unicast = !(ipa_classify(daddr) & IADDR_MULTICAST), + .auth = { + .sender = saddr, + }, + }; + + if (ifa->cf->auth_type == BABEL_AUTH_NONE) + return 0; + + TRACE(D_PACKETS, "Checking packet authentication signature"); + + if (babel_auth_check_mac(ifa, (byte *)pkt, + trailer, trailer_len, + saddr, sport, + daddr, dport)) + goto fail; + + /* MAC verified; parse packet to check packet counter and challenge */ + WALK_TLVS(FIRST_TLV(pkt), trailer, tlv, frame_err, saddr, ifa->iface->name) + { + union babel_msg msg; + enum parse_result res; + + res = babel_read_tlv(tlv, &msg, &state); + if (res == PARSE_ERROR) + { + LOG_PKT_AUTH("Bad TLV from %I via %s type %d pos %d - parse error", + saddr, ifa->iface->name, tlv->type, (byte *)tlv - (byte *)pkt); + goto fail; + } + } + WALK_TLVS_END; + + if (babel_auth_check_pc(ifa, &state.auth)) + goto fail; + + TRACE(D_PACKETS, "Packet from %I via %s authenticated successfully", + saddr, ifa->ifname); + return 0; + +fail: + LOG_PKT_AUTH("Packet from %I via %s failed authentication%s", + saddr, ifa->ifname, + ifa->cf->auth_permissive ? " but accepted in permissive mode" : ""); + + return !ifa->cf->auth_permissive; +} + +/** + * babel_auth_add_tlvs - Add authentication-related TLVs to a packet + * @ifa: Interface holding the transmission buffer + * @tlv: Pointer to the place where any new TLVs should be added + * @max_len: Maximum length available for adding new TLVs + * + * This function adds any new TLVs required by the authentication mode to a + * packet before it is shipped out. For MAC authentication, this is the packet + * counter TLV that must be included in every packet. + */ +int +babel_auth_add_tlvs(struct babel_iface *ifa, struct babel_tlv *tlv, int max_len) +{ + struct babel_proto *p = ifa->proto; + struct babel_tlv_pc *msg; + int len; + + if (ifa->cf->auth_type == BABEL_AUTH_NONE) + return 0; + + msg = (void *)tlv; + len = sizeof(*msg) + BABEL_AUTH_INDEX_LEN; + max_len += ifa->auth_tx_overhead; + + if (len > max_len) + { + LOG_WARN("Insufficient space to add MAC seqno TLV on iface %s: %d < %d", + ifa->ifname, max_len, len); + return 0; + } + + msg->type = BABEL_TLV_PC; + msg->length = len - sizeof(struct babel_tlv); + put_u32(&msg->pc, ifa->auth_pc++); + memcpy(msg->index, ifa->auth_index, BABEL_AUTH_INDEX_LEN); + + /* Reset index on overflow to 0 */ + if (!ifa->auth_pc) + babel_auth_reset_index(ifa); + + return len; +} + +/** + * babel_auth_sign - Sign an outgoing packet before transmission + * @ifa: Interface holding the transmission buffer + * @dest: Destination address of the packet + * + * This function adds authentication signature(s) to the packet trailer for each + * of the configured authentication keys on the interface. + */ +int +babel_auth_sign(struct babel_iface *ifa, ip_addr dest) +{ + struct babel_proto *p = ifa->proto; + struct babel_mac_pseudohdr phdr; + struct babel_pkt_header *hdr; + struct password_item *pass; + int tot_len = 0, i = 0; + struct babel_tlv *tlv; + sock *sk = ifa->sk; + byte *pos, *end; + btime now_; + int len; + + if (ifa->cf->auth_type == BABEL_AUTH_NONE) + return 0; + + hdr = (void *) sk->tbuf; + len = get_u16(&hdr->length) + sizeof(struct babel_pkt_header); + + pos = (byte *)hdr + len; + end = (byte *)hdr + ifa->tx_length + ifa->auth_tx_overhead; + tlv = (void *)pos; + now_ = current_real_time(); + + babel_mac_build_phdr(&phdr, sk->saddr, sk->fport, dest, sk->dport); + + WALK_LIST(pass, *ifa->cf->passwords) + { + struct babel_tlv_mac *msg = (void *)tlv; + uint buf_len = (uint) (end - (byte *)msg - sizeof(*msg)); + + if (pass->genfrom > now_ || pass->gento < now_) + continue; + + if (babel_mac_hash(pass, &phdr, + (byte *)hdr, len, + msg->mac, &buf_len)) + { + LOG_WARN("Insufficient space for MAC signatures on iface %s dest %I", + ifa->ifname, dest); + break; + } + + msg->type = BABEL_TLV_MAC; + msg->length = buf_len; + + tlv = NEXT_TLV(tlv); + tot_len += buf_len + sizeof(*msg); + i++; + } + + DBG("Added %d MAC signatures (%d bytes) on ifa %s for dest %I\n", + i, tot_len, ifa->ifname, dest); + + return tot_len; +} + +/** + * babel_auth_set_tx_overhead - Set interface TX overhead for authentication + * @ifa: Interface to configure + * + * This function sets the TX overhead for an interface based on its + * authentication configuration. + */ +void +babel_auth_set_tx_overhead(struct babel_iface *ifa) +{ + if (ifa->cf->auth_type == BABEL_AUTH_NONE) + { + ifa->auth_tx_overhead = 0; + return; + } + + ifa->auth_tx_overhead = (sizeof(struct babel_tlv_pc) + + sizeof(struct babel_tlv_mac) * ifa->cf->mac_num_keys + + ifa->cf->mac_total_len); + ifa->tx_length -= ifa->auth_tx_overhead; +} -- cgit v1.2.3