Modify the session, NTS-KE, and NTS-NTP code to support multiple sets of trusted certificates and identify the sets by a 32-bit ID.
284 lines
9 KiB
C
284 lines
9 KiB
C
/*
|
|
**********************************************************************
|
|
* 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, cert_set) 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", 0, 0);
|
|
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
|