#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:
#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 },
{ "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 }
};
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;
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;
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;
}
}
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) {
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;
{
string_t dhpub;
unsigned minor;
+ int ki;
st->retries=st->setup_retries;
BUF_ALLOC(&st->buffer,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; 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:
+ 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);
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;
{
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);
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);
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;
}
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);
+ 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)
{
/* 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; 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!"
+ " (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 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) {
* 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. */
- 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) ||
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;
/* initialised by named_for_us, or process_msgN for N!=1 */
if (msgtype==LABEL_MSG1) {
- if (!named_for_us(st,buf,msgtype,&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);
return True;
}
if (msgtype==LABEL_PROD) {
- if (!named_for_us(st,buf,msgtype,&msg))
+ if (!named_for_us(st,buf,msgtype,&msg,whynot))
return False;
dump_packet(st,buf,source,True,True);
if (st->state!=SITE_RUN) {
return True;
}
+ priomsg_update_fixed(whynot, comm_notify_whynot_general,
+ "not MSG1 or PROD; unknown dest index");
return False;
}
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);
- 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));
"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 */
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;