#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 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;
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);
if (type_is_msg34(type)) {
buf_append_uint16(&st->buffer,st->mtu_target);
}
- struct sigprivkey_if *privkey=st->privkey;
+ 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);
{
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,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)
{
/* 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 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->privkey=find_cl_if(dict,"local-key",CL_SIGPRIVKEY,True,"site",loc);
+ 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) {
+ 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) {
+ 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->privkey);
- 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 */