BFD: Authentication

Implement BFD authentication (part of RFC 5880). Supports plaintext
passwords and cryptographic MD5 / SHA-1 authentication.

Based on former commit from Pavel Tvrdik
This commit is contained in:
Ondrej Zajicek (work) 2016-10-30 23:51:23 +01:00
parent 29239ba2bb
commit e03dc6a984
5 changed files with 333 additions and 20 deletions

View file

@ -672,7 +672,7 @@ agreement").
authentication is enabled, authentication can be enabled by separate,
protocol-dependent <cf/authentication/ option.
This option is allowed in OSPF and RIP protocols. BGP has also
This option is allowed in BFD, OSPF and RIP protocols. BGP has also
<cf/password/ option, but it is slightly different and described
separately.
Default: none.
@ -1637,6 +1637,19 @@ protocol bfd [&lt;name&gt;] {
idle tx interval &lt;time&gt;;
multiplier &lt;num&gt;;
passive &lt;switch&gt;;
authentication none;
authentication simple;
authentication [meticulous] keyed md5|sha1;
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;";
};
};
multihop {
interval &lt;time&gt;;
@ -1720,6 +1733,32 @@ protocol bfd [&lt;name&gt;] {
sending control packets to the other side. This option allows to enable
passive mode, which means that the router does not send BFD packets
until it has received one from the other side. Default: disabled.
<tag>authentication none</tag>
No passwords are sent in BFD packets. This is the default value.
<tag>authentication simple</tag>
Every packet carries 16 bytes of password. Received packets lacking this
password are ignored. This authentication mechanism is very weak.
<tag>authentication [meticulous] keyed md5|sha1</tag>
An authentication code is appended to each packet. The cryptographic
algorithm is keyed MD5 or keyed SHA-1. Note that the algorithm is common
for all keys (on one interface), in contrast to OSPF or RIP, where it
is a per-key option. Passwords (keys) are not sent open via network.
The <cf/meticulous/ variant means that cryptographic sequence numbers
are increased for each sent packet, while in the basic variant they are
increased about once per second. Generally, the <cf/meticulous/ variant
offers better resistance to replay attacks but may require more
computation.
<tag>password "<M>text</M>"</tag>
Specifies a password used for authentication. See <ref id="dsc-pass"
name="password"> common option for detailed description. Note that
password option <cf/algorithm/ is not available in BFD protocol. The
algorithm is selected by <cf/authentication/ option for all passwords.
</descrip>
<sect1>Example

View file

@ -316,6 +316,7 @@ bfd_session_timeout(struct bfd_session *s)
s->rem_min_rx_int = 1;
s->rem_demand_mode = 0;
s->rem_detect_mult = 0;
s->rx_csn_known = 0;
s->poll_active = 0;
s->poll_scheduled = 0;
@ -429,6 +430,7 @@ bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *
s->rem_min_rx_int = 1;
s->detect_mult = ifa->cf->multiplier;
s->passive = ifa->cf->passive;
s->tx_csn = random_u32();
s->tx_timer = tm2_new_init(p->tpool, bfd_tx_timer_hook, s, 0, 0);
s->hold_timer = tm2_new_init(p->tpool, bfd_hold_timer_hook, s, 0, 0);

View file

@ -14,6 +14,7 @@
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/route.h"
#include "nest/password.h"
#include "conf/conf.h"
#include "lib/hash.h"
#include "lib/resource.h"
@ -52,6 +53,8 @@ struct bfd_iface_config
u32 idle_tx_int;
u8 multiplier;
u8 passive;
u8 auth_type; /* Authentication type (BFD_AUTH_*) */
list *passwords; /* Passwords for authentication */
};
struct bfd_neighbor
@ -141,6 +144,11 @@ struct bfd_session
list request_list; /* List of client requests (struct bfd_request) */
bird_clock_t last_state_change; /* Time of last state change */
u8 notify_running; /* 1 if notify hooks are running */
u8 rx_csn_known; /* Received crypto sequence number is known */
u32 rx_csn; /* Last received crypto sequence number */
u32 tx_csn; /* Last transmitted crypto sequence number */
u32 tx_csn_time; /* Timestamp of last tx_csn change */
};
@ -172,6 +180,15 @@ extern const char *bfd_state_names[];
#define BFD_FLAG_DEMAND (1 << 1)
#define BFD_FLAG_MULTIPOINT (1 << 0)
#define BFD_AUTH_NONE 0
#define BFD_AUTH_SIMPLE 1
#define BFD_AUTH_KEYED_MD5 2
#define BFD_AUTH_METICULOUS_KEYED_MD5 3
#define BFD_AUTH_KEYED_SHA1 4
#define BFD_AUTH_METICULOUS_KEYED_SHA1 5
extern const u8 bfd_auth_type_to_hash_alg[];
static inline void bfd_lock_sessions(struct bfd_proto *p) { pthread_spin_lock(&p->lock); }
static inline void bfd_unlock_sessions(struct bfd_proto *p) { pthread_spin_unlock(&p->lock); }

