X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=site.c;h=5d2f519e482b8bbcc0fc299a33f3d40a10569a23;hb=4bc0381500403be5f3e40bc39f65a2d8fb75cf19;hp=58db825992249ee07d03dff9ea008430e37d0545;hpb=2bd5686634133cda9a40c6050ac2174c11504f1e;p=secnet.git diff --git a/site.c b/site.c index 58db825..5d2f519 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: @@ -315,10 +318,10 @@ struct site { int ncomms; struct resolver_if *resolver; struct log_if *log; + struct hash_if *defhash; struct random_if *random; struct privcache_if *privkeys; struct sigprivkey_if *privkey_fixed; - struct sigpubkey_if *pubkey; struct transform_if **transforms; int ntransforms; struct dh_if *dh; @@ -347,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; @@ -559,6 +565,7 @@ struct msg { 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; @@ -664,13 +671,11 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what, buf_append_uint16(&st->buffer,st->mtu_target); } if (type_is_msg23(type)) { - /* The code to advertise a public key acceptance list will - * come in a moment. But right now we must add the byte - * indicating which key advertised by our peer we used, which - * comes after the public key acceptance list. So for now, - * explicitly advertise a list with just 0000000000. */ - buf_append_uint8(&st->buffer,1); - BUF_ADD_OBJ(append,&st->buffer,keyid_zero); + 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)) { @@ -706,6 +711,8 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what, 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); } @@ -763,6 +770,7 @@ static bool_t unpick_msg(struct site *st, uint32_t type, 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); @@ -792,6 +800,11 @@ static bool_t unpick_msg(struct site *st, uint32_t type, 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); @@ -827,8 +840,13 @@ static bool_t unpick_msg(struct site *st, uint32_t type, CHECK_AVAIL(msg,m->pklen); m->pk=buf_unprepend(msg,m->pklen); m->hashlen=msg->start-m->hashstart; - struct sigpubkey_if *pubkey=st->pubkey; + 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; } @@ -882,8 +900,82 @@ static bool_t check_msg(struct site *st, uint32_t type, struct msg *m, return False; } +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,st->defhash); + 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 True; } @@ -976,15 +1068,36 @@ static bool_t generate_msg3(struct site *st, const struct msg *prompt) static bool_t process_msg3_msg4(struct site *st, struct msg *m) { - struct sigpubkey_if *pubkey=st->pubkey; - /* Check signature and store g^x mod m */ + 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!"); + 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; @@ -1609,6 +1722,7 @@ static void enter_state_run(struct site *st) 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); @@ -2205,14 +2319,15 @@ static void site_childpersist_clearkeys(void *sst, uint32_t newphase) } static void setup_sethash(struct site *st, dict_t *dict, - struct hash_if **hash, struct cloc loc, + struct cloc loc, sig_sethash_fn *sethash, void *sigkey_st) { - if (!*hash) *hash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc); - sethash(sigkey_st,*hash); + if (!st->defhash) + cfgfatal(loc,"site","other settings imply `hash' key is needed"); + sethash(sigkey_st,st->defhash); } #define SETUP_SETHASH(k) do{ \ if ((k)->sethash) \ - setup_sethash(st,dict, &hash,loc, (k)->sethash,(k)->st); \ + setup_sethash(st,dict,loc, (k)->sethash,(k)->st); \ }while(0) static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, @@ -2233,6 +2348,9 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->ops.st=st; st->ops.control=site_control; 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); @@ -2307,7 +2425,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->log=find_cl_if(dict,"log",CL_LOG,True,"site",loc); st->random=find_cl_if(dict,"random",CL_RANDOMSRC,True,"site",loc); - struct hash_if *hash=0; + st->defhash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc); st->privkeys=find_cl_if(dict,"key-cache",CL_PRIVCACHE,False,"site",loc); if (!st->privkeys) { @@ -2316,18 +2434,40 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, SETUP_SETHASH(st->privkey_fixed); } + 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, + st->defhash); + if (fixed_pubkey) { + fixed_pubkey->dispose(fixed_pubkey->st); + } + } else { + assert(fixed_pubkey); + SETUP_SETHASH(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); - SETUP_SETHASH(st->pubkey); - #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));