/***** END of transport peers declarations *****/
+struct data_key {
+ struct transform_inst_if *transform;
+ uint64_t key_timeout; /* End of life of current key */
+ uint32_t remote_session_id;
+};
+
struct site {
closure_t cl;
struct site_if ops;
uint64_t now; /* Most recently seen time */
/* The currently established session */
- uint32_t remote_session_id;
- struct transform_inst_if *current_transform;
- bool_t current_valid;
- uint64_t current_key_timeout; /* End of life of current key */
+ struct data_key current;
+ struct data_key auxiliary_key;
+ bool_t auxiliary_is_new;
uint64_t renegotiate_key_time; /* When we can negotiate a new key */
+ uint64_t auxiliary_renegotiate_key_time;
transport_peers peers; /* Current address(es) of peer for data traffic */
/* The current key setup protocol exchange. We can only be
}
static void set_link_quality(struct site *st);
-static void delete_key(struct site *st, cstring_t reason, uint32_t loglevel);
+static void delete_keys(struct site *st, cstring_t reason, uint32_t loglevel);
+static void delete_one_key(struct site *st, struct data_key *key,
+ const char *reason /* may be 0 meaning don't log*/,
+ const char *which /* ignored if !reasonn */,
+ uint32_t loglevel /* ignored if !reasonn */);
static bool_t initiate_key_setup(struct site *st, cstring_t reason);
static void enter_state_run(struct site *st);
static bool_t enter_state_resolve(struct site *st);
static void enter_state_wait(struct site *st);
static void activate_new_key(struct site *st);
+static bool_t current_valid(struct site *st)
+{
+ return st->current.transform->valid(st->current.transform->st);
+}
+
#define CHECK_AVAIL(b,l) do { if ((b)->size<(l)) return False; } while(0)
#define CHECK_EMPTY(b) do { if ((b)->size!=0) return False; } while(0)
#define CHECK_TYPE(b,t) do { uint32_t type; \
return False;
}
if (type==LABEL_MSG2) return True;
- if (memcmp(m->nR,st->remoteN,NONCELEN)!=0) {
+ if (!consttime_memeq(m->nR,st->remoteN,NONCELEN)!=0) {
*error="wrong remotely-generated nonce";
return False;
}
}
static bool_t process_msg5(struct site *st, struct buffer_if *msg5,
- const struct comm_addr *src)
+ const struct comm_addr *src,
+ struct transform_inst_if *transform)
{
struct msg0 m;
cstring_t transform_err;
if (!unpick_msg0(st,msg5,&m)) return False;
- if (st->new_transform->reverse(st->new_transform->st,
- msg5,&transform_err)) {
+ if (transform->reverse(transform->st,msg5,&transform_err)) {
/* There's a problem */
slog(st,LOG_SEC,"process_msg5: transform: %s",transform_err);
return False;
return True;
}
-static bool_t generate_msg6(struct site *st)
+static void create_msg6(struct site *st, struct transform_inst_if *transform,
+ uint32_t session_id)
{
cstring_t transform_err;
/* Give the netlink code an opportunity to put its own stuff in the
message (configuration information, etc.) */
buf_prepend_uint32(&st->buffer,LABEL_MSG6);
- st->new_transform->forwards(st->new_transform->st,&st->buffer,
- &transform_err);
+ transform->forwards(transform->st,&st->buffer,&transform_err);
buf_prepend_uint32(&st->buffer,LABEL_MSG6);
buf_prepend_uint32(&st->buffer,st->index);
- buf_prepend_uint32(&st->buffer,st->setup_session_id);
+ buf_prepend_uint32(&st->buffer,session_id);
+}
+static bool_t generate_msg6(struct site *st)
+{
+ create_msg6(st,st->new_transform,st->setup_session_id);
st->retries=1; /* Peer will retransmit MSG5 if this packet gets lost */
return True;
}
static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0)
{
- cstring_t transform_err, newkey_err="n/a";
+ cstring_t transform_err, auxkey_err, newkey_err="n/a";
struct msg0 m;
uint32_t problem;
/* Keep a copy so we can try decrypting it with multiple keys */
buffer_copy(&st->scratch, msg0);
- problem = st->current_transform->reverse(st->current_transform->st,
+ problem = st->current.transform->reverse(st->current.transform->st,
msg0,&transform_err);
- if (!problem) return True;
+ if (!problem) {
+ if (!st->auxiliary_is_new)
+ delete_one_key(st,&st->auxiliary_key,
+ "peer has used new key","auxiliary key",LOG_SEC);
+ return True;
+ }
if (problem==2) {
slog(st,LOG_DROP,"transform: %s (merely skew)",transform_err);
return False;
}
+ buffer_copy(msg0, &st->scratch);
+ problem = st->auxiliary_key.transform->reverse
+ (st->auxiliary_key.transform->st,msg0,&auxkey_err);
+ if (problem==0) {
+ slog(st,LOG_DROP,"processing packet which uses auxiliary key");
+ if (st->auxiliary_is_new) {
+ /* We previously timed out in state SENTMSG5 but it turns
+ * out that our peer did in fact get our MSG5 and is
+ * using the new key. So we should switch to it too. */
+ /* This is a bit like activate_new_key. */
+ struct data_key t;
+ t=st->current;
+ st->current=st->auxiliary_key;
+ st->auxiliary_key=t;
+
+ delete_one_key(st,&st->auxiliary_key,"peer has used new key",
+ "previous key",LOG_SEC);
+ st->auxiliary_is_new=0;
+ st->renegotiate_key_time=st->auxiliary_renegotiate_key_time;
+ }
+ return True;
+ }
+
if (st->state==SITE_SENTMSG5) {
buffer_copy(msg0, &st->scratch);
if (!st->new_transform->reverse(st->new_transform->st,
}
}
- slog(st,LOG_SEC,"transform: %s (new: %s)",transform_err,newkey_err);
+ slog(st,LOG_SEC,"transform: %s (aux: %s, new: %s)",
+ transform_err,auxkey_err,newkey_err);
initiate_key_setup(st,"incoming message would not decrypt");
return False;
}
switch(type) {
case LABEL_MSG7:
/* We must forget about the current session. */
- delete_key(st,"request from peer",LOG_SEC);
+ delete_keys(st,"request from peer",LOG_SEC);
return True;
case LABEL_MSG9:
/* Deliver to netlink layer */
st->timeout=st->now+st->setup_retry_interval;
st->retries--;
return True;
+ } else if (st->state==SITE_SENTMSG5) {
+ slog(st,LOG_SETUP_TIMEOUT,"timed out sending MSG5, stashing new key");
+ /* We stash the key we have produced, in case it turns out that
+ * our peer did see our MSG5 after all and starts using it. */
+ /* This is a bit like some of activate_new_key */
+ struct transform_inst_if *t;
+ t=st->auxiliary_key.transform;
+ st->auxiliary_key.transform=st->new_transform;
+ st->new_transform=t;
+
+ t->delkey(t->st);
+ st->auxiliary_is_new=1;
+ st->auxiliary_key.key_timeout=st->now+st->key_lifetime;
+ st->auxiliary_renegotiate_key_time=st->now+st->key_renegotiate_time;
+ st->auxiliary_key.remote_session_id=st->setup_session_id;
+
+ enter_state_wait(st);
+ return False;
} else {
slog(st,LOG_SETUP_TIMEOUT,"timed out sending key setup packet "
"(in state %s)",state_name(st->state));
{
struct transform_inst_if *t;
- /* We have two transform instances, which we swap between active
- and setup */
- t=st->current_transform;
- st->current_transform=st->new_transform;
+ /* We have three transform instances, which we swap between old,
+ active and setup */
+ t=st->auxiliary_key.transform;
+ st->auxiliary_key.transform=st->current.transform;
+ st->current.transform=st->new_transform;
st->new_transform=t;
t->delkey(t->st);
st->timeout=0;
- st->current_valid=True;
- st->current_key_timeout=st->now+st->key_lifetime;
+ st->auxiliary_is_new=0;
+ st->auxiliary_key.key_timeout=st->current.key_timeout;
+ st->current.key_timeout=st->now+st->key_lifetime;
st->renegotiate_key_time=st->now+st->key_renegotiate_time;
transport_peers_copy(st,&st->peers,&st->setup_peers);
- st->remote_session_id=st->setup_session_id;
+ st->current.remote_session_id=st->setup_session_id;
slog(st,LOG_ACTIVATE_KEY,"new key activated");
enter_state_run(st);
}
-static void delete_key(struct site *st, cstring_t reason, uint32_t loglevel)
+static void delete_one_key(struct site *st, struct data_key *key,
+ cstring_t reason, cstring_t which, uint32_t loglevel)
{
- if (st->current_valid) {
+ if (!key->transform->valid(key->transform->st)) return;
+ if (reason) slog(st,loglevel,"%s deleted (%s)",which,reason);
+ key->transform->delkey(key->transform->st);
+ key->key_timeout=0;
+}
+
+static void delete_keys(struct site *st, cstring_t reason, uint32_t loglevel)
+{
+ if (current_valid(st)) {
slog(st,loglevel,"session closed (%s)",reason);
- st->current_valid=False;
- st->current_transform->delkey(st->current_transform->st);
- st->current_key_timeout=0;
+ delete_one_key(st,&st->current,0,0,0);
set_link_quality(st);
}
+ delete_one_key(st,&st->auxiliary_key,0,0,0);
}
static void state_assert(struct site *st, bool_t ok)
{
st->state=SITE_STOP;
st->timeout=0;
- delete_key(st,"entering state STOP",LOG_TIMEOUT_KEY);
+ delete_keys(st,"entering state STOP",LOG_TIMEOUT_KEY);
st->new_transform->delkey(st->new_transform->st);
}
static void set_link_quality(struct site *st)
{
uint32_t quality;
- if (st->current_valid)
+ if (current_valid(st))
quality=LINK_QUALITY_UP;
else if (st->state==SITE_WAIT || st->state==SITE_STOP)
quality=LINK_QUALITY_DOWN;
{
cstring_t transform_err;
- if (st->current_valid && st->buffer.free
+ if (current_valid(st) && st->buffer.free
&& transport_peers_valid(&st->peers)) {
BUF_ALLOC(&st->buffer,"site:MSG7");
buffer_init(&st->buffer,st->transform->max_start_pad+(4*3));
buf_append_uint32(&st->buffer,LABEL_MSG7);
buf_append_string(&st->buffer,reason);
- st->current_transform->forwards(st->current_transform->st,
+ st->current.transform->forwards(st->current.transform->st,
&st->buffer, &transform_err);
buf_prepend_uint32(&st->buffer,LABEL_MSG0);
buf_prepend_uint32(&st->buffer,st->index);
- buf_prepend_uint32(&st->buffer,st->remote_session_id);
+ buf_prepend_uint32(&st->buffer,st->current.remote_session_id);
transport_xmit(st,&st->peers,&st->buffer,True);
BUF_FREE(&st->buffer);
return True;
st->now=*now;
/* Work out when our next timeout is. The earlier of 'timeout' or
- 'current_key_timeout'. A stored value of '0' indicates no timeout
+ 'current.key_timeout'. A stored value of '0' indicates no timeout
active. */
site_settimeout(st->timeout, timeout_io);
- site_settimeout(st->current_key_timeout, timeout_io);
+ site_settimeout(st->current.key_timeout, timeout_io);
+ site_settimeout(st->auxiliary_key.key_timeout, timeout_io);
return 0; /* success */
}
+static void check_expiry(struct site *st, struct data_key *key,
+ const char *which)
+{
+ if (key->key_timeout && *now>key->key_timeout) {
+ delete_one_key(st,key,"maximum life exceeded",which,LOG_TIMEOUT_KEY);
+ }
+}
+
/* NB site_afterpoll will be called before site_beforepoll is ever called */
static void site_afterpoll(void *sst, struct pollfd *fds, int nfds)
{
st->state);
}
}
- if (st->current_key_timeout && *now>st->current_key_timeout) {
- delete_key(st,"maximum key life exceeded",LOG_TIMEOUT_KEY);
- }
+ check_expiry(st,&st->current,"current key");
+ check_expiry(st,&st->auxiliary_key,"auxiliary key");
}
/* This function is called by the netlink device to deliver packets
/* In all other states we consider delivering the packet if we have
a valid key and a valid address to send it to. */
- if (st->current_valid && transport_peers_valid(&st->peers)) {
+ if (current_valid(st) && transport_peers_valid(&st->peers)) {
/* Transform it and send it */
if (buf->size>0) {
buf_prepend_uint32(buf,LABEL_MSG9);
- st->current_transform->forwards(st->current_transform->st,
+ st->current.transform->forwards(st->current.transform->st,
buf, &transform_err);
buf_prepend_uint32(buf,LABEL_MSG0);
buf_prepend_uint32(buf,st->index);
- buf_prepend_uint32(buf,st->remote_session_id);
+ buf_prepend_uint32(buf,st->current.remote_session_id);
transport_xmit(st,&st->peers,buf,False);
}
BUF_FREE(buf);
case 0: /* NAK */
/* If the source is our current peer then initiate a key setup,
because our peer's forgotten the key */
- if (get_uint32(buf->start+4)==st->remote_session_id) {
+ if (get_uint32(buf->start+4)==st->current.remote_session_id) {
initiate_key_setup(st,"received a NAK");
} else {
slog(st,LOG_SEC,"bad incoming NAK");
case we discard it. The peer will realise that we
are using the new key when they see our data packets.
Until then the peer's data packets to us get discarded. */
- if (st->state!=SITE_SENTMSG4) {
- slog(st,LOG_UNEXPECTED,"unexpected MSG5");
- } else if (process_msg5(st,buf,source)) {
- transport_setup_msgok(st,source);
- enter_new_state(st,SITE_RUN);
+ if (st->state==SITE_SENTMSG4) {
+ if (process_msg5(st,buf,source,st->new_transform)) {
+ transport_setup_msgok(st,source);
+ enter_new_state(st,SITE_RUN);
+ } else {
+ slog(st,LOG_SEC,"invalid MSG5");
+ }
+ } else if (st->state==SITE_RUN) {
+ if (process_msg5(st,buf,source,st->current.transform)) {
+ slog(st,LOG_DROP,"got MSG5, retransmitting MSG6");
+ transport_setup_msgok(st,source);
+ create_msg6(st,st->current.transform,
+ st->current.remote_session_id);
+ transport_xmit(st,&st->peers,&st->buffer,True);
+ BUF_FREE(&st->buffer);
+ } else {
+ slog(st,LOG_SEC,"invalid MSG5 (in state RUN)");
+ }
} else {
- slog(st,LOG_SEC,"invalid MSG5");
+ slog(st,LOG_UNEXPECTED,"unexpected MSG5");
}
break;
case LABEL_MSG6:
register_for_poll(st, site_beforepoll, site_afterpoll, 0, "site");
st->timeout=0;
- st->current_valid=False;
- st->current_key_timeout=0;
+ st->current.key_timeout=0;
+ st->auxiliary_key.key_timeout=0;
transport_peers_clear(st,&st->peers);
transport_peers_clear(st,&st->setup_peers);
/* XXX mlock these */
for (i=0; i<st->ncomms; i++)
st->comms[i]->request_notify(st->comms[i]->st, st, site_incoming);
- st->current_transform=st->transform->create(st->transform->st);
+ st->current.transform=st->transform->create(st->transform->st);
+ st->auxiliary_key.transform=st->transform->create(st->transform->st);
st->new_transform=st->transform->create(st->transform->st);
+ st->auxiliary_is_new=0;
enter_state_stop(st);