#include "util.h"
#include "unaligned.h"
#include "magic.h"
+#include "pubkeys.h"
#define SETUP_BUFFER_LEN 2048
#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:
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;
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;
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;
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; 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)) {
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);
}
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);
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);
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;
}
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;
}
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; 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!");
+ 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;
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);
}
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,
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);
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);
}
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);
- 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) {
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));
"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 */