From 79c7384e5e47282420d65e2d761b1a835ebefd63 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Wed, 13 May 2020 16:54:55 +0200 Subject: [PATCH] cmdmon: add authdata command Add a command to display information about authentication of NTP sources. --- candm.h | 29 ++++++++++++- client.c | 82 ++++++++++++++++++++++++++++++++++++- cmdmon.c | 45 ++++++++++++++++++++ doc/chronyc.adoc | 67 ++++++++++++++++++++++++++++++ ntp_auth.c | 25 +++++++++++ ntp_auth.h | 4 ++ ntp_core.c | 8 ++++ ntp_core.h | 1 + ntp_sources.c | 18 ++++++++ ntp_sources.h | 2 + nts_ntp_client.c | 17 ++++++++ nts_ntp_client.h | 3 ++ pktlength.c | 2 + reports.h | 11 +++++ stubs.c | 5 +++ test/simulation/110-chronyc | 2 + 16 files changed, 318 insertions(+), 3 deletions(-) diff --git a/candm.h b/candm.h index 0b28d77..0a0ea98 100644 --- a/candm.h +++ b/candm.h @@ -104,7 +104,8 @@ #define REQ_ADD_SOURCE 64 #define REQ_NTP_SOURCE_NAME 65 #define REQ_RESET_SOURCES 66 -#define N_REQUEST_TYPES 67 +#define REQ_AUTH_DATA 67 +#define N_REQUEST_TYPES 68 /* Structure used to exchange timespecs independent of time_t size */ typedef struct { @@ -351,6 +352,11 @@ typedef struct { int32_t EOR; } REQ_NTPSourceName; +typedef struct { + IPAddr ip_addr; + int32_t EOR; +} REQ_AuthData; + /* ================================================== */ #define PKT_TYPE_CMD_REQUEST 1 @@ -454,6 +460,7 @@ typedef struct { REQ_SmoothTime smoothtime; REQ_NTPData ntp_data; REQ_NTPSourceName ntp_source_name; + REQ_AuthData auth_data; } data; /* Command specific parameters */ /* Padding used to prevent traffic amplification. It only defines the @@ -491,7 +498,8 @@ typedef struct { #define RPY_MANUAL_TIMESTAMP2 17 #define RPY_MANUAL_LIST2 18 #define RPY_NTP_SOURCE_NAME 19 -#define N_REPLY_TYPES 20 +#define RPY_AUTH_DATA 20 +#define N_REPLY_TYPES 21 /* Status codes */ #define STT_SUCCESS 0 @@ -712,6 +720,22 @@ typedef struct { int32_t EOR; } RPY_NTPSourceName; +#define RPY_AD_MD_NONE 0 +#define RPY_AD_MD_SYMMETRIC 1 +#define RPY_AD_MD_NTS 2 + +typedef struct { + uint16_t mode; + uint16_t key_type; + uint32_t key_id; + uint16_t key_length; + uint16_t ke_attempts; + uint32_t last_ke_ago; + uint16_t cookies; + uint16_t nak; + int32_t EOR; +} RPY_AuthData; + typedef struct { uint8_t version; uint8_t pkt_type; @@ -742,6 +766,7 @@ typedef struct { RPY_Smoothing smoothing; RPY_NTPData ntp_data; RPY_NTPSourceName ntp_source_name; + RPY_AuthData auth_data; } data; /* Reply specific parameters */ } CMD_Reply; diff --git a/client.c b/client.c index 5cbd402..77c9184 100644 --- a/client.c +++ b/client.c @@ -1214,6 +1214,7 @@ give_help(void) "\0\0" "NTP sources:\0\0" "activity\0Check how many NTP sources are online/offline\0" + "authdata [-a]\0Display information about authentication\0" "ntpdata [
]\0Display information about last valid measurement\0" "add server [options]\0Add new NTP server\0" "add pool [options]\0Add new pool of NTP servers\0" @@ -1312,7 +1313,7 @@ command_name_generator(const char *text, int state) { const char *name, **names[TAB_COMPLETE_MAX_INDEX]; const char *base_commands[] = { - "accheck", "activity", "add", "allow", "burst", + "accheck", "activity", "add", "allow", "authdata", "burst", "clients", "cmdaccheck", "cmdallow", "cmddeny", "cyclelogs", "delete", "deny", "dns", "dump", "exit", "help", "keygen", "local", "makestep", "manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll", @@ -2363,6 +2364,82 @@ process_cmd_tracking(char *line) /* ================================================== */ +static int +process_cmd_authdata(char *line) +{ + CMD_Request request; + CMD_Reply reply; + IPAddr ip_addr; + uint32_t i, source_mode, n_sources; + int all, verbose; + const char *mode_str; + char name[256]; + + parse_sources_options(line, &all, &verbose); + + request.command = htons(REQ_N_SOURCES); + if (!request_reply(&request, &reply, RPY_N_SOURCES, 0)) + return 0; + + n_sources = ntohl(reply.data.n_sources.n_sources); + + print_header("Name/IP address Mode KeyID Type Len Last Atmp Cook NAK"); + + /* "NNNNNNNNNNNNNNNNNNNNNNNNNNN MMMM KKKKK AAAA LLLL LLLL AAAA CCCC NNNN" */ + + for (i = 0; i < n_sources; i++) { + request.command = htons(REQ_SOURCE_DATA); + request.data.source_data.index = htonl(i); + if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0)) + return 0; + + source_mode = ntohs(reply.data.source_data.mode); + if (source_mode != RPY_SD_MD_CLIENT && source_mode != RPY_SD_MD_PEER) + continue; + + UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &ip_addr); + if (!all && ip_addr.family == IPADDR_ID) + continue; + + request.command = htons(REQ_AUTH_DATA); + request.data.auth_data.ip_addr = reply.data.source_data.ip_addr; + if (!request_reply(&request, &reply, RPY_AUTH_DATA, 0)) + return 0; + + format_name(name, sizeof (name), 25, 0, 0, 1, &ip_addr); + + switch (ntohs(reply.data.auth_data.mode)) { + case RPY_AD_MD_NONE: + mode_str = "-"; + break; + case RPY_AD_MD_SYMMETRIC: + mode_str = "SK"; + break; + case RPY_AD_MD_NTS: + mode_str = "NTS"; + break; + default: + mode_str = "?"; + break; + } + + print_report("%-27s %4s %5U %4d %4d %I %4d %4d %4d\n", + name, mode_str, + (unsigned long)ntohl(reply.data.auth_data.key_id), + ntohs(reply.data.auth_data.key_type), + ntohs(reply.data.auth_data.key_length), + (unsigned long)ntohl(reply.data.auth_data.last_ke_ago), + ntohs(reply.data.auth_data.ke_attempts), + ntohs(reply.data.auth_data.cookies), + ntohs(reply.data.auth_data.nak), + REPORT_END); + } + + return 1; +} + +/* ================================================== */ + static int process_cmd_ntpdata(char *line) { @@ -3050,6 +3127,9 @@ process_line(char *line) } else { do_normal_submit = process_cmd_allow(&tx_message, line); } + } else if (!strcmp(command, "authdata")) { + do_normal_submit = 0; + ret = process_cmd_authdata(line); } else if (!strcmp(command, "burst")) { do_normal_submit = process_cmd_burst(&tx_message, line); } else if (!strcmp(command, "clients")) { diff --git a/cmdmon.c b/cmdmon.c index 13a0fad..9a7927f 100644 --- a/cmdmon.c +++ b/cmdmon.c @@ -136,6 +136,7 @@ static const char permissions[] = { PERMIT_AUTH, /* ADD_SOURCE */ PERMIT_OPEN, /* NTP_SOURCE_NAME */ PERMIT_AUTH, /* RESET_SOURCES */ + PERMIT_AUTH, /* AUTH_DATA */ }; /* ================================================== */ @@ -1235,6 +1236,46 @@ handle_reset_sources(CMD_Request *rx_message, CMD_Reply *tx_message) LCL_NotifyExternalTimeStep(&now, &cooked_now, 0.0, 0.0); } +/* ================================================== */ + +static void +handle_auth_data(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + RPT_AuthReport report; + IPAddr ip_addr; + + UTI_IPNetworkToHost(&rx_message->data.auth_data.ip_addr, &ip_addr); + + if (!NSR_GetAuthReport(&ip_addr, &report)) { + tx_message->status = htons(STT_NOSUCHSOURCE); + return; + } + + tx_message->reply = htons(RPY_AUTH_DATA); + + switch (report.mode) { + case NTP_AUTH_NONE: + tx_message->data.auth_data.mode = htons(RPY_AD_MD_NONE); + break; + case NTP_AUTH_SYMMETRIC: + tx_message->data.auth_data.mode = htons(RPY_AD_MD_SYMMETRIC); + break; + case NTP_AUTH_NTS: + tx_message->data.auth_data.mode = htons(RPY_AD_MD_NTS); + break; + default: + break; + } + + tx_message->data.auth_data.key_type = htons(report.key_type); + tx_message->data.auth_data.key_id = htonl(report.key_id); + tx_message->data.auth_data.key_length = htons(report.key_length); + tx_message->data.auth_data.ke_attempts = htons(report.ke_attempts); + tx_message->data.auth_data.last_ke_ago = htonl(report.last_ke_ago); + tx_message->data.auth_data.cookies = htons(report.cookies); + tx_message->data.auth_data.nak = htons(report.nak); +} + /* ================================================== */ /* Read a packet and process it */ @@ -1617,6 +1658,10 @@ read_from_cmd_socket(int sock_fd, int event, void *anything) handle_reset_sources(&rx_message, &tx_message); break; + case REQ_AUTH_DATA: + handle_auth_data(&rx_message, &tx_message); + break; + default: DEBUG_LOG("Unhandled command %d", rx_command); tx_message.status = htons(STT_FAILED); diff --git a/doc/chronyc.adoc b/doc/chronyc.adoc index 1b7513f..514a95f 100644 --- a/doc/chronyc.adoc +++ b/doc/chronyc.adoc @@ -454,6 +454,73 @@ the offline state. the name of the server or peer was not resolved to an address yet; this source is not visible in the *sources* and *sourcestats* reports. +[[authdata]]*authdata* [*-a*]:: +The *authdata* command displays information specific to authentication of NTP +sources. If the *-a* option is specified, all sources are displayed, including +those that do not have a known address yet. An example of the output is +shown below. ++ +---- +Name/IP address Mode KeyID Type Len Last Atmp Cook NAK +==================================================================== +foo.example.com NTS 1 15 256 135m 0 8 0 +bar.example.com SK 30 13 128 - 0 0 0 +baz.example.com - 0 0 0 - 0 0 0 +---- ++ +The columns are as follows: ++ +*Name/IP address*::: +This column shows the name or the IP address of the source. +*Mode*::: +This column shows which mechanism authenticates NTP packets received from the +source. _NTS_ means Network Time Security, _SK_ means a symmetric key, and _-_ +means authentication is disabled. +*KeyID*::: +This column shows an identifier of the key used for authentication. With a +symmetric key, it is the ID from the <>. +With NTS, it is a number starting at zero and incremented by one with each +successful key establishment using the NTS-KE protocol, i.e. it shows how many +times the key establishment was performed with this source. +*Type*::: +This columns shows an identifier of the algorithm used for authentication. +With a symmetric key, it is the hash function or cipher specified in the key +file. With NTS, it is an authenticated encryption with associated data (AEAD) +algorithm, which is negotiated in the NTS-KE protocol. The following values can +be reported: +* 1: MD5 +* 2: SHA1 +* 3: SHA256 +* 4: SHA384 +* 5: SHA512 +* 6: SHA3-224 +* 7: SHA3-256 +* 8: SHA3-384 +* 9: SHA3-512 +* 10: TIGER +* 11: WHIRLPOOL +* 13: AES128 +* 14: AES256 +* 15: AEAD-AES-SIV-CMAC-256 +*Len*::: +This column shows the length of the key in bits. +*Last*::: +This column shows how long ago the last successful key establishment was +performed. It is in seconds, or letters _m_, _h_, _d_ or _y_ indicate minutes, +hours, days, or years. +*Atmp*::: +This column shows the number of attempts to perform the key establishment since +the last successful key establishment. A number larger than 1 indicates a +problem with the network or server. +*Cook*::: +This column shows the number of NTS cookies that *chronyd* currently has. If +the key establishment was successful, a number smaller than 8 indicates a +problem with the network or server. +*NAK*::: +This column shows whether an NTS NAK was received since the last authenticated +response. A non-zero number indicates that *chronyd* has used a cookie which is +no longer valid, or it might be under a denial-of-service attack. + [[ntpdata]]*ntpdata* [_address_]:: The *ntpdata* command displays the last valid measurement and other NTP-specific information about the specified NTP source, or all NTP sources diff --git a/ntp_auth.c b/ntp_auth.c index 1cf55da..18c9ea9 100644 --- a/ntp_auth.c +++ b/ntp_auth.c @@ -498,3 +498,28 @@ NAU_DumpData(NAU_Instance instance) break; } } + +/* ================================================== */ + +void +NAU_GetReport(NAU_Instance instance, RPT_AuthReport *report) +{ + memset(report, 0, sizeof (*report)); + + report->mode = instance->mode; + report->last_ke_ago = -1; + + switch (instance->mode) { + case NTP_AUTH_NONE: + break; + case NTP_AUTH_SYMMETRIC: + report->key_id = instance->key_id; + KEY_GetKeyInfo(instance->key_id, &report->key_type, &report->key_length); + break; + case NTP_AUTH_NTS: + NNC_GetReport(instance->nts, report); + break; + default: + assert(0); + } +} diff --git a/ntp_auth.h b/ntp_auth.h index 3d8014e..d336b55 100644 --- a/ntp_auth.h +++ b/ntp_auth.h @@ -29,6 +29,7 @@ #include "addressing.h" #include "ntp.h" +#include "reports.h" typedef struct NAU_Instance_Record *NAU_Instance; @@ -89,4 +90,7 @@ extern void NAU_ChangeAddress(NAU_Instance instance, IPAddr *address); /* Save authentication-specific data to speed up the next start */ extern void NAU_DumpData(NAU_Instance instance); +/* Provide a report about the current authentication state */ +extern void NAU_GetReport(NAU_Instance instance, RPT_AuthReport *report); + #endif diff --git a/ntp_core.c b/ntp_core.c index f2a3138..4d319fd 100644 --- a/ntp_core.c +++ b/ntp_core.c @@ -2444,6 +2444,14 @@ NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timespec *n /* ================================================== */ +void +NCR_GetAuthReport(NCR_Instance inst, RPT_AuthReport *report) +{ + NAU_GetReport(inst->auth, report); +} + +/* ================================================== */ + void NCR_GetNTPReport(NCR_Instance inst, RPT_NTPReport *report) { diff --git a/ntp_core.h b/ntp_core.h index 32a9581..7f86f83 100644 --- a/ntp_core.h +++ b/ntp_core.h @@ -122,6 +122,7 @@ extern void NCR_ModifyPolltarget(NCR_Instance inst, int new_poll_target); extern void NCR_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples); extern void NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timespec *now); +extern void NCR_GetAuthReport(NCR_Instance inst, RPT_AuthReport *report); extern void NCR_GetNTPReport(NCR_Instance inst, RPT_NTPReport *report); extern int NCR_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all); diff --git a/ntp_sources.c b/ntp_sources.c index 81e1fa3..f6710fa 100644 --- a/ntp_sources.c +++ b/ntp_sources.c @@ -1249,6 +1249,24 @@ NSR_ReportSource(RPT_SourceReport *report, struct timespec *now) } } +/* ================================================== */ + +int +NSR_GetAuthReport(IPAddr *address, RPT_AuthReport *report) +{ + NTP_Remote_Address rem_addr; + int slot, found; + + rem_addr.ip_addr = *address; + rem_addr.port = 0; + find_slot(&rem_addr, &slot, &found); + if (!found) + return 0; + + NCR_GetAuthReport(get_record(slot)->data, report); + return 1; +} + /* ================================================== */ /* The ip address is assumed to be completed on input, that is how we identify the source record. */ diff --git a/ntp_sources.h b/ntp_sources.h index 77e8803..adb241e 100644 --- a/ntp_sources.h +++ b/ntp_sources.h @@ -136,6 +136,8 @@ extern int NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples, IPAd extern void NSR_ReportSource(RPT_SourceReport *report, struct timespec *now); +extern int NSR_GetAuthReport(IPAddr *address, RPT_AuthReport *report); + extern int NSR_GetNTPReport(RPT_NTPReport *report); extern void NSR_GetActivityReport(RPT_ActivityReport *report); diff --git a/nts_ntp_client.c b/nts_ntp_client.c index 6acedd2..aa01b4f 100644 --- a/nts_ntp_client.c +++ b/nts_ntp_client.c @@ -633,3 +633,20 @@ NNC_DumpData(NNC_Instance inst) { save_cookies(inst); } + +/* ================================================== */ + +void +NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report) +{ + report->key_id = inst->context_id; + report->key_type = inst->context.algorithm; + report->key_length = 8 * inst->context.s2c.length; + report->ke_attempts = inst->nke_attempts; + if (report->key_length > 0) + report->last_ke_ago = SCH_GetLastEventMonoTime() - inst->last_nke_success; + else + report->last_ke_ago = -1; + report->cookies = inst->num_cookies; + report->nak = inst->nak_response; +} diff --git a/nts_ntp_client.h b/nts_ntp_client.h index 4c410da..18e3357 100644 --- a/nts_ntp_client.h +++ b/nts_ntp_client.h @@ -29,6 +29,7 @@ #include "addressing.h" #include "ntp.h" +#include "reports.h" typedef struct NNC_Instance_Record *NNC_Instance; @@ -45,4 +46,6 @@ extern void NNC_ChangeAddress(NNC_Instance inst, IPAddr *address); extern void NNC_DumpData(NNC_Instance inst); +extern void NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report); + #endif diff --git a/pktlength.c b/pktlength.c index 83f848f..69032db 100644 --- a/pktlength.c +++ b/pktlength.c @@ -124,6 +124,7 @@ static const struct request_length request_lengths[] = { REQ_LENGTH_ENTRY(ntp_source_name, ntp_source_name), /* NTP_SOURCE_NAME */ REQ_LENGTH_ENTRY(null, null), /* RESET_SOURCES */ + REQ_LENGTH_ENTRY(auth_data, auth_data), /* AUTH_DATA */ }; static const uint16_t reply_lengths[] = { @@ -147,6 +148,7 @@ static const uint16_t reply_lengths[] = { RPY_LENGTH_ENTRY(manual_timestamp), /* MANUAL_TIMESTAMP2 */ RPY_LENGTH_ENTRY(manual_list), /* MANUAL_LIST2 */ RPY_LENGTH_ENTRY(ntp_source_name), /* NTP_SOURCE_NAME */ + RPY_LENGTH_ENTRY(auth_data), /* AUTH_DATA */ }; /* ================================================== */ diff --git a/reports.h b/reports.h index 6a24670..20881b4 100644 --- a/reports.h +++ b/reports.h @@ -160,4 +160,15 @@ typedef struct { uint32_t total_valid_count; } RPT_NTPReport; +typedef struct { + NTP_AuthMode mode; + uint32_t key_id; + int key_type; + int key_length; + int ke_attempts; + uint32_t last_ke_ago; + int cookies; + int nak; +} RPT_AuthReport; + #endif /* GOT_REPORTS_H */ diff --git a/stubs.c b/stubs.c index 35c612f..fe1cd50 100644 --- a/stubs.c +++ b/stubs.c @@ -525,6 +525,11 @@ NNC_DumpData(NNC_Instance inst) { } +void +NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report) +{ +} + void NKC_Initialise(void) { diff --git a/test/simulation/110-chronyc b/test/simulation/110-chronyc index 40df087..058c9c3 100755 --- a/test/simulation/110-chronyc +++ b/test/simulation/110-chronyc @@ -76,6 +76,7 @@ check_chronyc_output "^Reference ID : C0A87B01 \(node1\.net1\.clk\)" \ server_strata=0 chronyc_start=0 client_conf="" +server_conf="server 192.168.123.1" limit=1 for chronyc_conf in \ @@ -93,6 +94,7 @@ for chronyc_conf in \ "allow ::/0" \ "allow" \ "allow all 10/24" \ + "authdata" \ "burst 5/10" \ "burst 3/5 255.255.255.0/1.2.3.0" \ "burst 1/2 1.2.3.0/24" \