diff -Naur libreswan-3.15-orig/include/ietf_constants.h libreswan-3.15/include/ietf_constants.h
--- libreswan-3.15-orig/include/ietf_constants.h 2015-08-24 22:28:32.000000000 -0400
+++ libreswan-3.15/include/ietf_constants.h 2016-06-14 11:36:09.865743133 -0400
@@ -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
diff -Naur libreswan-3.15-orig/include/pluto_constants.h libreswan-3.15/include/pluto_constants.h
--- libreswan-3.15-orig/include/pluto_constants.h 2015-08-24 22:28:32.000000000 -0400
+++ libreswan-3.15/include/pluto_constants.h 2016-06-14 11:36:09.865743133 -0400
@@ -260,6 +260,7 @@
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 */
};
@@ -304,6 +305,7 @@
#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
*
diff -Naur libreswan-3.15-orig/lib/libswan/constants.c libreswan-3.15/lib/libswan/constants.c
--- libreswan-3.15-orig/lib/libswan/constants.c 2015-08-24 22:28:32.000000000 -0400
+++ libreswan-3.15/lib/libswan/constants.c 2016-06-14 11:36:09.866743168 -0400
@@ -196,6 +196,7 @@
"impair-force-fips",
"impair-send-key-size-check",
"impair-send-zero-gx",
+ "impair-send-bogus-dcookie",
NULL /* termination for bitnamesof() */
};
diff -Naur libreswan-3.15-orig/programs/pluto/ikev1_main.c libreswan-3.15/programs/pluto/ikev1_main.c
--- libreswan-3.15-orig/programs/pluto/ikev1_main.c 2016-06-14 11:35:30.808370130 -0400
+++ libreswan-3.15/programs/pluto/ikev1_main.c 2016-06-14 11:36:09.867743203 -0400
@@ -492,9 +492,9 @@
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");
diff -Naur libreswan-3.15-orig/programs/pluto/ikev2.c libreswan-3.15/programs/pluto/ikev2.c
--- libreswan-3.15-orig/programs/pluto/ikev2.c 2015-08-24 22:28:32.000000000 -0400
+++ libreswan-3.15/programs/pluto/ikev2.c 2016-06-14 11:36:09.868743239 -0400
@@ -1856,9 +1856,22 @@
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 || len > IKEv2_MAXIMUM_NONCE_SIZE) {
+ 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");
diff -Naur libreswan-3.15-orig/programs/pluto/ikev2_parent.c libreswan-3.15/programs/pluto/ikev2_parent.c
--- libreswan-3.15-orig/programs/pluto/ikev2_parent.c 2016-06-14 11:35:30.801369884 -0400
+++ libreswan-3.15/programs/pluto/ikev2_parent.c 2016-06-14 12:09:35.881321956 -0400
@@ -102,7 +102,7 @@
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,
@@ -426,6 +426,15 @@
init_out_pbs(&reply_stream, reply_buffer, sizeof(reply_buffer),
"reply packet");
+ 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 */
{
struct isakmp_hdr hdr;
@@ -434,7 +443,7 @@
/* 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 */
@@ -615,13 +624,36 @@
stf_status ikev2parent_inI1outR1(struct msg_digest *md)
{
+ bool seen_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()) {
+ /* Did we receive a DCOOKIE? */
+ for (ntfy = md->chain[ISAKMP_NEXT_v2N]; ntfy != NULL; ntfy = ntfy->next) {
+ switch (ntfy->payload.v2n.isan_type) {
+ case v2N_COOKIE:
+ DBG(DBG_CONTROLMORE, DBG_log("Received a NOTIFY payload of type COOKIE - we will verify the COOKIE"));
+ seen_dcookie = TRUE;
+ break;
+ default:
+ DBG(DBG_CONTROLMORE, DBG_log("Received unauthenticated %s notify - ignored",
+ enum_name(&ikev2_notify_names,
+ ntfy->payload.v2n.isan_type)));
+ }
+ }
+
+ /*
+ * 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 (seen_dcookie || require_dcookie) {
u_char dcookie[SHA1_DIGEST_SIZE];
chunk_t dc, ni, spiI;
@@ -635,7 +667,12 @@
* 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.
@@ -646,11 +683,10 @@
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 (seen_dcookie) {
const pb_stream *dc_pbs;
chunk_t idc;
@@ -670,19 +706,19 @@
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 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"));
@@ -691,7 +727,7 @@
}
} 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 */
@@ -1534,6 +1570,14 @@
integ_start, integ_size));
}
+ 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);
+ }
return STF_OK;
}
@@ -3479,45 +3523,38 @@
/*
* 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));
}
/*
diff -Naur libreswan-3.15-orig/programs/pluto/plutomain.c libreswan-3.15/programs/pluto/plutomain.c
--- libreswan-3.15-orig/programs/pluto/plutomain.c 2015-08-24 22:28:32.000000000 -0400
+++ libreswan-3.15/programs/pluto/plutomain.c 2016-06-14 11:36:09.872743379 -0400
@@ -571,6 +571,7 @@
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 }
};
@@ -1564,6 +1565,8 @@
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 */
diff -Naur libreswan-3.15-orig/programs/pluto/whack.c libreswan-3.15/programs/pluto/whack.c
--- libreswan-3.15-orig/programs/pluto/whack.c 2015-08-24 22:28:32.000000000 -0400
+++ libreswan-3.15/programs/pluto/whack.c 2016-06-14 11:36:09.872743379 -0400
@@ -701,6 +701,8 @@
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 },