]>
Commit | Line | Data |
---|---|---|
6043632f ML |
1 | /* |
2 | chronyd/chronyc - Programs for keeping computer clocks accurate. | |
3 | ||
4 | ********************************************************************** | |
5f66722b | 5 | * Copyright (C) Miroslav Lichvar 2020, 2022 |
6043632f ML |
6 | * |
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. | |
10 | * | |
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. | |
15 | * | |
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. | |
19 | * | |
20 | ********************************************************************** | |
21 | ||
22 | ======================================================================= | |
23 | ||
24 | Server NTS-NTP authentication | |
25 | */ | |
26 | ||
27 | #include "config.h" | |
28 | ||
29 | #include "sysincl.h" | |
30 | ||
31 | #include "nts_ntp_server.h" | |
32 | ||
33 | #include "conf.h" | |
34 | #include "logging.h" | |
35 | #include "memory.h" | |
36 | #include "ntp.h" | |
37 | #include "ntp_ext.h" | |
38 | #include "nts_ke_server.h" | |
39 | #include "nts_ntp.h" | |
40 | #include "nts_ntp_auth.h" | |
41 | #include "siv.h" | |
42 | #include "util.h" | |
43 | ||
790a336e | 44 | #define MAX_SERVER_SIVS 2 |
adcf0734 | 45 | |
6043632f | 46 | struct NtsServer { |
790a336e ML |
47 | SIV_Instance sivs[MAX_SERVER_SIVS]; |
48 | SIV_Algorithm siv_algorithms[MAX_SERVER_SIVS]; | |
6043632f ML |
49 | unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH]; |
50 | NKE_Cookie cookies[NTS_MAX_COOKIES]; | |
51 | int num_cookies; | |
790a336e | 52 | int siv_index; |
6043632f ML |
53 | NTP_int64 req_tx; |
54 | }; | |
55 | ||
56 | /* The server instance handling all requests */ | |
57 | struct NtsServer *server; | |
58 | ||
59 | /* ================================================== */ | |
60 | ||
61 | void | |
62 | NNS_Initialise(void) | |
63 | { | |
90557cf1 | 64 | const char **certs, **keys; |
790a336e | 65 | int i; |
90557cf1 | 66 | |
6043632f | 67 | /* Create an NTS-NTP server instance only if NTS-KE server is enabled */ |
90557cf1 | 68 | if (CNF_GetNtsServerCertAndKeyFiles(&certs, &keys) <= 0) { |
6043632f ML |
69 | server = NULL; |
70 | return; | |
71 | } | |
72 | ||
73 | server = Malloc(sizeof (struct NtsServer)); | |
790a336e ML |
74 | |
75 | server->siv_algorithms[0] = AEAD_AES_SIV_CMAC_256; | |
76 | server->siv_algorithms[1] = AEAD_AES_128_GCM_SIV; | |
77 | assert(MAX_SERVER_SIVS == 2); | |
78 | ||
79 | for (i = 0; i < 2; i++) | |
80 | server->sivs[i] = SIV_CreateInstance(server->siv_algorithms[i]); | |
81 | ||
82 | /* AES-SIV-CMAC-256 is required on servers */ | |
83 | if (!server->sivs[0]) | |
84 | LOG_FATAL("Missing AES-SIV-CMAC-256"); | |
6043632f ML |
85 | } |
86 | ||
87 | /* ================================================== */ | |
88 | ||
89 | void | |
90 | NNS_Finalise(void) | |
91 | { | |
790a336e ML |
92 | int i; |
93 | ||
6043632f ML |
94 | if (!server) |
95 | return; | |
96 | ||
790a336e ML |
97 | for (i = 0; i < MAX_SERVER_SIVS; i++) { |
98 | if (server->sivs[i]) | |
99 | SIV_DestroyInstance(server->sivs[i]); | |
100 | } | |
6043632f ML |
101 | Free(server); |
102 | server = NULL; | |
103 | } | |
104 | ||
105 | /* ================================================== */ | |
106 | ||
107 | int | |
108 | NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod) | |
109 | { | |
110 | int ef_type, ef_body_length, ef_length, has_uniq_id = 0, has_auth = 0, has_cookie = 0; | |
111 | int i, plaintext_length, parsed, requested_cookies, cookie_length = -1, auth_start = 0; | |
112 | unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH]; | |
adcf0734 | 113 | NKE_Context context; |
6043632f | 114 | NKE_Cookie cookie; |
790a336e | 115 | SIV_Instance siv; |
6043632f ML |
116 | void *ef_body; |
117 | ||
f020d479 ML |
118 | *kod = 0; |
119 | ||
6043632f ML |
120 | if (!server) |
121 | return 0; | |
122 | ||
6043632f | 123 | server->num_cookies = 0; |
790a336e | 124 | server->siv_index = -1; |
6043632f ML |
125 | server->req_tx = packet->transmit_ts; |
126 | ||
127 | if (info->ext_fields == 0 || info->mode != MODE_CLIENT) | |
128 | return 0; | |
129 | ||
130 | requested_cookies = 0; | |
131 | ||
132 | for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) { | |
133 | if (!NEF_ParseField(packet, info->length, parsed, | |
134 | &ef_length, &ef_type, &ef_body, &ef_body_length)) | |
d48f0128 ML |
135 | /* This is not expected as the packet already passed NAU_ParsePacket() */ |
136 | return 0; | |
6043632f ML |
137 | |
138 | switch (ef_type) { | |
139 | case NTP_EF_NTS_UNIQUE_IDENTIFIER: | |
140 | has_uniq_id = 1; | |
141 | break; | |
142 | case NTP_EF_NTS_COOKIE: | |
d48f0128 ML |
143 | if (has_cookie || ef_body_length > sizeof (cookie.cookie)) { |
144 | DEBUG_LOG("Unexpected cookie/length"); | |
6043632f | 145 | return 0; |
d48f0128 | 146 | } |
6043632f ML |
147 | cookie.length = ef_body_length; |
148 | memcpy(cookie.cookie, ef_body, ef_body_length); | |
149 | has_cookie = 1; | |
150 | /* Fall through */ | |
151 | case NTP_EF_NTS_COOKIE_PLACEHOLDER: | |
152 | requested_cookies++; | |
153 | ||
154 | if (cookie_length >= 0 && cookie_length != ef_body_length) { | |
155 | DEBUG_LOG("Invalid cookie/placeholder length"); | |
156 | return 0; | |
157 | } | |
158 | cookie_length = ef_body_length; | |
159 | break; | |
160 | case NTP_EF_NTS_AUTH_AND_EEF: | |
de4ecc72 ML |
161 | if (parsed + ef_length != info->length) { |
162 | DEBUG_LOG("Auth not last EF"); | |
163 | return 0; | |
164 | } | |
165 | ||
6043632f ML |
166 | auth_start = parsed; |
167 | has_auth = 1; | |
168 | break; | |
169 | default: | |
170 | break; | |
171 | } | |
172 | } | |
173 | ||
174 | if (!has_uniq_id || !has_cookie || !has_auth) { | |
175 | DEBUG_LOG("Missing an NTS EF"); | |
176 | return 0; | |
177 | } | |
178 | ||
adcf0734 | 179 | if (!NKS_DecodeCookie(&cookie, &context)) { |
6043632f ML |
180 | *kod = NTP_KOD_NTS_NAK; |
181 | return 0; | |
182 | } | |
183 | ||
790a336e ML |
184 | /* Find the SIV instance needed for authentication */ |
185 | for (i = 0; i < MAX_SERVER_SIVS && context.algorithm != server->siv_algorithms[i]; i++) | |
186 | ; | |
187 | if (i == MAX_SERVER_SIVS || !server->sivs[i]) { | |
adcf0734 ML |
188 | DEBUG_LOG("Unexpected SIV"); |
189 | return 0; | |
190 | } | |
790a336e ML |
191 | server->siv_index = i; |
192 | siv = server->sivs[i]; | |
adcf0734 | 193 | |
790a336e | 194 | if (!SIV_SetKey(siv, context.c2s.key, context.c2s.length)) { |
6043632f ML |
195 | DEBUG_LOG("Could not set C2S key"); |
196 | return 0; | |
197 | } | |
198 | ||
790a336e | 199 | if (!NNA_DecryptAuthEF(packet, info, siv, auth_start, |
6043632f ML |
200 | plaintext, sizeof (plaintext), &plaintext_length)) { |
201 | *kod = NTP_KOD_NTS_NAK; | |
202 | return 0; | |
203 | } | |
204 | ||
205 | for (parsed = 0; parsed < plaintext_length; parsed += ef_length) { | |
206 | if (!NEF_ParseSingleField(plaintext, plaintext_length, parsed, | |
fd8fbcd0 ML |
207 | &ef_length, &ef_type, &ef_body, &ef_body_length)) { |
208 | DEBUG_LOG("Could not parse encrypted EF"); | |
209 | return 0; | |
210 | } | |
6043632f ML |
211 | |
212 | switch (ef_type) { | |
213 | case NTP_EF_NTS_COOKIE_PLACEHOLDER: | |
214 | if (cookie_length != ef_body_length) { | |
215 | DEBUG_LOG("Invalid cookie/placeholder length"); | |
216 | return 0; | |
217 | } | |
218 | requested_cookies++; | |
219 | break; | |
220 | default: | |
221 | break; | |
222 | } | |
223 | } | |
224 | ||
790a336e | 225 | if (!SIV_SetKey(siv, context.s2c.key, context.s2c.length)) { |
6043632f ML |
226 | DEBUG_LOG("Could not set S2C key"); |
227 | return 0; | |
228 | } | |
229 | ||
d48f0128 ML |
230 | /* Prepare data for NNS_GenerateResponseAuth() to minimise the time spent |
231 | there (when the TX timestamp is already set) */ | |
232 | ||
6043632f ML |
233 | UTI_GetRandomBytes(server->nonce, sizeof (server->nonce)); |
234 | ||
d48f0128 ML |
235 | assert(sizeof (server->cookies) / sizeof (server->cookies[0]) == NTS_MAX_COOKIES); |
236 | for (i = 0; i < NTS_MAX_COOKIES && i < requested_cookies; i++) | |
adcf0734 | 237 | if (!NKS_GenerateCookie(&context, &server->cookies[i])) |
6043632f ML |
238 | return 0; |
239 | ||
d48f0128 ML |
240 | server->num_cookies = i; |
241 | ||
6043632f ML |
242 | return 1; |
243 | } | |
244 | ||
245 | /* ================================================== */ | |
246 | ||
247 | int | |
248 | NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info, | |
249 | NTP_Packet *response, NTP_PacketInfo *res_info, | |
250 | uint32_t kod) | |
251 | { | |
252 | int i, ef_type, ef_body_length, ef_length, parsed; | |
253 | void *ef_body; | |
254 | unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH]; | |
255 | int plaintext_length; | |
256 | ||
257 | if (!server || req_info->mode != MODE_CLIENT || res_info->mode != MODE_SERVER) | |
258 | return 0; | |
259 | ||
d48f0128 ML |
260 | /* Make sure this is a response to the request from the last call |
261 | of NNS_CheckRequestAuth() */ | |
6043632f ML |
262 | if (UTI_CompareNtp64(&server->req_tx, &request->transmit_ts) != 0) |
263 | assert(0); | |
264 | ||
265 | for (parsed = NTP_HEADER_LENGTH; parsed < req_info->length; parsed += ef_length) { | |
266 | if (!NEF_ParseField(request, req_info->length, parsed, | |
267 | &ef_length, &ef_type, &ef_body, &ef_body_length)) | |
36356ef0 | 268 | /* This is not expected as the packet already passed parsing */ |
d48f0128 | 269 | return 0; |
6043632f ML |
270 | |
271 | switch (ef_type) { | |
272 | case NTP_EF_NTS_UNIQUE_IDENTIFIER: | |
273 | /* Copy the ID from the request */ | |
274 | if (!NEF_AddField(response, res_info, ef_type, ef_body, ef_body_length)) | |
275 | return 0; | |
276 | default: | |
277 | break; | |
278 | } | |
279 | } | |
280 | ||
281 | /* NTS NAK response does not have any other fields */ | |
ce956c99 | 282 | if (kod == NTP_KOD_NTS_NAK) |
6043632f ML |
283 | return 1; |
284 | ||
285 | for (i = 0, plaintext_length = 0; i < server->num_cookies; i++) { | |
286 | if (!NEF_SetField(plaintext, sizeof (plaintext), plaintext_length, | |
d48f0128 | 287 | NTP_EF_NTS_COOKIE, server->cookies[i].cookie, |
6043632f ML |
288 | server->cookies[i].length, &ef_length)) |
289 | return 0; | |
290 | ||
291 | plaintext_length += ef_length; | |
292 | assert(plaintext_length <= sizeof (plaintext)); | |
293 | } | |
294 | ||
295 | server->num_cookies = 0; | |
296 | ||
790a336e ML |
297 | if (server->siv_index < 0) |
298 | return 0; | |
299 | ||
d48f0128 ML |
300 | /* Generate an authenticator field which will make the length |
301 | of the response equal to the length of the request */ | |
790a336e | 302 | if (!NNA_GenerateAuthEF(response, res_info, server->sivs[server->siv_index], |
6043632f ML |
303 | server->nonce, sizeof (server->nonce), |
304 | plaintext, plaintext_length, | |
305 | req_info->length - res_info->length)) | |
306 | return 0; | |
307 | ||
308 | return 1; | |
309 | } |