X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=secnet.git;a=blobdiff_plain;f=site.c;h=191c36463da7cf7bd1d6186a1a1f51ebb0484877;hp=897366cf27817d67d778f08bdbd28e03d32d9e03;hb=HEAD;hpb=bac322db85a7951f61029e77c67f3ea6672f58a9 diff --git a/site.c b/site.c index 897366c..191c364 100644 --- a/site.c +++ b/site.c @@ -43,6 +43,7 @@ #include "util.h" #include "unaligned.h" #include "magic.h" +#include "pubkeys.h" #define SETUP_BUFFER_LEN 2048 @@ -60,6 +61,8 @@ #define DEFAULT_MOBILE_PEER_EXPIRY (2*60) /* [s] */ +#define PEERKEYS_SUFFIX_MAXLEN (sizeof("~incoming")-1) + /* Each site can be in one of several possible states. */ /* States: @@ -109,6 +112,8 @@ #define CASES_MSG3_KNOWN LABEL_MSG3: case LABEL_MSG3BIS +struct msg; + int32_t site_max_start_pad = 4*4; static cstring_t state_name(uint32_t state) @@ -140,6 +145,7 @@ static cstring_t state_name(uint32_t state) #define LOG_DUMP 0x00000100 #define LOG_ERROR 0x00000400 #define LOG_PEER_ADDRS 0x00000800 +#define LOG_SIGKEYS 0x00001000 static struct flagstr log_event_table[]={ { "unexpected", LOG_UNEXPECTED }, @@ -153,8 +159,9 @@ static struct flagstr log_event_table[]={ { "dump-packets", LOG_DUMP }, { "errors", LOG_ERROR }, { "peer-addrs", LOG_PEER_ADDRS }, + { "sigkeys", LOG_SIGKEYS }, { "default", LOG_SETUP_INIT|LOG_SETUP_TIMEOUT| - LOG_ACTIVATE_KEY|LOG_TIMEOUT_KEY|LOG_SEC|LOG_ERROR }, + LOG_ACTIVATE_KEY|LOG_TIMEOUT_KEY|LOG_SEC|LOG_ERROR|LOG_SIGKEYS }, { "all", 0xffffffff }, { NULL, 0 } }; @@ -250,6 +257,7 @@ typedef struct { } transport_peers; /* Basic operations on transport peer address sets */ +static void transport_peers_init(struct site *st, transport_peers *peers); static void transport_peers_clear(struct site *st, transport_peers *peers); static int transport_peers_valid(transport_peers *peers); static void transport_peers_copy(struct site *st, transport_peers *dst, @@ -312,8 +320,8 @@ struct site { struct resolver_if *resolver; struct log_if *log; struct random_if *random; - struct sigprivkey_if *privkey; - struct sigpubkey_if *pubkey; + struct privcache_if *privkeys; + struct sigprivkey_if *privkey_fixed; struct transform_if **transforms; int ntransforms; struct dh_if *dh; @@ -342,6 +350,9 @@ struct site { int resolving_n_results_all; int resolving_n_results_stored; struct comm_addr resolving_results[MAX_PEER_ADDRS]; + const char *peerkeys_path; + struct pathprefix_template peerkeys_tmpl; + struct peer_keyset *peerkeys_current, *peerkeys_kex; /* The currently established session */ struct data_key current; @@ -390,19 +401,28 @@ static uint32_t event_log_priority(struct site *st, uint32_t event) case LOG_DUMP: return M_DEBUG; case LOG_ERROR: return M_ERR; case LOG_PEER_ADDRS: return M_DEBUG; + case LOG_SIGKEYS: return M_INFO; default: return M_ERR; } } +static uint32_t slog_start(struct site *st, uint32_t event) +{ + uint32_t class=event_log_priority(st, event); + if (class) { + slilog_part(st->log,class,"%s: ",st->tunname); + } + return class; +} + static void vslog(struct site *st, uint32_t event, cstring_t msg, va_list ap) FORMAT(printf,3,0); static void vslog(struct site *st, uint32_t event, cstring_t msg, va_list ap) { uint32_t class; - class=event_log_priority(st, event); + class=slog_start(st,event); if (class) { - slilog_part(st->log,class,"%s: ",st->tunname); vslilog_part(st->log,class,msg,ap); slilog_part(st->log,class,"\n"); } @@ -457,7 +477,9 @@ 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 decrement_resolving_count(struct site *st, int by); -static bool_t enter_new_state(struct site *st,uint32_t next); +static bool_t enter_new_state(struct site *st,uint32_t next, + const struct msg *prompt + /* may be 0 for SENTMSG1 */); static void enter_state_wait(struct site *st); static void activate_new_key(struct site *st); @@ -505,6 +527,13 @@ static void dispose_transform(struct transform_inst_if **transform_var) type=buf_unprepend_uint32((b)); \ if (type!=(t)) return False; } while(0) +static _Bool type_is_msg23(uint32_t type) +{ + switch (type) { + case LABEL_MSG2: case CASES_MSG3_KNOWN: return True; + default: return False; + } +} static _Bool type_is_msg34(uint32_t type) { switch (type) { @@ -534,8 +563,13 @@ struct msg { char *pk; int32_t hashlen; struct alg_msg_data sig; + int n_pubkeys_accepted_nom; /* may be > MAX_SIG_KEYS ! */ + const struct sigkeyid *pubkeys_accepted[MAX_SIG_KEYS]; + int signing_key_index; }; +static const struct sigkeyid keyid_zero; + static int32_t wait_timeout(struct site *st) { int32_t t = st->wait_timeout_mean; int8_t factor; @@ -611,10 +645,13 @@ static void append_string_xinfo_done(struct buffer_if *buf, /* 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) +static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what, + const struct msg *prompt + /* may be 0 for MSG1 */) { string_t dhpub; unsigned minor; + int ki; st->retries=st->setup_retries; BUF_ALLOC(&st->buffer,what); @@ -626,13 +663,56 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what) struct xinfoadd xia; append_string_xinfo_start(&st->buffer,&xia,st->localname); - if ((st->local_capabilities & st->early_capabilities) || - (type != LABEL_MSG1)) { - buf_append_uint32(&st->buffer,st->local_capabilities); - } + buf_append_uint32(&st->buffer,st->local_capabilities); if (type_is_msg34(type)) { buf_append_uint16(&st->buffer,st->mtu_target); } + if (type_is_msg23(type)) { + buf_append_uint8(&st->buffer,st->peerkeys_kex->nkeys); + for (ki=0; kipeerkeys_kex->nkeys; ki++) { + struct peer_pubkey *pk = &st->peerkeys_kex->keys[ki]; + BUF_ADD_OBJ(append,&st->buffer,pk->id); + } + } + struct sigprivkey_if *privkey=0; + if (type_is_msg34(type)) { + assert(prompt->n_pubkeys_accepted_nom>0); + for (ki=0; + kin_pubkeys_accepted_nom && kipubkeys_accepted[ki]; + if (st->privkeys) { + privkey=st->privkeys->lookup(st->privkeys->st,kid,st->log); + if (privkey) goto privkey_found; + } else { + if (sigkeyid_equal(&keyid_zero,kid)) { + privkey=st->privkey_fixed; + goto privkey_found; + } + } + } + uint32_t class = slog_start(st,LOG_ERROR); + if (class) { + slilog_part(st->log,class,"no suitable private key, peer wanted"); + for (ki=0; + kin_pubkeys_accepted_nom && kilog,class, " " SIGKEYID_PR_FMT, + SIGKEYID_PR_VAL(prompt->pubkeys_accepted[ki])); + } + if (prompt->n_pubkeys_accepted_nom > MAX_SIG_KEYS) + slilog_part(st->log,class," +%d", + prompt->n_pubkeys_accepted_nom - MAX_SIG_KEYS); + slilog_part(st->log,class,"\n"); + } + return False; + + privkey_found: + slog(st,LOG_SIGKEYS,"using private key #%d " SIGKEYID_PR_FMT, + ki, SIGKEYID_PR_VAL(prompt->pubkeys_accepted[ki])); + buf_append_uint8(&st->buffer,ki); + } + append_string_xinfo_done(&st->buffer,&xia); buf_append_string(&st->buffer,st->remotename); @@ -653,10 +733,10 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what) buf_append_string(&st->buffer,dhpub); free(dhpub); - bool_t ok=st->privkey->sign(st->privkey->st, - st->buffer.start, - st->buffer.size, - &st->buffer); + bool_t ok=privkey->sign(privkey->st, + st->buffer.start, + st->buffer.size, + &st->buffer); if (!ok) goto fail; return True; @@ -685,7 +765,9 @@ static bool_t unpick_msg(struct site *st, uint32_t type, { unsigned minor; + m->n_pubkeys_accepted_nom=-1; m->capab_transformnum=-1; + m->signing_key_index=-1; m->hashstart=msg->start; CHECK_AVAIL(msg,4); m->dest=buf_unprepend_uint32(msg); @@ -703,6 +785,23 @@ static bool_t unpick_msg(struct site *st, uint32_t type, CHECK_AVAIL(&m->remote.extrainfo,2); m->remote_mtu=buf_unprepend_uint16(&m->remote.extrainfo); } + if (type_is_msg23(type) && m->remote.extrainfo.size) { + m->n_pubkeys_accepted_nom = buf_unprepend_uint8(&m->remote.extrainfo); + if (!m->n_pubkeys_accepted_nom) return False; + for (int ki_nom=0; ki_nomn_pubkeys_accepted_nom; ki_nom++) { + CHECK_AVAIL(&m->remote.extrainfo,KEYIDSZ); + struct sigkeyid *kid = buf_unprepend(&m->remote.extrainfo,KEYIDSZ); + if (ki_nompubkeys_accepted[ki_nom] = kid; + } + } else { + m->n_pubkeys_accepted_nom = 1; + m->pubkeys_accepted[0] = &keyid_zero; + } + if (type_is_msg34(type) && m->remote.extrainfo.size) { + m->signing_key_index=buf_unprepend_uint8(&m->remote.extrainfo); + } else { + m->signing_key_index=0; + } if (!unpick_name(msg,&m->local)) return False; if (type==LABEL_PROD) { CHECK_EMPTY(msg); @@ -739,7 +838,13 @@ static bool_t unpick_msg(struct site *st, uint32_t type, m->pk=buf_unprepend(msg,m->pklen); m->hashlen=msg->start-m->hashstart; - if (!st->pubkey->unpick(st->pubkey->st,msg,&m->sig)) { + if (m->signing_key_index < 0 || + m->signing_key_index >= st->peerkeys_kex->nkeys) { + return False; + } + struct sigpubkey_if *pubkey= + st->peerkeys_kex->keys[m->signing_key_index].pubkey; + if (!pubkey->unpick(pubkey->st,msg,&m->sig)) { return False; } @@ -792,14 +897,95 @@ static bool_t check_msg(struct site *st, uint32_t type, struct msg *m, return False; } -static bool_t generate_msg1(struct site *st) +static void peerkeys_maybe_incorporate(struct site *st, const char *file, + const char *whatmore, + int logcl_enoent) +{ + struct peer_keyset *atsuffix= + keyset_load(file,&st->scratch,st->log,logcl_enoent); + if (!atsuffix) return; + + if (st->peerkeys_current && + serial_cmp(atsuffix->serial,st->peerkeys_current->serial) <= 0) { + slog(st,LOG_SIGKEYS,"keys from %s%s are older, discarding", + file,whatmore); + keyset_dispose(&atsuffix); + int r=unlink(file); + if (r) slog(st,LOG_ERROR,"failed to remove old key update %s: %s\n", + st->peerkeys_tmpl.buffer,strerror(errno)); + return; + } else { + slog(st,LOG_SIGKEYS,"keys from %s%s are newer, installing", + file,whatmore); + keyset_dispose(&st->peerkeys_current); + st->peerkeys_current=atsuffix; + int r=rename(file,st->peerkeys_path); + if (r) slog(st,LOG_ERROR,"failed to install key update %s as %s: %s\n", + st->peerkeys_tmpl.buffer,st->peerkeys_path, + strerror(errno)); + } +} + +static void peerkeys_check_for_update(struct site *st) +{ + if (!st->peerkeys_path) return; + + pathprefix_template_setsuffix(&st->peerkeys_tmpl,"~proc"); + peerkeys_maybe_incorporate(st,st->peerkeys_tmpl.buffer, + " (found old update)", + M_DEBUG); + + pathprefix_template_setsuffix(&st->peerkeys_tmpl,"~update"); + const char *inputp=st->peerkeys_tmpl.buffer; + if (access(inputp,R_OK)) { + if (errno!=ENOENT) + slog(st,LOG_ERROR,"cannot access peer key update file %s\n", + inputp); + return; + } + + buffer_init(&st->scratch,0); + BUF_ADD_BYTES(append,&st->scratch, + st->peerkeys_tmpl.buffer, + strlen(st->peerkeys_tmpl.buffer)+1); + inputp=st->scratch.start; + + pathprefix_template_setsuffix(&st->peerkeys_tmpl,"~proc"); + const char *oursp=st->peerkeys_tmpl.buffer; + + int r=rename(inputp,oursp); + if (r) { + slog(st,LOG_ERROR,"failed to claim key update file %s as %s: %s", + inputp,oursp,strerror(errno)); + return; + } + + peerkeys_maybe_incorporate(st,oursp," (update)",M_ERR); +} + + +static bool_t kex_init(struct site *st) { + keyset_dispose(&st->peerkeys_kex); + peerkeys_check_for_update(st); + if (!st->peerkeys_current) { + slog(st,LOG_SETUP_INIT,"no peer public keys, abandoning key setup"); + return False; + } + st->peerkeys_kex = keyset_dup(st->peerkeys_current); st->random->generate(st->random->st,NONCELEN,st->localN); - return generate_msg(st,LABEL_MSG1,"site:MSG1"); + return True; +} + +static bool_t generate_msg1(struct site *st, const struct msg *prompt_maybe_0) +{ + return + generate_msg(st,LABEL_MSG1,"site:MSG1",prompt_maybe_0); } static bool_t process_msg1(struct site *st, struct buffer_if *msg1, - const struct comm_addr *src, struct msg *m) + const struct comm_addr *src, + const 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 @@ -811,16 +997,17 @@ static bool_t process_msg1(struct site *st, struct buffer_if *msg1, return True; } -static bool_t generate_msg2(struct site *st) +static bool_t generate_msg2(struct site *st, + const struct msg *prompt_may_be_null) { - st->random->generate(st->random->st,NONCELEN,st->localN); - return generate_msg(st,LABEL_MSG2,"site:MSG2"); + return + generate_msg(st,LABEL_MSG2,"site:MSG2",prompt_may_be_null); } static bool_t process_msg2(struct site *st, struct buffer_if *msg2, - const struct comm_addr *src) + const struct comm_addr *src, + struct msg *m /* returned */) { - struct msg m[1]; cstring_t err; if (!unpick_msg(st,LABEL_MSG2,msg2,m)) return False; @@ -864,7 +1051,7 @@ kind##_found: \ return True; } -static bool_t generate_msg3(struct site *st) +static bool_t generate_msg3(struct site *st, const struct msg *prompt) { /* Now we have our nonce and their nonce. Think of a secret key, and create message number 3. */ @@ -873,18 +1060,41 @@ static bool_t generate_msg3(struct site *st) (st->remote_capabilities & CAPAB_TRANSFORM_MASK) ? LABEL_MSG3BIS : LABEL_MSG3, - "site:MSG3"); + "site:MSG3",prompt); } static bool_t process_msg3_msg4(struct site *st, struct msg *m) { /* Check signature and store g^x mod m */ - if (!st->pubkey->check(st->pubkey->st, - m->hashstart,m->hashlen, - &m->sig)) { - slog(st,LOG_SEC,"msg3/msg4 signature failed check!"); + int ki; + + if (m->signing_key_index >= 0) { + if (m->signing_key_index >= st->peerkeys_kex->nkeys) + return False; + ki=m->signing_key_index; + } else { + for (ki=0; kipeerkeys_kex->nkeys; ki++) + if (sigkeyid_equal(&keyid_zero,&st->peerkeys_kex->keys[ki].id)) + goto found; + /* not found */ + slog(st,LOG_ERROR, + "peer signed with keyid zero, which we do not accept"); return False; + found:; } + struct sigpubkey_if *pubkey=st->peerkeys_kex->keys[ki].pubkey; + + if (!pubkey->check(pubkey->st, + m->hashstart,m->hashlen, + &m->sig)) { + slog(st,LOG_SEC,"msg3/msg4 signature failed check!" + " (key #%d " SIGKEYID_PR_FMT ")", + ki, SIGKEYID_PR_VAL(&st->peerkeys_kex->keys[ki].id)); + return False; + } + slog(st,LOG_SIGKEYS,"verified peer signature with key #%d " + SIGKEYID_PR_FMT, ki, + SIGKEYID_PR_VAL(&st->peerkeys_kex->keys[ki].id)); st->remote_adv_mtu=m->remote_mtu; @@ -892,9 +1102,9 @@ static bool_t process_msg3_msg4(struct site *st, struct msg *m) } static bool_t process_msg3(struct site *st, struct buffer_if *msg3, - const struct comm_addr *src, uint32_t msgtype) + const struct comm_addr *src, uint32_t msgtype, + struct msg *m /* returned */) { - struct msg m[1]; cstring_t err; switch (msgtype) { @@ -959,17 +1169,17 @@ kind##_found: \ return True; } -static bool_t generate_msg4(struct site *st) +static bool_t generate_msg4(struct site *st, const struct msg *prompt) { /* We have both nonces, their public key and our private key. Generate our public key, sign it and send it to them. */ - return generate_msg(st,LABEL_MSG4,"site:MSG4"); + return generate_msg(st,LABEL_MSG4,"site:MSG4",prompt); } static bool_t process_msg4(struct site *st, struct buffer_if *msg4, - const struct comm_addr *src) + const struct comm_addr *src, + struct msg *m /* returned */) { - struct msg m[1]; cstring_t err; if (!unpick_msg(st,LABEL_MSG4,msg4,m)) return False; @@ -1009,7 +1219,7 @@ static bool_t unpick_msg0(struct site *st, struct buffer_if *msg0, /* Leaves transformed part of buffer untouched */ } -static bool_t generate_msg5(struct site *st) +static bool_t generate_msg5(struct site *st, const struct msg *prompt) { cstring_t transform_err; @@ -1075,7 +1285,7 @@ static void create_msg6(struct site *st, struct transform_inst_if *transform, buf_prepend_uint32(&st->buffer,session_id); } -static bool_t generate_msg6(struct site *st) +static bool_t generate_msg6(struct site *st, const struct msg *prompt) { if (!is_transform_valid(st->new_transform)) return False; @@ -1352,7 +1562,7 @@ static void decrement_resolving_count(struct site *st, int by) switch (st->state) { case SITE_RESOLVE: if (transport_compute_setupinit_peers(st,addrs,naddrs,0)) { - enter_new_state(st,SITE_SENTMSG1); + enter_new_state(st,SITE_SENTMSG1,0); } else { /* Can't figure out who to try to to talk to */ slog(st,LOG_SETUP_INIT, @@ -1412,7 +1622,7 @@ static bool_t initiate_key_setup(struct site *st, cstring_t reason, slog(st,LOG_SETUP_INIT,"resolving peer address(es)"); return enter_state_resolve(st); } else if (transport_compute_setupinit_peers(st,0,0,prod_hint)) { - return enter_new_state(st,SITE_SENTMSG1); + return enter_new_state(st,SITE_SENTMSG1,0); } slog(st,LOG_SETUP_INIT,"key exchange failed: no address for peer"); return False; @@ -1502,13 +1712,15 @@ static void set_link_quality(struct site *st) static void enter_state_run(struct site *st) { - slog(st,LOG_STATE,"entering state RUN%s", - current_valid(st) ? " (keyed)" : " (unkeyed)"); + if (st->state!=SITE_STOP) + slog(st,LOG_STATE,"entering state RUN%s", + current_valid(st) ? " (keyed)" : " (unkeyed)"); st->state=SITE_RUN; st->timeout=0; st->setup_session_id=0; transport_peers_clear(st,&st->setup_peers); + keyset_dispose(&st->peerkeys_kex); FILLZERO(st->localN); FILLZERO(st->remoteN); dispose_transform(&st->new_transform); @@ -1566,21 +1778,25 @@ static bool_t enter_state_resolve(struct site *st) return ensure_resolving(st); } -static bool_t enter_new_state(struct site *st, uint32_t next) +static bool_t enter_new_state(struct site *st, uint32_t next, + const struct msg *prompt + /* may be 0 for SENTMSG1 */) { - bool_t (*gen)(struct site *st); + bool_t (*gen)(struct site *st, const struct msg *prompt); int r; slog(st,LOG_STATE,"entering state %s",state_name(next)); switch(next) { case SITE_SENTMSG1: state_assert(st,st->state==SITE_RUN || st->state==SITE_RESOLVE); + if (!kex_init(st)) return False; gen=generate_msg1; st->msg1_crossed_logged = False; break; case SITE_SENTMSG2: state_assert(st,st->state==SITE_RUN || st->state==SITE_RESOLVE || st->state==SITE_SENTMSG1 || st->state==SITE_WAIT); + if (!kex_init(st)) return False; gen=generate_msg2; break; case SITE_SENTMSG3: @@ -1611,7 +1827,7 @@ static bool_t enter_new_state(struct site *st, uint32_t next) if (hacky_par_start_failnow()) return False; - r= gen(st) && send_msg(st); + r= gen(st,prompt) && send_msg(st); hacky_par_end(&r, st->setup_retries, st->setup_retry_interval, @@ -1795,15 +2011,31 @@ static void site_outgoing(void *sst, struct buffer_if *buf) } static bool_t named_for_us(struct site *st, const struct buffer_if *buf_in, - uint32_t type, struct msg *m) + uint32_t type, struct msg *m, + struct priomsg *whynot) /* 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); + + if (!unpick_msg(st,type,buf,m)) { + priomsg_update_fixed(whynot, comm_notify_whynot_unpick, "malformed"); + return False; + } +#define NAME_MATCHES(lr) \ + if (!name_matches(&m->lr, st->lr##name)) { \ + if (priomsg_update_fixed(whynot, comm_notify_whynot_name_##lr, \ + "unknown " #lr " name: ")) { \ + truncmsg_add_packet_string(&whynot->m, m->lr.len, m->lr.name); \ + } \ + return False; \ + } + NAME_MATCHES(remote); + NAME_MATCHES(local ); +#undef NAME_MATCHES + + return True; } static bool_t we_have_priority(struct site *st, const struct msg *m) { @@ -1818,13 +2050,13 @@ static bool_t we_have_priority(struct site *st, const struct msg *m) { static bool_t setup_late_msg_ok(struct site *st, const struct buffer_if *buf_in, uint32_t msgtype, - const struct comm_addr *source) { + const struct comm_addr *source, + struct msg *m /* returned */) { /* For setup packets which seem from their type like they are * late. Maybe they came via a different path. All we do is make * a note of the sending address, iff they look like they are part * of the current key setup attempt. */ - struct msg m[1]; - if (!named_for_us(st,buf_in,msgtype,m)) + if (!named_for_us(st,buf_in,msgtype,m,0)) /* named_for_us calls unpick_msg which gets the nonces */ return False; if (!consttime_memeq(m->nR,st->remoteN,NONCELEN) || @@ -1841,7 +2073,8 @@ static bool_t setup_late_msg_ok(struct site *st, 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) + const struct comm_addr *source, + struct priomsg *whynot) { struct site *st=sst; @@ -1849,10 +2082,11 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, uint32_t dest=get_uint32(buf->start); uint32_t msgtype=get_uint32(buf->start+8); - struct msg named_msg; + struct msg msg; + /* initialised by named_for_us, or process_msgN for N!=1 */ if (msgtype==LABEL_MSG1) { - if (!named_for_us(st,buf,msgtype,&named_msg)) + if (!named_for_us(st,buf,msgtype,&msg,whynot)) return False; /* It's a MSG1 addressed to us. Decide what to do about it. */ dump_packet(st,buf,source,True,True); @@ -1860,9 +2094,9 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, st->state==SITE_WAIT) { /* We should definitely process it */ transport_compute_setupinit_peers(st,0,0,source); - if (process_msg1(st,buf,source,&named_msg)) { + if (process_msg1(st,buf,source,&msg)) { slog(st,LOG_SETUP_INIT,"key setup initiated by peer"); - bool_t entered=enter_new_state(st,SITE_SENTMSG2); + bool_t entered=enter_new_state(st,SITE_SENTMSG2,&msg); if (entered && st->addresses && st->local_mobile) /* We must do this as the very last thing, because the resolver callback might reenter us. */ @@ -1876,7 +2110,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, /* 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 (we_have_priority(st,&named_msg)) { + if (we_have_priority(st,&msg)) { BUF_FREE(buf); if (!st->msg1_crossed_logged++) slog(st,LOG_SETUP_INIT,"crossed msg1s; we are higher " @@ -1885,10 +2119,10 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, } else { slog(st,LOG_SETUP_INIT,"crossed msg1s; we are lower " "priority => use incoming msg1"); - if (process_msg1(st,buf,source,&named_msg)) { + if (process_msg1(st,buf,source,&msg)) { BUF_FREE(&st->buffer); /* Free our old message 1 */ transport_setup_msgok(st,source); - enter_new_state(st,SITE_SENTMSG2); + enter_new_state(st,SITE_SENTMSG2,&msg); } else { slog(st,LOG_ERROR,"failed to process an incoming " "crossed msg1 (we have low priority)"); @@ -1898,7 +2132,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, } } else if (st->state==SITE_SENTMSG2 || st->state==SITE_SENTMSG4) { - if (consttime_memeq(named_msg.nR,st->remoteN,NONCELEN)) { + if (consttime_memeq(msg.nR,st->remoteN,NONCELEN)) { /* We are ahead in the protocol, but that msg1 had the * peer's nonce so presumably it is from this key * exchange run, via a slower route */ @@ -1916,7 +2150,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, return True; } if (msgtype==LABEL_PROD) { - if (!named_for_us(st,buf,msgtype,&named_msg)) + if (!named_for_us(st,buf,msgtype,&msg,whynot)) return False; dump_packet(st,buf,source,True,True); if (st->state!=SITE_RUN) { @@ -1957,12 +2191,12 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, if (st->state!=SITE_SENTMSG1) { if ((st->state==SITE_SENTMSG3 || st->state==SITE_SENTMSG5) && - setup_late_msg_ok(st,buf,msgtype,source)) + setup_late_msg_ok(st,buf,msgtype,source,&msg)) break; slog(st,LOG_UNEXPECTED,"unexpected MSG2"); - } else if (process_msg2(st,buf,source)) { + } else if (process_msg2(st,buf,source,&msg)) { transport_setup_msgok(st,source); - enter_new_state(st,SITE_SENTMSG3); + enter_new_state(st,SITE_SENTMSG3,&msg); } else { slog(st,LOG_SEC,"invalid MSG2"); } @@ -1971,12 +2205,12 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, /* Setup packet: expected only in state SENTMSG2 */ if (st->state!=SITE_SENTMSG2) { if ((st->state==SITE_SENTMSG4) && - setup_late_msg_ok(st,buf,msgtype,source)) + setup_late_msg_ok(st,buf,msgtype,source,&msg)) break; slog(st,LOG_UNEXPECTED,"unexpected MSG3"); - } else if (process_msg3(st,buf,source,msgtype)) { + } else if (process_msg3(st,buf,source,msgtype,&msg)) { transport_setup_msgok(st,source); - enter_new_state(st,SITE_SENTMSG4); + enter_new_state(st,SITE_SENTMSG4,&msg); } else { slog(st,LOG_SEC,"invalid MSG3"); } @@ -1985,12 +2219,12 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, /* Setup packet: expected only in state SENTMSG3 */ if (st->state!=SITE_SENTMSG3) { if ((st->state==SITE_SENTMSG5) && - setup_late_msg_ok(st,buf,msgtype,source)) + setup_late_msg_ok(st,buf,msgtype,source,&msg)) break; slog(st,LOG_UNEXPECTED,"unexpected MSG4"); - } else if (process_msg4(st,buf,source)) { + } else if (process_msg4(st,buf,source,&msg)) { transport_setup_msgok(st,source); - enter_new_state(st,SITE_SENTMSG5); + enter_new_state(st,SITE_SENTMSG5,&msg); } else { slog(st,LOG_SEC,"invalid MSG4"); } @@ -2005,7 +2239,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, 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); + enter_new_state(st,SITE_RUN,&msg); } else { slog(st,LOG_SEC,"invalid MSG5"); } @@ -2045,17 +2279,18 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, return True; } + priomsg_update_fixed(whynot, comm_notify_whynot_general, + "not MSG1 or PROD; unknown dest index"); return False; } -static void site_control(void *vst, bool_t run) +static void site_startup(void *vst) { struct site *st=vst; - if (run) enter_state_run(st); - else enter_state_stop(st); + enter_state_run(st); } -static void site_phase_hook(void *sst, uint32_t newphase) +static void site_phase_shutdown_hook(void *sst, uint32_t newphase) { struct site *st=sst; @@ -2063,6 +2298,13 @@ static void site_phase_hook(void *sst, uint32_t newphase) send_msg7(st,"shutting down"); } +static void site_phase_run_hook(void *sst, uint32_t newphase) +{ + struct site *st=sst; + slog(st,LOG_STATE,"entering phase RUN in state %s", + state_name(st->state)); +} + static void site_childpersist_clearkeys(void *sst, uint32_t newphase) { struct site *st=sst; @@ -2096,8 +2338,11 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->cl.apply=NULL; st->cl.interface=&st->ops; st->ops.st=st; - st->ops.control=site_control; + st->ops.startup=site_startup; st->ops.status=site_status; + st->peerkeys_path=0; + st->peerkeys_tmpl.buffer=0; + st->peerkeys_current=st->peerkeys_kex=0; /* First parameter must be a dict */ item=list_elem(args,0); @@ -2105,9 +2350,19 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, cfgfatal(loc,"site","parameter must be a dictionary\n"); dict=item->data.dict; + st->log=find_cl_if(dict,"log",CL_LOG,True,"site",loc); + st->log_events=string_list_to_word(dict_lookup(dict,"log-events"), + log_event_table,"site"); + st->localname=dict_read_string(dict, "local-name", True, "site", loc); st->remotename=dict_read_string(dict, "name", True, "site", loc); + st->tunname=safe_malloc(strlen(st->localname)+strlen(st->remotename)+5, + "site_apply"); + sprintf(st->tunname,"%s<->%s",st->localname,st->remotename); + + /* Now slog is working */ + st->keepalive=dict_read_bool(dict,"keepalive",False,"site",loc,False); st->peer_mobile=dict_read_bool(dict,"mobile",False,"site",loc,False); @@ -2169,26 +2424,46 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, } st->resolver=find_cl_if(dict,"resolver",CL_RESOLVER,True,"site",loc); - st->log=find_cl_if(dict,"log",CL_LOG,True,"site",loc); st->random=find_cl_if(dict,"random",CL_RANDOMSRC,True,"site",loc); - st->privkey=find_cl_if(dict,"local-key",CL_SIGPRIVKEY,True,"site",loc); + st->privkeys=find_cl_if(dict,"key-cache",CL_PRIVCACHE,False,"site",loc); + if (!st->privkeys) { + st->privkey_fixed= + find_cl_if(dict,"local-key",CL_SIGPRIVKEY,True,"site",loc); + } + + struct sigpubkey_if *fixed_pubkey + =find_cl_if(dict,"key",CL_SIGPUBKEY,False,"site",loc); + st->peerkeys_path=dict_read_string(dict,"peer-keys",fixed_pubkey==0, + "site",loc); + if (st->peerkeys_path) { + pathprefix_template_init(&st->peerkeys_tmpl,st->peerkeys_path, + PEERKEYS_SUFFIX_MAXLEN + 1 /* nul */); + st->peerkeys_current=keyset_load(st->peerkeys_path, + &st->scratch,st->log,M_ERR); + if (fixed_pubkey) { + fixed_pubkey->dispose(fixed_pubkey->st); + } + } else { + assert(fixed_pubkey); + NEW(st->peerkeys_current); + st->peerkeys_current->refcount=1; + st->peerkeys_current->nkeys=1; + st->peerkeys_current->keys[0].id=keyid_zero; + st->peerkeys_current->keys[0].pubkey=fixed_pubkey; + slog(st,LOG_SIGKEYS, + "using old-style fixed peer public key (no `peer-keys')"); + } + st->addresses=dict_read_string_array(dict,"address",False,"site",loc,0); if (st->addresses) st->remoteport=dict_read_number(dict,"port",True,"site",loc,0); else st->remoteport=0; - st->pubkey=find_cl_if(dict,"key",CL_SIGPUBKEY,True,"site",loc); GET_CLOSURE_LIST("transform",transforms,ntransforms,CL_TRANSFORM); st->dh=find_cl_if(dict,"dh",CL_DH,True,"site",loc); - if (st->privkey->sethash || st->pubkey->sethash) { - struct hash_if *hash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc); - if (st->privkey->sethash) st->privkey->sethash(st->privkey->st,hash); - if (st->pubkey->sethash) st->pubkey->sethash(st->pubkey->st,hash); - } - #define DEFAULT(D) (st->peer_mobile || st->local_mobile \ ? DEFAULT_MOBILE_##D : DEFAULT_##D) #define CFG_NUMBER(k,D) dict_read_number(dict,(k),False,"site",loc,DEFAULT(D)); @@ -2223,16 +2498,9 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, "renegotiate-time must be less than key-lifetime\n"); } - st->log_events=string_list_to_word(dict_lookup(dict,"log-events"), - log_event_table,"site"); - st->resolving_count=0; 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); - /* 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 */ @@ -2252,8 +2520,8 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, 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); + transport_peers_init(st,&st->peers); + transport_peers_init(st,&st->setup_peers); /* XXX mlock these */ st->dhsecret=safe_malloc(st->dh->len,"site:dhsecret"); st->sharedsecretlen=st->sharedsecretallocd=0; @@ -2291,7 +2559,8 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, enter_state_stop(st); - add_hook(PHASE_SHUTDOWN,site_phase_hook,st); + add_hook(PHASE_SHUTDOWN,site_phase_shutdown_hook,st); + add_hook(PHASE_RUN, site_phase_run_hook, st); add_hook(PHASE_CHILDPERSIST,site_childpersist_clearkeys,st); return new_closure(&st->cl); @@ -2339,6 +2608,8 @@ static void transport_peers_debug(struct site *st, transport_peers *dst, static void transport_peers_expire(struct site *st, transport_peers *peers) { /* peers must be sorted first */ + if (st->local_mobile) return; + int previous_peers=peers->npeers; struct timeval oldest; oldest.tv_sec = tv_now->tv_sec - st->mobile_peer_expiry; @@ -2467,9 +2738,14 @@ static void transport_data_msgok(struct site *st, const struct comm_addr *a) { static int transport_peers_valid(transport_peers *peers) { return peers->npeers; } +static void transport_peers_init(struct site *st, transport_peers *peers) { + peers->npeers= 0; +} static void transport_peers_clear(struct site *st, transport_peers *peers) { + bool_t need_debug=!!peers->npeers; peers->npeers= 0; - transport_peers_debug(st,peers,"clear",0,0,0); + if (need_debug) + transport_peers_debug(st,peers,"clear",0,0,0); } static void transport_peers_copy(struct site *st, transport_peers *dst, const transport_peers *src) {