X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=secnet.git;a=blobdiff_plain;f=site.c;h=324a2061682e69188605c04650d6e012d4993624;hp=4358cad32f3f102829675b6f70e6c238619659fa;hb=34d3bf4cdcb2d2938c3b92573f66815e4d9392ac;hpb=02b0959b82910224e40025e06c01b59b65340c93 diff --git a/site.c b/site.c index 4358cad..324a206 100644 --- a/site.c +++ b/site.c @@ -89,6 +89,8 @@ #define SITE_SENTMSG5 7 #define SITE_WAIT 8 +int32_t site_max_start_pad = 4*4; + static cstring_t state_name(uint32_t state) { switch (state) { @@ -204,7 +206,8 @@ static void transport_peers_copy(struct site *st, transport_peers *dst, static void transport_setup_msgok(struct site *st, const struct comm_addr *a); static void transport_data_msgok(struct site *st, const struct comm_addr *a); static bool_t transport_compute_setupinit_peers(struct site *st, - const struct comm_addr *configured_addr /* 0 if none or not found */); + const struct comm_addr *configured_addr /* 0 if none or not found */, + const struct comm_addr *prod_hint_addr /* 0 if none */); static void transport_record_peer(struct site *st, transport_peers *peers, const struct comm_addr *addr, const char *m); @@ -239,11 +242,13 @@ struct site { struct random_if *random; struct rsaprivkey_if *privkey; struct rsapubkey_if *pubkey; - struct transform_if *transform; + struct transform_if **transforms; + int ntransforms; struct dh_if *dh; 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 +258,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; @@ -263,11 +265,14 @@ struct site { /* runtime information */ uint32_t state; uint64_t now; /* Most recently seen time */ + bool_t allow_send_prod; /* The currently established session */ 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 @@ -276,6 +281,8 @@ 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; + struct transform_if *chosen_transform; uint32_t setup_session_id; transport_peers setup_peers; uint8_t localN[NONCELEN]; /* Nonces for key exchange */ @@ -286,9 +293,12 @@ struct site { uint64_t timeout; /* Timeout for current state */ uint8_t *dhsecret; uint8_t *sharedsecret; + uint32_t sharedsecretlen, sharedsecretallocd; struct transform_inst_if *new_transform; /* For key setup/verify */ }; +static void slog(struct site *st, uint32_t event, cstring_t msg, ...) +FORMAT(printf,3,4); static void slog(struct site *st, uint32_t event, cstring_t msg, ...) { va_list ap; @@ -314,7 +324,7 @@ static void slog(struct site *st, uint32_t event, cstring_t msg, ...) } vsnprintf(buf,sizeof(buf),msg,ap); - st->log->log(st->log->st,class,"%s: %s",st->tunname,buf); + slilog(st->log,class,"%s: %s",st->tunname,buf); } va_end(ap); } @@ -325,18 +335,50 @@ 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 bool_t initiate_key_setup(struct site *st, cstring_t reason, + const struct comm_addr *prod_hint); static void enter_state_run(struct site *st); static bool_t enter_state_resolve(struct site *st); static bool_t enter_new_state(struct site *st,uint32_t next); static void enter_state_wait(struct site *st); static void activate_new_key(struct site *st); +static bool_t is_transform_valid(struct transform_inst_if *transform) +{ + return transform && transform->valid(transform->st); +} + static bool_t current_valid(struct site *st) { - return st->current.transform->valid(st->current.transform->st); + return is_transform_valid(st->current.transform); +} + +#define DEFINE_CALL_TRANSFORM(fwdrev) \ +static int call_transform_##fwdrev(struct site *st, \ + struct transform_inst_if *transform, \ + struct buffer_if *buf, \ + const char **errmsg) \ +{ \ + if (!is_transform_valid(transform)) { \ + *errmsg="transform not set up"; \ + return 1; \ + } \ + return transform->fwdrev(transform->st,buf,errmsg); \ } +DEFINE_CALL_TRANSFORM(forwards) +DEFINE_CALL_TRANSFORM(reverse) + +static void dispose_transform(struct transform_inst_if **transform_var) +{ + struct transform_inst_if *transform=*transform_var; + if (transform) { + transform->delkey(transform->st); + transform->destroy(transform->st); + } + *transform_var = 0; +} + #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; \ @@ -344,14 +386,20 @@ 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; + int capab_transformnum; uint8_t *nR; uint8_t *nL; int32_t pklen; @@ -361,6 +409,63 @@ struct msg { char *sig; }; +static void set_new_transform(struct site *st, char *pk) +{ + /* Make room for the shared key */ + st->sharedsecretlen=st->chosen_transform->keylen?:st->dh->ceil_len; + assert(st->sharedsecretlen); + if (st->sharedsecretlen > st->sharedsecretallocd) { + st->sharedsecretallocd=st->sharedsecretlen; + st->sharedsecret=realloc(st->sharedsecret,st->sharedsecretallocd); + } + if (!st->sharedsecret) fatal_perror("site:sharedsecret"); + + /* Generate the shared key */ + st->dh->makeshared(st->dh->st,st->dhsecret,st->dh->len,pk, + st->sharedsecret,st->sharedsecretlen); + + /* Set up the transform */ + struct transform_if *generator=st->chosen_transform; + struct transform_inst_if *generated=generator->create(generator->st); + generated->setkey(generated->st,st->sharedsecret, + st->sharedsecretlen,st->setup_priority); + dispose_transform(&st->new_transform); + st->new_transform=generated; + + slog(st,LOG_SETUP_INIT,"key exchange negotiated transform" + " %d (capabilities ours=%#"PRIx32" theirs=%#"PRIx32")", + st->chosen_transform->capab_transformnum, + st->local_capabilities, st->remote_capabilities); +} + +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) @@ -376,7 +481,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; @@ -385,6 +497,9 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what) if (hacky_par_mid_failnow()) return False; + if (type==LABEL_MSG3BIS) + buf_append_uint8(&st->buffer,st->chosen_transform->capab_transformnum); + dhpub=st->dh->makepublic(st->dh->st,st->dhsecret,st->dh->len); buf_append_string(&st->buffer,dhpub); free(dhpub); @@ -399,23 +514,43 @@ 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) { + m->capab_transformnum=-1; m->hashstart=msg->start; CHECK_AVAIL(msg,4); m->dest=buf_unprepend_uint32(msg); 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; + if (type==LABEL_PROD) { + CHECK_EMPTY(msg); + return True; + } CHECK_AVAIL(msg,NONCELEN); m->nR=buf_unprepend(msg,NONCELEN); if (type==LABEL_MSG1) { @@ -428,6 +563,12 @@ static bool_t unpick_msg(struct site *st, uint32_t type, CHECK_EMPTY(msg); return True; } + if (type==LABEL_MSG3BIS) { + CHECK_AVAIL(msg,1); + m->capab_transformnum = buf_unprepend_uint8(msg); + } else { + m->capab_transformnum = CAPAB_TRANSFORMNUM_ANCIENT; + } CHECK_AVAIL(msg,2); m->pklen=buf_unprepend_uint16(msg); CHECK_AVAIL(msg,m->pklen); @@ -441,6 +582,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) { @@ -448,11 +597,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; } @@ -461,11 +610,17 @@ static bool_t check_msg(struct site *st, uint32_t type, struct msg *m, 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; } - if (type==LABEL_MSG3) return True; + /* MSG3 has complicated rules about capabilities, which are + * handled in process_msg3. */ + if (type==LABEL_MSG3 || type==LABEL_MSG3BIS) 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; @@ -478,19 +633,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; } @@ -512,6 +664,30 @@ 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; + + /* Select the transform to use */ + + uint32_t remote_transforms = st->remote_capabilities & CAPAB_TRANSFORM_MASK; + if (!remote_transforms) + /* old secnets only had this one transform */ + remote_transforms = 1UL << CAPAB_TRANSFORMNUM_ANCIENT; + + struct transform_if *ti; + int i; + for (i=0; intransforms; i++) { + ti=st->transforms[i]; + if ((1UL << ti->capab_transformnum) & remote_transforms) + goto transform_found; + } + slog(st,LOG_ERROR,"no transforms in common" + " (us %#"PRIx32"; them: %#"PRIx32")", + st->local_capabilities & CAPAB_TRANSFORM_MASK, + remote_transforms); + return False; + transform_found: + st->chosen_transform=ti; + memcpy(st->remoteN,m.nR,NONCELEN); return True; } @@ -521,22 +697,49 @@ static bool_t generate_msg3(struct site *st) /* Now we have our nonce and their nonce. Think of a secret key, and create message number 3. */ st->random->generate(st->random->st,st->dh->len,st->dhsecret); - return generate_msg(st,LABEL_MSG3,"site:MSG3"); + return generate_msg(st, + (st->remote_capabilities & CAPAB_TRANSFORM_MASK + ? LABEL_MSG3BIS : LABEL_MSG3), + "site:MSG3"); } static bool_t process_msg3(struct site *st, struct buffer_if *msg3, - const struct comm_addr *src) + const struct comm_addr *src, uint32_t msgtype) { struct msg m; uint8_t *hash; void *hst; cstring_t err; - if (!unpick_msg(st,LABEL_MSG3,msg3,&m)) return False; - if (!check_msg(st,LABEL_MSG3,&m,&err)) { + assert(msgtype==LABEL_MSG3 || msgtype==LABEL_MSG3BIS); + + if (!unpick_msg(st,msgtype,msg3,&m)) return False; + if (!check_msg(st,msgtype,&m,&err)) { 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; + + struct transform_if *ti; + int i; + for (i=0; intransforms; i++) { + ti=st->transforms[i]; + if (ti->capab_transformnum == m.capab_transformnum) + goto transform_found; + } + slog(st,LOG_SEC,"peer chose unknown-to-us transform %d!", + m.capab_transformnum); + return False; + transform_found: + st->chosen_transform=ti; /* Check signature and store g^x mod m */ hash=safe_malloc(st->hash->len, "process_msg3"); @@ -557,13 +760,8 @@ static bool_t process_msg3(struct site *st, struct buffer_if *msg3, /* Invent our DH secret key */ st->random->generate(st->random->st,st->dh->len,st->dhsecret); - /* Generate the shared key */ - st->dh->makeshared(st->dh->st,st->dhsecret,st->dh->len,m.pk, - st->sharedsecret,st->transform->keylen); - - /* Set up the transform */ - st->new_transform->setkey(st->new_transform->st,st->sharedsecret, - st->transform->keylen); + /* Generate the shared key and set up the transform */ + set_new_transform(st,m.pk); return True; } @@ -605,12 +803,9 @@ static bool_t process_msg4(struct site *st, struct buffer_if *msg4, /* Terminate their DH public key with a '0' */ m.pk[m.pklen]=0; - /* Generate the shared key */ - st->dh->makeshared(st->dh->st,st->dhsecret,st->dh->len,m.pk, - st->sharedsecret,st->transform->keylen); - /* Set up the transform */ - st->new_transform->setkey(st->new_transform->st,st->sharedsecret, - st->transform->keylen); + + /* Generate the shared key and set up the transform */ + set_new_transform(st,m.pk); return True; } @@ -640,12 +835,13 @@ static bool_t generate_msg5(struct site *st) BUF_ALLOC(&st->buffer,"site:MSG5"); /* We are going to add four words to the message */ - buffer_init(&st->buffer,st->transform->max_start_pad+(4*4)); + buffer_init(&st->buffer,calculate_max_start_pad()); /* Give the netlink code an opportunity to put its own stuff in the message (configuration information, etc.) */ buf_prepend_uint32(&st->buffer,LABEL_MSG5); - st->new_transform->forwards(st->new_transform->st,&st->buffer, - &transform_err); + if (call_transform_forwards(st,st->new_transform, + &st->buffer,&transform_err)) + return False; buf_prepend_uint32(&st->buffer,LABEL_MSG5); buf_prepend_uint32(&st->buffer,st->index); buf_prepend_uint32(&st->buffer,st->setup_session_id); @@ -663,7 +859,7 @@ static bool_t process_msg5(struct site *st, struct buffer_if *msg5, if (!unpick_msg0(st,msg5,&m)) return False; - if (transform->reverse(transform->st,msg5,&transform_err)) { + if (call_transform_reverse(st,transform,msg5,&transform_err)) { /* There's a problem */ slog(st,LOG_SEC,"process_msg5: transform: %s",transform_err); return False; @@ -686,11 +882,13 @@ static void create_msg6(struct site *st, struct transform_inst_if *transform, BUF_ALLOC(&st->buffer,"site:MSG6"); /* We are going to add four words to the message */ - buffer_init(&st->buffer,st->transform->max_start_pad+(4*4)); + buffer_init(&st->buffer,calculate_max_start_pad()); /* Give the netlink code an opportunity to put its own stuff in the message (configuration information, etc.) */ buf_prepend_uint32(&st->buffer,LABEL_MSG6); - transform->forwards(transform->st,&st->buffer,&transform_err); + int problem = call_transform_forwards(st,transform, + &st->buffer,&transform_err); + assert(!problem); buf_prepend_uint32(&st->buffer,LABEL_MSG6); buf_prepend_uint32(&st->buffer,st->index); buf_prepend_uint32(&st->buffer,session_id); @@ -698,6 +896,8 @@ static void create_msg6(struct site *st, struct transform_inst_if *transform, static bool_t generate_msg6(struct site *st) { + if (!is_transform_valid(st->new_transform)) + return False; create_msg6(st,st->new_transform,st->setup_session_id); st->retries=1; /* Peer will retransmit MSG5 if this packet gets lost */ return True; @@ -711,8 +911,7 @@ static bool_t process_msg6(struct site *st, struct buffer_if *msg6, if (!unpick_msg0(st,msg6,&m)) return False; - if (st->new_transform->reverse(st->new_transform->st, - msg6,&transform_err)) { + if (call_transform_reverse(st,st->new_transform,msg6,&transform_err)) { /* There's a problem */ slog(st,LOG_SEC,"process_msg6: transform: %s",transform_err); return False; @@ -728,7 +927,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; @@ -739,31 +939,47 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0) /* 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, - msg0,&transform_err); + problem = call_transform_reverse(st,st->current.transform, + msg0,&transform_err); if (!problem) { - delete_one_key(st,&st->auxiliary_key, - "peer has used new key","auxiliary key",LOG_SEC); + 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; - } + if (problem==2) + goto skew; buffer_copy(msg0, &st->scratch); - problem = st->auxiliary_key.transform->reverse - (st->auxiliary_key.transform->st,msg0,&auxkey_err); + problem = call_transform_reverse(st,st->auxiliary_key.transform, + 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 (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 = call_transform_reverse(st,st->new_transform, + 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)"); @@ -772,11 +988,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"); + initiate_key_setup(st,"incoming message would not decrypt",0); + 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; } @@ -785,7 +1008,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); @@ -801,7 +1024,7 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0, transport_data_msgok(st,src); /* See whether we should start negotiating a new key */ if (st->now > st->renegotiate_key_time) - initiate_key_setup(st,"incoming packet in renegotiation window"); + initiate_key_setup(st,"incoming packet in renegotiation window",0); return True; default: slog(st,LOG_SEC,"incoming encrypted message of type %08x " @@ -814,9 +1037,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:", @@ -836,6 +1059,24 @@ static bool_t send_msg(struct site *st) 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; + dispose_transform(&st->new_transform); + + 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)); @@ -864,7 +1105,7 @@ static void site_resolve_callback(void *sst, struct in_addr *address) slog(st,LOG_ERROR,"resolution of %s failed",st->address); ca_use=0; } - if (transport_compute_setupinit_peers(st,ca_use)) { + if (transport_compute_setupinit_peers(st,ca_use,0)) { enter_new_state(st,SITE_SENTMSG1); } else { /* Can't figure out who to try to to talk to */ @@ -873,14 +1114,15 @@ static void site_resolve_callback(void *sst, struct in_addr *address) } } -static bool_t initiate_key_setup(struct site *st, cstring_t reason) +static bool_t initiate_key_setup(struct site *st, cstring_t reason, + const struct comm_addr *prod_hint) { if (st->state!=SITE_RUN) return False; slog(st,LOG_SETUP_INIT,"initiating key exchange (%s)",reason); if (st->address) { slog(st,LOG_SETUP_INIT,"resolving peer address"); return enter_state_resolve(st); - } else if (transport_compute_setupinit_peers(st,0)) { + } else if (transport_compute_setupinit_peers(st,0,prod_hint)) { return enter_new_state(st,SITE_SENTMSG1); } slog(st,LOG_SETUP_INIT,"key exchange failed: no address for peer"); @@ -897,9 +1139,10 @@ static void activate_new_key(struct site *st) st->auxiliary_key.transform=st->current.transform; st->current.transform=st->new_transform; st->new_transform=t; + dispose_transform(&st->new_transform); - t->delkey(t->st); st->timeout=0; + 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; @@ -913,9 +1156,9 @@ static void activate_new_key(struct site *st) static void delete_one_key(struct site *st, struct data_key *key, cstring_t reason, cstring_t which, uint32_t loglevel) { - if (!key->transform->valid(key->transform->st)) return; + if (!is_transform_valid(key->transform)) return; if (reason) slog(st,loglevel,"%s deleted (%s)",which,reason); - key->transform->delkey(key->transform->st); + dispose_transform(&key->transform); key->key_timeout=0; } @@ -940,7 +1183,7 @@ static void enter_state_stop(struct site *st) st->state=SITE_STOP; st->timeout=0; delete_keys(st,"entering state STOP",LOG_TIMEOUT_KEY); - st->new_transform->delkey(st->new_transform->st); + dispose_transform(&st->new_transform); } static void set_link_quality(struct site *st) @@ -970,9 +1213,9 @@ static void enter_state_run(struct site *st) transport_peers_clear(st,&st->setup_peers); memset(st->localN,0,NONCELEN); memset(st->remoteN,0,NONCELEN); - st->new_transform->delkey(st->new_transform->st); + dispose_transform(&st->new_transform); memset(st->dhsecret,0,st->dh->len); - memset(st->sharedsecret,0,st->transform->keylen); + memset(st->sharedsecret,0,st->sharedsecretlen); set_link_quality(st); } @@ -1061,16 +1304,18 @@ static bool_t send_msg7(struct site *st, cstring_t reason) 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)); + buffer_init(&st->buffer,calculate_max_start_pad()); buf_append_uint32(&st->buffer,LABEL_MSG7); buf_append_string(&st->buffer,reason); - st->current.transform->forwards(st->current.transform->st, - &st->buffer, &transform_err); + if (call_transform_forwards(st, st->current.transform, + &st->buffer, &transform_err)) + goto free_out; buf_prepend_uint32(&st->buffer,LABEL_MSG0); buf_prepend_uint32(&st->buffer,st->index); buf_prepend_uint32(&st->buffer,st->current.remote_session_id); transport_xmit(st,&st->peers,&st->buffer,True); BUF_FREE(&st->buffer); + free_out: return True; } return False; @@ -1089,6 +1334,30 @@ static void enter_state_wait(struct site *st) /* XXX Erase keys etc. */ } +static void generate_prod(struct site *st, struct buffer_if *buf) +{ + buffer_init(buf,0); + buf_append_uint32(buf,0); + buf_append_uint32(buf,0); + buf_append_uint32(buf,LABEL_PROD); + buf_append_string(buf,st->localname); + buf_append_string(buf,st->remotename); +} + +static void generate_send_prod(struct site *st, + const struct comm_addr *source) +{ + if (!st->allow_send_prod) return; /* too soon */ + if (!(st->state==SITE_RUN || st->state==SITE_RESOLVE || + st->state==SITE_WAIT)) return; /* we'd ignore peer's MSG1 */ + + slog(st,LOG_SETUP_INIT,"prodding peer for key exchange"); + st->allow_send_prod=0; + generate_prod(st,&st->scratch); + dump_packet(st,&st->scratch,source,False); + source->comm->sendmsg(source->comm->st, &st->scratch, source); +} + static inline void site_settimeout(uint64_t timeout, int *timeout_io) { if (timeout) { @@ -1161,30 +1430,49 @@ static void site_outgoing(void *sst, struct buffer_if *buf) return; } + st->allow_send_prod=1; + /* In all other states we consider delivering the packet if we have a valid key and a valid address to send it to. */ 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, - buf, &transform_err); + if (call_transform_forwards(st, st->current.transform, + buf, &transform_err)) + goto free_out; buf_prepend_uint32(buf,LABEL_MSG0); buf_prepend_uint32(buf,st->index); buf_prepend_uint32(buf,st->current.remote_session_id); transport_xmit(st,&st->peers,buf,False); } + free_out: BUF_FREE(buf); return; } slog(st,LOG_DROP,"discarding outgoing packet of size %d",buf->size); BUF_FREE(buf); - initiate_key_setup(st,"outgoing packet"); + initiate_key_setup(st,"outgoing packet",0); +} + +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. */ + packets from our peers. + It should return True if the packet is recognised as being for + this current site instance (and should therefore not be processed + by other sites), even if the packet was otherwise ignored. */ static bool_t site_incoming(void *sst, struct buffer_if *buf, const struct comm_addr *source) { @@ -1192,67 +1480,80 @@ 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 (msgtype==LABEL_PROD) { + if (!named_for_us(st,buf,msgtype,&named_msg)) + return False; + dump_packet(st,buf,source,True); + if (st->state!=SITE_RUN) { + slog(st,LOG_DROP,"ignoring PROD when not in state RUN"); + } else if (current_valid(st)) { + slog(st,LOG_DROP,"ignoring PROD when we think we have a key"); + } else { + initiate_key_setup(st,"peer sent PROD packet",source); + } + 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) { - initiate_key_setup(st,"received a NAK"); + bool_t initiated; + initiated = initiate_key_setup(st,"received a NAK",0); + if (!initiated) generate_send_prod(st,source); } else { slog(st,LOG_SEC,"bad incoming NAK"); } @@ -1277,10 +1578,11 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, } break; case LABEL_MSG3: + case LABEL_MSG3BIS: /* Setup packet: expected only in state SENTMSG2 */ if (st->state!=SITE_SENTMSG2) { slog(st,LOG_UNEXPECTED,"unexpected MSG3"); - } else if (process_msg3(st,buf,source)) { + } else if (process_msg3(st,buf,source,msgtype)) { transport_setup_msgok(st,source); enter_new_state(st,SITE_SENTMSG4); } else { @@ -1420,21 +1722,28 @@ 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"); - if (!comms_cfg) cfgfatal(loc,"site","closure list \"comm\" not found\n"); - st->ncomms=list_length(comms_cfg); - st->comms=safe_malloc_ary(sizeof(*st->comms),st->ncomms,"comms"); - assert(st->ncomms); - for (i=0; incomms; i++) { - item_t *item=list_elem(comms_cfg,i); - if (item->type!=t_closure) - cfgfatal(loc,"site","comm is not a closure\n"); - closure_t *cl=item->data.closure; - if (cl->type!=CL_COMM) cfgfatal(loc,"site","comm closure wrong type\n"); - st->comms[i]=cl->interface; - } +#define GET_CLOSURE_LIST(dictkey,things,nthings,CL_TYPE) do{ \ + list_t *things##_cfg=dict_lookup(dict,dictkey); \ + if (!things##_cfg) \ + cfgfatal(loc,"site","closure list \"%s\" not found\n",dictkey); \ + st->nthings=list_length(things##_cfg); \ + st->things=safe_malloc_ary(sizeof(*st->things),st->nthings,dictkey "s"); \ + assert(st->nthings); \ + for (i=0; inthings; i++) { \ + item_t *item=list_elem(things##_cfg,i); \ + if (item->type!=t_closure) \ + cfgfatal(loc,"site","%s is not a closure\n",dictkey); \ + closure_t *cl=item->data.closure; \ + if (cl->type!=CL_TYPE) \ + cfgfatal(loc,"site","%s closure wrong type\n",dictkey); \ + st->things[i]=cl->interface; \ + } \ +}while(0) + + GET_CLOSURE_LIST("comm",comms,ncomms,CL_COMM); st->resolver=find_cl_if(dict,"resolver",CL_RESOLVER,True,"site",loc); st->log=find_cl_if(dict,"log",CL_LOG,True,"site",loc); @@ -1447,8 +1756,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, else st->remoteport=0; st->pubkey=find_cl_if(dict,"key",CL_RSAPUBKEY,True,"site",loc); - st->transform= - find_cl_if(dict,"transform",CL_TRANSFORM,True,"site",loc); + GET_CLOSURE_LIST("transform",transforms,ntransforms,CL_TRANSFORM); st->dh=find_cl_if(dict,"dh",CL_DH,True,"site",loc); st->hash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc); @@ -1487,6 +1795,8 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->log_events=string_list_to_word(dict_lookup(dict,"log-events"), log_event_table,"site"); + st->allow_send_prod=0; + st->tunname=safe_malloc(strlen(st->localname)+strlen(st->remotename)+5, "site_apply"); sprintf(st->tunname,"%s<->%s",st->localname,st->remotename); @@ -1494,19 +1804,11 @@ 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); - buffer_new(&st->scratch,0); + buffer_new(&st->scratch,SETUP_BUFFER_LEN); BUF_ALLOC(&st->scratch,"site:scratch"); /* We are interested in poll(), but only for timeouts. We don't have @@ -1514,37 +1816,36 @@ 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->chosen_transform=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 */ st->dhsecret=safe_malloc(st->dh->len,"site:dhsecret"); - st->sharedsecret=safe_malloc(st->transform->keylen,"site:sharedsecret"); - - /* We need to compute some properties of our comms */ -#define COMPUTE_WORST(pad) \ - int worst_##pad=0; \ - for (i=0; incomms; i++) { \ - int thispad=st->comms[i]->pad; \ - if (thispad > worst_##pad) \ - worst_##pad=thispad; \ + st->sharedsecretlen=st->sharedsecretallocd=0; + st->sharedsecret=0; + + for (i=0; intransforms; i++) { + struct transform_if *ti=st->transforms[i]; + uint32_t capbit = 1UL << ti->capab_transformnum; + if (st->local_capabilities & capbit) + slog(st,LOG_ERROR,"transformnum capability bit" + " %d (%#"PRIx32") reused", ti->capab_transformnum, capbit); + st->local_capabilities |= capbit; } - COMPUTE_WORST(min_start_pad) - COMPUTE_WORST(min_end_pad) /* We need to register the remote networks with the netlink device */ - st->netlink->reg(st->netlink->st, site_outgoing, st, - st->transform->max_start_pad+(4*4)+ - worst_min_start_pad, - st->transform->max_end_pad+worst_min_end_pad); + st->netlink->reg(st->netlink->st, site_outgoing, st); for (i=0; incomms; i++) st->comms[i]->request_notify(st->comms[i]->st, st, site_incoming); - 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->current.transform=0; + st->auxiliary_key.transform=0; + st->new_transform=0; + st->auxiliary_is_new=0; enter_state_stop(st); @@ -1643,23 +1944,30 @@ static void transport_record_peer(struct site *st, transport_peers *peers, } static bool_t transport_compute_setupinit_peers(struct site *st, - const struct comm_addr *configured_addr /* 0 if none or not found */) { + const struct comm_addr *configured_addr /* 0 if none or not found */, + const struct comm_addr *prod_hint_addr /* 0 if none */) { - if (!configured_addr && !transport_peers_valid(&st->peers)) + if (!configured_addr && !prod_hint_addr && + !transport_peers_valid(&st->peers)) return False; slog(st,LOG_SETUP_INIT, - (!configured_addr ? "using only %d old peer address(es)" - : "using configured address, and/or perhaps %d old peer address(es)"), - st->peers); + "using:%s%s %d old peer address(es)", + configured_addr ? " configured address;" : "", + prod_hint_addr ? " PROD hint address;" : "", + st->peers.npeers); /* Non-mobile peers havve st->peers.npeers==0 or ==1, since they * have transport_peers_max==1. The effect is that this code * always uses the configured address if supplied, or otherwise - * the existing data peer if one exists; this is as desired. */ + * the address of the incoming PROD, or the existing data peer if + * one exists; this is as desired. */ transport_peers_copy(st,&st->setup_peers,&st->peers); + if (prod_hint_addr) + transport_record_peer(st,&st->setup_peers,prod_hint_addr,"prod"); + if (configured_addr) transport_record_peer(st,&st->setup_peers,configured_addr,"setupinit");