diff options
Diffstat (limited to '')
-rw-r--r-- | test/unit/nts_ntp_client.c | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/test/unit/nts_ntp_client.c b/test/unit/nts_ntp_client.c new file mode 100644 index 0000000..4ee33b0 --- /dev/null +++ b/test/unit/nts_ntp_client.c @@ -0,0 +1,284 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTS + +#include "socket.h" +#include "ntp.h" +#include "nts_ke_client.h" + +#define NKC_CreateInstance(address, name) Malloc(1) +#define NKC_DestroyInstance(inst) Free(inst) +#define NKC_Start(inst) (random() % 2) +#define NKC_IsActive(inst) (random() % 2) +#define NKC_GetRetryFactor(inst) (1) + +static int get_nts_data(NKC_Instance inst, NKE_Context *context, + NKE_Cookie *cookies, int *num_cookies, int max_cookies, + IPSockAddr *ntp_address); +#define NKC_GetNtsData get_nts_data + +#include <nts_ntp_client.c> + +static int +get_nts_data(NKC_Instance inst, NKE_Context *context, + NKE_Cookie *cookies, int *num_cookies, int max_cookies, + IPSockAddr *ntp_address) +{ + int i; + + if (random() % 2) + return 0; + + context->algorithm = AEAD_AES_SIV_CMAC_256; + + context->c2s.length = SIV_GetKeyLength(context->algorithm); + UTI_GetRandomBytes(context->c2s.key, context->c2s.length); + context->s2c.length = SIV_GetKeyLength(context->algorithm); + UTI_GetRandomBytes(context->s2c.key, context->s2c.length); + + *num_cookies = random() % max_cookies + 1; + for (i = 0; i < *num_cookies; i++) { + cookies[i].length = random() % (sizeof (cookies[i].cookie) + 1); + if (random() % 4 != 0) + cookies[i].length = cookies[i].length / 4 * 4; + memset(cookies[i].cookie, random(), cookies[i].length); + } + + ntp_address->ip_addr.family = IPADDR_UNSPEC; + ntp_address->port = 0; + + return 1; +} + +static int +get_request(NNC_Instance inst) +{ + unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH], uniq_id[NTS_MIN_UNIQ_ID_LENGTH]; + NTP_PacketInfo info; + NTP_Packet packet; + int expected_length, req_cookies; + + memset(&packet, 0, sizeof (packet)); + memset(&info, 0, sizeof (info)); + info.version = 4; + info.mode = MODE_CLIENT; + info.length = random() % (sizeof (packet) + 1); + if (random() % 4 != 0) + info.length = info.length / 4 * 4; + + if (inst->num_cookies > 0 && random() % 2) { + inst->num_cookies = 0; + + TEST_CHECK(!NNC_GenerateRequestAuth(inst, &packet, &info)); + } + + while (!NNC_PrepareForAuth(inst)) { + inst->next_nke_attempt = SCH_GetLastEventMonoTime() + random() % 10 - 7; + } + + TEST_CHECK(inst->num_cookies > 0); + TEST_CHECK(inst->siv); + + memcpy(nonce, inst->nonce, sizeof (nonce)); + memcpy(uniq_id, inst->uniq_id, sizeof (uniq_id)); + TEST_CHECK(NNC_PrepareForAuth(inst)); + TEST_CHECK(memcmp(nonce, inst->nonce, sizeof (nonce)) != 0); + TEST_CHECK(memcmp(uniq_id, inst->uniq_id, sizeof (uniq_id)) != 0); + + req_cookies = MIN(NTS_MAX_COOKIES - inst->num_cookies + 1, + MAX_TOTAL_COOKIE_LENGTH / + (inst->cookies[inst->cookie_index].length + 4)); + expected_length = info.length + 4 + sizeof (inst->uniq_id) + + req_cookies * (4 + inst->cookies[inst->cookie_index].length) + + 4 + 4 + sizeof (inst->nonce) + SIV_GetTagLength(inst->siv); + DEBUG_LOG("length=%d cookie_length=%d expected_length=%d", + info.length, inst->cookies[inst->cookie_index].length, expected_length); + + if (info.length % 4 == 0 && info.length >= NTP_HEADER_LENGTH && + inst->cookies[inst->cookie_index].length % 4 == 0 && + inst->cookies[inst->cookie_index].length >= (NTP_MIN_EF_LENGTH - 4) && + expected_length <= sizeof (packet)) { + TEST_CHECK(NNC_GenerateRequestAuth(inst, &packet, &info)); + TEST_CHECK(info.length == expected_length); + return 1; + } else { + TEST_CHECK(!NNC_GenerateRequestAuth(inst, &packet, &info)); + return 0; + } +} + +static void +prepare_response(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info, int valid, int nak) +{ + unsigned char cookie[508], plaintext[528], nonce[448]; + int nonce_length, ef_length, cookie_length, plaintext_length, min_auth_length; + int i, index, auth_start; + SIV_Instance siv; + + memset(packet, 0, sizeof (*packet)); + packet->lvm = NTP_LVM(0, 4, MODE_SERVER); + memset(info, 0, sizeof (*info)); + info->version = 4; + info->mode = MODE_SERVER; + info->length = NTP_HEADER_LENGTH; + + if (valid) + index = -1; + else + index = random() % (nak ? 2 : 8); + + DEBUG_LOG("index=%d nak=%d", index, nak); + + if (index != 0) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER, inst->uniq_id, + sizeof (inst->uniq_id))); + if (index == 1) + ((unsigned char *)packet)[NTP_HEADER_LENGTH + 4]++; + + if (nak) { + packet->stratum = NTP_INVALID_STRATUM; + packet->reference_id = htonl(NTP_KOD_NTS_NAK); + return; + } + + nonce_length = random() % (sizeof (nonce)) + 1; + + do { + cookie_length = random() % (sizeof (cookie) + 1); + } while (cookie_length % 4 != 0 || + ((index != 2) == (cookie_length < NTP_MIN_EF_LENGTH - 4 || + cookie_length > NKE_MAX_COOKIE_LENGTH))); + + min_auth_length = random() % (512 + 1); + + DEBUG_LOG("nonce_length=%d cookie_length=%d min_auth_length=%d", + nonce_length, cookie_length, min_auth_length); + + UTI_GetRandomBytes(nonce, nonce_length); + UTI_GetRandomBytes(cookie, cookie_length); + + if (cookie_length >= 12 && cookie_length <= 32 && random() % 2 == 0) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE, cookie, cookie_length)); + + plaintext_length = 0; + if (index != 3) { + for (i = random() % ((sizeof (plaintext) - 16) / (cookie_length + 4)); i >= 0; i--) { + TEST_CHECK(NEF_SetField(plaintext, sizeof (plaintext), plaintext_length, + NTP_EF_NTS_COOKIE, cookie, + i == 0 ? cookie_length : random() % (cookie_length + 1) / 4 * 4, + &ef_length)); + plaintext_length += ef_length; + } + } + auth_start = info->length; + if (index != 4) { + if (index == 5) { + assert(plaintext_length + 16 <= sizeof (plaintext)); + memset(plaintext + plaintext_length, 0, 16); + plaintext_length += 16; + } + siv = SIV_CreateInstance(inst->context.algorithm); + TEST_CHECK(siv); + TEST_CHECK(SIV_SetKey(siv, inst->context.s2c.key, inst->context.s2c.length)); + TEST_CHECK(NNA_GenerateAuthEF(packet, info, siv, + nonce, nonce_length, plaintext, plaintext_length, + min_auth_length)); + SIV_DestroyInstance(siv); + } + if (index == 6) + ((unsigned char *)packet)[auth_start + 8]++; + if (index == 7) + TEST_CHECK(NEF_AddField(packet, info, 0x7000, inst->uniq_id, sizeof (inst->uniq_id))); +} + +void +test_unit(void) +{ + NNC_Instance inst; + NTP_PacketInfo info; + NTP_Packet packet; + IPSockAddr addr; + IPAddr ip_addr; + int i, j, prev_num_cookies, valid; + + TEST_CHECK(SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) > 0); + + SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr); + addr.port = 0; + + inst = NNC_CreateInstance(&addr, "test", &addr); + TEST_CHECK(inst); + + for (i = 0; i < 100000; i++) { + if (!get_request(inst)) + continue; + + valid = random() % 2; + + TEST_CHECK(!inst->nak_response); + TEST_CHECK(!inst->ok_response); + + if (random() % 2) { + prepare_response(inst, &packet, &info, 0, 1); + TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info)); + TEST_CHECK(!inst->nak_response); + TEST_CHECK(!inst->ok_response); + for (j = random() % 3; j > 0; j--) { + prepare_response(inst, &packet, &info, 1, 1); + TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info)); + TEST_CHECK(inst->nak_response); + TEST_CHECK(!inst->ok_response); + } + } + + prev_num_cookies = inst->num_cookies; + prepare_response(inst, &packet, &info, valid, 0); + + if (valid) { + TEST_CHECK(NNC_CheckResponseAuth(inst, &packet, &info)); + TEST_CHECK(inst->num_cookies >= MIN(NTS_MAX_COOKIES, prev_num_cookies + 1)); + TEST_CHECK(inst->ok_response); + } + + prev_num_cookies = inst->num_cookies; + TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info)); + TEST_CHECK(inst->num_cookies == prev_num_cookies); + TEST_CHECK(inst->ok_response == valid); + + if (random() % 10 == 0) { + TST_GetRandomAddress(&ip_addr, IPADDR_INET4, 32); + NNC_ChangeAddress(inst, &ip_addr); + TEST_CHECK(UTI_CompareIPs(&inst->nts_address.ip_addr, &ip_addr, NULL) == 0); + } + } + + NNC_DestroyInstance(inst); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif |