Commit b218a28f authored by Toke Høiland-Jørgensen's avatar Toke Høiland-Jørgensen Committed by Ondrej Zajicek (work)
Browse files

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.
parent 69d10132
Loading
Loading
Loading
Loading
+34 −2
Original line number Diff line number Diff line
@@ -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 "&lt;text&gt;";
		password "&lt;text&gt;" {
			id &lt;num&gt;;
			generate from "&lt;date&gt;";
			generate to "&lt;date&gt;";
			accept from "&lt;date&gt;";
			accept to "&lt;date&gt;";
			from "&lt;date&gt;";
			to "&lt;date&gt;";
			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
+147 −9
Original line number Diff line number Diff line
@@ -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");
    }
  }
}
+59 −7
Original line number Diff line number Diff line
@@ -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
+41 −1
Original line number Diff line number Diff line
@@ -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:
+490 −6

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1

File changed.

Contains only whitespace changes.

Loading