#define SITE_SENTMSG5 7
#define SITE_WAIT 8
+#define CASES_MSG3_KNOWN LABEL_MSG3: case LABEL_MSG3BIS
+
int32_t site_max_start_pad = 4*4;
static cstring_t state_name(uint32_t state)
struct resolver_if *resolver;
struct log_if *log;
struct random_if *random;
- struct rsaprivkey_if *privkey;
- struct rsapubkey_if *pubkey;
+ struct sigprivkey_if *privkey;
+ struct sigpubkey_if *pubkey;
struct transform_if **transforms;
int ntransforms;
struct dh_if *dh;
struct hash_if *hash;
uint32_t index; /* Index of this site */
+ uint32_t early_capabilities;
uint32_t local_capabilities;
int32_t setup_retries; /* How many times to send setup packets */
int32_t setup_retry_interval; /* Initial timeout for setup packets */
- int32_t wait_timeout; /* How long to wait if setup unsuccessful */
+ int32_t wait_timeout_mean; /* How long to wait if setup unsuccessful */
int32_t mobile_peer_expiry; /* How long to remember 2ary addresses */
int32_t key_lifetime; /* How long a key lasts once set up */
int32_t key_renegotiate_time; /* If we see traffic (or a keepalive)
static _Bool type_is_msg34(uint32_t type)
{
- return
- type == LABEL_MSG3 ||
- type == LABEL_MSG3BIS ||
- type == LABEL_MSG4;
+ switch (type) {
+ case CASES_MSG3_KNOWN: case LABEL_MSG4: return True;
+ default: return False;
+ }
}
struct parsedname {
int32_t pklen;
char *pk;
int32_t hashlen;
- int32_t siglen;
- char *sig;
+ struct alg_msg_data sig;
};
+static int32_t wait_timeout(struct site *st) {
+ int32_t t = st->wait_timeout_mean;
+ int8_t factor;
+ if (t < INT_MAX/2) {
+ st->random->generate(st->random->st,sizeof(factor),&factor);
+ t += (t / 256) * factor;
+ }
+ return t;
+}
+
static _Bool set_new_transform(struct site *st, char *pk)
{
_Bool ok;
slog(st,LOG_SETUP_INIT,"key exchange negotiated transform"
" %d (capabilities ours=%#"PRIx32" theirs=%#"PRIx32")",
- st->chosen_transform->capab_transformnum,
+ st->chosen_transform->capab_bit,
st->local_capabilities, st->remote_capabilities);
return True;
}
{
void *hst;
uint8_t *hash;
- string_t dhpub, sig;
+ string_t dhpub;
+ unsigned minor;
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 & CAPAB_EARLY) || (type != LABEL_MSG1)) {
+ if ((st->local_capabilities & st->early_capabilities) ||
+ (type != LABEL_MSG1)) {
buf_append_uint32(&st->buffer,st->local_capabilities);
}
if (type_is_msg34(type)) {
if (hacky_par_mid_failnow()) return False;
- if (type==LABEL_MSG3BIS)
- buf_append_uint8(&st->buffer,st->chosen_transform->capab_transformnum);
+ if (MSGMAJOR(type) == 3) do {
+ minor = MSGMINOR(type);
+ if (minor < 1) break;
+ buf_append_uint8(&st->buffer,st->chosen_transform->capab_bit);
+ } while (0);
dhpub=st->dh->makepublic(st->dh->st,st->dhsecret,st->dh->len);
buf_append_string(&st->buffer,dhpub);
hst=st->hash->init();
st->hash->update(hst,st->buffer.start,st->buffer.size);
st->hash->final(hst,hash);
- sig=st->privkey->sign(st->privkey->st,hash,st->hash->len);
- buf_append_string(&st->buffer,sig);
- free(sig);
+ bool_t ok=st->privkey->sign(st->privkey->st,hash,st->hash->len,
+ &st->buffer);
+ if (!ok) goto fail;
free(hash);
return True;
+
+ fail:
+ free(hash);
+ return False;
}
static bool_t unpick_name(struct buffer_if *msg, struct parsedname *nm)
static bool_t unpick_msg(struct site *st, uint32_t type,
struct buffer_if *msg, struct msg *m)
{
+ unsigned minor;
+
m->capab_transformnum=-1;
m->hashstart=msg->start;
CHECK_AVAIL(msg,4);
CHECK_EMPTY(msg);
return True;
}
- if (type==LABEL_MSG3BIS) {
- CHECK_AVAIL(msg,1);
- m->capab_transformnum = buf_unprepend_uint8(msg);
- } else {
- m->capab_transformnum = CAPAB_TRANSFORMNUM_ANCIENT;
- }
+ if (MSGMAJOR(type) == 3) do {
+ minor = MSGMINOR(type);
+#define MAYBE_READ_CAP(minminor, kind, dflt) do { \
+ if (minor < (minminor)) \
+ m->capab_##kind##num = (dflt); \
+ else { \
+ CHECK_AVAIL(msg, 1); \
+ m->capab_##kind##num = buf_unprepend_uint8(msg); \
+ } \
+} while (0)
+ MAYBE_READ_CAP(1, transform, CAPAB_BIT_ANCIENTTRANSFORM);
+#undef MAYBE_READ_CAP
+ } while (0);
CHECK_AVAIL(msg,2);
m->pklen=buf_unprepend_uint16(msg);
CHECK_AVAIL(msg,m->pklen);
m->pk=buf_unprepend(msg,m->pklen);
m->hashlen=msg->start-m->hashstart;
- CHECK_AVAIL(msg,2);
- m->siglen=buf_unprepend_uint16(msg);
- CHECK_AVAIL(msg,m->siglen);
- m->sig=buf_unprepend(msg,m->siglen);
- CHECK_EMPTY(msg);
- /* In `process_msg3_msg4' below, we assume that we can write a nul
- * terminator following the signature. Make sure there's enough space.
- */
- if (msg->start >= msg->base + msg->alloclen)
+ if (!st->pubkey->unpick(st->pubkey->st,msg,&m->sig)) {
return False;
+ }
+
+ CHECK_EMPTY(msg);
return True;
}
return False;
}
if (type==LABEL_MSG2) return True;
- if (!consttime_memeq(m->nR,st->remoteN,NONCELEN)!=0) {
+ if (!consttime_memeq(m->nR,st->remoteN,NONCELEN)) {
*error="wrong remotely-generated nonce";
return False;
}
/* MSG3 has complicated rules about capabilities, which are
* handled in process_msg3. */
- if (type==LABEL_MSG3 || type==LABEL_MSG3BIS) return True;
+ if (MSGMAJOR(type) == 3) return True;
if (m->remote_capabilities!=st->remote_capabilities) {
*error="remote capabilities changed";
return False;
/* Select the transform to use */
- uint32_t remote_transforms = st->remote_capabilities & CAPAB_TRANSFORM_MASK;
- if (!remote_transforms)
+ uint32_t remote_crypto_caps = st->remote_capabilities & CAPAB_TRANSFORM_MASK;
+ if (!remote_crypto_caps)
/* old secnets only had this one transform */
- remote_transforms = 1UL << CAPAB_TRANSFORMNUM_ANCIENT;
+ remote_crypto_caps = 1UL << CAPAB_BIT_ANCIENTTRANSFORM;
+
+#define CHOOSE_CRYPTO(kind, whats) do { \
+ struct kind##_if *iface; \
+ uint32_t bit, ours = 0; \
+ int i; \
+ for (i= 0; i < st->n##kind##s; i++) { \
+ iface=st->kind##s[i]; \
+ bit = 1UL << iface->capab_bit; \
+ if (bit & remote_crypto_caps) goto kind##_found; \
+ ours |= bit; \
+ } \
+ slog(st,LOG_ERROR,"no " whats " in common" \
+ " (us %#"PRIx32"; them: %#"PRIx32")", \
+ st->local_capabilities & ours, remote_crypto_caps); \
+ return False; \
+kind##_found: \
+ st->chosen_##kind = iface; \
+} while (0)
- struct transform_if *ti;
- int i;
- for (i=0; i<st->ntransforms; i++) {
- ti=st->transforms[i];
- if ((1UL << ti->capab_transformnum) & remote_transforms)
- goto transform_found;
- }
- slog(st,LOG_ERROR,"no transforms in common"
- " (us %#"PRIx32"; them: %#"PRIx32")",
- st->local_capabilities & CAPAB_TRANSFORM_MASK,
- remote_transforms);
- return False;
- transform_found:
- st->chosen_transform=ti;
+ CHOOSE_CRYPTO(transform, "transforms");
+
+#undef CHOOSE_CRYPTO
memcpy(st->remoteN,m.nR,NONCELEN);
return True;
and create message number 3. */
st->random->generate(st->random->st,st->dh->len,st->dhsecret);
return generate_msg(st,
- (st->remote_capabilities & CAPAB_TRANSFORM_MASK
- ? LABEL_MSG3BIS : LABEL_MSG3),
+ (st->remote_capabilities & CAPAB_TRANSFORM_MASK)
+ ? LABEL_MSG3BIS
+ : LABEL_MSG3,
"site:MSG3");
}
hst=st->hash->init();
st->hash->update(hst,m->hashstart,m->hashlen);
st->hash->final(hst,hash);
- /* Terminate signature with a '0' - already checked that this will fit */
- m->sig[m->siglen]=0;
- if (!st->pubkey->check(st->pubkey->st,hash,st->hash->len,m->sig)) {
+ if (!st->pubkey->check(st->pubkey->st,
+ hash,st->hash->len,
+ &m->sig)) {
slog(st,LOG_SEC,"msg3/msg4 signature failed check!");
free(hash);
return False;
struct msg m;
cstring_t err;
- assert(msgtype==LABEL_MSG3 || msgtype==LABEL_MSG3BIS);
+ switch (msgtype) {
+ case CASES_MSG3_KNOWN: break;
+ default: assert(0);
+ }
if (!unpick_msg(st,msgtype,msg3,&m)) return False;
if (!check_msg(st,msgtype,&m,&err)) {
return False;
}
uint32_t capab_adv_late = m.remote_capabilities
- & ~st->remote_capabilities & CAPAB_EARLY;
+ & ~st->remote_capabilities & st->early_capabilities;
if (capab_adv_late) {
slog(st,LOG_SEC,"msg3 impermissibly adds early capability flag(s)"
" %#"PRIx32" (was %#"PRIx32", now %#"PRIx32")",
capab_adv_late, st->remote_capabilities, m.remote_capabilities);
return False;
}
- st->remote_capabilities|=m.remote_capabilities;
- struct transform_if *ti;
- int i;
- for (i=0; i<st->ntransforms; i++) {
- ti=st->transforms[i];
- if (ti->capab_transformnum == m.capab_transformnum)
- goto transform_found;
- }
- slog(st,LOG_SEC,"peer chose unknown-to-us transform %d!",
- m.capab_transformnum);
- return False;
- transform_found:
- st->chosen_transform=ti;
+#define CHOSE_CRYPTO(kind, what) do { \
+ struct kind##_if *iface; \
+ int i; \
+ for (i=0; i<st->n##kind##s; i++) { \
+ iface=st->kind##s[i]; \
+ if (iface->capab_bit == m.capab_##kind##num) \
+ goto kind##_found; \
+ } \
+ slog(st,LOG_SEC,"peer chose unknown-to-us " what " %d!", \
+ m.capab_##kind##num); \
+ return False; \
+kind##_found: \
+ st->chosen_##kind=iface; \
+} while (0)
+
+ CHOSE_CRYPTO(transform, "transform");
+
+#undef CHOSE_CRYPTO
if (!process_msg3_msg4(st,&m))
return False;
+ /* Update our idea of the remote site's capabilities, now that we've
+ * verified that its message was authentic.
+ *
+ * Our previous idea of the remote site's capabilities came from the
+ * unauthenticated MSG1. We've already checked that this new message
+ * doesn't change any of the bits we relied upon in the past, but it may
+ * also have set additional capability bits. We simply throw those away
+ * now, and use the authentic capabilities from this MSG3. */
+ st->remote_capabilities=m.remote_capabilities;
+
/* Terminate their DH public key with a '0' */
m.pk[m.pklen]=0;
/* Invent our DH secret key */
return True;
}
-static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
+static transform_apply_return
+decrypt_msg0(struct site *st, struct buffer_if *msg0,
const struct comm_addr *src)
{
cstring_t transform_err, auxkey_err, newkey_err="n/a";
if (!st->auxiliary_is_new)
delete_one_key(st,&st->auxiliary_key,
"peer has used new key","auxiliary key",LOG_SEC);
- return True;
+ return 0;
}
- if (problem==transform_apply_seqrange)
- goto skew;
+ if (transform_apply_return_badseq(problem))
+ goto badseq;
buffer_copy(msg0, &st->scratch);
problem = call_transform_reverse(st,st->auxiliary_key.transform,
st->auxiliary_is_new=0;
st->renegotiate_key_time=st->auxiliary_renegotiate_key_time;
}
- return True;
+ return 0;
}
- if (problem==transform_apply_seqrange)
- goto skew;
+ if (transform_apply_return_badseq(problem))
+ goto badseq;
if (st->state==SITE_SENTMSG5) {
buffer_copy(msg0, &st->scratch);
BUF_FREE(&st->buffer);
st->timeout=0;
activate_new_key(st);
- return True; /* do process the data in this packet */
+ return 0; /* do process the data in this packet */
}
- if (problem==transform_apply_seqrange)
- goto skew;
+ if (transform_apply_return_badseq(problem))
+ goto badseq;
}
slog(st,LOG_SEC,"transform: %s (aux: %s, new: %s)",
transform_err,auxkey_err,newkey_err);
initiate_key_setup(st,"incoming message would not decrypt",0);
send_nak(src,m.dest,m.source,m.type,msg0,"message would not decrypt");
- return False;
+ assert(problem);
+ return problem;
- skew:
- slog(st,LOG_DROP,"transform: %s (merely skew)",transform_err);
+ badseq:
+ slog(st,LOG_DROP,"transform: %s (bad seq.)",transform_err);
assert(problem);
return problem;
}
const struct comm_addr *src)
{
uint32_t type;
+ transform_apply_return problem;
- if (!decrypt_msg0(st,msg0,src))
+ problem = decrypt_msg0(st,msg0,src);
+ if (problem==transform_apply_seqdupe) {
+ /* We recently received another copy of this packet, maybe due
+ * to polypath. That's not a problem; indeed, for the
+ * purposes of transport address management it is a success.
+ * But we don't want to process the packet. */
+ transport_data_msgok(st,src);
+ return False;
+ }
+ if (problem)
return False;
CHECK_AVAIL(msg0,4);
} else if (st->local_mobile) {
/* Not very good. We should queue (another) renegotiation
* so that we can update the peer address. */
- st->key_renegotiate_time=st->now+st->wait_timeout;
+ st->key_renegotiate_time=st->now+wait_timeout(st);
} else {
slog(st,LOG_SETUP_INIT,"resolution failed: "
" continuing to use source address of peer's packets");
static void enter_state_run(struct site *st)
{
- slog(st,LOG_STATE,"entering state RUN");
+ slog(st,LOG_STATE,"entering state RUN%s",
+ current_valid(st) ? " (keyed)" : " (unkeyed)");
st->state=SITE_RUN;
st->timeout=0;
static void enter_state_wait(struct site *st)
{
slog(st,LOG_STATE,"entering state WAIT");
- st->timeout=st->now+st->wait_timeout;
+ st->timeout=st->now+wait_timeout(st);
st->state=SITE_WAIT;
set_link_quality(st);
BUF_FREE(&st->buffer); /* will have had an outgoing packet in it */
}
static bool_t we_have_priority(struct site *st, const struct msg *m) {
- if ((st->local_capabilities & m->remote_capabilities)
- && CAPAB_PRIORITY_MOBILE) {
+ if (st->local_capabilities & m->remote_capabilities &
+ CAPAB_PRIORITY_MOBILE) {
if (st->local_mobile) return True;
if (st-> peer_mobile) return False;
}
return st->our_name_later;
}
+static bool_t setup_late_msg_ok(struct site *st,
+ const struct buffer_if *buf_in,
+ uint32_t msgtype,
+ const struct comm_addr *source) {
+ /* 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;
+ if (!named_for_us(st,buf_in,msgtype,&m))
+ /* named_for_us calls unpick_msg which gets the nonces */
+ return False;
+ if (!consttime_memeq(m.nR,st->remoteN,NONCELEN) ||
+ !consttime_memeq(m.nL,st->localN, NONCELEN))
+ /* spoof ? from stale run ? who knows */
+ return False;
+ transport_setup_msgok(st,source);
+ return True;
+}
+
/* This function is called by the communication device to deliver
packets from our peers.
It should return True if the packet is recognised as being for
BUF_FREE(buf);
return True;
}
+ } else if (st->state==SITE_SENTMSG2 ||
+ st->state==SITE_SENTMSG4) {
+ if (consttime_memeq(named_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 */
+ transport_setup_msgok(st,source);
+ } else {
+ slog(st,LOG_UNEXPECTED,"competing incoming message 1");
+ }
+ BUF_FREE(buf);
+ return True;
}
/* The message 1 was received at an unexpected stage of the
- key setup. XXX POLICY - what do we do? */
+ key setup. Well, they lost the race. */
slog(st,LOG_UNEXPECTED,"unexpected incoming message 1");
BUF_FREE(buf);
return True;
case LABEL_MSG2:
/* Setup packet: expected only in state SENTMSG1 */
if (st->state!=SITE_SENTMSG1) {
+ if ((st->state==SITE_SENTMSG3 ||
+ st->state==SITE_SENTMSG5) &&
+ setup_late_msg_ok(st,buf,msgtype,source))
+ break;
slog(st,LOG_UNEXPECTED,"unexpected MSG2");
} else if (process_msg2(st,buf,source)) {
transport_setup_msgok(st,source);
slog(st,LOG_SEC,"invalid MSG2");
}
break;
- case LABEL_MSG3:
- case LABEL_MSG3BIS:
+ case CASES_MSG3_KNOWN:
/* 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))
+ break;
slog(st,LOG_UNEXPECTED,"unexpected MSG3");
} else if (process_msg3(st,buf,source,msgtype)) {
transport_setup_msgok(st,source);
case LABEL_MSG4:
/* 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))
+ break;
slog(st,LOG_UNEXPECTED,"unexpected MSG4");
} else if (process_msg4(st,buf,source)) {
transport_setup_msgok(st,source);
assert(index_sequence < 0xffffffffUL);
st->index = ++index_sequence;
st->local_capabilities = 0;
+ st->early_capabilities = CAPAB_PRIORITY_MOBILE;
st->netlink=find_cl_if(dict,"link",CL_NETLINK,True,"site",loc);
#define GET_CLOSURE_LIST(dictkey,things,nthings,CL_TYPE) do{ \
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_RSAPRIVKEY,True,"site",loc);
+ st->privkey=find_cl_if(dict,"local-key",CL_SIGPRIVKEY,True,"site",loc);
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_RSAPUBKEY,True,"site",loc);
+ st->pubkey=find_cl_if(dict,"key",CL_SIGPUBKEY,True,"site",loc);
GET_CLOSURE_LIST("transform",transforms,ntransforms,CL_TRANSFORM);
st->key_lifetime= CFG_NUMBER("key-lifetime", KEY_LIFETIME);
st->setup_retries= CFG_NUMBER("setup-retries", SETUP_RETRIES);
st->setup_retry_interval= CFG_NUMBER("setup-timeout", SETUP_RETRY_INTERVAL);
- st->wait_timeout= CFG_NUMBER("wait-time", WAIT_TIME);
+ st->wait_timeout_mean= CFG_NUMBER("wait-time", WAIT_TIME);
st->mtu_target= dict_read_number(dict,"mtu-target",False,"site",loc,0);
st->mobile_peer_expiry= dict_read_number(
st->sharedsecretlen=st->sharedsecretallocd=0;
st->sharedsecret=0;
- for (i=0; i<st->ntransforms; i++) {
- struct transform_if *ti=st->transforms[i];
- uint32_t capbit = 1UL << ti->capab_transformnum;
- if (st->local_capabilities & capbit)
- slog(st,LOG_ERROR,"transformnum capability bit"
- " %d (%#"PRIx32") reused", ti->capab_transformnum, capbit);
- st->local_capabilities |= capbit;
- }
+#define SET_CAPBIT(bit) do { \
+ uint32_t capflag = 1UL << (bit); \
+ if (st->local_capabilities & capflag) \
+ slog(st,LOG_ERROR,"capability bit" \
+ " %d (%#"PRIx32") reused", (bit), capflag); \
+ st->local_capabilities |= capflag; \
+} while (0)
+
+ for (i=0; i<st->ntransforms; i++)
+ SET_CAPBIT(st->transforms[i]->capab_bit);
+
+#undef SET_CAPBIT
if (st->local_mobile || st->peer_mobile)
st->local_capabilities |= CAPAB_PRIORITY_MOBILE;