diff --git a/lib/mac.h b/lib/mac.h index 9dba8f89..5fc216fd 100644 --- a/lib/mac.h +++ b/lib/mac.h @@ -21,6 +21,7 @@ #define ALG_SHA256 0x04 #define ALG_SHA384 0x05 #define ALG_SHA512 0x06 +#define ALG_HMAC 0x10 #define ALG_HMAC_MD5 0x11 #define ALG_HMAC_SHA1 0x12 #define ALG_HMAC_SHA224 0x13 @@ -34,6 +35,9 @@ #define HASH_STORAGE sizeof(struct sha512_context) #define MAC_STORAGE sizeof(struct hmac_context) +/* This value is used by several IETF protocols for padding */ +#define HMAC_MAGIC htonl(0x878FE1F3) + /* Generic context used by hash functions */ struct hash_context { diff --git a/lib/string.h b/lib/string.h index bf0b7cb0..75cb88dd 100644 --- a/lib/string.h +++ b/lib/string.h @@ -39,6 +39,16 @@ xstrdup(const char *c) return z; } +static inline void +memset32(void *D, u32 val, uint n) +{ + u32 *dst = D; + uint i; + + for (i = 0; i < n; i++) + dst[i] = val; +} + #define ROUTER_ID_64_LENGTH 23 #endif diff --git a/nest/password.c b/nest/password.c index d6e2087f..e4813741 100644 --- a/nest/password.c +++ b/nest/password.c @@ -10,6 +10,7 @@ #include "nest/bird.h" #include "nest/password.h" #include "lib/string.h" +#include "lib/mac.h" struct password_item *last_password_item = NULL; @@ -66,3 +67,17 @@ password_find_by_value(list *l, char *pass, uint size) return NULL; } +uint +max_mac_length(list *l) +{ + struct password_item *pi; + uint val = 0; + + if (!l) + return 0; + + WALK_LIST(pi, *l) + val = MAX(val, mac_type_length(pi->alg)); + + return val; +} diff --git a/nest/password.h b/nest/password.h index 7392389b..f21483c4 100644 --- a/nest/password.h +++ b/nest/password.h @@ -34,4 +34,6 @@ static inline int password_verify(struct password_item *p1, char *p2, uint size) return !memcmp(buf, p2, size); } +uint max_mac_length(list *l); + #endif diff --git a/proto/rip/config.Y b/proto/rip/config.Y index e15599e0..4ec45c7a 100644 --- a/proto/rip/config.Y +++ b/proto/rip/config.Y @@ -98,15 +98,29 @@ rip_iface_start: rip_iface_finish: { + /* Default mode is broadcast for RIPv1, multicast for RIPv2 and RIPng */ + if (!RIP_IFACE->mode) + RIP_IFACE->mode = (rip_cfg_is_v2() && (RIP_IFACE->version == RIP_V1)) ? + RIP_IM_BROADCAST : RIP_IM_MULTICAST; + RIP_IFACE->passwords = get_passwords(); if (!RIP_IFACE->auth_type != !RIP_IFACE->passwords) log(L_WARN "Authentication and password options should be used together"); - /* Default mode is broadcast for RIPv1, multicast for RIPv2 and RIPng */ - if (!RIP_IFACE->mode) - RIP_IFACE->mode = (rip_cfg_is_v2() && (RIP_IFACE->version == RIP_V1)) ? - RIP_IM_BROADCAST : RIP_IM_MULTICAST; + if (RIP_IFACE->passwords) + { + struct password_item *pass; + WALK_LIST(pass, *RIP_IFACE->passwords) + { + if (pass->alg && (RIP_IFACE->auth_type != RIP_AUTH_CRYPTO)) + cf_error("Password algorithm option requires cryptographic authentication"); + + /* Set default crypto algorithm (MD5) */ + if (!pass->alg && (RIP_IFACE->auth_type == RIP_AUTH_CRYPTO)) + pass->alg = ALG_MD5; + } + } RIP_CFG->min_timeout_time = MIN_(RIP_CFG->min_timeout_time, RIP_IFACE->timeout_time); RIP_CFG->max_garbage_time = MAX_(RIP_CFG->max_garbage_time, RIP_IFACE->garbage_time); diff --git a/proto/rip/packets.c b/proto/rip/packets.c index 381b4771..468927e6 100644 --- a/proto/rip/packets.c +++ b/proto/rip/packets.c @@ -10,7 +10,6 @@ */ #include "rip.h" -#include "lib/md5.h" #include "lib/mac.h" @@ -18,9 +17,7 @@ #define RIP_CMD_RESPONSE 2 /* responding to request */ #define RIP_BLOCK_LENGTH 20 - #define RIP_PASSWD_LENGTH 16 -#define RIP_MD5_LENGTH 16 #define RIP_AF_IPV4 2 #define RIP_AF_AUTH 0xffff @@ -73,7 +70,7 @@ struct rip_auth_tail { u16 must_be_ffff; u16 must_be_0001; - byte auth_data[]; + byte auth_data[0]; }; /* Internal representation of RTE block data */ @@ -221,16 +218,24 @@ rip_fill_authentication(struct rip_proto *p, struct rip_iface *ifa, struct rip_p auth->auth_type = htons(RIP_AUTH_CRYPTO); auth->packet_len = htons(*plen); auth->key_id = pass->id; - auth->auth_len = sizeof(struct rip_auth_tail) + RIP_MD5_LENGTH; + auth->auth_len = mac_type_length(pass->alg); auth->seq_num = ifa->csn_ready ? htonl(ifa->csn) : 0; auth->unused1 = 0; auth->unused2 = 0; ifa->csn_ready = 1; + if (pass->alg < ALG_HMAC) + auth->auth_len += sizeof(struct rip_auth_tail); + /* * Note that RFC 4822 is unclear whether auth_len should cover whole * authentication trailer or just auth_data length. * + * FIXME: We should use just auth_data length by default. Currently we put + * the whole auth trailer length in keyed hash case to keep old behavior, + * but we put just auth_data length in the new HMAC case. Note that Quagga + * has config option for this. + * * Crypto sequence numbers are increased by sender in rip_update_csn(). * First CSN should be zero, this is handled by csn_ready. */ @@ -238,14 +243,18 @@ rip_fill_authentication(struct rip_proto *p, struct rip_iface *ifa, struct rip_p struct rip_auth_tail *tail = (void *) ((byte *) pkt + *plen); tail->must_be_ffff = htons(0xffff); tail->must_be_0001 = htons(0x0001); - strncpy(tail->auth_data, pass->password, RIP_MD5_LENGTH); - *plen += sizeof(struct rip_auth_tail) + RIP_MD5_LENGTH; + uint auth_len = mac_type_length(pass->alg); + *plen += sizeof(struct rip_auth_tail) + auth_len; - struct hash_context ctx; - md5_init(&ctx); - md5_update(&ctx, (byte *) pkt, *plen); - memcpy(tail->auth_data, md5_final(&ctx), RIP_MD5_LENGTH); + /* Append key for keyed hash, append padding for HMAC (RFC 4822 2.5) */ + if (pass->alg < ALG_HMAC) + strncpy(tail->auth_data, pass->password, auth_len); + else + memset32(tail->auth_data, HMAC_MAGIC, auth_len / 4); + + mac_fill(pass->alg, pass->password, pass->length, + (byte *) pkt, *plen, tail->auth_data); return; default: @@ -288,13 +297,25 @@ rip_check_authentication(struct rip_proto *p, struct rip_iface *ifa, struct rip_ DROP("no suitable password found", auth->key_id); uint data_len = ntohs(auth->packet_len); - uint auth_len = sizeof(struct rip_auth_tail) + RIP_MD5_LENGTH; + uint auth_len = mac_type_length(pass->alg); + uint auth_len2 = sizeof(struct rip_auth_tail) + auth_len; - if (data_len + auth_len != *plen) - DROP("packet length mismatch", data_len); + /* + * Ideally, first check should be check for internal consistency: + * (data_len + sizeof(struct rip_auth_tail) + auth->auth_len) != *plen + * + * Second one should check expected code length: + * auth->auth_len != auth_len + * + * But as auth->auth_len has two interpretations, we simplify this + */ - if ((auth->auth_len != RIP_MD5_LENGTH) && (auth->auth_len != auth_len)) - DROP("authentication data length mismatch", auth->auth_len); + if (data_len + auth_len2 != *plen) + DROP("packet length mismatch", *plen); + + /* Warning: two interpretations of auth_len field */ + if ((auth->auth_len != auth_len) && (auth->auth_len != auth_len2)) + DROP("wrong authentication length", auth->auth_len); struct rip_auth_tail *tail = (void *) ((byte *) pkt + data_len); if ((tail->must_be_ffff != htons(0xffff)) || (tail->must_be_0001 != htons(0x0001))) @@ -312,17 +333,18 @@ rip_check_authentication(struct rip_proto *p, struct rip_iface *ifa, struct rip_ return 0; } - char received[RIP_MD5_LENGTH]; - memcpy(received, tail->auth_data, RIP_MD5_LENGTH); - strncpy(tail->auth_data, pass->password, RIP_MD5_LENGTH); + byte *auth_data = alloca(auth_len); + memcpy(auth_data, tail->auth_data, auth_len); - struct hash_context ctx; - md5_init(&ctx); - md5_update(&ctx, (byte *) pkt, *plen); - char *computed = md5_final(&ctx); + /* Append key for keyed hash, append padding for HMAC (RFC 4822 2.5) */ + if (pass->alg < ALG_HMAC) + strncpy(tail->auth_data, pass->password, auth_len); + else + memset32(tail->auth_data, HMAC_MAGIC, auth_len / 4); - if (memcmp(received, computed, RIP_MD5_LENGTH)) - DROP("wrong MD5 digest", pass->id); + if (!mac_verify(pass->alg, pass->password, pass->length, + (byte *) pkt, *plen, auth_data)) + DROP("wrong authentication code", pass->id); *plen = data_len; n->csn = rcv_csn; diff --git a/proto/rip/rip.c b/proto/rip/rip.c index 37cfa9ac..7b380097 100644 --- a/proto/rip/rip.c +++ b/proto/rip/rip.c @@ -590,7 +590,7 @@ rip_iface_update_buffers(struct rip_iface *ifa) ifa->tx_plen = tbsize - headers; if (ifa->cf->auth_type == RIP_AUTH_CRYPTO) - ifa->tx_plen -= RIP_AUTH_TAIL_LENGTH; + ifa->tx_plen -= RIP_AUTH_TAIL_LENGTH + max_mac_length(ifa->cf->passwords); } static inline void @@ -702,12 +702,11 @@ rip_reconfigure_iface(struct rip_proto *p, struct rip_iface *ifa, struct rip_ifa ifa->cf = new; + rip_iface_update_buffers(ifa); + if (ifa->next_regular > (now + new->update_time)) ifa->next_regular = now + (random() % new->update_time) + 1; - if ((new->tx_length != old->tx_length) || (new->rx_buffer != old->rx_buffer)) - rip_iface_update_buffers(ifa); - if (new->check_link != old->check_link) rip_iface_update_state(ifa); diff --git a/proto/rip/rip.h b/proto/rip/rip.h index f245e612..b24d9536 100644 --- a/proto/rip/rip.h +++ b/proto/rip/rip.h @@ -40,7 +40,7 @@ #define RIP_NG_PORT 521 /* RIPng */ #define RIP_MAX_PKT_LENGTH 532 /* 512 + IP4_HEADER_LENGTH */ -#define RIP_AUTH_TAIL_LENGTH 20 /* 4 + MD5 length */ +#define RIP_AUTH_TAIL_LENGTH 4 /* Without auth_data */ #define RIP_DEFAULT_ECMP_LIMIT 16 #define RIP_DEFAULT_INFINITY 16