Commit e72f49b1 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Implement HMAC authentication.

Known issues:

  - we create a neighbour entry before the first successful challenge;
  - we compute HMAC for each HMAC TLV rather than just once;
  - we only support sending one HMAC TLV;
  - we don't support key rotation.
Co-authored-by: 's avatarClara Do <clarado_perso@yahoo.fr>
Co-authored-by: 's avatarWeronika Kolodziejak <weronika.kolodziejak@gmail.com>
parent 3bc154c8
......@@ -11,11 +11,13 @@ LDLIBS = -lrt
SRCS = babeld.c net.c kernel.c util.c interface.c source.c neighbour.c \
route.c xroute.c message.c resend.c configuration.c local.c \
disambiguation.c rule.c rfc6234/sha224-256.c BLAKE2/ref/blake2s-ref.c
disambiguation.c rule.c hmac.c \
rfc6234/sha224-256.c BLAKE2/ref/blake2s-ref.c
OBJS = babeld.o net.o kernel.o util.o interface.o source.o neighbour.o \
route.o xroute.o message.o resend.o configuration.o local.o \
disambiguation.o rule.o rfc6234/sha224-256.o BLAKE2/ref/blake2s-ref.o
disambiguation.o rule.o hmac.o \
rfc6234/sha224-256.o BLAKE2/ref/blake2s-ref.o
babeld: $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o babeld $(OBJS) $(LDLIBS)
......
......@@ -661,9 +661,10 @@ main(int argc, char **argv)
}
if(FD_ISSET(protocol_socket, &readfds)) {
unsigned char to[16];
rc = babel_recv(protocol_socket,
receive_buffer, receive_buffer_size,
(struct sockaddr*)&sin6, sizeof(sin6));
(struct sockaddr*)&sin6, sizeof(sin6), to);
if(rc < 0) {
if(errno != EAGAIN && errno != EINTR) {
perror("recv");
......@@ -675,7 +676,7 @@ main(int argc, char **argv)
continue;
if(ifp->ifindex == sin6.sin6_scope_id) {
parse_packet((unsigned char*)&sin6.sin6_addr, ifp,
receive_buffer, rc);
receive_buffer, rc, to);
VALGRIND_MAKE_MEM_UNDEFINED(receive_buffer,
receive_buffer_size);
break;
......
......@@ -39,6 +39,7 @@ THE SOFTWARE.
#include "interface.h"
#include "route.h"
#include "kernel.h"
#include "hmac.h"
#include "configuration.h"
#include "rule.h"
......@@ -322,6 +323,35 @@ get_interface_type(int c, int *type_r, gnc_t gnc, void *closure)
return c;
}
static int
gethex(int c, unsigned char **value_r, int *len_r, gnc_t gnc, void *closure)
{
char *t;
unsigned char *value;
int len, rc;
c = getword(c, &t, gnc, closure);
if(c < -1)
return c;
len = strlen(t);
if(len % 2 != 0) {
free(t);
return -2;
}
value = malloc(len / 2);
if(value == NULL)
return -2;
rc = fromhex(value, t, len);
free(t);
if(rc < 0) {
free(value);
return -2;
}
*value_r = value;
*len_r = len / 2;
return c;
}
static void
free_filter(struct filter *f)
{
......@@ -647,7 +677,18 @@ parse_anonymous_ifconf(int c, gnc_t gnc, void *closure,
if(c < -1 || penalty <= 0 || penalty > 0xFFFF)
goto error;
if_conf->max_rtt_penalty = penalty;
} else {
} else if(strcmp(token, "hmac") == 0) {
char *key_id;
struct key *key;
c = getword(c, &key_id, gnc, closure);
if(c < -1) {
free(key_id);
goto error;
}
key = find_key(key_id);
if_conf->key = key;
free(key_id);
} else {
goto error;
}
free(token);
......@@ -691,6 +732,65 @@ parse_ifconf(int c, gnc_t gnc, void *closure,
return -2;
}
static int
parse_key(int c, gnc_t gnc, void *closure, struct key **key_return)
{
char *token = NULL;
struct key *key;
key = calloc(1, sizeof(struct key));
if(key == NULL)
goto error;
while(1) {
c = skip_whitespace(c, gnc, closure);
if(c < 0 || c == '\n' || c == '#') {
c = skip_to_eol(c, gnc, closure);
break;
}
c = getword(c, &token, gnc, closure);
if(c < -1) {
goto error;
}
if(strcmp(token, "id") == 0) {
c = getword(c, &key->id, gnc, closure);
if(c < -1 || key->id == NULL) {
goto error;
}
} else if(strcmp(token, "type") == 0) {
char *auth_type;
c = getword(c, &auth_type, gnc, closure);
if(c < -1 || auth_type == NULL)
goto error;
if(strcmp(auth_type, "none") == 0) {
key->type = AUTH_TYPE_NONE;
} else if(strcmp(auth_type, "sha256") == 0) {
key->type = AUTH_TYPE_SHA256;
} else if(strcmp(auth_type, "blake2s") == 0) {
key->type = AUTH_TYPE_BLAKE2S;
} else {
key->type = 0;
free(auth_type);
goto error;
}
free(auth_type);
} else if(strcmp(token, "value") == 0) {
c = gethex(c, &key->value, &key->len, gnc, closure);
if(c < -1 || key->value == NULL)
goto error;
} else {
goto error;
}
free(token);
}
*key_return = key;
return c;
error:
free(token);
free(key);
return -2;
}
static void
add_filter(struct filter *filter, struct filter **filters)
{
......@@ -736,6 +836,7 @@ merge_ifconf(struct interface_conf *dest,
MERGE(rtt_min);
MERGE(rtt_max);
MERGE(max_rtt_penalty);
MERGE(key);
#undef MERGE
}
......@@ -1100,6 +1201,32 @@ parse_config_line(int c, gnc_t gnc, void *closure,
if(c < -1 || !action_return)
goto fail;
reopen_logfile();
} else if(strcmp(token, "key") == 0) {
struct key *key = NULL;
c = parse_key(c, gnc, closure, &key);
if(c < -1)
goto fail;
if(key->id == NULL)
goto fail;
switch(key->type) {
case AUTH_TYPE_SHA256:
if(key->len != 32) {
free(key);
goto fail;
}
break;
case AUTH_TYPE_BLAKE2S:
if(key->len != 16) {
free(key);
goto fail;
}
break;
default:
free(key);
goto fail;
}
add_key(key->id, key->type, key->len, key->value);
free(key);
} else {
c = parse_option(c, gnc, closure, token);
if(c < -1)
......
......@@ -29,6 +29,10 @@ THE SOFTWARE.
#define CONFIG_ACTION_UNMONITOR 4
#define CONFIG_ACTION_NO 5
#define AUTH_TYPE_NONE 0
#define AUTH_TYPE_SHA256 1
#define AUTH_TYPE_BLAKE2S 2
struct filter_result {
unsigned int add_metric; /* allow = 0, deny = INF, metric = <0..INF> */
unsigned char *src_prefix;
......
/*
Copyright (c) 2018 by Clara Dô and Weronika Kolodziejak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <netinet/in.h>
#include "rfc6234/sha.h"
#include "BLAKE2/ref/blake2.h"
#include "babeld.h"
#include "interface.h"
#include "neighbour.h"
#include "util.h"
#include "hmac.h"
#include "configuration.h"
#include "message.h"
struct key **keys = NULL;
int numkeys = 0, maxkeys = 0;
struct key *
find_key(const char *id)
{
int i;
for(i = 0; i < numkeys; i++) {
if(strcmp(keys[i]->id, id) == 0)
return retain_key(keys[i]);
}
return NULL;
}
struct key *
retain_key(struct key *key)
{
assert(key->ref_count < 0xffff);
key->ref_count++;
return key;
}
void
release_key(struct key *key)
{
assert(key->ref_count > 0);
key->ref_count--;
}
struct key *
add_key(char *id, int type, int len, unsigned char *value)
{
struct key *key;
assert(value != NULL && type != 0);
key = find_key(id);
if(key) {
key->type = type;
key->len = len;
key->value = value;
return key;
}
if(type == AUTH_TYPE_NONE)
return NULL;
if(numkeys >= maxkeys) {
struct key **new_keys;
int n = maxkeys < 1 ? 8 : 2 * maxkeys;
new_keys = realloc(keys, n * sizeof(struct key*));
if(new_keys == NULL)
return NULL;
maxkeys = n;
keys = new_keys;
}
key = calloc(1, sizeof(struct key));
if(key == NULL)
return NULL;
key->id = id;
key->type = type;
key->len = len;
key->value = value;
keys[numkeys++] = key;
return key;
}
static int
compute_hmac(const unsigned char *src, const unsigned char *dst,
const unsigned char *packet_header,
const unsigned char *body, int bodylen, struct key *key,
unsigned char *hmac_return)
{
unsigned char port[2];
int rc;
DO_HTONS(port, (unsigned short)protocol_port);
switch(key->type) {
case 1: {
SHA256Context inner, outer;
unsigned char ipad[64], ihash[32], opad[64];
if(key->len != 32)
return -1;
for(int i = 0; i < 32; i++)
ipad[i] = key->value[i] ^ 0x36;
for(int i = 32; i < 64; i++)
ipad[i] = 0x36;
rc = SHA256Reset(&inner);
if(rc < 0)
return -1;
rc = SHA256Input(&inner, ipad, 64);
if(rc < 0)
return -1;
rc = SHA256Input(&inner, dst, 16);
if(rc != 0)
return -1;
rc = SHA256Input(&inner, port, 2);
if(rc != 0)
return -1;
rc = SHA256Input(&inner, src, 16);
if(rc != 0)
return -1;
rc = SHA256Input(&inner, port, 2);
if(rc != 0)
return -1;
rc = SHA256Input(&inner, packet_header, 4);
if(rc != 0)
return -1;
rc = SHA256Input(&inner, body, bodylen);
if(rc != 0)
return -1;
rc = SHA256Result(&inner, ihash);
if(rc != 0)
return -1;
for(int i = 0; i < 32; i++)
opad[i] = ihash[i] ^ 0x5c;
for(int i = 32; i < 64; i++)
opad[i] = 0x5c;
rc = SHA256Reset(&outer);
if(rc != 0)
return -1;
rc = SHA256Input(&outer, opad, 64);
if(rc != 0)
return -1;
rc = SHA256Input(&outer, ihash, 32);
if(rc != 0)
return -1;
rc = SHA256Result(&outer, hmac_return);
if(rc < 0)
return -1;
return 32;
}
case 2: {
blake2s_state s;
if(key->len != 16)
return -1;
rc = blake2s_init_key(&s, 16, key->value, key->len);
if(rc < 0)
return -1;
rc = blake2s_update(&s, dst, 16);
if(rc < 0)
return -1;
rc = blake2s_update(&s, port, 2);
if(rc < 0)
return -1;
rc = blake2s_update(&s, src, 16);
if(rc < 0)
return -1;
rc = blake2s_update(&s, port, 2);
if(rc < 0)
return -1;
rc = blake2s_update(&s, packet_header, 4);
if(rc < 0)
return -1;
rc = blake2s_update(&s, body, bodylen);
if(rc < 0)
return -1;
rc = blake2s_final(&s, hmac_return, 16);
if(rc < 0)
return -1;
return 16;
}
default:
return -1;
}
}
int
add_hmac(struct buffered *buf, struct interface *ifp,
unsigned char *packet_header)
{
int hmaclen;
int i = buf->len;
unsigned char *dst = buf->sin6.sin6_addr.s6_addr;
unsigned char *src;
if(ifp->numll < 1) {
fprintf(stderr, "add_hmac: no link-local address.\n");
return -1;
}
src = ifp->ll[0];
if(buf->len + 2 + DIGEST_LEN > buf->size) {
fprintf(stderr, "Buffer overflow in add_hmac.\n");
return -1;
}
hmaclen = compute_hmac(src, dst, packet_header,
buf->buf, buf->len, ifp->key,
buf->buf + i + 2);
if(hmaclen < 0)
return -1;
buf->buf[i++] = MESSAGE_HMAC;
buf->buf[i++] = hmaclen;
i += hmaclen;
return i;
}
static int
compare_hmac(const unsigned char *src, const unsigned char *dst,
const unsigned char *packet, int bodylen,
const unsigned char *hmac, int hmaclen)
{
unsigned char true_hmac[DIGEST_LEN];
int true_hmaclen;
int i;
for(i = 0; i < numkeys; i++) {
true_hmaclen = compute_hmac(src, dst, packet,
packet + 4, bodylen, keys[i],
true_hmac);
if(true_hmaclen != hmaclen) {
debugf("Bad hmac length (%d != %d).\n", true_hmaclen, hmaclen);
return -1;
}
if(memcmp(true_hmac, hmac, hmaclen) == 0)
return 1;
}
return 0;
}
int
check_hmac(const unsigned char *packet, int packetlen, int bodylen,
const unsigned char *src, const unsigned char *dst)
{
int i = bodylen + 4;
int len;
debugf("check_hmac %s -> %s\n",
format_address(src), format_address(dst));
while(i < packetlen) {
if(i + 1 > packetlen) {
fprintf(stderr, "Received truncated message.\n");
break;
}
len = packet[i+1];
if(packet[i] == MESSAGE_HMAC) {
if(i + len > packetlen) {
fprintf(stderr, "Received truncated message.\n");
return -1;
}
if(compare_hmac(src, dst, packet, bodylen,
packet + i + 2 , len) == 1) {
return 1;
}
}
i += len + 2;
}
return 0;
}
/*
Copyright (c) 2018 by Clara Dô and Weronika Kolodziejak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#define DIGEST_LEN 20
#define SHA1_BLOCK_SIZE 64
#define RIPEMD160_BLOCK_SIZE 64
struct key *find_key(const char *id);
struct key *retain_key(struct key *key);
void release_key(struct key *key);
struct key *add_key(char *id, int type, int len, unsigned char *value);
int add_hmac(struct buffered *buf, struct interface *ifp,
unsigned char *packet_header);
int check_hmac(const unsigned char *packet, int packetlen, int bodylen,
const unsigned char *src, const unsigned char *dst);
......@@ -42,6 +42,7 @@ THE SOFTWARE.
#include "configuration.h"
#include "local.h"
#include "xroute.h"
#include "hmac.h"
struct interface *interfaces = NULL;
......@@ -474,6 +475,15 @@ interface_updown(struct interface *ifp, int up)
update_interface_metric(ifp);
rc = check_interface_ipv4(ifp);
if(IF_CONF(ifp, key) != ifp->key) {
if(ifp->key != NULL)
release_key(ifp->key);
if(IF_CONF(ifp, key) != NULL)
ifp->key = retain_key(IF_CONF(ifp, key));
else
ifp->key = NULL;
}
debugf("Upped interface %s (cost=%d, channel=%d%s).\n",
ifp->name,
ifp->cost,
......
......@@ -35,6 +35,13 @@ struct buffered_update {
#define IF_TYPE_TUNNEL 3
/* If you modify this structure, also modify the merge_ifconf function. */
struct key {
char *id;
int type;
int len;
unsigned char *value;
unsigned short ref_count;
};
struct interface_conf {
char *ifname;
......@@ -53,6 +60,7 @@ struct interface_conf {
unsigned int rtt_min;
unsigned int rtt_max;
unsigned int max_rtt_penalty;
struct key *key;
struct interface_conf *next;
};
......@@ -102,6 +110,8 @@ struct buffered {
int hello;
};
#define INDEX_LEN 8
struct interface {
struct interface *next;
struct interface_conf *conf;
......@@ -131,6 +141,9 @@ struct interface {
unsigned int rtt_min;
unsigned int rtt_max;
unsigned int max_rtt_penalty;
struct key *key;
unsigned int pc;
unsigned char index[INDEX_LEN];
};
#define IF_CONF(_ifp, _field) \
......
......@@ -27,6 +27,7 @@ THE SOFTWARE.
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include "babeld.h"
#include "util.h"
......@@ -40,6 +41,7 @@ THE SOFTWARE.
#include "resend.h"
#include "message.h"
#include "configuration.h"
#include "hmac.h"
unsigned char packet_header[4] = {42, 2};
......@@ -248,7 +250,7 @@ parse_ihu_subtlv(const unsigned char *a, int alen,
{
int type, len, i = 0;
int have_timestamp = 0;
unsigned int timestamp1, timestamp2;
unsigned int timestamp1 = 0, timestamp2 = 0;
while(i < alen) {
type = a[0];
......@@ -293,9 +295,8 @@ parse_ihu_subtlv(const unsigned char *a, int alen,
*timestamp1_return = timestamp1;
*timestamp2_return = timestamp2;
}
if(have_timestamp_return) {
if(have_timestamp_return)
*have_timestamp_return = have_timestamp;
}
return 1;
}
......@@ -439,9 +440,100 @@ network_address(int ae, const unsigned char *a, unsigned int len,
return network_prefix(ae, -1, 0, a, NULL, len, a_r);
}
static int
preparse_packet(const unsigned char *packet, int bodylen,
struct neighbour *neigh, struct interface *ifp)
{
int i;
const unsigned char *message;
unsigned char type, len;
int challenge_success = 0;
int have_index = 0, index_len;
unsigned char pc[4], index[256];
i = 0;
while(i < bodylen) {
message = packet + 4 + i;
type = message[0];
if(type == MESSAGE_PAD1) {
i++;
continue;
}
if(i + 1 > bodylen) {
fprintf(stderr, "Received truncated message.\n");
break;
}
len = message[1];
if(i + len > bodylen) {
fprintf(stderr, "Received truncated message.\n");
break;
}
if(type == MESSAGE_CRYPTO_SEQNO) {
if(len < 4) {
fprintf(stderr, "Received truncated PC TLV.\n");
break;
}
if(len > 4 + 32) {
fprintf(stderr, "Overlong PC TLV.\n");
break;
}
debugf("Received PC from %s.\n",
format_address(neigh->address));
memcpy(pc, message + 2, 4);
index_len = len - 4;
memcpy(index, message + 6, len - 4);
have_index = 1;
} else if(type == MESSAGE_CHALLENGE_RESPONSE) {
debugf("Received challenge response from %s.\n",
format_address(neigh->address));
gettime(&now);
if(len == sizeof(neigh->nonce) &&
memcmp(neigh->nonce, message + 2, len) == 0 &&
timeval_compare(&now, &neigh->challenge_deadline) <= 0) {
challenge_success = 1;
} else {
debugf("Challenge failed.\n");
}
} else if(type == MESSAGE_CHALLENGE_REQUEST) {
unsigned char nonce[len];
debugf("Received challenge request from %s.\n",
format_address(neigh->address));
memcpy(nonce, message + 2, len);
send_challenge_reply(neigh, nonce, len);
}
i += len + 2;
}
if(!have_index) {
debugf("No PC in packet.\n");
return 0;
}
if(neigh->have_index && neigh->index_len == index_len &&
memcmp(index, neigh->index, index_len) == 0) {
if(memcmp(neigh->pc, pc, 4) < 0) {
memcpy(neigh->pc, pc, 4);
return 1;
} else {
debugf("Out of order PC.\n");
return 0;
}
} else if(challenge_success) {
neigh->index_len = index_len;
memcpy(neigh->index, index, index_len);
memcpy(neigh->pc, pc, 4);
neigh->have_index = 1;
return 1;
} else {
send_challenge_req(neigh);
return 0;
}
}
void
parse_packet(const unsigned char *from, struct interface *ifp,
const unsigned char *packet, int packetlen)
const unsigned char *packet, int packetlen,