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" \