X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=secnet.git;a=blobdiff_plain;f=site.c;h=0a0278592c52c12e63de2148aadcf249d07c0e8f;hp=f1a03179603f4f9b402b5d10b0986b7c6af98722;hb=09a385fbbb18ace5daedd473c91c5b584dab3b2a;hpb=0118121ae6578c69527fb80a60294c48663033b7 diff --git a/site.c b/site.c index f1a0317..0a02785 100644 --- a/site.c +++ b/site.c @@ -244,6 +244,7 @@ struct site { struct hash_if *hash; uint32_t index; /* Index of this site */ + uint32_t local_capabilities; int32_t setup_retries; /* How many times to send setup packets */ int32_t setup_retry_interval; /* Initial timeout for setup packets */ int32_t wait_timeout; /* How long to wait if setup unsuccessful */ @@ -253,9 +254,6 @@ struct site { after this time, initiate a new key exchange */ - uint8_t *setupsig; /* Expected signature of incoming MSG1 packets */ - int32_t setupsiglen; /* Allows us to discard packets quickly if - they are not for us */ bool_t setup_priority; /* Do we have precedence if both sites emit message 1 simultaneously? */ uint32_t log_events; @@ -278,6 +276,7 @@ struct site { packet; we keep trying to continue the exchange, and have to timeout before we can listen for another setup packet); perhaps we should keep a list of 'bad' sources for setup packets. */ + uint32_t remote_capabilities; uint32_t setup_session_id; transport_peers setup_peers; uint8_t localN[NONCELEN]; /* Nonces for key exchange */ @@ -347,14 +346,19 @@ static bool_t current_valid(struct site *st) type=buf_unprepend_uint32((b)); \ if (type!=(t)) return False; } while(0) +struct parsedname { + int32_t len; + uint8_t *name; + struct buffer_if extrainfo; +}; + struct msg { uint8_t *hashstart; uint32_t dest; uint32_t source; - int32_t remlen; - uint8_t *remote; - int32_t loclen; - uint8_t *local; + struct parsedname remote; + struct parsedname local; + uint32_t remote_capabilities; uint8_t *nR; uint8_t *nL; int32_t pklen; @@ -364,6 +368,34 @@ struct msg { char *sig; }; +struct xinfoadd { + int32_t lenpos, afternul; +}; +static void append_string_xinfo_start(struct buffer_if *buf, + struct xinfoadd *xia, + const char *str) + /* Helps construct one of the names with additional info as found + * in MSG1..4. Call this function first, then append all the + * desired extra info (not including the nul byte) to the buffer, + * then call append_string_xinfo_done. */ +{ + xia->lenpos = buf->size; + buf_append_string(buf,str); + buf_append_uint8(buf,0); + xia->afternul = buf->size; +} +static void append_string_xinfo_done(struct buffer_if *buf, + struct xinfoadd *xia) +{ + /* we just need to adjust the string length */ + if (buf->size == xia->afternul) { + /* no extra info, strip the nul too */ + buf_unappend_uint8(buf); + } else { + put_uint16(buf->start+xia->lenpos, buf->size-(xia->lenpos+2)); + } +} + /* Build any of msg1 to msg4. msg5 and msg6 are built from the inside out using a transform of config data supplied by netlink */ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what) @@ -379,7 +411,14 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what) (type==LABEL_MSG1?0:st->setup_session_id)); buf_append_uint32(&st->buffer,st->index); buf_append_uint32(&st->buffer,type); - buf_append_string(&st->buffer,st->localname); + + struct xinfoadd xia; + append_string_xinfo_start(&st->buffer,&xia,st->localname); + if ((st->local_capabilities & CAPAB_EARLY) || (type != LABEL_MSG1)) { + buf_append_uint32(&st->buffer,st->local_capabilities); + } + append_string_xinfo_done(&st->buffer,&xia); + buf_append_string(&st->buffer,st->remotename); memcpy(buf_append(&st->buffer,NONCELEN),st->localN,NONCELEN); if (type==LABEL_MSG1) return True; @@ -402,6 +441,22 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what) return True; } +static bool_t unpick_name(struct buffer_if *msg, struct parsedname *nm) +{ + CHECK_AVAIL(msg,2); + nm->len=buf_unprepend_uint16(msg); + CHECK_AVAIL(msg,nm->len); + nm->name=buf_unprepend(msg,nm->len); + uint8_t *nul=memchr(nm->name,0,nm->len); + if (!nul) { + buffer_readonly_view(&nm->extrainfo,0,0); + } else { + buffer_readonly_view(&nm->extrainfo, nul+1, msg->start-(nul+1)); + nm->len=nul-nm->name; + } + return True; +} + static bool_t unpick_msg(struct site *st, uint32_t type, struct buffer_if *msg, struct msg *m) { @@ -411,14 +466,13 @@ static bool_t unpick_msg(struct site *st, uint32_t type, CHECK_AVAIL(msg,4); m->source=buf_unprepend_uint32(msg); CHECK_TYPE(msg,type); - CHECK_AVAIL(msg,2); - m->remlen=buf_unprepend_uint16(msg); - CHECK_AVAIL(msg,m->remlen); - m->remote=buf_unprepend(msg,m->remlen); - CHECK_AVAIL(msg,2); - m->loclen=buf_unprepend_uint16(msg); - CHECK_AVAIL(msg,m->loclen); - m->local=buf_unprepend(msg,m->loclen); + if (!unpick_name(msg,&m->remote)) return False; + m->remote_capabilities=0; + if (m->remote.extrainfo.size) { + CHECK_AVAIL(&m->remote.extrainfo,4); + m->remote_capabilities=buf_unprepend_uint32(&m->remote.extrainfo); + } + if (!unpick_name(msg,&m->local)) return False; CHECK_AVAIL(msg,NONCELEN); m->nR=buf_unprepend(msg,NONCELEN); if (type==LABEL_MSG1) { @@ -444,6 +498,14 @@ static bool_t unpick_msg(struct site *st, uint32_t type, return True; } +static bool_t name_matches(const struct parsedname *nm, const char *expected) +{ + int expected_len=strlen(expected); + return + nm->len == expected_len && + !memcmp(nm->name, expected, expected_len); +} + static bool_t check_msg(struct site *st, uint32_t type, struct msg *m, cstring_t *error) { @@ -451,11 +513,11 @@ static bool_t check_msg(struct site *st, uint32_t type, struct msg *m, /* Check that the site names and our nonce have been sent back correctly, and then store our peer's nonce. */ - if (memcmp(m->remote,st->remotename,strlen(st->remotename)!=0)) { + if (!name_matches(&m->remote,st->remotename)) { *error="wrong remote site name"; return False; } - if (memcmp(m->local,st->localname,strlen(st->localname)!=0)) { + if (!name_matches(&m->local,st->localname)) { *error="wrong local site name"; return False; } @@ -468,7 +530,13 @@ static bool_t check_msg(struct site *st, uint32_t type, struct msg *m, *error="wrong remotely-generated nonce"; return False; } + /* MSG3 has complicated rules about capabilities, which are + * handled in process_msg3. */ if (type==LABEL_MSG3) return True; + if (m->remote_capabilities!=st->remote_capabilities) { + *error="remote capabilities changed"; + return False; + } if (type==LABEL_MSG4) return True; *error="unknown message type"; return False; @@ -481,19 +549,16 @@ static bool_t generate_msg1(struct site *st) } static bool_t process_msg1(struct site *st, struct buffer_if *msg1, - const struct comm_addr *src) + const struct comm_addr *src, struct msg *m) { - struct msg m; - /* We've already determined we're in an appropriate state to process an incoming MSG1, and that the MSG1 has correct values of A and B. */ - if (!unpick_msg(st,LABEL_MSG1,msg1,&m)) return False; - transport_record_peer(st,&st->setup_peers,src,"msg1"); - st->setup_session_id=m.source; - memcpy(st->remoteN,m.nR,NONCELEN); + st->setup_session_id=m->source; + st->remote_capabilities=m->remote_capabilities; + memcpy(st->remoteN,m->nR,NONCELEN); return True; } @@ -515,6 +580,7 @@ static bool_t process_msg2(struct site *st, struct buffer_if *msg2, return False; } st->setup_session_id=m.source; + st->remote_capabilities=m.remote_capabilities; memcpy(st->remoteN,m.nR,NONCELEN); return True; } @@ -540,6 +606,15 @@ static bool_t process_msg3(struct site *st, struct buffer_if *msg3, slog(st,LOG_SEC,"msg3: %s",err); return False; } + uint32_t capab_adv_late = m.remote_capabilities + & ~st->remote_capabilities & CAPAB_EARLY; + if (capab_adv_late) { + slog(st,LOG_SEC,"msg3 impermissibly adds early capability flag(s)" + " %#"PRIx32" (was %#"PRIx32", now %#"PRIx32")", + capab_adv_late, st->remote_capabilities, m.remote_capabilities); + return False; + } + st->remote_capabilities|=m.remote_capabilities; /* Check signature and store g^x mod m */ hash=safe_malloc(st->hash->len, "process_msg3"); @@ -731,7 +806,8 @@ static bool_t process_msg6(struct site *st, struct buffer_if *msg6, return True; } -static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0) +static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0, + const struct comm_addr *src) { cstring_t transform_err, auxkey_err, newkey_err="n/a"; struct msg0 m; @@ -750,11 +826,8 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0) "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; - } + if (problem==2) + goto skew; buffer_copy(msg0, &st->scratch); problem = st->auxiliary_key.transform->reverse @@ -778,11 +851,14 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0) } return True; } + if (problem==2) + goto skew; if (st->state==SITE_SENTMSG5) { buffer_copy(msg0, &st->scratch); - if (!st->new_transform->reverse(st->new_transform->st, - msg0,&newkey_err)) { + problem = st->new_transform->reverse(st->new_transform->st, + msg0,&newkey_err); + if (!problem) { /* It looks like we didn't get the peer's MSG6 */ /* This is like a cut-down enter_new_state(SITE_RUN) */ slog(st,LOG_STATE,"will enter state RUN (MSG0 with new key)"); @@ -791,11 +867,18 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0) activate_new_key(st); return True; /* do process the data in this packet */ } + if (problem==2) + goto skew; } 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"); + send_nak(src,m.dest,m.source,m.type,msg0,"message would not decrypt"); + return False; + + skew: + slog(st,LOG_DROP,"transform: %s (merely skew)",transform_err); return False; } @@ -804,7 +887,7 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0, { uint32_t type; - if (!decrypt_msg0(st,msg0)) + if (!decrypt_msg0(st,msg0,src)) return False; CHECK_AVAIL(msg0,4); @@ -833,9 +916,9 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0, static void dump_packet(struct site *st, struct buffer_if *buf, const struct comm_addr *addr, bool_t incoming) { - uint32_t dest=ntohl(*(uint32_t *)buf->start); - uint32_t source=ntohl(*(uint32_t *)(buf->start+4)); - uint32_t msgtype=ntohl(*(uint32_t *)(buf->start+8)); + uint32_t dest=get_uint32(buf->start); + uint32_t source=get_uint32(buf->start+4); + uint32_t msgtype=get_uint32(buf->start+8); if (st->log_events & LOG_DUMP) slilog(st->log,M_DEBUG,"%s: %s: %08x<-%08x: %08x:", @@ -1221,6 +1304,18 @@ static void site_outgoing(void *sst, struct buffer_if *buf) initiate_key_setup(st,"outgoing packet"); } +static bool_t named_for_us(struct site *st, const struct buffer_if *buf_in, + uint32_t type, struct msg *m) + /* For packets which are identified by the local and remote names. + * If it has our name and our peer's name in it it's for us. */ +{ + struct buffer_if buf[1]; + buffer_readonly_clone(buf,buf_in); + return unpick_msg(st,type,buf,m) + && name_matches(&m->remote,st->remotename) + && name_matches(&m->local,st->localname); +} + /* This function is called by the communication device to deliver packets from our peers. */ static bool_t site_incoming(void *sst, struct buffer_if *buf, @@ -1230,63 +1325,60 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, if (buf->size < 12) return False; - uint32_t dest=ntohl(*(uint32_t *)buf->start); - - if (dest==0) { - /* It could be for any site - it should have LABEL_MSG1 and - might have our name and our peer's name in it */ - if (buf->size<(st->setupsiglen+8+NONCELEN)) return False; - if (memcmp(buf->start+8,st->setupsig,st->setupsiglen)==0) { - /* It's addressed to us. Decide what to do about it. */ - dump_packet(st,buf,source,True); - if (st->state==SITE_RUN || st->state==SITE_RESOLVE || - st->state==SITE_WAIT) { - /* We should definitely process it */ - if (process_msg1(st,buf,source)) { - slog(st,LOG_SETUP_INIT,"key setup initiated by peer"); + uint32_t dest=get_uint32(buf->start); + uint32_t msgtype=get_uint32(buf->start+8); + struct msg named_msg; + + if (msgtype==LABEL_MSG1) { + if (!named_for_us(st,buf,msgtype,&named_msg)) + return False; + /* It's a MSG1 addressed to us. Decide what to do about it. */ + dump_packet(st,buf,source,True); + if (st->state==SITE_RUN || st->state==SITE_RESOLVE || + st->state==SITE_WAIT) { + /* We should definitely process it */ + if (process_msg1(st,buf,source,&named_msg)) { + slog(st,LOG_SETUP_INIT,"key setup initiated by peer"); + enter_new_state(st,SITE_SENTMSG2); + } else { + slog(st,LOG_ERROR,"failed to process incoming msg1"); + } + BUF_FREE(buf); + return True; + } else if (st->state==SITE_SENTMSG1) { + /* We've just sent a message 1! They may have crossed on + the wire. If we have priority then we ignore the + incoming one, otherwise we process it as usual. */ + if (st->setup_priority) { + BUF_FREE(buf); + slog(st,LOG_DUMP,"crossed msg1s; we are higher " + "priority => ignore incoming msg1"); + return True; + } else { + slog(st,LOG_DUMP,"crossed msg1s; we are lower " + "priority => use incoming msg1"); + if (process_msg1(st,buf,source,&named_msg)) { + BUF_FREE(&st->buffer); /* Free our old message 1 */ enter_new_state(st,SITE_SENTMSG2); } else { - slog(st,LOG_ERROR,"failed to process incoming msg1"); + slog(st,LOG_ERROR,"failed to process an incoming " + "crossed msg1 (we have low priority)"); } BUF_FREE(buf); return True; - } else if (st->state==SITE_SENTMSG1) { - /* We've just sent a message 1! They may have crossed on - the wire. If we have priority then we ignore the - incoming one, otherwise we process it as usual. */ - if (st->setup_priority) { - BUF_FREE(buf); - slog(st,LOG_DUMP,"crossed msg1s; we are higher " - "priority => ignore incoming msg1"); - return True; - } else { - slog(st,LOG_DUMP,"crossed msg1s; we are lower " - "priority => use incoming msg1"); - if (process_msg1(st,buf,source)) { - BUF_FREE(&st->buffer); /* Free our old message 1 */ - enter_new_state(st,SITE_SENTMSG2); - } else { - slog(st,LOG_ERROR,"failed to process an incoming " - "crossed msg1 (we have low priority)"); - } - BUF_FREE(buf); - return True; - } } - /* The message 1 was received at an unexpected stage of the - key setup. XXX POLICY - what do we do? */ - slog(st,LOG_UNEXPECTED,"unexpected incoming message 1"); - BUF_FREE(buf); - return True; } - return False; /* Not for us. */ + /* The message 1 was received at an unexpected stage of the + key setup. XXX POLICY - what do we do? */ + slog(st,LOG_UNEXPECTED,"unexpected incoming message 1"); + BUF_FREE(buf); + return True; } if (dest==st->index) { /* Explicitly addressed to us */ - uint32_t msgtype=ntohl(get_uint32(buf->start+8)); if (msgtype!=LABEL_MSG0) dump_packet(st,buf,source,True); switch (msgtype) { - case 0: /* NAK */ + case LABEL_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->current.remote_session_id) { @@ -1458,6 +1550,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, assert(index_sequence < 0xffffffffUL); st->index = ++index_sequence; + st->local_capabilities = 0; st->netlink=find_cl_if(dict,"link",CL_NETLINK,True,"site",loc); list_t *comms_cfg=dict_lookup(dict,"comm"); @@ -1532,14 +1625,6 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, /* The information we expect to see in incoming messages of type 1 */ /* fixme: lots of unchecked overflows here, but the results are only corrupted packets rather than undefined behaviour */ - st->setupsiglen=strlen(st->remotename)+strlen(st->localname)+8; - st->setupsig=safe_malloc(st->setupsiglen,"site_apply"); - put_uint32(st->setupsig+0,LABEL_MSG1); - put_uint16(st->setupsig+4,strlen(st->remotename)); - memcpy(&st->setupsig[6],st->remotename,strlen(st->remotename)); - put_uint16(st->setupsig+(6+strlen(st->remotename)),strlen(st->localname)); - memcpy(&st->setupsig[8+strlen(st->remotename)],st->localname, - strlen(st->localname)); st->setup_priority=(strcmp(st->localname,st->remotename)>0); buffer_new(&st->buffer,SETUP_BUFFER_LEN); @@ -1552,6 +1637,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, register_for_poll(st, site_beforepoll, site_afterpoll, 0, "site"); st->timeout=0; + st->remote_capabilities=0; st->current.key_timeout=0; st->auxiliary_key.key_timeout=0; transport_peers_clear(st,&st->peers);