Blob Blame History Raw
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 },