2 chronyd/chronyc - Programs for keeping computer clocks accurate.
4 **********************************************************************
5 * Copyright (C) Miroslav Lichvar 2020-2021
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of version 2 of the GNU General Public License as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 **********************************************************************
22 =======================================================================
31 #include "nts_ke_client.h"
36 #include "nameserv_async.h"
37 #include "nts_ke_session.h"
42 #define CLIENT_TIMEOUT 16.0
44 struct NKC_Instance_Record
{
47 NKSN_Credentials credentials
;
48 NKSN_Instance session
;
54 NKE_Cookie cookies
[NKE_MAX_COOKIES
];
56 char server_name
[NKE_MAX_RECORD_BODY_LENGTH
+ 2];
57 IPSockAddr ntp_address
;
60 /* ================================================== */
62 static NKSN_Credentials default_credentials
= NULL
;
63 static int default_credentials_refs
= 0;
65 /* ================================================== */
68 name_resolve_handler(DNS_Status status
, int n_addrs
, IPAddr
*ip_addrs
, void *arg
)
70 NKC_Instance inst
= arg
;
73 inst
->resolving_name
= 0;
75 if (inst
->destroying
) {
80 if (status
!= DNS_Success
|| n_addrs
< 1) {
81 LOG(LOGS_ERR
, "Could not resolve NTP server %s from %s", inst
->server_name
, inst
->name
);
83 inst
->got_response
= 0;
87 inst
->ntp_address
.ip_addr
= ip_addrs
[0];
89 /* Prefer an address in the same family as the NTS-KE server */
90 for (i
= 0; i
< n_addrs
; i
++) {
91 DEBUG_LOG("%s resolved to %s", inst
->server_name
, UTI_IPToString(&ip_addrs
[i
]));
92 if (ip_addrs
[i
].family
== inst
->address
.ip_addr
.family
) {
93 inst
->ntp_address
.ip_addr
= ip_addrs
[i
];
99 /* ================================================== */
102 prepare_request(NKC_Instance inst
)
104 NKSN_Instance session
= inst
->session
;
107 NKSN_BeginMessage(session
);
109 datum
= htons(NKE_NEXT_PROTOCOL_NTPV4
);
110 if (!NKSN_AddRecord(session
, 1, NKE_RECORD_NEXT_PROTOCOL
, &datum
, sizeof (datum
)))
113 datum
= htons(AEAD_AES_SIV_CMAC_256
);
114 if (!NKSN_AddRecord(session
, 1, NKE_RECORD_AEAD_ALGORITHM
, &datum
, sizeof (datum
)))
117 if (!NKSN_EndMessage(session
))
123 /* ================================================== */
126 process_response(NKC_Instance inst
)
128 int next_protocol
= -1, aead_algorithm
= -1, error
= 0;
129 int i
, critical
, type
, length
;
130 uint16_t data
[NKE_MAX_RECORD_BODY_LENGTH
/ sizeof (uint16_t)];
132 assert(NKE_MAX_COOKIE_LENGTH
<= NKE_MAX_RECORD_BODY_LENGTH
);
133 assert(sizeof (data
) % sizeof (uint16_t) == 0);
134 assert(sizeof (uint16_t) == 2);
136 inst
->num_cookies
= 0;
137 inst
->ntp_address
.ip_addr
.family
= IPADDR_UNSPEC
;
138 inst
->ntp_address
.port
= 0;
139 inst
->server_name
[0] = '\0';
142 if (!NKSN_GetRecord(inst
->session
, &critical
, &type
, &length
, &data
, sizeof (data
)))
145 if (length
> sizeof (data
)) {
146 DEBUG_LOG("Record too long type=%d length=%d critical=%d", type
, length
, critical
);
153 case NKE_RECORD_NEXT_PROTOCOL
:
154 if (!critical
|| length
!= 2 || ntohs(data
[0]) != NKE_NEXT_PROTOCOL_NTPV4
) {
155 DEBUG_LOG("Unexpected NTS-KE next protocol");
159 next_protocol
= NKE_NEXT_PROTOCOL_NTPV4
;
161 case NKE_RECORD_AEAD_ALGORITHM
:
162 if (length
!= 2 || ntohs(data
[0]) != AEAD_AES_SIV_CMAC_256
) {
163 DEBUG_LOG("Unexpected NTS-KE AEAD algorithm");
167 aead_algorithm
= AEAD_AES_SIV_CMAC_256
;
168 inst
->context
.algorithm
= aead_algorithm
;
170 case NKE_RECORD_ERROR
:
172 DEBUG_LOG("NTS-KE error %d", ntohs(data
[0]));
175 case NKE_RECORD_WARNING
:
177 DEBUG_LOG("NTS-KE warning %d", ntohs(data
[0]));
180 case NKE_RECORD_COOKIE
:
181 DEBUG_LOG("Got cookie length=%d", length
);
183 if (length
< 1 || length
> NKE_MAX_COOKIE_LENGTH
|| length
% 4 != 0 ||
184 inst
->num_cookies
>= NKE_MAX_COOKIES
) {
185 DEBUG_LOG("Unexpected length/cookie");
189 assert(NKE_MAX_COOKIE_LENGTH
== sizeof (inst
->cookies
[inst
->num_cookies
].cookie
));
190 assert(NKE_MAX_COOKIES
== sizeof (inst
->cookies
) /
191 sizeof (inst
->cookies
[inst
->num_cookies
]));
192 inst
->cookies
[inst
->num_cookies
].length
= length
;
193 memcpy(inst
->cookies
[inst
->num_cookies
].cookie
, data
, length
);
197 case NKE_RECORD_NTPV4_SERVER_NEGOTIATION
:
198 if (length
< 1 || length
>= sizeof (inst
->server_name
)) {
199 DEBUG_LOG("Invalid server name");
204 memcpy(inst
->server_name
, data
, length
);
205 inst
->server_name
[length
] = '\0';
207 /* Make sure the name is printable and has no spaces */
208 for (i
= 0; i
< length
&& isgraph((unsigned char)inst
->server_name
[i
]); i
++)
211 DEBUG_LOG("Invalid server name");
216 DEBUG_LOG("Negotiated server %s", inst
->server_name
);
218 case NKE_RECORD_NTPV4_PORT_NEGOTIATION
:
220 DEBUG_LOG("Invalid port");
224 inst
->ntp_address
.port
= ntohs(data
[0]);
225 DEBUG_LOG("Negotiated port %d", inst
->ntp_address
.port
);
228 DEBUG_LOG("Unknown record type=%d length=%d critical=%d", type
, length
, critical
);
234 DEBUG_LOG("NTS-KE response: error=%d next=%d aead=%d",
235 error
, next_protocol
, aead_algorithm
);
237 if (error
|| inst
->num_cookies
== 0 ||
238 next_protocol
!= NKE_NEXT_PROTOCOL_NTPV4
||
239 aead_algorithm
!= AEAD_AES_SIV_CMAC_256
)
245 /* ================================================== */
248 handle_message(void *arg
)
250 NKC_Instance inst
= arg
;
252 if (!process_response(inst
)) {
253 LOG(LOGS_ERR
, "Received invalid NTS-KE response from %s", inst
->name
);
257 if (!NKSN_GetKeys(inst
->session
, inst
->context
.algorithm
,
258 &inst
->context
.c2s
, &inst
->context
.s2c
))
261 if (inst
->server_name
[0] != '\0') {
262 if (inst
->resolving_name
)
264 if (!UTI_StringToIP(inst
->server_name
, &inst
->ntp_address
.ip_addr
)) {
265 int length
= strlen(inst
->server_name
);
267 /* Add a trailing dot if not present to force the name to be
268 resolved as a fully qualified domain name */
269 if (length
< 1 || length
+ 1 >= sizeof (inst
->server_name
))
271 if (inst
->server_name
[length
- 1] != '.') {
272 inst
->server_name
[length
] = '.';
273 inst
->server_name
[length
+ 1] = '\0';
276 DNS_Name2IPAddressAsync(inst
->server_name
, name_resolve_handler
, inst
);
277 inst
->resolving_name
= 1;
281 inst
->got_response
= 1;
286 /* ================================================== */
289 NKC_CreateInstance(IPSockAddr
*address
, const char *name
, uint32_t cert_set
)
291 const char **trusted_certs
;
296 inst
= MallocNew(struct NKC_Instance_Record
);
298 inst
->address
= *address
;
299 inst
->name
= Strdup(name
);
300 inst
->session
= NKSN_CreateInstance(0, inst
->name
, handle_message
, inst
);
301 inst
->resolving_name
= 0;
302 inst
->destroying
= 0;
303 inst
->got_response
= 0;
305 n_certs
= CNF_GetNtsTrustedCertsPaths(&trusted_certs
, &certs_ids
);
307 /* Share the credentials among clients using the default set of trusted
308 certificates, which likely contains most certificates */
310 if (!default_credentials
)
311 default_credentials
= NKSN_CreateClientCertCredentials(trusted_certs
, certs_ids
,
313 inst
->credentials
= default_credentials
;
314 if (default_credentials
)
315 default_credentials_refs
++;
317 inst
->credentials
= NKSN_CreateClientCertCredentials(trusted_certs
, certs_ids
,
324 /* ================================================== */
327 NKC_DestroyInstance(NKC_Instance inst
)
329 NKSN_DestroyInstance(inst
->session
);
333 if (inst
->credentials
) {
334 if (inst
->credentials
== default_credentials
) {
335 default_credentials_refs
--;
336 if (default_credentials_refs
<= 0) {
337 NKSN_DestroyCertCredentials(default_credentials
);
338 default_credentials
= NULL
;
341 NKSN_DestroyCertCredentials(inst
->credentials
);
345 /* If the asynchronous resolver is running, let the handler free
346 the instance later */
347 if (inst
->resolving_name
) {
348 inst
->destroying
= 1;
355 /* ================================================== */
358 NKC_Start(NKC_Instance inst
)
360 IPSockAddr local_addr
;
361 char label
[512], *iface
;
364 assert(!NKC_IsActive(inst
));
366 inst
->got_response
= 0;
368 if (!inst
->credentials
) {
369 DEBUG_LOG("Missing client credentials");
373 /* Follow the bindacqaddress and bindacqdevice settings */
374 CNF_GetBindAcquisitionAddress(inst
->address
.ip_addr
.family
, &local_addr
.ip_addr
);
376 iface
= CNF_GetBindAcquisitionInterface();
378 /* Make a label containing both the address and name of the server */
379 if (snprintf(label
, sizeof (label
), "%s (%s)",
380 UTI_IPSockAddrToString(&inst
->address
), inst
->name
) >= sizeof (label
))
383 sock_fd
= SCK_OpenTcpSocket(&inst
->address
, &local_addr
, iface
, 0);
385 LOG(LOGS_ERR
, "Could not connect to %s", label
);
389 /* Start an NTS-KE session */
390 if (!NKSN_StartSession(inst
->session
, sock_fd
, label
, inst
->credentials
, CLIENT_TIMEOUT
)) {
391 SCK_CloseSocket(sock_fd
);
396 if (!prepare_request(inst
)) {
397 DEBUG_LOG("Could not prepare NTS-KE request");
398 NKSN_StopSession(inst
->session
);
405 /* ================================================== */
408 NKC_IsActive(NKC_Instance inst
)
410 return !NKSN_IsStopped(inst
->session
) || inst
->resolving_name
;
413 /* ================================================== */
416 NKC_GetNtsData(NKC_Instance inst
, NKE_Context
*context
,
417 NKE_Cookie
*cookies
, int *num_cookies
, int max_cookies
,
418 IPSockAddr
*ntp_address
)
422 if (!inst
->got_response
|| inst
->resolving_name
)
425 *context
= inst
->context
;
427 for (i
= 0; i
< inst
->num_cookies
&& i
< max_cookies
; i
++)
428 cookies
[i
] = inst
->cookies
[i
];
431 *ntp_address
= inst
->ntp_address
;
436 /* ================================================== */
439 NKC_GetRetryFactor(NKC_Instance inst
)
441 return NKSN_GetRetryFactor(inst
->session
);