View file

@ -22,11 +22,12 @@ extern struct bfd_config *bfd_cf;
CF_DECLS
CF_KEYWORDS(BFD, MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE,
INTERFACE, MULTIHOP, NEIGHBOR, DEV, LOCAL)
INTERFACE, MULTIHOP, NEIGHBOR, DEV, LOCAL, AUTHENTICATION,
NONE, SIMPLE, METICULOUS, KEYED, MD5, SHA1)
%type <iface> bfd_neigh_iface
%type <a> bfd_neigh_local
%type <i> bfd_neigh_multihop
%type <i> bfd_neigh_multihop bfd_auth_type
CF_GRAMMAR
@ -62,12 +63,35 @@ bfd_proto:
bfd_iface_start:
{
this_ipatt = cfg_allocz(sizeof(struct bfd_iface_config));
add_tail(&BFD_CFG->patt_list, NODE this_ipatt);
init_list(&this_ipatt->ipn_list);
BFD_IFACE->min_rx_int = BFD_DEFAULT_MIN_RX_INT;
BFD_IFACE->min_tx_int = BFD_DEFAULT_MIN_TX_INT;
BFD_IFACE->idle_tx_int = BFD_DEFAULT_IDLE_TX_INT;
BFD_IFACE->multiplier = BFD_DEFAULT_MULTIPLIER;
reset_passwords();
};
bfd_iface_finish:
{
BFD_IFACE->passwords = get_passwords();
if (!BFD_IFACE->auth_type != !BFD_IFACE->passwords)
log(L_WARN "Authentication and password options should be used together");
if (BFD_IFACE->passwords)
{
struct password_item *pass;
WALK_LIST(pass, *BFD_IFACE->passwords)
{
if (pass->alg)
cf_error("Password algorithm option not available in BFD protocol");
pass->alg = bfd_auth_type_to_hash_alg[BFD_IFACE->auth_type];
}
}
};
bfd_iface_item:
@ -77,6 +101,17 @@ bfd_iface_item:
| IDLE TX INTERVAL expr_us { BFD_IFACE->idle_tx_int = $4; }
| MULTIPLIER expr { BFD_IFACE->multiplier = $2; }
| PASSIVE bool { BFD_IFACE->passive = $2; }
| AUTHENTICATION bfd_auth_type { BFD_IFACE->auth_type = $2; }
| password_list {}
;
bfd_auth_type:
NONE { $$ = BFD_AUTH_NONE; }
| SIMPLE { $$ = BFD_AUTH_SIMPLE; }
| KEYED MD5 { $$ = BFD_AUTH_KEYED_MD5; }
| KEYED SHA1 { $$ = BFD_AUTH_KEYED_SHA1; }
| METICULOUS KEYED MD5 { $$ = BFD_AUTH_METICULOUS_KEYED_MD5; }
| METICULOUS KEYED SHA1 { $$ = BFD_AUTH_METICULOUS_KEYED_SHA1; }
;
bfd_iface_opts:
@ -89,10 +124,11 @@ bfd_iface_opt_list:
| '{' bfd_iface_opts '}'
;
bfd_iface: bfd_iface_start iface_patt_list_nopx bfd_iface_opt_list
{ add_tail(&BFD_CFG->patt_list, NODE this_ipatt); };
bfd_iface:
bfd_iface_start iface_patt_list_nopx bfd_iface_opt_list bfd_iface_finish;
bfd_multihop: bfd_iface_start bfd_iface_opt_list
bfd_multihop:
bfd_iface_start bfd_iface_opt_list bfd_iface_finish
{ BFD_CFG->multihop = BFD_IFACE; };

View file

@ -5,24 +5,60 @@
*/
#include "bfd.h"
#include "lib/mac.h"
struct bfd_ctl_packet
{
u8 vdiag; /* version and diagnostic */
u8 flags; /* state and flags */
u8 vdiag; /* Version and diagnostic */
u8 flags; /* State and flags */
u8 detect_mult;
u8 length;
u32 snd_id; /* sender ID, aka 'my discriminator' */
u32 rcv_id; /* receiver ID, aka 'your discriminator' */
u8 length; /* Whole packet length */
u32 snd_id; /* Sender ID, aka 'my discriminator' */
u32 rcv_id; /* Receiver ID, aka 'your discriminator' */
u32 des_min_tx_int;
u32 req_min_rx_int;
u32 req_min_echo_rx_int;
};
struct bfd_auth
{
u8 type; /* Authentication type (BFD_AUTH_*) */
u8 length; /* Authentication section length */
};
struct bfd_simple_auth
{
u8 type; /* BFD_AUTH_SIMPLE */
u8 length; /* Length of bfd_simple_auth + pasword length */
u8 key_id; /* Key ID */
byte password[0]; /* Password itself, variable length */
};
#define BFD_MAX_PASSWORD_LENGTH 16
struct bfd_crypto_auth
{
u8 type; /* BFD_AUTH_*_MD5 or BFD_AUTH_*_SHA1 */
u8 length; /* Length of bfd_crypto_auth + hash length */
u8 key_id; /* Key ID */
u8 zero; /* Reserved, zero on transmit */
u32 csn; /* Cryptographic sequence number */
byte data[0]; /* Authentication key/hash, length 16 or 20 */
};
#define BFD_BASE_LEN sizeof(struct bfd_ctl_packet)
#define BFD_MAX_LEN 64
#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
#define LOG_PKT(msg, args...) \
log(L_REMOTE "%s: " msg, p->p.name, args)
#define LOG_PKT_AUTH(msg, args...) \
log(L_AUTH "%s: " msg, p->p.name, args)
static inline u8 bfd_pack_vdiag(u8 version, u8 diag)
{ return (version << 5) | diag; }
@ -59,6 +95,189 @@ bfd_format_flags(u8 flags, char *buf)
return buf;
}
const u8 bfd_auth_type_to_hash_alg[] = {
[BFD_AUTH_NONE] = ALG_UNDEFINED,
[BFD_AUTH_SIMPLE] = ALG_UNDEFINED,
[BFD_AUTH_KEYED_MD5] = ALG_MD5,
[BFD_AUTH_METICULOUS_KEYED_MD5] = ALG_MD5,
[BFD_AUTH_KEYED_SHA1] = ALG_SHA1,
[BFD_AUTH_METICULOUS_KEYED_SHA1] = ALG_SHA1,
};
/* Fill authentication section and modifies final length in control section packet */
static void
bfd_fill_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt)
{
struct bfd_iface_config *cf = s->ifa->cf;
struct password_item *pass = password_find(cf->passwords, 0);
uint meticulous = 0;
if (!pass)
{
/* FIXME: This should not happen */
log(L_ERR "%s: No suitable password found for authentication", p->p.name);
return;
}
switch (cf->auth_type)
{
case BFD_AUTH_SIMPLE:
{
struct bfd_simple_auth *auth = (void *) (pkt + 1);
uint pass_len = MIN(pass->length, BFD_MAX_PASSWORD_LENGTH);
auth->type = BFD_AUTH_SIMPLE;
auth->length = sizeof(struct bfd_simple_auth) + pass_len;
auth->key_id = pass->id;
pkt->flags |= BFD_FLAG_AP;
pkt->length += auth->length;
memcpy(auth->password, pass->password, pass_len);
return;
}
case BFD_AUTH_METICULOUS_KEYED_MD5:
case BFD_AUTH_METICULOUS_KEYED_SHA1:
meticulous = 1;
case BFD_AUTH_KEYED_MD5:
case BFD_AUTH_KEYED_SHA1:
{
struct bfd_crypto_auth *auth = (void *) (pkt + 1);
uint hash_alg = bfd_auth_type_to_hash_alg[cf->auth_type];
uint hash_len = mac_type_length(pass->alg);
/* Increase CSN about one time per second */
u32 new_time = (u64) current_time() >> 20;
if ((new_time != s->tx_csn_time) || meticulous)
{
s->tx_csn++;
s->tx_csn_time = new_time;
}
DBG("[%I] CSN: %u\n", s->addr, s->last_tx_csn);
auth->type = cf->auth_type;
auth->length = sizeof(struct bfd_crypto_auth) + hash_len;
auth->key_id = pass->id;
auth->zero = 0;
auth->csn = htonl(s->tx_csn);
pkt->flags |= BFD_FLAG_AP;
pkt->length += auth->length;
strncpy(auth->data, pass->password, hash_len);
mac_fill(hash_alg, NULL, 0, (byte *) pkt, pkt->length, auth->data);
return;
}
}
}
static int
bfd_check_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt)
{
struct bfd_iface_config *cf = s->ifa->cf;
const char *err_dsc = NULL;
uint err_val = 0;
uint auth_type = 0;
uint meticulous = 0;
if (pkt->flags & BFD_FLAG_AP)
{
struct bfd_auth *auth = (void *) (pkt + 1);
if ((pkt->length < (BFD_BASE_LEN + sizeof(struct bfd_auth))) ||
(pkt->length < (BFD_BASE_LEN + auth->length)))
DROP("packet length mismatch", pkt->length);
/* Zero is reserved, we use it as BFD_AUTH_NONE internally */
if (auth->type == 0)
DROP("reserved authentication type", 0);
auth_type = auth->type;
}
if (auth_type != cf->auth_type)
DROP("authentication method mismatch", auth_type);
switch (auth_type)
{
case BFD_AUTH_NONE:
return 1;
case BFD_AUTH_SIMPLE:
{
struct bfd_simple_auth *auth = (void *) (pkt + 1);
if (auth->length < sizeof(struct bfd_simple_auth))
DROP("wrong authentication length", auth->length);
struct password_item *pass = password_find_by_id(cf->passwords, auth->key_id);
if (!pass)
DROP("no suitable password found", auth->key_id);
uint pass_len = MIN(pass->length, BFD_MAX_PASSWORD_LENGTH);
uint auth_len = sizeof(struct bfd_simple_auth) + pass_len;
if ((auth->length != auth_len) || memcmp(auth->password, pass->password, pass_len))
DROP("wrong password", pass->id);
return 1;
}
case BFD_AUTH_METICULOUS_KEYED_MD5:
case BFD_AUTH_METICULOUS_KEYED_SHA1:
meticulous = 1;
case BFD_AUTH_KEYED_MD5:
case BFD_AUTH_KEYED_SHA1:
{
struct bfd_crypto_auth *auth = (void *) (pkt + 1);
uint hash_alg = bfd_auth_type_to_hash_alg[cf->auth_type];
uint hash_len = mac_type_length(hash_alg);
if (auth->length != (sizeof(struct bfd_crypto_auth) + hash_len))
DROP("wrong authentication length", auth->length);
struct password_item *pass = password_find_by_id(cf->passwords, auth->key_id);
if (!pass)
DROP("no suitable password found", auth->key_id);
/* BFD CSNs are in 32-bit circular number space */
u32 csn = ntohl(auth->csn);
if (s->rx_csn_known &&
(((csn - s->rx_csn) > (3 * s->detect_mult)) ||
(meticulous && (csn == s->rx_csn))))
{
/* We want to report both new and old CSN */
LOG_PKT_AUTH("Authentication failed for %I - "
"wrong sequence number (rcv %u, old %u)",
s->addr, csn, s->rx_csn);
return 0;
}
byte *auth_data = alloca(hash_len);
memcpy(auth_data, auth->data, hash_len);
strncpy(auth->data, pass->password, hash_len);
if (!mac_verify(hash_alg, NULL, 0, (byte *) pkt, pkt->length, auth_data))
DROP("wrong authentication code", pass->id);
s->rx_csn = csn;
s->rx_csn_known = 1;
return 1;
}
}
drop:
LOG_PKT_AUTH("Authentication failed for %I - %s (%u)",
s->addr, err_dsc, err_val);
return 0;
}
void
bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
{
@ -85,6 +304,9 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
else if (s->poll_active)
pkt->flags |= BFD_FLAG_POLL;
if (s->ifa->cf->auth_type)
bfd_fill_authentication(p, s, pkt);
if (sk->tbuf != sk->tpos)
log(L_WARN "%s: Old packet overwritten in TX buffer", p->p.name);
@ -94,8 +316,6 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
sk_send_to(sk, pkt->length, s->addr, sk->dport);
}
#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
static int
bfd_rx_hook(sock *sk, uint len)
{
@ -151,10 +371,9 @@ bfd_rx_hook(sock *sk, uint len)
return 1;
}
/* FIXME: better authentication handling and message */
if (pkt->flags & BFD_FLAG_AP)
DROP("authentication not supported", 0);
/* bfd_check_authentication() has its own error logging */
if (!bfd_check_authentication(p, s, pkt))
return 1;
u32 old_tx_int = s->des_min_tx_int;
u32 old_rx_int = s->rem_min_rx_int;
@ -174,7 +393,7 @@ bfd_rx_hook(sock *sk, uint len)
return 1;
drop:
log(L_REMOTE "%s: Bad packet from %I - %s (%u)", p->p.name, sk->faddr, err_dsc, err_val);
LOG_PKT("Bad packet from %I - %s (%u)", sk->faddr, err_dsc, err_val);
return 1;
}