diff options
-rw-r--r-- | doc/bird.sgml | 36 | ||||
-rw-r--r-- | proto/babel/Makefile | 2 | ||||
-rw-r--r-- | proto/babel/babel.c | 156 | ||||
-rw-r--r-- | proto/babel/babel.h | 66 | ||||
-rw-r--r-- | proto/babel/config.Y | 42 | ||||
-rw-r--r-- | proto/babel/packets.c | 496 |
6 files changed, 772 insertions, 26 deletions
diff --git a/doc/bird.sgml b/doc/bird.sgml index 01725128..8235740e 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -828,8 +828,8 @@ agreement"). <tag><label id="proto-pass-algorithm">algorithm ( keyed md5 | keyed sha1 | hmac sha1 | hmac sha256 | hmac sha384 | hmac sha512 | blake2s128 | blake2s256 | blake2b256 | blake2b512 )</tag> The message authentication algorithm for the password when cryptographic authentication is enabled. The default value depends on the protocol. - For RIP and OSPFv2 it is Keyed-MD5 (for compatibility), for OSPFv3 - protocol it is HMAC-SHA-256. + For RIP and OSPFv2 it is Keyed-MD5 (for compatibility), for OSPFv3 and + Babel it is HMAC-SHA-256. </descrip> @@ -1817,6 +1817,19 @@ protocol babel [<name>] { check link <switch>; next hop ipv4 <address>; next hop ipv6 <address>; + authentication none|mac [permissive]; + password "<text>"; + password "<text>" { + id <num>; + generate from "<date>"; + generate to "<date>"; + accept from "<date>"; + accept to "<date>"; + from "<date>"; + to "<date>"; + algorithm ( hmac sha1 | hmac sha256 | hmac sha384 | hmac + sha512 | blake2s | blake2b ); + }; }; } </code> @@ -1907,6 +1920,25 @@ protocol babel [<name>] { interface. If not set, the same link-local address that is used as the source for Babel packets will be used. In normal operation, it should not be necessary to set this option. + + <tag><label id="babel-authentication">authentication none|mac [permissive]</tag> + Selects authentication method to be used. <cf/none/ means that packets + are not authenticated at all, <cf/mac/ means MAC authentication is + performed as described in <rfc id="8967">. If MAC authentication is + selected, the <cf/permissive/ suffix can be used to select an operation + mode where outgoing packets are signed, but incoming packets will be + accepted even if they fail authentication. This can be useful for + incremental deployment of MAC authentication across a network. If MAC + authentication is selected, a key must be specified with the + <cf/password/ configuration option. Default: none. + + <tag><label id="babel-password">password "<m/text/"</tag> Specifies a + password used for authentication. See the <ref id="proto-pass" + name="password"> common option for a detailed description. The Babel + protocol will only accept HMAC-based algorithms or one of the Blake + algorithms, and the length of the supplied password string must match the + key size used by the selected algorithm. + </descrip> <sect1>Attributes diff --git a/proto/babel/Makefile b/proto/babel/Makefile index a5b4a13b..06b58e95 100644 --- a/proto/babel/Makefile +++ b/proto/babel/Makefile @@ -3,4 +3,4 @@ obj := $(src-o-files) $(all-daemon) $(cf-local) -tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file +tests_objs := $(tests_objs) $(src-o-files) diff --git a/proto/babel/babel.c b/proto/babel/babel.c index fdebc352..82ba7da1 100644 --- a/proto/babel/babel.c +++ b/proto/babel/babel.c @@ -38,6 +38,8 @@ #include <stdlib.h> #include "babel.h" +#define LOG_PKT_AUTH(msg, args...) \ + log_rl(&p->log_pkt_tbf, L_AUTH "%s: " msg, p->p.name, args) /* * Is one number greater or equal than another mod 2^16? This is based on the @@ -55,6 +57,7 @@ static void babel_send_seqno_request(struct babel_proto *p, struct babel_entry * static void babel_update_cost(struct babel_neighbor *n); static inline void babel_kick_timer(struct babel_proto *p); static inline void babel_iface_kick_timer(struct babel_iface *ifa); +static void babel_auth_init_neighbor(struct babel_neighbor *n); /* * Functions to maintain data structures @@ -428,6 +431,7 @@ babel_get_neighbor(struct babel_iface *ifa, ip_addr addr) init_list(&nbr->routes); init_list(&nbr->requests); add_tail(&ifa->neigh_list, NODE nbr); + babel_auth_init_neighbor(nbr); return nbr; } @@ -509,11 +513,13 @@ babel_expire_neighbors(struct babel_proto *p) if (nbr->hello_expiry && nbr->hello_expiry <= now_) babel_expire_hello(p, nbr, now_); + + if (nbr->auth_expiry && nbr->auth_expiry <= now_) + babel_flush_neighbor(p, nbr); } } } - /* * Best route selection */ @@ -1388,6 +1394,127 @@ babel_handle_seqno_request(union babel_msg *m, struct babel_iface *ifa) } } +/* + * Authentication functions + */ + +/** + * babel_auth_reset_index - Reset authentication index on interface + * @ifa: Interface to reset + * + * This function resets the authentication index and packet counter for an + * interface, and should be called on interface configuration, or when the + * packet counter overflows. + */ +void +babel_auth_reset_index(struct babel_iface *ifa) +{ + random_bytes(ifa->auth_index, BABEL_AUTH_INDEX_LEN); + ifa->auth_pc = 1; +} + +/** + * babel_auth_init_neighbor - Initialise authentication data for neighbor + * @n: Neighbor to initialise + * + * This function initialises the authentication-related state for a new neighbor + * that has just been created. + */ +void +babel_auth_init_neighbor(struct babel_neighbor *n) +{ + if (n->ifa->cf->auth_type != BABEL_AUTH_NONE) + n->auth_expiry = current_time() + BABEL_AUTH_NEIGHBOR_TIMEOUT; +} + +static void +babel_auth_send_challenge(struct babel_iface *ifa, struct babel_neighbor *n) +{ + struct babel_proto *p = ifa->proto; + union babel_msg msg = {}; + + TRACE(D_PACKETS, "Sending AUTH challenge to %I on %s", + n->addr, ifa->ifname); + + random_bytes(n->auth_nonce, BABEL_AUTH_NONCE_LEN); + n->auth_nonce_expiry = current_time() + BABEL_AUTH_CHALLENGE_TIMEOUT; + n->auth_next_challenge = current_time() + BABEL_AUTH_CHALLENGE_INTERVAL; + + msg.type = BABEL_TLV_CHALLENGE_REQ; + msg.challenge.nonce_len = BABEL_AUTH_NONCE_LEN; + msg.challenge.nonce = n->auth_nonce; + + babel_send_unicast(&msg, ifa, n->addr); +} + +int +babel_auth_check_pc(struct babel_iface *ifa, struct babel_msg_auth *msg) +{ + struct babel_proto *p = ifa->proto; + struct babel_neighbor *n; + + TRACE(D_PACKETS, "Handling MAC check from %I on %s", + msg->sender, ifa->ifname); + + /* We create the neighbour entry at this point because it makes it easier to + * rate limit challenge replies; this is explicitly allowed by the spec (see + * Section 4.3). + */ + n = babel_get_neighbor(ifa, msg->sender); + + if (msg->challenge_seen && n->auth_next_challenge_reply <= current_time()) + { + union babel_msg resp = {}; + TRACE(D_PACKETS, "Sending MAC challenge response to %I", msg->sender); + resp.type = BABEL_TLV_CHALLENGE_REPLY; + resp.challenge.nonce_len = msg->challenge_len; + resp.challenge.nonce = msg->challenge; + n->auth_next_challenge_reply = current_time() + BABEL_AUTH_CHALLENGE_INTERVAL; + babel_send_unicast(&resp, ifa, msg->sender); + } + + if (msg->index_len > BABEL_AUTH_INDEX_LEN || !msg->pc_seen) + { + LOG_PKT_AUTH("Invalid index or no PC from %I on %s", + msg->sender, ifa->ifname); + return 1; + } + + /* On successful challenge, update PC and index to current values */ + if (msg->challenge_reply_seen && + n->auth_nonce_expiry && + n->auth_nonce_expiry >= current_time() && + !memcmp(msg->challenge_reply, n->auth_nonce, BABEL_AUTH_NONCE_LEN)) + { + n->auth_index_len = msg->index_len; + memcpy(n->auth_index, msg->index, msg->index_len); + n->auth_pc = msg->pc; + } + + /* If index differs, send challenge */ + if ((n->auth_index_len != msg->index_len || + memcmp(n->auth_index, msg->index, msg->index_len)) && + n->auth_next_challenge <= current_time()) + { + LOG_PKT_AUTH("Index mismatch from %I on %s; sending challenge", + msg->sender, ifa->ifname); + babel_auth_send_challenge(ifa, n); + return 1; + } + + /* Index matches; only accept if PC is greater than last */ + if (n->auth_pc >= msg->pc) + { + LOG_PKT_AUTH("Packet counter too low from %I on %s", + msg->sender, ifa->ifname); + return 1; + } + + n->auth_pc = msg->pc; + n->auth_expiry = current_time() + BABEL_AUTH_NEIGHBOR_TIMEOUT; + n->auth_passed = 1; + return 0; +} /* * Babel interfaces @@ -1556,6 +1683,8 @@ babel_iface_update_buffers(struct babel_iface *ifa) sk_set_tbsize(ifa->sk, tbsize); ifa->tx_length = tbsize - BABEL_OVERHEAD; + + babel_auth_set_tx_overhead(ifa); } static struct babel_iface* @@ -1615,6 +1744,9 @@ babel_add_iface(struct babel_proto *p, struct iface *new, struct babel_iface_con init_list(&ifa->neigh_list); ifa->hello_seqno = 1; + if (ic->auth_type != BABEL_AUTH_NONE) + babel_auth_reset_index(ifa); + ifa->timer = tm_new_init(ifa->pool, babel_iface_timer, ifa, 0, 0); init_list(&ifa->msg_queue); @@ -1722,6 +1854,9 @@ babel_reconfigure_iface(struct babel_proto *p, struct babel_iface *ifa, struct b ifa->next_hop_ip4 = ipa_nonzero(new->next_hop_ip4) ? new->next_hop_ip4 : addr4; ifa->next_hop_ip6 = ipa_nonzero(new->next_hop_ip6) ? new->next_hop_ip6 : ifa->addr; + if (new->auth_type != BABEL_AUTH_NONE && old->auth_type != new->auth_type) + babel_auth_reset_index(ifa); + if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel) log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname); @@ -1910,8 +2045,8 @@ babel_show_interfaces(struct proto *P, const char *iff) } cli_msg(-1023, "%s:", p->p.name); - cli_msg(-1023, "%-10s %-6s %7s %6s %7s %-15s %s", - "Interface", "State", "RX cost", "Nbrs", "Timer", + cli_msg(-1023, "%-10s %-6s %-5s %7s %6s %7s %-15s %s", + "Interface", "State", "Auth", "RX cost", "Nbrs", "Timer", "Next hop (v4)", "Next hop (v6)"); WALK_LIST(ifa, p->interfaces) @@ -1924,8 +2059,10 @@ babel_show_interfaces(struct proto *P, const char *iff) nbrs++; btime timer = MIN(ifa->next_regular, ifa->next_hello) - current_time(); - cli_msg(-1023, "%-10s %-6s %7u %6u %7t %-15I %I", + cli_msg(-1023, "%-10s %-6s %-5s %7u %6u %7t %-15I %I", ifa->iface->name, (ifa->up ? "Up" : "Down"), + (ifa->cf->auth_type == BABEL_AUTH_MAC ? + (ifa->cf->auth_permissive ? "Perm" : "Yes") : "No"), ifa->cf->rxcost, nbrs, MAX(timer, 0), ifa->next_hop_ip4, ifa->next_hop_ip6); } @@ -1946,8 +2083,8 @@ babel_show_neighbors(struct proto *P, const char *iff) } cli_msg(-1024, "%s:", p->p.name); - cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s", - "IP address", "Interface", "Metric", "Routes", "Hellos", "Expires"); + cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s %4s", + "IP address", "Interface", "Metric", "Routes", "Hellos", "Expires", "Auth"); WALK_LIST(ifa, p->interfaces) { @@ -1961,9 +2098,10 @@ babel_show_neighbors(struct proto *P, const char *iff) rts++; uint hellos = u32_popcount(n->hello_map); - btime timer = n->hello_expiry - current_time(); - cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t", - n->addr, ifa->iface->name, n->cost, rts, hellos, MAX(timer, 0)); + btime timer = (n->hello_expiry ?: n->auth_expiry) - current_time(); + cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t %-4s", + n->addr, ifa->iface->name, n->cost, rts, hellos, MAX(timer, 0), + n->auth_passed ? "Yes" : "No"); } } } diff --git a/proto/babel/babel.h b/proto/babel/babel.h index 09bf530c..ef5b4a5d 100644 --- a/proto/babel/babel.h +++ b/proto/babel/babel.h @@ -19,6 +19,7 @@ #include "nest/route.h" #include "nest/protocol.h" #include "nest/locks.h" +#include "nest/password.h" #include "lib/resource.h" #include "lib/lists.h" #include "lib/socket.h" @@ -60,6 +61,14 @@ #define BABEL_OVERHEAD (IP6_HEADER_LENGTH+UDP_HEADER_LENGTH) #define BABEL_MIN_MTU (512 + BABEL_OVERHEAD) +#define BABEL_AUTH_NONE 0 +#define BABEL_AUTH_MAC 1 +#define BABEL_AUTH_NONCE_LEN 10 /* we send 80 bit nonces */ +#define BABEL_AUTH_MAX_NONCE_LEN 192 /* max allowed by spec */ +#define BABEL_AUTH_INDEX_LEN 32 /* max size in spec */ +#define BABEL_AUTH_NEIGHBOR_TIMEOUT (300 S_) +#define BABEL_AUTH_CHALLENGE_TIMEOUT (30 S_) +#define BABEL_AUTH_CHALLENGE_INTERVAL (300 MS_) /* used for both challenges and replies */ enum babel_tlv_type { BABEL_TLV_PAD1 = 0, @@ -73,13 +82,10 @@ enum babel_tlv_type { BABEL_TLV_UPDATE = 8, BABEL_TLV_ROUTE_REQUEST = 9, BABEL_TLV_SEQNO_REQUEST = 10, - /* extensions - not implemented - BABEL_TLV_TS_PC = 11, - BABEL_TLV_HMAC = 12, - BABEL_TLV_SS_UPDATE = 13, - BABEL_TLV_SS_REQUEST = 14, - BABEL_TLV_SS_SEQNO_REQUEST = 15, - */ + BABEL_TLV_MAC = 16, + BABEL_TLV_PC = 17, + BABEL_TLV_CHALLENGE_REQ = 18, + BABEL_TLV_CHALLENGE_REPLY = 19, BABEL_TLV_MAX }; @@ -137,6 +143,12 @@ struct babel_iface_config { ip_addr next_hop_ip4; ip_addr next_hop_ip6; + + u8 auth_type; /* Authentication type (BABEL_AUTH_*) */ + u8 auth_permissive; /* Don't drop packets failing auth check */ + uint mac_num_keys; /* Number of configured HMAC keys */ + uint mac_total_len; /* Total digest length for all configured keys */ + list *passwords; /* Passwords for authentication */ }; struct babel_proto { @@ -184,6 +196,10 @@ struct babel_iface { u16 hello_seqno; /* To be increased on each hello */ + u32 auth_pc; + int auth_tx_overhead; + u8 auth_index[BABEL_AUTH_INDEX_LEN]; + btime next_hello; btime next_regular; btime next_triggered; @@ -206,9 +222,20 @@ struct babel_neighbor { u16 hello_map; u16 next_hello_seqno; uint last_hello_int; + + u32 auth_pc; + u8 auth_passed; + u8 auth_index_len; + u8 auth_index[BABEL_AUTH_INDEX_LEN]; + u8 auth_nonce[BABEL_AUTH_NONCE_LEN]; + btime auth_nonce_expiry; + btime auth_next_challenge; + btime auth_next_challenge_reply; + /* expiry timers */ btime hello_expiry; btime ihu_expiry; + btime auth_expiry; list routes; /* Routes this neighbour has sent us (struct babel_route) */ list requests; /* Seqno requests bound to this neighbor */ @@ -340,6 +367,12 @@ struct babel_msg_seqno_request { ip_addr sender; }; +struct babel_msg_challenge { + u8 type; + u8 nonce_len; + u8 *nonce; +}; + union babel_msg { u8 type; struct babel_msg_ack_req ack_req; @@ -349,6 +382,7 @@ union babel_msg { struct babel_msg_update update; struct babel_msg_route_request route_request; struct babel_msg_seqno_request seqno_request; + struct babel_msg_challenge challenge; }; struct babel_msg_node { @@ -356,6 +390,20 @@ struct babel_msg_node { union babel_msg msg; }; +/* only used for auth checking, so not a part of union above */ +struct babel_msg_auth { + ip_addr sender; + u32 pc; + u8 pc_seen; + u8 index_len; + u8 *index; + u8 challenge_reply_seen; + u8 challenge_reply[BABEL_AUTH_NONCE_LEN]; + u8 challenge_seen; + u8 challenge_len; + u8 challenge[BABEL_AUTH_MAX_NONCE_LEN]; +}; + static inline int babel_sadr_enabled(struct babel_proto *p) { return p->ip6_rtable.addr_type == NET_IP6_SADR; } @@ -374,11 +422,15 @@ void babel_show_neighbors(struct proto *P, const char *iff); void babel_show_entries(struct proto *P); void babel_show_routes(struct proto *P); +void babel_auth_reset_index(struct babel_iface *ifa); +int babel_auth_check_pc(struct babel_iface *ifa, struct babel_msg_auth *msg); + /* packets.c */ void babel_enqueue(union babel_msg *msg, struct babel_iface *ifa); void babel_send_unicast(union babel_msg *msg, struct babel_iface *ifa, ip_addr dest); int babel_open_socket(struct babel_iface *ifa); void babel_send_queue(void *arg); +void babel_auth_set_tx_overhead(struct babel_iface *ifa); #endif diff --git a/proto/babel/config.Y b/proto/babel/config.Y index 2f3b637b..5e0710b5 100644 --- a/proto/babel/config.Y +++ b/proto/babel/config.Y @@ -25,7 +25,7 @@ CF_DECLS CF_KEYWORDS(BABEL, INTERFACE, METRIC, RXCOST, HELLO, UPDATE, INTERVAL, PORT, TYPE, WIRED, WIRELESS, RX, TX, BUFFER, PRIORITY, LENGTH, CHECK, LINK, NEXT, HOP, IPV4, IPV6, BABEL_METRIC, SHOW, INTERFACES, NEIGHBORS, - ENTRIES, RANDOMIZE, ROUTER, ID) + ENTRIES, RANDOMIZE, ROUTER, ID, AUTHENTICATION, NONE, MAC, PERMISSIVE) CF_GRAMMAR @@ -59,6 +59,8 @@ babel_iface_start: this_ipatt = cfg_allocz(sizeof(struct babel_iface_config)); add_tail(&BABEL_CFG->iface_list, NODE this_ipatt); init_list(&this_ipatt->ipn_list); + reset_passwords(); + BABEL_IFACE->port = BABEL_PORT; BABEL_IFACE->type = BABEL_IFACE_TYPE_WIRED; BABEL_IFACE->limit = BABEL_HELLO_LIMIT; @@ -91,6 +93,40 @@ babel_iface_finish: BABEL_IFACE->ihu_interval = MIN_(BABEL_IFACE->hello_interval*BABEL_IHU_INTERVAL_FACTOR, BABEL_MAX_INTERVAL); BABEL_CFG->hold_time = MAX_(BABEL_CFG->hold_time, BABEL_IFACE->update_interval*BABEL_HOLD_TIME_FACTOR); + + BABEL_IFACE->passwords = get_passwords(); + + if (!BABEL_IFACE->auth_type != !BABEL_IFACE->passwords) + cf_error("Authentication and password options should be used together"); + + if (BABEL_IFACE->passwords) + { + struct password_item *pass; + uint len = 0, i = 0; + WALK_LIST(pass, *BABEL_IFACE->passwords) + { + /* Set default crypto algorithm (HMAC-SHA256) */ + if (!pass->alg) + pass->alg = ALG_HMAC_SHA256; + + if (pass->alg & ALG_HMAC) { + if (pass->length < mac_type_length(pass->alg) || + pass->length > mac_type_block_size(pass->alg)) + cf_error("key length %d is not between output size %d and block size %d for algorithm %s", + pass->length, mac_type_length(pass->alg), + mac_type_block_size(pass->alg), mac_type_name(pass->alg)); + } else if (!(pass->alg == ALG_BLAKE2S_128 || pass->alg == ALG_BLAKE2S_256 || + pass->alg == ALG_BLAKE2B_256 || pass->alg == ALG_BLAKE2B_512)) { + cf_error("Only HMAC and Blake algorithms are supported"); + } + + len += mac_type_length(pass->alg); + i++; + } + BABEL_IFACE->mac_num_keys = i; + BABEL_IFACE->mac_total_len = len; + } + }; @@ -109,6 +145,10 @@ babel_iface_item: | CHECK LINK bool { BABEL_IFACE->check_link = $3; } | NEXT HOP IPV4 ipa { BABEL_IFACE->next_hop_ip4 = $4; if (!ipa_is_ip4($4)) cf_error("Must be an IPv4 address"); } | NEXT HOP IPV6 ipa { BABEL_IFACE->next_hop_ip6 = $4; if (!ipa_is_ip6($4)) cf_error("Must be an IPv6 address"); } + | AUTHENTICATION NONE { BABEL_IFACE->auth_type = BABEL_AUTH_NONE; } + | AUTHENTICATION MAC { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; } + | AUTHENTICATION MAC PERMISSIVE { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; BABEL_IFACE->auth_permissive = 1; } + | password_list { } ; babel_iface_opts: 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; +} |