1 /*
2   chronyd/chronyc - Programs for keeping computer clocks accurate.
3 
4  **********************************************************************
5  * Copyright (C) Miroslav Lichvar  2020
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   Client NTS-NTP authentication
25   */
26 
27 #include "config.h"
28 
29 #include "sysincl.h"
30 
31 #include "nts_ntp_client.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 "ntp_sources.h"
39 #include "nts_ke_client.h"
40 #include "nts_ntp.h"
41 #include "nts_ntp_auth.h"
42 #include "sched.h"
43 #include "siv.h"
44 #include "util.h"
45 
46 /* Maximum length of all cookies to avoid IP fragmentation */
47 #define MAX_TOTAL_COOKIE_LENGTH (8 * 108)
48 
49 /* Magic string of files containing keys and cookies */
50 #define DUMP_IDENTIFIER "NNC0\n"
51 
52 struct NNC_Instance_Record {
53   /* Address of NTS-KE server */
54   IPSockAddr nts_address;
55   /* Hostname or IP address for certificate verification */
56   char *name;
57   /* ID of trusted certificates */
58   uint32_t cert_set;
59   /* Configured NTP port */
60   uint16_t default_ntp_port;
61   /* Address of NTP server (can be negotiated in NTS-KE) */
62   IPSockAddr ntp_address;
63 
64   NKC_Instance nke;
65   SIV_Instance siv;
66 
67   int nke_attempts;
68   double next_nke_attempt;
69   double last_nke_success;
70 
71   NKE_Context context;
72   unsigned int context_id;
73   NKE_Cookie cookies[NTS_MAX_COOKIES];
74   int num_cookies;
75   int cookie_index;
76   int auth_ready;
77   int nak_response;
78   int ok_response;
79   unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH];
80   unsigned char uniq_id[NTS_MIN_UNIQ_ID_LENGTH];
81 };
82 
83 /* ================================================== */
84 
85 static void save_cookies(NNC_Instance inst);
86 static void load_cookies(NNC_Instance inst);
87 
88 /* ================================================== */
89 
90 static void
reset_instance(NNC_Instance inst)91 reset_instance(NNC_Instance inst)
92 {
93   if (inst->nke)
94     NKC_DestroyInstance(inst->nke);
95   inst->nke = NULL;
96   if (inst->siv)
97     SIV_DestroyInstance(inst->siv);
98   inst->siv = NULL;
99 
100   inst->nke_attempts = 0;
101   inst->next_nke_attempt = 0.0;
102   inst->last_nke_success = 0.0;
103 
104   memset(&inst->context, 0, sizeof (inst->context));
105   inst->context_id = 0;
106   memset(inst->cookies, 0, sizeof (inst->cookies));
107   inst->num_cookies = 0;
108   inst->cookie_index = 0;
109   inst->auth_ready = 0;
110   inst->nak_response = 0;
111   inst->ok_response = 1;
112   memset(inst->nonce, 0, sizeof (inst->nonce));
113   memset(inst->uniq_id, 0, sizeof (inst->uniq_id));
114 }
115 
116 /* ================================================== */
117 
118 NNC_Instance
NNC_CreateInstance(IPSockAddr * nts_address,const char * name,uint32_t cert_set,uint16_t ntp_port)119 NNC_CreateInstance(IPSockAddr *nts_address, const char *name, uint32_t cert_set, uint16_t ntp_port)
120 {
121   NNC_Instance inst;
122 
123   inst = MallocNew(struct NNC_Instance_Record);
124 
125   inst->nts_address = *nts_address;
126   inst->name = Strdup(name);
127   inst->cert_set = cert_set;
128   inst->default_ntp_port = ntp_port;
129   inst->ntp_address.ip_addr = nts_address->ip_addr;
130   inst->ntp_address.port = ntp_port;
131   inst->siv = NULL;
132   inst->nke = NULL;
133 
134   reset_instance(inst);
135 
136   /* Try to reload saved keys and cookies */
137   load_cookies(inst);
138 
139   return inst;
140 }
141 
142 /* ================================================== */
143 
144 void
NNC_DestroyInstance(NNC_Instance inst)145 NNC_DestroyInstance(NNC_Instance inst)
146 {
147   save_cookies(inst);
148 
149   reset_instance(inst);
150 
151   Free(inst->name);
152   Free(inst);
153 }
154 
155 /* ================================================== */
156 
157 static int
check_cookies(NNC_Instance inst)158 check_cookies(NNC_Instance inst)
159 {
160   /* Force a new NTS-KE session if a NAK was received without a valid response,
161      or the keys encrypting the cookies need to be refreshed */
162   if (inst->num_cookies > 0 &&
163       ((inst->nak_response && !inst->ok_response) ||
164        SCH_GetLastEventMonoTime() - inst->last_nke_success > CNF_GetNtsRefresh())) {
165     inst->num_cookies = 0;
166     DEBUG_LOG("Dropped cookies");
167   }
168 
169   return inst->num_cookies > 0;
170 }
171 
172 /* ================================================== */
173 
174 static int
set_ntp_address(NNC_Instance inst,NTP_Remote_Address * negotiated_address)175 set_ntp_address(NNC_Instance inst, NTP_Remote_Address *negotiated_address)
176 {
177   NTP_Remote_Address old_address, new_address;
178 
179   old_address = inst->ntp_address;
180   new_address = *negotiated_address;
181 
182   if (new_address.ip_addr.family == IPADDR_UNSPEC)
183     new_address.ip_addr = inst->nts_address.ip_addr;
184   if (new_address.port == 0)
185     new_address.port = inst->default_ntp_port;
186 
187   if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip_addr, NULL) == 0 &&
188       old_address.port == new_address.port)
189     /* Nothing to do */
190     return 1;
191 
192   if (NSR_UpdateSourceNtpAddress(&old_address, &new_address) != NSR_Success) {
193     LOG(LOGS_ERR, "Could not change %s to negotiated address %s",
194         UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip_addr));
195     return 0;
196   }
197 
198   inst->ntp_address = new_address;
199 
200   return 1;
201 }
202 
203 /* ================================================== */
204 
205 static void
update_next_nke_attempt(NNC_Instance inst,double now)206 update_next_nke_attempt(NNC_Instance inst, double now)
207 {
208   int factor, interval;
209 
210   if (!inst->nke)
211     return;
212 
213   factor = NKC_GetRetryFactor(inst->nke);
214   interval = MIN(factor + inst->nke_attempts - 1, NKE_MAX_RETRY_INTERVAL2);
215   inst->next_nke_attempt = now + UTI_Log2ToDouble(interval);
216 }
217 
218 /* ================================================== */
219 
220 static int
get_cookies(NNC_Instance inst)221 get_cookies(NNC_Instance inst)
222 {
223   NTP_Remote_Address ntp_address;
224   double now;
225   int got_data;
226 
227   assert(inst->num_cookies == 0);
228 
229   now = SCH_GetLastEventMonoTime();
230 
231   /* Create and start a new NTS-KE session if not already present */
232   if (!inst->nke) {
233     if (now < inst->next_nke_attempt) {
234       DEBUG_LOG("Limiting NTS-KE request rate (%f seconds)",
235                 inst->next_nke_attempt - now);
236       return 0;
237     }
238 
239     inst->nke = NKC_CreateInstance(&inst->nts_address, inst->name, inst->cert_set);
240 
241     inst->nke_attempts++;
242     update_next_nke_attempt(inst, now);
243 
244     if (!NKC_Start(inst->nke))
245       return 0;
246   }
247 
248   update_next_nke_attempt(inst, now);
249 
250   /* Wait until the session stops */
251   if (NKC_IsActive(inst->nke))
252     return 0;
253 
254   assert(sizeof (inst->cookies) / sizeof (inst->cookies[0]) == NTS_MAX_COOKIES);
255 
256   /* Get the new keys, cookies and NTP address if the session was successful */
257   got_data = NKC_GetNtsData(inst->nke, &inst->context,
258                             inst->cookies, &inst->num_cookies, NTS_MAX_COOKIES,
259                             &ntp_address);
260 
261   NKC_DestroyInstance(inst->nke);
262   inst->nke = NULL;
263 
264   if (!got_data)
265     return 0;
266 
267   if (inst->siv)
268     SIV_DestroyInstance(inst->siv);
269   inst->siv = NULL;
270 
271   inst->context_id++;
272 
273   /* Force a new session if the NTP address is used by another source, with
274      an expectation that it will eventually get a non-conflicting address */
275   if (!set_ntp_address(inst, &ntp_address)) {
276     inst->num_cookies = 0;
277     return 0;
278   }
279 
280   inst->last_nke_success = now;
281   inst->cookie_index = 0;
282 
283   return 1;
284 }
285 
286 /* ================================================== */
287 
288 int
NNC_PrepareForAuth(NNC_Instance inst)289 NNC_PrepareForAuth(NNC_Instance inst)
290 {
291   inst->auth_ready = 0;
292 
293   /* Prepare data for the next request and invalidate any responses to the
294      previous request */
295   UTI_GetRandomBytes(inst->uniq_id, sizeof (inst->uniq_id));
296   UTI_GetRandomBytes(inst->nonce, sizeof (inst->nonce));
297 
298   /* Get new cookies if there are not any, or they are no longer usable */
299   if (!check_cookies(inst)) {
300     if (!get_cookies(inst))
301       return 0;
302   }
303 
304   inst->nak_response = 0;
305 
306   if (!inst->siv)
307     inst->siv = SIV_CreateInstance(inst->context.algorithm);
308 
309   if (!inst->siv ||
310       !SIV_SetKey(inst->siv, inst->context.c2s.key, inst->context.c2s.length)) {
311     DEBUG_LOG("Could not set SIV key");
312     return 0;
313   }
314 
315   inst->auth_ready = 1;
316 
317   return 1;
318 }
319 
320 /* ================================================== */
321 
322 int
NNC_GenerateRequestAuth(NNC_Instance inst,NTP_Packet * packet,NTP_PacketInfo * info)323 NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet,
324                         NTP_PacketInfo *info)
325 {
326   NKE_Cookie *cookie;
327   int i, req_cookies;
328   void *ef_body;
329 
330   if (!inst->auth_ready)
331     return 0;
332 
333   inst->auth_ready = 0;
334 
335   if (inst->num_cookies <= 0 || !inst->siv)
336     return 0;
337 
338   if (info->mode != MODE_CLIENT)
339     return 0;
340 
341   cookie = &inst->cookies[inst->cookie_index];
342   inst->num_cookies--;
343   inst->cookie_index = (inst->cookie_index + 1) % NTS_MAX_COOKIES;
344 
345   req_cookies = MIN(NTS_MAX_COOKIES - inst->num_cookies,
346                     MAX_TOTAL_COOKIE_LENGTH / (cookie->length + 4));
347 
348   if (!NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER,
349                     inst->uniq_id, sizeof (inst->uniq_id)))
350     return 0;
351 
352   if (!NEF_AddField(packet, info, NTP_EF_NTS_COOKIE,
353                     cookie->cookie, cookie->length))
354     return 0;
355 
356   for (i = 0; i < req_cookies - 1; i++) {
357     if (!NEF_AddBlankField(packet, info, NTP_EF_NTS_COOKIE_PLACEHOLDER,
358                            cookie->length, &ef_body))
359       return 0;
360     memset(ef_body, 0, cookie->length);
361   }
362 
363   if (!NNA_GenerateAuthEF(packet, info, inst->siv, inst->nonce, sizeof (inst->nonce),
364                           (const unsigned char *)"", 0, NTP_MAX_V4_MAC_LENGTH + 4))
365     return 0;
366 
367   inst->ok_response = 0;
368 
369   return 1;
370 }
371 
372 /* ================================================== */
373 
374 static int
parse_encrypted_efs(NNC_Instance inst,unsigned char * plaintext,int length)375 parse_encrypted_efs(NNC_Instance inst, unsigned char *plaintext, int length)
376 {
377   int ef_length, parsed;
378 
379   for (parsed = 0; parsed < length; parsed += ef_length) {
380     if (!NEF_ParseSingleField(plaintext, length, parsed, &ef_length, NULL, NULL, NULL)) {
381       DEBUG_LOG("Could not parse encrypted EF");
382       return 0;
383     }
384   }
385 
386   return 1;
387 }
388 
389 /* ================================================== */
390 
391 static int
extract_cookies(NNC_Instance inst,unsigned char * plaintext,int length)392 extract_cookies(NNC_Instance inst, unsigned char *plaintext, int length)
393 {
394   int ef_type, ef_body_length, ef_length, parsed, index, acceptable, saved;
395   void *ef_body;
396 
397   acceptable = saved = 0;
398 
399   for (parsed = 0; parsed < length; parsed += ef_length) {
400     if (!NEF_ParseSingleField(plaintext, length, parsed,
401                               &ef_length, &ef_type, &ef_body, &ef_body_length))
402       return 0;
403 
404     if (ef_type != NTP_EF_NTS_COOKIE)
405       continue;
406 
407     if (ef_length < NTP_MIN_EF_LENGTH || ef_body_length > sizeof (inst->cookies[0].cookie)) {
408       DEBUG_LOG("Unexpected cookie length %d", ef_body_length);
409       continue;
410     }
411 
412     acceptable++;
413 
414     if (inst->num_cookies >= NTS_MAX_COOKIES)
415       continue;
416 
417     index = (inst->cookie_index + inst->num_cookies) % NTS_MAX_COOKIES;
418     assert(index >= 0 && index < NTS_MAX_COOKIES);
419     assert(sizeof (inst->cookies) / sizeof (inst->cookies[0]) == NTS_MAX_COOKIES);
420 
421     memcpy(inst->cookies[index].cookie, ef_body, ef_body_length);
422     inst->cookies[index].length = ef_body_length;
423     inst->num_cookies++;
424 
425     saved++;
426   }
427 
428   DEBUG_LOG("Extracted %d cookies (saved %d)", acceptable, saved);
429 
430   return acceptable > 0;
431 }
432 
433 /* ================================================== */
434 
435 int
NNC_CheckResponseAuth(NNC_Instance inst,NTP_Packet * packet,NTP_PacketInfo * info)436 NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet,
437                       NTP_PacketInfo *info)
438 {
439   int ef_type, ef_body_length, ef_length, parsed, plaintext_length;
440   int has_valid_uniq_id = 0, has_valid_auth = 0;
441   unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH];
442   void *ef_body;
443 
444   if (info->ext_fields == 0 || info->mode != MODE_SERVER)
445     return 0;
446 
447   /* Accept at most one response per request */
448   if (inst->ok_response || inst->auth_ready)
449     return 0;
450 
451   if (!inst->siv ||
452       !SIV_SetKey(inst->siv, inst->context.s2c.key, inst->context.s2c.length)) {
453     DEBUG_LOG("Could not set SIV key");
454     return 0;
455   }
456 
457   for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) {
458     if (!NEF_ParseField(packet, info->length, parsed,
459                         &ef_length, &ef_type, &ef_body, &ef_body_length))
460       /* This is not expected as the packet already passed parsing */
461       return 0;
462 
463     switch (ef_type) {
464       case NTP_EF_NTS_UNIQUE_IDENTIFIER:
465         if (ef_body_length != sizeof (inst->uniq_id) ||
466             memcmp(ef_body, inst->uniq_id, sizeof (inst->uniq_id)) != 0) {
467           DEBUG_LOG("Invalid uniq id");
468           return 0;
469         }
470         has_valid_uniq_id = 1;
471         break;
472       case NTP_EF_NTS_COOKIE:
473         DEBUG_LOG("Unencrypted cookie");
474         break;
475       case NTP_EF_NTS_AUTH_AND_EEF:
476         if (parsed + ef_length != info->length) {
477           DEBUG_LOG("Auth not last EF");
478           return 0;
479         }
480 
481         if (!NNA_DecryptAuthEF(packet, info, inst->siv, parsed,
482                                plaintext, sizeof (plaintext), &plaintext_length))
483           return 0;
484 
485         if (!parse_encrypted_efs(inst, plaintext, plaintext_length))
486           return 0;
487 
488         has_valid_auth = 1;
489         break;
490       default:
491         break;
492     }
493   }
494 
495   if (!has_valid_uniq_id || !has_valid_auth) {
496     if (has_valid_uniq_id && packet->stratum == NTP_INVALID_STRATUM &&
497         ntohl(packet->reference_id) == NTP_KOD_NTS_NAK) {
498       DEBUG_LOG("NTS NAK");
499       inst->nak_response = 1;
500       return 0;
501     }
502 
503     DEBUG_LOG("Missing NTS EF");
504     return 0;
505   }
506 
507   if (!extract_cookies(inst, plaintext, plaintext_length))
508     return 0;
509 
510   inst->ok_response = 1;
511 
512   /* At this point we know the client interoperates with the server.  Allow a
513      new NTS-KE session to be started as soon as the cookies run out. */
514   inst->nke_attempts = 0;
515   inst->next_nke_attempt = 0.0;
516 
517   return 1;
518 }
519 
520 /* ================================================== */
521 
522 void
NNC_ChangeAddress(NNC_Instance inst,IPAddr * address)523 NNC_ChangeAddress(NNC_Instance inst, IPAddr *address)
524 {
525   save_cookies(inst);
526 
527   inst->nts_address.ip_addr = *address;
528   inst->ntp_address.ip_addr = *address;
529 
530   reset_instance(inst);
531 
532   DEBUG_LOG("NTS reset");
533 
534   load_cookies(inst);
535 }
536 
537 /* ================================================== */
538 
539 static void
save_cookies(NNC_Instance inst)540 save_cookies(NNC_Instance inst)
541 {
542   char buf[2 * NKE_MAX_COOKIE_LENGTH + 2], *dump_dir, *filename;
543   struct timespec now;
544   double context_time;
545   FILE *f;
546   int i;
547 
548   if (inst->num_cookies < 1 || !UTI_IsIPReal(&inst->nts_address.ip_addr))
549     return;
550 
551   dump_dir = CNF_GetNtsDumpDir();
552   if (!dump_dir)
553     return;
554 
555   filename = UTI_IPToString(&inst->nts_address.ip_addr);
556 
557   f = UTI_OpenFile(dump_dir, filename, ".tmp", 'w', 0600);
558   if (!f)
559     return;
560 
561   SCH_GetLastEventTime(&now, NULL, NULL);
562   context_time = inst->last_nke_success - SCH_GetLastEventMonoTime();
563   context_time += UTI_TimespecToDouble(&now);
564 
565   if (fprintf(f, "%s%s\n%.1f\n%s %d\n%u %d ",
566               DUMP_IDENTIFIER, inst->name, context_time,
567               UTI_IPToString(&inst->ntp_address.ip_addr), inst->ntp_address.port,
568               inst->context_id, (int)inst->context.algorithm) < 0 ||
569       !UTI_BytesToHex(inst->context.s2c.key, inst->context.s2c.length, buf, sizeof (buf)) ||
570       fprintf(f, "%s ", buf) < 0 ||
571       !UTI_BytesToHex(inst->context.c2s.key, inst->context.c2s.length, buf, sizeof (buf)) ||
572       fprintf(f, "%s\n", buf) < 0)
573     goto error;
574 
575   for (i = 0; i < inst->num_cookies; i++) {
576     if (!UTI_BytesToHex(inst->cookies[i].cookie, inst->cookies[i].length, buf, sizeof (buf)) ||
577         fprintf(f, "%s\n", buf) < 0)
578       goto error;
579   }
580 
581   fclose(f);
582 
583   if (!UTI_RenameTempFile(dump_dir, filename, ".tmp", ".nts"))
584     ;
585   return;
586 
587 error:
588   DEBUG_LOG("Could not %s cookies for %s", "save", filename);
589   fclose(f);
590 
591   if (!UTI_RemoveFile(dump_dir, filename, ".nts"))
592     ;
593 }
594 
595 /* ================================================== */
596 
597 #define MAX_WORDS 4
598 
599 static void
load_cookies(NNC_Instance inst)600 load_cookies(NNC_Instance inst)
601 {
602   char line[2 * NKE_MAX_COOKIE_LENGTH + 2], *dump_dir, *filename, *words[MAX_WORDS];
603   unsigned int context_id;
604   int i, algorithm, port;
605   double context_time;
606   struct timespec now;
607   IPSockAddr ntp_addr;
608   FILE *f;
609 
610   dump_dir = CNF_GetNtsDumpDir();
611   if (!dump_dir)
612     return;
613 
614   filename = UTI_IPToString(&inst->nts_address.ip_addr);
615 
616   f = UTI_OpenFile(dump_dir, filename, ".nts", 'r', 0);
617   if (!f)
618     return;
619 
620   /* Don't load this file again */
621   if (!UTI_RemoveFile(dump_dir, filename, ".nts"))
622     ;
623 
624   if (inst->siv)
625     SIV_DestroyInstance(inst->siv);
626   inst->siv = NULL;
627 
628   if (!fgets(line, sizeof (line), f) || strcmp(line, DUMP_IDENTIFIER) != 0 ||
629       !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 1 ||
630         strcmp(words[0], inst->name) != 0 ||
631       !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 1 ||
632         sscanf(words[0], "%lf", &context_time) != 1 ||
633       !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 2 ||
634         !UTI_StringToIP(words[0], &ntp_addr.ip_addr) || sscanf(words[1], "%d", &port) != 1 ||
635       !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 4 ||
636         sscanf(words[0], "%u", &context_id) != 1 || sscanf(words[1], "%d", &algorithm) != 1)
637     goto error;
638 
639   inst->context.algorithm = algorithm;
640   inst->context.s2c.length = UTI_HexToBytes(words[2], inst->context.s2c.key,
641                                             sizeof (inst->context.s2c.key));
642   inst->context.c2s.length = UTI_HexToBytes(words[3], inst->context.c2s.key,
643                                             sizeof (inst->context.c2s.key));
644 
645   if (inst->context.s2c.length != SIV_GetKeyLength(algorithm) ||
646       inst->context.c2s.length != inst->context.s2c.length)
647     goto error;
648 
649   for (i = 0; i < NTS_MAX_COOKIES && fgets(line, sizeof (line), f); i++) {
650     if (UTI_SplitString(line, words, MAX_WORDS) != 1)
651       goto error;
652 
653     inst->cookies[i].length = UTI_HexToBytes(words[0], inst->cookies[i].cookie,
654                                              sizeof (inst->cookies[i].cookie));
655     if (inst->cookies[i].length == 0)
656       goto error;
657   }
658 
659   inst->num_cookies = i;
660 
661   ntp_addr.port = port;
662   if (!set_ntp_address(inst, &ntp_addr))
663     goto error;
664 
665   SCH_GetLastEventTime(&now, NULL, NULL);
666   context_time -= UTI_TimespecToDouble(&now);
667   if (context_time > 0)
668     context_time = 0;
669   inst->last_nke_success = context_time + SCH_GetLastEventMonoTime();
670   inst->context_id = context_id;
671 
672   fclose(f);
673 
674   DEBUG_LOG("Loaded %d cookies for %s", i, filename);
675   return;
676 
677 error:
678   DEBUG_LOG("Could not %s cookies for %s", "load", filename);
679   fclose(f);
680 
681   memset(&inst->context, 0, sizeof (inst->context));
682   inst->num_cookies = 0;
683 }
684 
685 /* ================================================== */
686 
687 void
NNC_DumpData(NNC_Instance inst)688 NNC_DumpData(NNC_Instance inst)
689 {
690   save_cookies(inst);
691 }
692 
693 /* ================================================== */
694 
695 void
NNC_GetReport(NNC_Instance inst,RPT_AuthReport * report)696 NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report)
697 {
698   report->key_id = inst->context_id;
699   report->key_type = inst->context.algorithm;
700   report->key_length = 8 * inst->context.s2c.length;
701   report->ke_attempts = inst->nke_attempts;
702   if (report->key_length > 0)
703     report->last_ke_ago = SCH_GetLastEventMonoTime() - inst->last_nke_success;
704   else
705     report->last_ke_ago = -1;
706   report->cookies = inst->num_cookies;
707   report->cookie_length = inst->num_cookies > 0 ? inst->cookies[inst->cookie_index].length : 0;
708   report->nak = inst->nak_response;
709 }
710