diff --git a/src/test/unit/unit_esp.c b/src/test/unit/unit_esp.c index 1dbe906d..755495fd 100644 --- a/src/test/unit/unit_esp.c +++ b/src/test/unit/unit_esp.c @@ -86,10 +86,12 @@ static uint8_t spi_a[4] = { 0x11, 0x22, 0x33, 0x44 }; static uint8_t spi_b[4] = { 0x55, 0x66, 0x77, 0x88 }; static uint8_t spi_c[4] = { 0x99, 0xAA, 0xBB, 0xCC }; static uint8_t spi_d[4] = { 0xFF, 0xEE, 0xDD, 0xCC }; /* overflows pool */ +static uint8_t spi_zero[4] = { 0x00, 0x00, 0x00, 0x00 }; /* Test IP addresses. */ #define T_SRC "192.168.1.1" #define T_DST "192.168.1.2" +#define T_MISC "192.168.1.9" /* * builds a minimal IPv4 packet (with Ethernet frame header). @@ -206,6 +208,14 @@ START_TEST(test_sa_hmac_bad) int ret; esp_setup(); + /* everything good but spi */ + ret = wolfIP_esp_sa_new_hmac(1, (uint8_t *)spi_zero, + atoip4(T_SRC), atoip4(T_DST), + ESP_AUTH_SHA256_RFC4868, + (uint8_t *)k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, -1); + /* auth with wrong icv len */ ret = wolfIP_esp_sa_new_hmac(1, (uint8_t *)spi_a, atoip4(T_SRC), atoip4(T_DST), @@ -255,6 +265,12 @@ START_TEST(test_sa_cbc_hmac_bad) { int ret; esp_setup(); + ret = wolfIP_esp_sa_new_cbc_hmac(1, (uint8_t *)spi_zero, + atoip4(T_SRC), atoip4(T_DST), + (uint8_t *)k_aes128, sizeof(k_aes128), + ESP_AUTH_NONE, NULL, 0, 0); + ck_assert_int_eq(ret, -1); + ret = wolfIP_esp_sa_new_cbc_hmac(1, (uint8_t *)spi_a, atoip4(T_SRC), atoip4(T_DST), (uint8_t *)k_aes128, sizeof(k_aes128), @@ -886,6 +902,196 @@ START_TEST(test_unwrap_invalid_pad_pattern) } END_TEST +/* + * test esp ip src/dst filtering + * + * This tests 4 scenarios: + * 1. misc src, dst address that does not match the SA triplet. + * 2. wildcard dst ip in outbound SA only, with misc dst address. + * 3. wildcard dst ip in both in/out SAs, with misc dst address. + * 4. wildcard src ip in both in/out SAs, with misc src address. + * */ +START_TEST(test_unwrap_ip_filtering) +{ + static uint8_t buf[LINK_MTU + 256]; + uint8_t ref[64]; + uint32_t frame_len; + uint16_t ip_len; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)buf; + uint8_t restored_proto; + int ret; + uint32_t i; + + /* Fill reference payload with a known pattern. */ + for (i = 0U; i < sizeof(ref); i++) { + ref[i] = (uint8_t)(i & 0xFFU); + } + + esp_setup(); + + /* + * 0th Scenario: + * do quick sanity check that wrap/unwrap works. + * */ + ret = wolfIP_esp_sa_new_cbc_hmac(0, (uint8_t *)spi_rt, + atoip4(T_SRC), atoip4(T_DST), + k_aes128, sizeof(k_aes128), + ESP_AUTH_SHA256_RFC4868, + k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + ret = wolfIP_esp_sa_new_cbc_hmac(1, (uint8_t *)spi_rt, + atoip4(T_SRC), atoip4(T_DST), + k_aes128, sizeof(k_aes128), + ESP_AUTH_SHA256_RFC4868, + k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + frame_len = build_udp_ip_packet(buf, sizeof(buf), + atoip4(T_SRC), atoip4(T_DST), + 4321, 1234, ref, sizeof(ref)); + ip_len = (uint16_t)(frame_len - ETH_HEADER_LEN); + /* wrap */ + ret = esp_transport_wrap(ip, &ip_len); + ck_assert_int_eq(ret, 0); + frame_len = (uint32_t)ip_len + ETH_HEADER_LEN; + /* unwrap */ + ret = esp_transport_unwrap(ip, &frame_len); + ck_assert_int_eq(ret, 0); + restored_proto = ip->proto; + ck_assert_uint_eq(restored_proto, WI_IPPROTO_UDP); + /* The udp payload must match the original plaintext exactly. */ + ck_assert_mem_eq(ip->data + UDP_HEADER_LEN, ref, sizeof(ref)); + + /* + * 1st Scenario: + * misc src, dst address that do not match the SA triplet + * */ + frame_len = build_udp_ip_packet(buf, sizeof(buf), + atoip4(T_MISC), atoip4(T_DST), + 4321, 1234, ref, sizeof(ref)); + ip_len = (uint16_t)(frame_len - ETH_HEADER_LEN); + ret = esp_transport_wrap(ip, &ip_len); + /* wrap fails */ + ck_assert_int_eq(ret, 1); + + frame_len = build_udp_ip_packet(buf, sizeof(buf), + atoip4(T_SRC), atoip4(T_MISC), + 4321, 1234, ref, sizeof(ref)); + ip_len = (uint16_t)(frame_len - ETH_HEADER_LEN); + ret = esp_transport_wrap(ip, &ip_len); + /* wrap fails */ + ck_assert_int_eq(ret, 1); + + /* + * 2nd Scenario: + * wildcard dst ip in outbound SA only, with misc dst address. + * */ + wolfIP_esp_sa_del_all(); + ret = wolfIP_esp_sa_new_cbc_hmac(0, (uint8_t *)spi_rt, + atoip4(T_SRC), 0, + k_aes128, sizeof(k_aes128), + ESP_AUTH_SHA256_RFC4868, + k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + + ret = wolfIP_esp_sa_new_cbc_hmac(1, (uint8_t *)spi_rt, + atoip4(T_SRC), atoip4(T_DST), + k_aes128, sizeof(k_aes128), + ESP_AUTH_SHA256_RFC4868, + k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + /* Build a plaintext IPv4/UDP packet to MISC address. */ + frame_len = build_udp_ip_packet(buf, sizeof(buf), + atoip4(T_SRC), atoip4(T_MISC), + 4321, 1234, ref, sizeof(ref)); + + ip_len = (uint16_t)(frame_len - ETH_HEADER_LEN); + + /* wrap succeeds because it can wildcard. */ + ret = esp_transport_wrap(ip, &ip_len); + ck_assert_int_eq(ret, 0); + frame_len = (uint32_t)ip_len + ETH_HEADER_LEN; + /* but unwrap does not have wildcard and fails to match triplet */ + ret = esp_transport_unwrap(ip, &frame_len); + ck_assert_int_eq(ret, -1); + + /* + * 3rd Scenario: + * wildcard dst ip in both in/out SAs, with misc dst address. + * */ + wolfIP_esp_sa_del_all(); + ret = wolfIP_esp_sa_new_cbc_hmac(0, (uint8_t *)spi_rt, + atoip4(T_SRC), 0, + k_aes128, sizeof(k_aes128), + ESP_AUTH_SHA256_RFC4868, + k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + + ret = wolfIP_esp_sa_new_cbc_hmac(1, (uint8_t *)spi_rt, + atoip4(T_SRC), 0, + k_aes128, sizeof(k_aes128), + ESP_AUTH_SHA256_RFC4868, + k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + + /* Build a plaintext IPv4/UDP packet to MISC. */ + frame_len = build_udp_ip_packet(buf, sizeof(buf), + atoip4(T_SRC), atoip4(T_MISC), + 4321, 1234, ref, sizeof(ref)); + + ip_len = (uint16_t)(frame_len - ETH_HEADER_LEN); + + /* now wrap and unwrap succeeds because dst can wildcard match */ + ret = esp_transport_wrap(ip, &ip_len); + ck_assert_int_eq(ret, 0); + frame_len = (uint32_t)ip_len + ETH_HEADER_LEN; + + ret = esp_transport_unwrap(ip, &frame_len); + ck_assert_int_eq(ret, 0); + + /* + * 4th Scenario: + * wildcard src ip in both in/out SAs, with misc src address. + * */ + wolfIP_esp_sa_del_all(); + ret = wolfIP_esp_sa_new_cbc_hmac(0, (uint8_t *)spi_rt, + 0, atoip4(T_DST), + k_aes128, sizeof(k_aes128), + ESP_AUTH_SHA256_RFC4868, + k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + + ret = wolfIP_esp_sa_new_cbc_hmac(1, (uint8_t *)spi_rt, + 0, atoip4(T_DST), + k_aes128, sizeof(k_aes128), + ESP_AUTH_SHA256_RFC4868, + k_auth16, sizeof(k_auth16), + ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + + /* Build a plaintext IPv4/UDP packet from MISC. */ + frame_len = build_udp_ip_packet(buf, sizeof(buf), + atoip4(T_MISC),atoip4(T_DST), + 4321, 1234, ref, sizeof(ref)); + + ip_len = (uint16_t)(frame_len - ETH_HEADER_LEN); + + /* now wrap and unwrap succeeds because src can wildcard match */ + ret = esp_transport_wrap(ip, &ip_len); + ck_assert_int_eq(ret, 0); + frame_len = (uint32_t)ip_len + ETH_HEADER_LEN; + + ret = esp_transport_unwrap(ip, &frame_len); + ck_assert_int_eq(ret, 0); +} +END_TEST + /* * full enc/dec round-trips * */ @@ -1248,8 +1454,8 @@ END_TEST /* * no matching outbound SA (esp_transport_wrap returns 1) - * */ -/* When no outbound SA matches ip->dst, wrap must return 1 (caller should + * + * When no outbound SA matches ip->dst, wrap must return 1 (caller should * send plaintext). */ START_TEST(test_wrap_no_matching_sa) { @@ -1725,6 +1931,7 @@ static Suite *esp_suite(void) tcase_add_test(tc, test_unwrap_below_min_len); tcase_add_test(tc, test_unwrap_pad_too_big); tcase_add_test(tc, test_unwrap_invalid_pad_pattern); + tcase_add_test(tc, test_unwrap_ip_filtering); suite_add_tcase(s, tc); /* Crypto round-trips */ diff --git a/src/wolfesp.c b/src/wolfesp.c index 00e5aa4e..e5e221e0 100644 --- a/src/wolfesp.c +++ b/src/wolfesp.c @@ -71,15 +71,19 @@ void wolfIP_esp_sa_del_all(void) return; } +static const uint8_t zero_spi[ESP_SPI_LEN] = {0x00, 0x00, 0x00, 0x00}; + +/* Get an SA by spi. + * If spi is null, return the first empty slot (an SA with all zero SPI). + * */ static inline wolfIP_esp_sa * esp_sa_get(int in, const uint8_t * spi) { - uint8_t empty_sa[4] = {0x00, 0x00, 0x00, 0x00}; wolfIP_esp_sa * list = NULL; size_t i = 0; if (spi == NULL) { - spi = empty_sa; + spi = zero_spi; } in = (in == 0 ? 0 : 1); @@ -109,6 +113,32 @@ void wolfIP_esp_sa_del(int in, uint8_t * spi) return; } +/* return 0 if valid spi number. + * return -1 if invalid + * */ +static inline int +esp_spi_valid(const uint8_t * spi, int log_it) +{ + if (spi == NULL) { + return -1; + } + /* RFC4303: + * The SPI value of zero (0) is reserved for local, + * implementation-specific use and MUST NOT be sent on the wire. + * */ + if (memcmp(spi, zero_spi, ESP_SPI_LEN) == 0) { + if (log_it) { + ESP_LOG("info: invalid zero (0) value spi\n"); + } + return -1; + } + + /* SPI values 1 through 255 are reserved by IANA, and technically we + * could check and reject them. Probably not worth being this fastidious. + * */ + return 0; +} + /* Configure a new Security Association based on either * enc = ESP_ENC_GCM_RFC4106 (gcm), or enc = ESP_AUTH_GCM_RFC4543 (gmac). * */ @@ -120,6 +150,10 @@ int wolfIP_esp_sa_new_gcm(int in, uint8_t * spi, ip4 src, ip4 dst, int err = 0; esp_auth_t auth = 0; + if (esp_spi_valid(spi, 1) < 0) { + return -1; + } + new_sa = esp_sa_get(in, NULL); if (new_sa == NULL) { ESP_LOG("error: sa %s pool is full\n", in == 1 ? "in" : "out"); @@ -233,6 +267,10 @@ int wolfIP_esp_sa_new_hmac(int in, uint8_t * spi, ip4 src, ip4 dst, { wolfIP_esp_sa * new_sa = NULL; + if (esp_spi_valid(spi, 1) < 0) { + return -1; + } + new_sa = esp_sa_get(in, NULL); if (new_sa == NULL) { ESP_LOG("error: sa %s pool is full\n", in == 1 ? "in" : "out"); @@ -275,6 +313,10 @@ int wolfIP_esp_sa_new_cbc_hmac(int in, uint8_t * spi, ip4 src, ip4 dst, { wolfIP_esp_sa * new_sa = NULL; + if (esp_spi_valid(spi, 1) < 0) { + return -1; + } + new_sa = esp_sa_get(in, NULL); if (new_sa == NULL) { ESP_LOG("error: sa %s pool is full\n", in == 1 ? "in" : "out"); @@ -329,6 +371,10 @@ wolfIP_esp_sa_new_des3_hmac(int in, uint8_t * spi, ip4 src, ip4 dst, { wolfIP_esp_sa * new_sa = NULL; + if (esp_spi_valid(spi, 1) < 0) { + return -1; + } + new_sa = esp_sa_get(in, NULL); if (new_sa == NULL) { ESP_LOG("error: sa %s pool is full\n", in == 1 ? "in" : "out"); @@ -1312,13 +1358,42 @@ esp_transport_unwrap(struct wolfIP_ip_packet *ip, uint32_t * frame_len) memcpy(&seq, ip->data + ESP_SPI_LEN, sizeof(seq)); seq = ee32(seq); + if (esp_spi_valid(spi, 1) < 0) { + return -1; + } + + /* ESP SA lookup: + * - If user configured {spi, src, dst}, then match on full triplet. + * - If either src or dst are 0, don't require a full match. + * - The spi must always match. + * */ for (size_t i = 0; i < in_sa_num; ++i) { - if (memcmp(spi, in_sa_list[i].spi, sizeof(spi)) == 0) { - ESP_DEBUG("info: found sa: 0x%02x%02x%02x%02x\n", - spi[0], spi[1], spi[2], spi[3]); - esp_sa = &in_sa_list[i]; - break; + if (esp_spi_valid(in_sa_list[i].spi, 0) < 0) { + /* skip empty slots */ + continue; + } + + if (memcmp(spi, in_sa_list[i].spi, sizeof(spi)) != 0) { + /* SPI doesn't match */ + continue; + } + + if (in_sa_list[i].dst != 0 && + ip->dst != ee32(in_sa_list[i].dst)) { + /* SA ip dst is configured, and doesn't match */ + continue; + } + + if (in_sa_list[i].src != 0 && + ip->src != ee32(in_sa_list[i].src)) { + /* SA ip src is configured, and doesn't match */ + continue; } + + ESP_DEBUG("info: found sa: 0x%02x%02x%02x%02x\n", + spi[0], spi[1], spi[2], spi[3]); + esp_sa = &in_sa_list[i]; + break; } if (esp_sa == NULL) { @@ -1521,15 +1596,30 @@ esp_transport_wrap(struct wolfIP_ip_packet *ip, uint16_t * ip_len) } /* todo: priority, proto / port filtering. currently this grabs - * the first dst match. */ + * the first dst and src match. */ for (size_t i = 0; i < out_sa_num; ++i) { - if (ip->dst == ee32(out_sa_list[i].dst)) { - esp_sa = &out_sa_list[i]; - ESP_DEBUG("info: found out sa: 0x%02x%02x%02x%02x\n", - esp_sa->spi[0], esp_sa->spi[1], esp_sa->spi[2], - esp_sa->spi[3]); - break; + if (esp_spi_valid(out_sa_list[i].spi, 0) < 0) { + /* skip empty slots */ + continue; } + + if (out_sa_list[i].dst != 0 && + ip->dst != ee32(out_sa_list[i].dst)) { + /* SA ip dst is configured, and doesn't match */ + continue; + } + + if (out_sa_list[i].src != 0 && + ip->src != ee32(out_sa_list[i].src)) { + /* SA ip src is configured, and doesn't match */ + continue; + } + + esp_sa = &out_sa_list[i]; + ESP_DEBUG("info: found out sa: 0x%02x%02x%02x%02x\n", + esp_sa->spi[0], esp_sa->spi[1], esp_sa->spi[2], + esp_sa->spi[3]); + break; } if (esp_sa == NULL) {