From 50204a125b33baed90153b0697245e3eef3a5c6c Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Wed, 26 Feb 2020 14:14:49 +0100 Subject: [PATCH] test: add nts unit tests --- test/unit/nts_ke.crt | 8 ++ test/unit/nts_ke.key | 25 ++++ test/unit/nts_ke_client.c | 139 ++++++++++++++++++++ test/unit/nts_ke_server.c | 223 ++++++++++++++++++++++++++++++++ test/unit/nts_ke_session.c | 189 +++++++++++++++++++++++++++ test/unit/nts_ntp_auth.c | 108 ++++++++++++++++ test/unit/nts_ntp_client.c | 253 +++++++++++++++++++++++++++++++++++++ test/unit/nts_ntp_server.c | 174 +++++++++++++++++++++++++ 8 files changed, 1119 insertions(+) create mode 100644 test/unit/nts_ke.crt create mode 100644 test/unit/nts_ke.key create mode 100644 test/unit/nts_ke_client.c create mode 100644 test/unit/nts_ke_server.c create mode 100644 test/unit/nts_ke_session.c create mode 100644 test/unit/nts_ntp_auth.c create mode 100644 test/unit/nts_ntp_client.c create mode 100644 test/unit/nts_ntp_server.c diff --git a/test/unit/nts_ke.crt b/test/unit/nts_ke.crt new file mode 100644 index 0000000..8165287 --- /dev/null +++ b/test/unit/nts_ke.crt @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE----- +MIIBDjCBwaADAgECAgEBMAUGAytlcDAPMQ0wCwYDVQQDEwR0ZXN0MCAXDTcwMDEw +MTAwMDAwMFoYDzIxMDAwMTAyMDAwMDAwWjAPMQ0wCwYDVQQDEwR0ZXN0MCowBQYD +K2VwAyEA3oh/FZeOxRYvJVLfCDEwGI6Oe23gTgLHx8a87tvwgfyjQDA+MAwGA1Ud +EwEB/wQCMAAwDwYDVR0PAQH/BAUDAweAADAdBgNVHQ4EFgQUYwAqF9q3jxUk68m1 +cuz8DueOHeMwBQYDK2VwA0EAne0dCRXb0dW8bn2v3RhVTqeTJWXfl74x8MTQMTM7 +/uGTqoYOA0YJffypd+p27pvx2BEoNQWRYM6pqBg55KbwDw== +-----END CERTIFICATE----- diff --git a/test/unit/nts_ke.key b/test/unit/nts_ke.key new file mode 100644 index 0000000..6414c64 --- /dev/null +++ b/test/unit/nts_ke.key @@ -0,0 +1,25 @@ +Public Key Info: + Public Key Algorithm: EdDSA (Ed25519) + Key Security Level: High (256 bits) + +curve: Ed25519 +private key: + 01:d9:75:42:7c:52:cc:29:9e:90:01:f3:da:26:f6:d7 + ad:af:a5:2a:82:36:1d:86:c6:57:a7:b4:99:9b:6c:6d + + +x: + de:88:7f:15:97:8e:c5:16:2f:25:52:df:08:31:30:18 + 8e:8e:7b:6d:e0:4e:02:c7:c7:c6:bc:ee:db:f0:81:fc + + + +Public Key PIN: + pin-sha256:C4LBJP2cRxvLcZ6pjowcOEQhcW3ZPMVTpLgRGsBDeMw= +Public Key ID: + sha256:0b82c124fd9c471bcb719ea98e8c1c384421716dd93cc553a4b8111ac04378cc + sha1:63002a17dab78f1524ebc9b572ecfc0ee78e1de3 + +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIAHZdUJ8UswpnpAB89om9tetr6UqgjYdhsZXp7SZm2xt +-----END PRIVATE KEY----- diff --git a/test/unit/nts_ke_client.c b/test/unit/nts_ke_client.c new file mode 100644 index 0000000..74a17e4 --- /dev/null +++ b/test/unit/nts_ke_client.c @@ -0,0 +1,139 @@ +/* + ********************************************************************** + * 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 +#include "test.h" + +#ifdef FEAT_NTS + +#include + +static void +prepare_response(NKSN_Instance session, int valid) +{ + uint16_t data[16]; + int i, index, length; + + if (valid) + index = -1; + else + index = random() % 9; + DEBUG_LOG("index=%d", index); + + NKSN_BeginMessage(session); + + memset(data, 0, sizeof (data)); + length = 2; + assert(sizeof (data[0]) == 2); + + if (index == 0) { + data[0] = htons(random() % 100); + TEST_CHECK(NKSN_AddRecord(session, 1, random() % 2 ? NKE_RECORD_ERROR : NKE_RECORD_WARNING, + data, length)); + } else if (index == 1) { + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_ERROR + 1000, data, length)); + } + + if (index != 2) { + if (index == 3) + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4 + random() % 10 + 1); + else + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); + if (index == 4) + length = 3 + random() % 10; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + } + + if (index != 5) { + if (index == 6) + data[0] = htons(AEAD_AES_SIV_CMAC_256 + random() % 10 + 1); + else + data[0] = htons(AEAD_AES_SIV_CMAC_256); + if (index == 7) + length = 3 + random() % 10; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + } + + if (random() % 2) { + const char server[] = "127.0.0.1"; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_SERVER_NEGOTIATION, + server, sizeof (server) - 1)); + } + + if (random() % 2) { + data[0] = htons(123); + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_PORT_NEGOTIATION, data, length)); + } + + if (random() % 2) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, 1000 + random() % 1000, data, length)); + } + + if (index != 8) { + for (i = 0; i < NKE_MAX_COOKIES; i++) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_COOKIE, data, length)); + } + + TEST_CHECK(NKSN_EndMessage(session)); +} + +void +test_unit(void) +{ + NKC_Instance inst; + IPSockAddr addr; + int i, r, valid; + + char conf[][100] = { + "nosystemcert", + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + NKC_Initialise(); + + SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr); + addr.port = 0; + + inst = NKC_CreateInstance(&addr, "test"); + TEST_CHECK(inst); + + for (i = 0; i < 10000; i++) { + valid = random() % 2; + prepare_response(inst->session, valid); + r = process_response(inst); + TEST_CHECK(r == valid); + } + + NKC_DestroyInstance(inst); + + NKC_Finalise(); + CNF_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ke_server.c b/test/unit/nts_ke_server.c new file mode 100644 index 0000000..b5660e9 --- /dev/null +++ b/test/unit/nts_ke_server.c @@ -0,0 +1,223 @@ +/* + ********************************************************************** + * 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 +#include "test.h" + +#ifdef FEAT_NTS + +#include +#include +#include + +#define NKSN_GetKeys get_keys + +static int +get_keys(NKSN_Instance session, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c) +{ + c2s->length = SIV_GetKeyLength(siv); + UTI_GetRandomBytes(c2s->key, c2s->length); + s2c->length = SIV_GetKeyLength(siv); + UTI_GetRandomBytes(s2c->key, s2c->length); + return 1; +} + +#include + +static void +prepare_request(NKSN_Instance session, int valid) +{ + uint16_t data[16]; + int index, length; + + if (valid) + index = -1; + else + index = random() % 7; + DEBUG_LOG("index=%d", index); + + NKSN_BeginMessage(session); + + memset(data, 0, sizeof (data)); + length = 2; + assert(sizeof (data[0]) == 2); + + if (index != 0) { + memset(data, NKE_NEXT_PROTOCOL_NTPV4 + 1, sizeof (data)); + if (index == 1) + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4 + random() % 10 + 1); + else + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); + if (index == 2) + length = 3 + random() % 15 * 2; + else + length = 2 + random() % 16 * 2; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + } + + if (index != 3) { + if (index == 4) + data[0] = htons(AEAD_AES_SIV_CMAC_256 + random() % 10 + 1); + else + data[0] = htons(AEAD_AES_SIV_CMAC_256); + if (index == 5) + length = 3 + random() % 15 * 2; + else + length = 2 + random() % 16 * 2; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + } + + if (index == 6) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 1, 1000 + random() % 1000, data, length)); + } + + if (random() % 2) { + const char server[] = "127.0.0.1"; + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_SERVER_NEGOTIATION, + server, sizeof (server) - 1)); + } + + if (random() % 2) { + data[0] = htons(123); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_PORT_NEGOTIATION, data, length)); + } + + if (random() % 2) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, 1000 + random() % 1000, data, length)); + } + + TEST_CHECK(NKSN_EndMessage(session)); +} + +static void +process_response(NKSN_Instance session, int valid) +{ + int records, errors, critical, type, length; + + for (records = errors = 0; ; records++) { + if (!NKSN_GetRecord(session, &critical, &type, &length, NULL, 0)) + break; + if (type == NKE_RECORD_ERROR) + errors++; + } + + if (valid) { + TEST_CHECK(records >= 2); + } else { + TEST_CHECK(records == 1); + TEST_CHECK(errors == 1); + } +} + +void +test_unit(void) +{ + NKSN_Instance session; + NKE_Cookie cookie; + NKE_Key c2s, s2c, c2s2, s2c2; + int i, valid, l; + uint32_t sum, sum2; + + char conf[][100] = { + "ntscachedir .", + "ntsport 0", + "ntsprocesses 0", + "ntsserverkey nts_ke.key", + "ntsservercert nts_ke.crt", + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + SCH_Initialise(); + + unlink("ntskeys"); + NKS_Initialise(0); + + session = NKSN_CreateInstance(1, "", handle_message, NULL); + + for (i = 0; i < 10000; i++) { + valid = random() % 2; + prepare_request(session, valid); + TEST_CHECK(process_request(session)); + process_response(session, valid); + } + + + for (i = 0; i < 10000; i++) { + get_keys(session, AEAD_AES_SIV_CMAC_256, &c2s, &s2c); + memset(&cookie, 0, sizeof (cookie)); + TEST_CHECK(NKS_GenerateCookie(&c2s, &s2c, &cookie)); + TEST_CHECK(NKS_DecodeCookie(&cookie, &c2s2, &s2c2)); + TEST_CHECK(c2s.length == c2s2.length); + TEST_CHECK(s2c.length == s2c2.length); + TEST_CHECK(memcmp(c2s.key, c2s2.key, c2s.length) == 0); + TEST_CHECK(memcmp(s2c.key, s2c2.key, s2c.length) == 0); + + if (random() % 4) { + cookie.cookie[random() % (cookie.length)]++; + } else if (random() % 4) { + generate_key(current_server_key); + } else { + l = cookie.length; + while (l == cookie.length) + cookie.length = random() % (sizeof (cookie.cookie) + 1); + } + TEST_CHECK(!NKS_DecodeCookie(&cookie, &c2s2, &s2c2)); + } + + unlink("ntskeys"); + save_keys(); + + for (i = 0, sum = 0; i < MAX_SERVER_KEYS; i++) { + sum += server_keys[i].id + server_keys[i].key[0]; + generate_key(i); + } + + load_keys(); + TEST_CHECK(unlink("ntskeys") == 0); + + for (i = 0, sum2 = 0; i < MAX_SERVER_KEYS; i++) { + sum2 += server_keys[i].id + server_keys[i].key[0]; + } + + TEST_CHECK(sum == sum2); + + NKSN_DestroyInstance(session); + + NKS_Finalise(); + TEST_CHECK(unlink("ntskeys") == 0); + + SCH_Finalise(); + LCL_Finalise(); + CNF_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ke_session.c b/test/unit/nts_ke_session.c new file mode 100644 index 0000000..74a6607 --- /dev/null +++ b/test/unit/nts_ke_session.c @@ -0,0 +1,189 @@ +/* + ********************************************************************** + * 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 +#include "test.h" + +#ifdef FEAT_NTS + +#include + +#include +#include +#include + +static NKSN_Instance client, server; +static unsigned char record[NKE_MAX_MESSAGE_LENGTH]; +static int record_length, critical, type_start, records; +static int request_received; +static int response_received; + +static void +send_message(NKSN_Instance inst) +{ + int i; + + record_length = random() % (NKE_MAX_MESSAGE_LENGTH - 4 + 1); + for (i = 0; i < record_length; i++) + record[i] = random() % 256; + critical = random() % 2; + type_start = random() % 30000 + 1; + assert(sizeof (struct RecordHeader) == 4); + records = random() % ((NKE_MAX_MESSAGE_LENGTH - 4) / (4 + record_length) + 1); + + DEBUG_LOG("critical=%d type_start=%d records=%d*%d", + critical, type_start, records, record_length); + + NKSN_BeginMessage(inst); + + TEST_CHECK(!NKSN_AddRecord(inst, 0, 1, record, NKE_MAX_MESSAGE_LENGTH - 4 + 1)); + + for (i = 0; i < records; i++) { + TEST_CHECK(NKSN_AddRecord(inst, critical, type_start + i, record, record_length)); + TEST_CHECK(!NKSN_AddRecord(inst, 0, 1, &record, + NKE_MAX_MESSAGE_LENGTH - inst->message.length - 4 + 1)); + } + + TEST_CHECK(NKSN_EndMessage(inst)); +} + +static void +verify_message(NKSN_Instance inst) +{ + unsigned char buffer[NKE_MAX_MESSAGE_LENGTH]; + int i, c, t, length, buffer_length; + NKE_Key c2s, s2c; + + for (i = 0; i < records; i++) { + memset(buffer, 0, sizeof (buffer)); + buffer_length = random() % (record_length + 1); + assert(buffer_length <= sizeof (buffer)); + + TEST_CHECK(NKSN_GetRecord(inst, &c, &t, &length, buffer, buffer_length)); + TEST_CHECK(c == critical); + TEST_CHECK(t == type_start + i); + TEST_CHECK(length == record_length); + TEST_CHECK(memcmp(record, buffer, buffer_length) == 0); + if (buffer_length < record_length) + TEST_CHECK(buffer[buffer_length] == 0); + } + + TEST_CHECK(!NKSN_GetRecord(inst, &critical, &t, &length, buffer, sizeof (buffer))); + + TEST_CHECK(NKSN_GetKeys(inst, AEAD_AES_SIV_CMAC_256, &c2s, &s2c)); + TEST_CHECK(c2s.length == SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256)); + TEST_CHECK(s2c.length == SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256)); +} + +static int +handle_request(void *arg) +{ + NKSN_Instance server = arg; + + verify_message(server); + + request_received = 1; + + send_message(server); + + return 1; +} + +static int +handle_response(void *arg) +{ + NKSN_Instance client = arg; + + response_received = 1; + + verify_message(client); + + return 1; +} + +static void +check_finished(void *arg) +{ + DEBUG_LOG("checking for stopped sessions"); + if (!NKSN_IsStopped(server) || !NKSN_IsStopped(client)) { + SCH_AddTimeoutByDelay(0.001, check_finished, NULL); + return; + } + + SCH_QuitProgram(); +} + +void +test_unit(void) +{ + void *client_cred, *server_cred; + int sock_fds[2], i; + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + + for (i = 0; i < 50; i++) { + SCH_Initialise(); + + server = NKSN_CreateInstance(1, "client", handle_request, NULL); + client = NKSN_CreateInstance(0, "test", handle_response, NULL); + + server_cred = NKSN_CreateCertCredentials("nts_ke.crt", "nts_ke.key", NULL); + client_cred = NKSN_CreateCertCredentials(NULL, NULL, "nts_ke.crt"); + + TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds) == 0); + TEST_CHECK(fcntl(sock_fds[0], F_SETFL, O_NONBLOCK) == 0); + TEST_CHECK(fcntl(sock_fds[1], F_SETFL, O_NONBLOCK) == 0); + + TEST_CHECK(NKSN_StartSession(server, sock_fds[0], server_cred, 4.0)); + TEST_CHECK(NKSN_StartSession(client, sock_fds[1], client_cred, 4.0)); + + send_message(client); + + request_received = response_received = 0; + + check_finished(NULL); + + SCH_MainLoop(); + + TEST_CHECK(NKSN_IsStopped(server)); + TEST_CHECK(NKSN_IsStopped(client)); + + TEST_CHECK(request_received); + TEST_CHECK(response_received); + + NKSN_DestroyInstance(server); + NKSN_DestroyInstance(client); + + NKSN_DestroyCertCredentials(server_cred); + NKSN_DestroyCertCredentials(client_cred); + + SCH_Finalise(); + } + + LCL_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ntp_auth.c b/test/unit/nts_ntp_auth.c new file mode 100644 index 0000000..dc5143d --- /dev/null +++ b/test/unit/nts_ntp_auth.c @@ -0,0 +1,108 @@ +/* + ********************************************************************** + * 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 +#include "test.h" + +#ifdef FEAT_NTS + +#include + +#include "ntp_ext.h" +#include "siv.h" + +void +test_unit(void) +{ + unsigned char key[SIV_MAX_KEY_LENGTH], nonce[256], plaintext[256], plaintext2[256]; + NTP_PacketInfo info; + NTP_Packet packet; + SIV_Instance siv; + int i, j, r, packet_length, nonce_length, key_length; + int plaintext_length, plaintext2_length, min_ef_length; + + siv = SIV_CreateInstance(AEAD_AES_SIV_CMAC_256); + TEST_CHECK(siv); + + for (i = 0; i < 10000; i++) { + key_length = SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256); + for (j = 0; j < key_length; j++) + key[j] = random() % 256; + TEST_CHECK(SIV_SetKey(siv, key, key_length)); + + nonce_length = random() % sizeof (nonce) + 1; + for (j = 0; j < nonce_length; j++) + nonce[j] = random() % 256; + + plaintext_length = random() % (sizeof (plaintext) + 1); + for (j = 0; j < plaintext_length; j++) + plaintext[j] = random() % 256; + + packet_length = NTP_HEADER_LENGTH + random() % 100 * 4; + min_ef_length = random() % (sizeof (packet) - packet_length); + + memset(&packet, 0, sizeof (packet)); + packet.lvm = NTP_LVM(0, 4, 0); + memset(&info, 0, sizeof (info)); + info.version = 4; + info.length = packet_length; + + DEBUG_LOG("packet_length=%d nonce_length=%d plaintext_length=%d min_ef_length=%d", + packet_length, nonce_length, plaintext_length, min_ef_length); + + r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext, + -1, 0); + TEST_CHECK(!r); + r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, 0, plaintext, + plaintext_length, 0); + TEST_CHECK(!r); + r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext, + plaintext_length, sizeof (packet) - info.length + 1); + TEST_CHECK(!r); + + r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext, + plaintext_length, min_ef_length); + TEST_CHECK(r); + TEST_CHECK(info.length - packet_length >= min_ef_length); + + r = NNA_DecryptAuthEF(&packet, &info, siv, packet_length, plaintext2, + sizeof (plaintext2), &plaintext2_length); + TEST_CHECK(r); + TEST_CHECK(plaintext_length == plaintext2_length); + TEST_CHECK(memcmp(plaintext, plaintext2, plaintext_length) == 0); + + j = random() % (packet_length + plaintext_length + + nonce_length + SIV_GetTagLength(siv) + 8) / 4 * 4; + ((unsigned char *)&packet)[j]++; + r = NNA_DecryptAuthEF(&packet, &info, siv, packet_length, plaintext2, + sizeof (plaintext2), &plaintext2_length); + TEST_CHECK(!r); + ((unsigned char *)&packet)[j]--; + } + + SIV_DestroyInstance(siv); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ntp_client.c b/test/unit/nts_ntp_client.c new file mode 100644 index 0000000..65efc40 --- /dev/null +++ b/test/unit/nts_ntp_client.c @@ -0,0 +1,253 @@ +/* + ********************************************************************** + * 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 +#include "test.h" + +#ifdef FEAT_NTS + +#include "socket.h" +#include "ntp.h" +#include "nts_ke_client.h" + +#define NKC_CreateInstance(address, name) NULL +#define NKC_DestroyInstance(inst) +#define NKC_Start(inst) (random() % 2) +#define NKC_IsActive(inst) (random() % 2) + +static int get_nts_data(NKC_Instance inst, SIV_Algorithm *siv_algorithm, NKE_Key *c2s, NKE_Key *s2c, + NKE_Cookie *cookies, int *num_cookies, int max_cookies, IPSockAddr *ntp_address); +#define NKC_GetNtsData get_nts_data + +#include + +static int +get_nts_data(NKC_Instance inst, SIV_Algorithm *siv_algorithm, NKE_Key *c2s, NKE_Key *s2c, + NKE_Cookie *cookies, int *num_cookies, int max_cookies, IPSockAddr *ntp_address) +{ + int i; + + if (random() % 2) + return 0; + + *siv_algorithm = AEAD_AES_SIV_CMAC_256; + + c2s->length = SIV_GetKeyLength(*siv_algorithm); + UTI_GetRandomBytes(c2s->key, c2s->length); + s2c->length = SIV_GetKeyLength(*siv_algorithm); + UTI_GetRandomBytes(s2c->key, s2c->length); + + *num_cookies = random() % max_cookies + 1; + for (i = 0; i < *num_cookies; i++) { + cookies[i].length = random() % (sizeof (cookies[i].cookie) + 1); + 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); + + inst->num_cookies = 0; + + TEST_CHECK(!NNC_GenerateRequestAuth(inst, &packet, &info)); + + while (!NNC_PrepareForAuth(inst)) { + inst->last_nke_attempt.tv_sec = random(); + } + + TEST_CHECK(inst->num_cookies > 0); + TEST_CHECK(inst->siv_c2s); + TEST_CHECK(inst->siv_s2c); + + 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_c2s); + 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[512], nonce[512]; + int nonce_length, cookie_length, plaintext_length, min_auth_length; + int index, auth_start; + + 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 : 6); + + 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); + + plaintext_length = 0; + if (index != 3) + TEST_CHECK(NEF_SetField(plaintext, sizeof (plaintext), 0, NTP_EF_NTS_COOKIE, + cookie, cookie_length, &plaintext_length)); + auth_start = info->length; + if (index != 4) + TEST_CHECK(NNA_GenerateAuthEF(packet, info, inst->siv_s2c, + nonce, nonce_length, plaintext, plaintext_length, + min_auth_length)); + if (index == 5) + ((unsigned char *)packet)[auth_start + 8]++; +} + +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; + + 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 diff --git a/test/unit/nts_ntp_server.c b/test/unit/nts_ntp_server.c new file mode 100644 index 0000000..c6369ec --- /dev/null +++ b/test/unit/nts_ntp_server.c @@ -0,0 +1,174 @@ +/* + ********************************************************************** + * 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 +#include "test.h" + +#ifdef FEAT_NTS + +#include +#include + +#include + +static void +prepare_request(NTP_Packet *packet, NTP_PacketInfo *info, int valid, int nak) +{ + unsigned char uniq_id[NTS_MIN_UNIQ_ID_LENGTH], nonce[NTS_MIN_UNPADDED_NONCE_LENGTH]; + NKE_Key c2s, s2c; + SIV_Instance siv; + NKE_Cookie cookie; + int i, index, cookie_start, auth_start; + + c2s.length = SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256); + UTI_GetRandomBytes(&c2s.key, c2s.length); + s2c.length = SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256); + UTI_GetRandomBytes(&s2c.key, s2c.length); + + TEST_CHECK(NKS_GenerateCookie(&c2s, &s2c, &cookie)); + + UTI_GetRandomBytes(uniq_id, sizeof (uniq_id)); + UTI_GetRandomBytes(nonce, sizeof (nonce)); + + memset(packet, 0, sizeof (*packet)); + packet->lvm = NTP_LVM(0, 4, MODE_CLIENT); + memset(info, 0, sizeof (*info)); + info->version = 4; + info->mode = MODE_CLIENT; + info->length = NTP_HEADER_LENGTH; + + if (valid) + index = -1; + else + index = random() % 3; + + DEBUG_LOG("valid=%d nak=%d index=%d", valid, nak, index); + + if (index != 0) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER, + uniq_id, sizeof (uniq_id))); + + cookie_start = info->length; + + if (index != 1) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE, + cookie.cookie, cookie.length)); + + for (i = random() % 4; i > 0; i--) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE_PLACEHOLDER, + cookie.cookie, cookie.length)); + + auth_start = info->length; + + if (index != 2) { + siv = SIV_CreateInstance(AEAD_AES_SIV_CMAC_256); + TEST_CHECK(SIV_SetKey(siv, c2s.key, c2s.length)); + TEST_CHECK(NNA_GenerateAuthEF(packet, info, siv, nonce, sizeof (nonce), + (const unsigned char *)"", 0, 0)); + SIV_DestroyInstance(siv); + } + + if (nak) + ((unsigned char *)packet)[(index == 2 ? cookie_start : + (index == 1 ? auth_start : + (random() % 2 ? cookie_start : auth_start))) + + 4 + random() % 16]++; +} + +static void +init_response(NTP_Packet *packet, NTP_PacketInfo *info) +{ + 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; +} + +void +test_unit(void) +{ + NTP_PacketInfo req_info, res_info; + NTP_Packet request, response; + int i, valid, nak; + uint32_t kod; + + char conf[][100] = { + "ntsport 0", + "ntsprocesses 0", + "ntsserverkey nts_ke.key", + "ntsservercert nts_ke.crt", + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + SCH_Initialise(); + NKS_Initialise(0); + NNS_Initialise(); + + for (i = 0; i < 50000; i++) { + valid = random() % 2; + nak = random() % 2; + prepare_request(&request, &req_info, valid, nak); + + TEST_CHECK(NNS_CheckRequestAuth(&request, &req_info, &kod) == (valid && !nak)); + + if (valid && !nak) { + TEST_CHECK(kod == 0); + TEST_CHECK(server->num_cookies > 0); + + init_response(&response, &res_info); + TEST_CHECK(NNS_GenerateResponseAuth(&request, &req_info, &response, &res_info, kod)); + + TEST_CHECK(res_info.ext_fields == 2); + TEST_CHECK(server->num_cookies == 0); + } else if (valid && nak) { + TEST_CHECK(kod == NTP_KOD_NTS_NAK); + TEST_CHECK(server->num_cookies == 0); + + init_response(&response, &res_info); + TEST_CHECK(NNS_GenerateResponseAuth(&request, &req_info, &response, &res_info, kod)); + + TEST_CHECK(res_info.ext_fields == 1); + TEST_CHECK(server->num_cookies == 0); + } else { + TEST_CHECK(kod == 0); + TEST_CHECK(server->num_cookies == 0); + } + } + + NNS_Finalise(); + NKS_Finalise(); + SCH_Finalise(); + LCL_Finalise(); + CNF_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif