Permalink
Browse files

IKEv2: Harden dcookies and nonces

- Split minimum/maximum nonce sizes for IKEv1 and IKEv2
- Raise minimum nonce size in IKEv2 from 8 to 16 bytes as per RFC-7296
- Raise the default nonce size for IKEv1 and IKEv2 from 16 to 32 bytes
- Add --impair-send-bogus-dcookie option to pluto for testing
- Use SHA2-256 instead of SHA1 as hash algorithm for dcookies
- Validate dcookies even if we did not expect one [violates RFC-7296]
  • Loading branch information...
letoams committed Jan 6, 2016
1 parent 2943ee0 commit d935515aeef12225a54ba34478d394ac1f3c0750
View
@@ -270,10 +270,29 @@
#define LOCALSECRETSIZE BYTES_FOR_BITS(256)
/* limits on nonce sizes. See RFC2409 "The internet key exchange (IKE)" 5 */
#define MINIMUM_NONCE_SIZE 8 /* bytes */
#define DEFAULT_NONCE_SIZE 16 /* bytes */
#define MAXIMUM_NONCE_SIZE 256 /* bytes */
/* Limits on nonce sizes */
/*
* IKEv1 RFC-2409:
* The length of nonce payload MUST be between 8 and 256 bytes inclusive.
*/
#define IKEv1_MINIMUM_NONCE_SIZE 8 /* bytes */
#define IKEv1_MAXIMUM_NONCE_SIZE 256 /* bytes */
/*
* IKEv2 RFC-7296:
* Nonces used in IKEv2 MUST be randomly chosen, MUST be at least 128 bits
* in size, and MUST be at least half the key size of the negotiated
* pseudorandom function (PRF). However, the initiator chooses the nonce
* before the outcome of the negotiation is known. Because of that, the
* nonce has to be long enough for all the PRFs being proposed.
*/
#define IKEv2_MINIMUM_NONCE_SIZE 16 /* bytes */
#define IKEv2_MAXIMUM_NONCE_SIZE 256 /* bytes */
/* Default is based on minimum IKEv2 requirement */
#define DEFAULT_NONCE_SIZE 32 /* bytes */
#define COOKIE_SIZE 8
#define MAX_ISAKMP_SPI_SIZE 16
@@ -263,6 +263,7 @@ enum {
IMPAIR_FORCE_FIPS_IX, /* causes pluto to believe we are in fips mode, NSS needs its own hack */
IMPAIR_SEND_KEY_SIZE_CHECK_IX, /* causes pluto to omit checking configured ESP key sizes for testing */
IMPAIR_SEND_ZERO_GX_IX, /* causes pluto to send a g^x that is zero, breaking DH calculation */
IMPAIR_SEND_BOGUS_DCOOKIE_IX, /* causes pluto to send a a bogus IKEv2 DCOOKIE */
IMPAIR_roof_IX /* first unassigned IMPAIR */
};
@@ -307,6 +308,7 @@ enum {
#define IMPAIR_FORCE_FIPS LELEM(IMPAIR_FORCE_FIPS_IX)
#define IMPAIR_SEND_KEY_SIZE_CHECK LELEM(IMPAIR_SEND_KEY_SIZE_CHECK_IX)
#define IMPAIR_SEND_ZERO_GX LELEM(IMPAIR_SEND_ZERO_GX_IX)
#define IMPAIR_SEND_BOGUS_DCOOKIE LELEM(IMPAIR_SEND_BOGUS_DCOOKIE_IX)
/* State of exchanges
*
View
@@ -196,6 +196,7 @@ const char *const debug_bit_names[] = {
"impair-force-fips",
"impair-send-key-size-check",
"impair-send-zero-gx",
"impair-send-bogus-dcookie",
NULL /* termination for bitnamesof() */
};
@@ -505,9 +505,9 @@ notification_t accept_v1_nonce(struct msg_digest *md, chunk_t *dest,
pb_stream *nonce_pbs = &md->chain[ISAKMP_NEXT_NONCE]->pbs;
size_t len = pbs_left(nonce_pbs);
if (len < MINIMUM_NONCE_SIZE || MAXIMUM_NONCE_SIZE < len) {
if (len < IKEv1_MINIMUM_NONCE_SIZE || IKEv1_MAXIMUM_NONCE_SIZE < len) {
loglog(RC_LOG_SERIOUS, "%s length not between %d and %d",
name, MINIMUM_NONCE_SIZE, MAXIMUM_NONCE_SIZE);
name, IKEv1_MINIMUM_NONCE_SIZE, IKEv1_MAXIMUM_NONCE_SIZE);
return PAYLOAD_MALFORMED; /* ??? */
}
clonereplacechunk(*dest, nonce_pbs->cur, len, "nonce");
View
@@ -1834,9 +1834,22 @@ v2_notification_t accept_v2_nonce(struct msg_digest *md,
nonce_pbs = &md->chain[ISAKMP_NEXT_v2Ni]->pbs;
len = pbs_left(nonce_pbs);
if (len < MINIMUM_NONCE_SIZE || MAXIMUM_NONCE_SIZE < len) {
loglog(RC_LOG_SERIOUS, "%s length not between %d and %d",
name, MINIMUM_NONCE_SIZE, MAXIMUM_NONCE_SIZE);
/*
* RFC 7296 Section 2.10:
* Nonces used in IKEv2 MUST be randomly chosen, MUST be at least 128
* bits in size, and MUST be at least half the key size of the
* negotiated pseudorandom function (PRF). However, the initiator
* chooses the nonce before the outcome of the negotiation is known.
* Because of that, the nonce has to be long enough for all the PRFs
* being proposed.
*
* We will check for a minimum/maximum here. Once the PRF is selected,
* we verify the nonce is big enough.
*/
if (len < IKEv2_MINIMUM_NONCE_SIZE || IKEv2_MAXIMUM_NONCE_SIZE < len) {
loglog(RC_LOG_SERIOUS, "%s length %zu not between %d and %d",
name, len, IKEv2_MINIMUM_NONCE_SIZE, IKEv2_MAXIMUM_NONCE_SIZE);
return v2N_INVALID_SYNTAX; /* ??? */
}
clonereplacechunk(*dest, nonce_pbs->cur, len, "nonce");
@@ -99,7 +99,7 @@ static crypto_req_cont_func ikev2_parent_outI1_continue; /* type assertion */
static stf_status ikev2_parent_outI1_tail(struct pluto_crypto_req_cont *ke,
struct pluto_crypto_req *r);
static bool ikev2_get_dcookie(u_char *dcookie, chunk_t st_ni,
static void ikev2_get_dcookie(u_char *dcookie, chunk_t st_ni,
ip_address *addr, chunk_t spiI);
static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
@@ -432,6 +432,14 @@ static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
if (c->fake_strongswan)
vids++;
if (DBGP(IMPAIR_SEND_BOGUS_DCOOKIE)) {
/* add or mangle a dcookie so what we will send is bogus */
DBG_log("Mangling dcookie because --impair-send-bogus-dcookie is set");
freeanychunk(st->st_dcookie);
st->st_dcookie.ptr = alloc_bytes(1, "mangled dcookie");
st->st_dcookie.len = 1;
messupn(st->st_dcookie.ptr, 1);
}
/* HDR out */
{
@@ -441,7 +449,7 @@ static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
/* Impair function will raise major/minor by 1 for testing */
hdr.isa_version = build_ikev2_version();
hdr.isa_np = st->st_dcookie.ptr != NULL?
hdr.isa_np = st->st_dcookie.ptr != NULL ?
ISAKMP_NEXT_v2N : ISAKMP_NEXT_v2SA;
hdr.isa_xchg = ISAKMP_v2_SA_INIT;
/* add original initiator flag - version flag could be set */
@@ -648,14 +656,33 @@ static stf_status ikev2_parent_inI1outR1_tail(
stf_status ikev2parent_inI1outR1(struct msg_digest *md)
{
bool got_dcookie = FALSE;
bool require_dcookie = require_ddos_cookies();
struct payload_digest *ntfy;
if (drop_new_exchanges()) {
/* only log for debug to prevent disk filling up */
DBG(DBG_CONTROL,DBG_log("pluto is overloaded with half-open IKE SAs - dropping IKE_INIT request"));
return STF_IGNORE;
}
if (require_ddos_cookies()) {
u_char dcookie[SHA1_DIGEST_SIZE];
/* Did we receive a DCOOKIE? */
for (ntfy = md->chain[ISAKMP_NEXT_v2N]; ntfy != NULL; ntfy = ntfy->next) {
if (ntfy->payload.v2n.isan_type == v2N_COOKIE) {
DBG(DBG_CONTROLMORE, DBG_log(
"Received a NOTIFY payload of type COOKIE - we will verify the COOKIE"));
got_dcookie = TRUE;
break;
}
}
/*
* The RFC states we should ignore unexpected cookies. We purposefully
* violate the RFC and validate the cookie anyway. This prevents an
* attacker from being able to inject a lot of data used later to HMAC
*/
if (got_dcookie || require_dcookie) {
u_char dcookie[SHA2_256_DIGEST_SIZE];
chunk_t dc, ni, spiI;
setchunk(spiI, md->hdr.isa_icookie, COOKIE_SIZE);
@@ -668,7 +695,14 @@ stf_status ikev2parent_inI1outR1(struct msg_digest *md)
* size of the negotiated pseudorandom function (PRF).
* (We can check for minimum 128bit length)
*/
if (ni.len < BYTES_FOR_BITS(128)) {
/*
* XXX: Note that we check the nonce size in accept_v2_nonce() so this
* check is extra. I guess since we need to extract the nonce to calculate
* the cookie, it is cheap to check here and reject.
*/
if (ni.len < IKEv2_MINIMUM_NONCE_SIZE || IKEv2_MAXIMUM_NONCE_SIZE < ni.len) {
/*
* If this were a DDOS, we cannot afford to log.
* We do log if we are debugging.
@@ -679,11 +713,9 @@ stf_status ikev2parent_inI1outR1(struct msg_digest *md)
ikev2_get_dcookie(dcookie, ni, &md->sender, spiI);
dc.ptr = dcookie;
dc.len = SHA1_DIGEST_SIZE;
dc.len = SHA2_256_DIGEST_SIZE;
/* check a v2N payload with type COOKIE */
if (md->chain[ISAKMP_NEXT_v2N] != NULL &&
md->chain[ISAKMP_NEXT_v2N]->payload.v2n.isan_type == v2N_COOKIE) {
if (ntfy != NULL) {
const pb_stream *dc_pbs;
chunk_t idc;
@@ -703,19 +735,19 @@ stf_status ikev2parent_inI1outR1(struct msg_digest *md)
DBG(DBG_CONTROLMORE,
DBG_dump_chunk("received dcookie", idc);
DBG_dump("dcookie computed", dcookie,
SHA1_DIGEST_SIZE));
SHA2_256_DIGEST_SIZE));
if (idc.len != SHA1_DIGEST_SIZE ||
!memeq(idc.ptr, dcookie, SHA1_DIGEST_SIZE)) {
if (idc.len != SHA2_256_DIGEST_SIZE ||
!memeq(idc.ptr, dcookie, SHA2_256_DIGEST_SIZE)) {
DBG(DBG_CONTROLMORE, DBG_log(
"mismatch in DOS v2N_COOKIE: dropping message (possible DoS attack)"
"mismatch in DOS v2N_COOKIE: dropping message (possible attack)"
));
return STF_IGNORE;
}
DBG(DBG_CONTROLMORE, DBG_log(
"dcookie received matched computed one"));
} else {
/* we are under DOS attack I1 contains no DOS COOKIE */
/* we are under DOS attack and I1 contains no COOKIE */
DBG(DBG_CONTROLMORE,
DBG_log("busy mode on. received I1 without a valid dcookie");
DBG_log("send a dcookie and forget this state"));
@@ -724,7 +756,7 @@ stf_status ikev2parent_inI1outR1(struct msg_digest *md)
}
} else {
DBG(DBG_CONTROLMORE,
DBG_log("anti-DDoS cookies not required"));
DBG_log("anti-DDoS cookies not required (and no cookie received)"));
}
/* authentication policy alternatives in order of decreasing preference */
@@ -1203,8 +1235,8 @@ static stf_status ikev2_parent_inI1outR1_tail(
*
*/
/* STATE_PARENT_I1: R1B --> I1B
* <-- HDR, N
* HDR, N, SAi1, KEi, Ni -->
* <-- HDR, N(COOKIE)
* HDR, N(COOKIE), SAi1, KEi, Ni -->
*/
stf_status ikev2parent_inR1BoutI1B(struct msg_digest *md)
{
@@ -3649,45 +3681,38 @@ stf_status ikev2parent_inR2(struct msg_digest *md)
/*
* Cookie = <VersionIDofSecret> | Hash(Ni | IPi | SPIi | <secret>)
* where <secret> is a randomly generated secret known only to the
* in LSW implementation <VersionIDofSecret> is not used.
* where <secret> is a randomly generated secret known only to us
*
* Our implementation does not use <VersionIDofSecret> which means
* once a day and while under DOS attack, we could fail a few cookies
* until the peer restarts from scratch.
*/
static bool ikev2_get_dcookie(u_char *dcookie, chunk_t ni,
static void ikev2_get_dcookie(u_char *dcookie, chunk_t ni,
ip_address *addr, chunk_t spiI)
{
size_t addr_length;
SHA1_CTX ctx_sha1;
sha256_context ctx_sha256;
unsigned char addr_buff[
sizeof(union { struct in_addr A;
struct in6_addr B;
})];
addr_length = addrbytesof(addr, addr_buff, sizeof(addr_buff));
SHA1Init(&ctx_sha1);
SHA1Update(&ctx_sha1, ni.ptr, ni.len);
SHA1Update(&ctx_sha1, addr_buff, addr_length);
SHA1Update(&ctx_sha1, spiI.ptr, spiI.len);
SHA1Update(&ctx_sha1, ikev2_secret_of_the_day,
SHA1_DIGEST_SIZE);
SHA1Final(dcookie, &ctx_sha1);
sha256_init(&ctx_sha256);
sha256_write(&ctx_sha256, ni.ptr, ni.len);
sha256_write(&ctx_sha256, addr_buff, addr_length);
sha256_write(&ctx_sha256, spiI.ptr, spiI.len);
sha256_write(&ctx_sha256, ikev2_secret_of_the_day,
SHA2_256_DIGEST_SIZE);
sha256_final(dcookie, &ctx_sha256);
DBG(DBG_PRIVATE,
DBG_log("ikev2 secret_of_the_day used %s, length %d",
ikev2_secret_of_the_day,
SHA1_DIGEST_SIZE));
SHA2_256_DIGEST_SIZE));
DBG(DBG_CRYPT,
DBG_dump("computed dcookie: HASH(Ni | IPi | SPIi | <secret>)",
dcookie, SHA1_DIGEST_SIZE));
#if 0
ikev2_secrets_recycle++;
if (ikev2_secrets_recycle >= 32768) {
/* handed out too many cookies, cycle secrets */
ikev2_secrets_recycle = 0;
/* can we call init_secrets() without adding an EVENT? */
init_secrets();
}
#endif
return TRUE;
dcookie, SHA2_256_DIGEST_SIZE));
}
/*
@@ -574,6 +574,7 @@ static const struct option long_opts[] = {
I("send-no-ikev2-auth\0", IMPAIR_SEND_NO_IKEV2_AUTH_IX),
I("force-fips\0", IMPAIR_FORCE_FIPS_IX),
I("send-zero-gx\0", IMPAIR_SEND_ZERO_GX_IX),
I("send-bogus-dcookie\0", IMPAIR_SEND_BOGUS_DCOOKIE_IX),
#undef I
{ 0, 0, 0, 0 }
};
@@ -1574,6 +1575,8 @@ int main(int argc, char **argv)
libreswan_log("Warning: IMPAIR_SEND_NO_IKEV2_AUTH enabled");
if (DBGP(IMPAIR_SEND_ZERO_GX))
libreswan_log("Warning: IMPAIR_SEND_ZERO_GX enabled");
if (DBGP(IMPAIR_SEND_BOGUS_DCOOKIE))
libreswan_log("Warning: IMPAIR_SEND_BOGUS_DCOOKIE enabled");
/* Initialize all of the various features */
View
@@ -709,6 +709,8 @@ static const struct option long_opts[] = {
IMPAIR_SEND_KEY_SIZE_CHECK_IX + DO },
{ "impair-send-zero-gx", no_argument, NULL,
IMPAIR_SEND_ZERO_GX_IX + DO },
{ "impair-send-bogus-dcookie", no_argument, NULL,
IMPAIR_SEND_BOGUS_DCOOKIE_IX + DO },
# undef DO
{ "whackrecord", required_argument, NULL, OPT_WHACKRECORD + OO },
{ "whackstoprecord", no_argument, NULL, OPT_WHACKSTOPRECORD + OO },

0 comments on commit d935515

Please sign in to comment.