chiark / gitweb /
site: Read public peer keys from key file
[secnet.git] / site.c
diff --git a/site.c b/site.c
index 897366cf27817d67d778f08bdbd28e03d32d9e03..002b7dd48b84b8fb90347f8cbd9d98e8c94908e7 100644 (file)
--- 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
 
 
 #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 +143,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 +157,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 }
 };
@@ -312,8 +317,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 +347,8 @@ 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 peer_keyset *peerkeys_current, *peerkeys_kex;
 
     /* The currently established session */
     struct data_key current;
@@ -390,19 +397,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 +473,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 +523,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 +559,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 +641,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);
@@ -633,6 +666,50 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what)
     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; ki<st->peerkeys_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;
+            ki<prompt->n_pubkeys_accepted_nom && ki<MAX_SIG_KEYS;
+            ki++) {
+           const struct sigkeyid *kid=prompt->pubkeys_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;
+                ki<prompt->n_pubkeys_accepted_nom && ki<MAX_SIG_KEYS;
+                ki++) {
+               slilog_part(st->log,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:
+       buf_append_uint8(&st->buffer,ki);
+    }
+
     append_string_xinfo_done(&st->buffer,&xia);
 
     buf_append_string(&st->buffer,st->remotename);
@@ -653,10 +730,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 +762,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 +782,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_nom<m->n_pubkeys_accepted_nom; ki_nom++) {
+           CHECK_AVAIL(&m->remote.extrainfo,KEYIDSZ);
+           struct sigkeyid *kid = buf_unprepend(&m->remote.extrainfo,KEYIDSZ);
+           if (ki_nom<MAX_SIG_KEYS) m->pubkeys_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 +835,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 +894,27 @@ 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 bool_t kex_init(struct site *st)
 {
+    keyset_dispose(&st->peerkeys_kex);
+    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 +926,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 +980,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,15 +989,33 @@ 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)) {
+    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; ki<st->peerkeys_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!");
        return False;
     }
@@ -892,9 +1026,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 +1093,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 +1143,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 +1209,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 +1486,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 +1546,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;
@@ -1509,6 +1643,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);
@@ -1566,21 +1701,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 +1750,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 +1934,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 +1973,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 +1996,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 +2005,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 +2017,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 +2033,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 +2042,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 +2055,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 +2073,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 +2114,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 +2128,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 +2142,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 +2162,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,6 +2202,8 @@ 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;
 }
 
@@ -2080,6 +2239,17 @@ static void site_childpersist_clearkeys(void *sst, uint32_t newphase)
        crypto operations, but that's a task for another day. */
 }
 
+static void setup_sethash(struct site *st, dict_t *dict,
+                         struct hash_if **hash, 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);
+}
+#define SETUP_SETHASH(k) do{                                           \
+    if ((k)->sethash)                                                  \
+        setup_sethash(st,dict, &hash,loc, (k)->sethash,(k)->st);       \
+}while(0)
+
 static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
                          list_t *args)
 {
@@ -2098,6 +2268,8 @@ 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_current=st->peerkeys_kex=0;
 
     /* First parameter must be a dict */
     item=list_elem(args,0);
@@ -2172,23 +2344,46 @@ 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);
 
-    st->privkey=find_cl_if(dict,"local-key",CL_SIGPRIVKEY,True,"site",loc);
+    struct hash_if *hash=0;
+
+    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);
+       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) {
+       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);
+       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);
 
-    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));