From: Job Snijders Subject: rpki-client: normalize & hash the ROA validated payloads To: tech@openbsd.org Date: Mon, 14 Jul 2025 22:07:21 +0000 These hashes in effect are a normalisation of the CSV output: $ grep -v IP /var/db/rpki-client/csv \ | awk --csv '{ print $2 " " $3 " " $1 }' \ | sed 's/AS//' | sha256 | tr '[[:lower:]]' '[[:upper:]]' < 096EE41401D794747286B3F1EDFBD8A1C8B2CDCD39CA6A66D9FB7CC7B2AC7A60 $ grep -v IP /var/db/rpki-client/csv \ | awk --csv '{ print $2 " " $3 " " $1 " " $NF }' \ | sed 's/AS//' | sha256 | tr '[[:lower:]]' '[[:upper:]]' 1CADECE75DD1590B3B41CD58C921E4FF0BA601BDFC073BB5EF68E52C282012BA Comparing validation states between different computer system is easier if the utility calculates and emits the hashes on stdout and the json output. With this diff: $ doas rpki-client -c -j 2>&1 | grep hash Effective VRP set hash: 096EE41401D794747286B3F1EDFBD8A1C8B2CDCD39CA6A66D9FB7CC7B2AC7A60 Transitive VRP window view hash: 1CADECE75DD1590B3B41CD58C921E4FF0BA601BDFC073BB5EF68E52C282012BA $ grep hash /var/db/rpki-client/json "effective_vrp_set_hash": "096EE41401D794747286B3F1EDFBD8A1C8B2CDCD39CA6A66D9FB7CC7B2AC7A60", "transitive_window_view_hash": "1CADECE75DD1590B3B41CD58C921E4FF0BA601BDFC073BB5EF68E52C282012BA" CA Manifest/CRL issuances which are merely a thisUpdate refreshes, are "noticable" through the 'transitive window view' hash; but not through the over the "effective VRP set". The 'effective set' hash probably changes less often compared to the hash calculated also using the 'transitive expiry' moment. OK? Index: extern.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v diff -u -p -r1.247 extern.h --- extern.h 11 Jul 2025 09:20:23 -0000 1.247 +++ extern.h 14 Jul 2025 21:35:25 -0000 @@ -507,6 +507,8 @@ struct validation_data { struct vap_tree vaps; struct vsp_tree vsps; struct nca_tree ncas; + char *effective_vrps_hash; + char *transitive_vrps_hash; }; /* Index: main.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v diff -u -p -r1.285 main.c --- main.c 8 Jul 2025 14:19:21 -0000 1.285 +++ main.c 14 Jul 2025 21:35:25 -0000 @@ -970,6 +970,53 @@ suicide(int sig __attribute__((unused))) killme = 1; } +static void +output_fingerprints(struct validation_data *vd) +{ + struct vrp *v; + SHA256_CTX ectx, tctx; + static char buf[64], sbuf[200]; + unsigned char emd[SHA256_DIGEST_LENGTH], tmd[SHA256_DIGEST_LENGTH]; + int ret; + + SHA256_Init(&ectx); + SHA256_Init(&tctx); + + RB_FOREACH(v, vrp_tree, &vd->vrps) { + ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); + + /* + * The 'effective' fingerprint depends on the stable dedup sort + * in vrpcmp(), which is: + * "{network address}/{plen} {maxlen} {asid}\n" + */ + + memset(&sbuf, 0, sizeof(sbuf)); + ret = snprintf(sbuf, sizeof(sbuf), "%s %u %u\n", buf, + v->maxlength, v->asid); + if (ret < 0 || (size_t)ret >= sizeof(sbuf)) + err(1, NULL); + SHA256_Update(&ectx, sbuf, ret); + + /* + * The transitive tree head is calculated by also including + * the posix timestamp in the list entries used for the hash. + */ + memset(&sbuf, 0, sizeof(sbuf)); + ret = snprintf(sbuf, sizeof(sbuf), "%s %u %u %lld\n", buf, + v->maxlength, v->asid, (long long)v->expires); + if (ret < 0 || (size_t)ret >= sizeof(sbuf)) + err(1, NULL); + SHA256_Update(&tctx, sbuf, ret); + } + + SHA256_Final(emd, &ectx); + SHA256_Final(tmd, &tctx); + + vd->effective_vrps_hash = hex_encode(emd, sizeof(emd)); + vd->transitive_vrps_hash = hex_encode(tmd, sizeof(tmd)); +} + #define NPFD 4 int @@ -1507,6 +1554,8 @@ main(int argc, char *argv[]) } repo_stats_collect(sum_repostats, &stats.repo_stats); + output_fingerprints(&vd); + if (outputfiles(&vd, &stats)) rc = 1; @@ -1551,6 +1600,9 @@ main(int argc, char *argv[]) stats.repo_stats.extra_files, stats.repo_stats.del_extra_files); printf("VRP Entries: %u (%u unique)\n", stats.repo_tal_stats.vrps, stats.repo_tal_stats.vrps_uniqs); + printf("Effective VRP set hash: %s\n", vd.effective_vrps_hash); + printf("Transitive VRP window view hash: %s\n", + vd.transitive_vrps_hash); printf("VAP Entries: %u (%u unique, %u overflowed)\n", stats.repo_tal_stats.vaps, stats.repo_tal_stats.vaps_uniqs, stats.repo_tal_stats.vaps_overflowed); Index: output-json.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/output-json.c,v diff -u -p -r1.54 output-json.c --- output-json.c 8 Jul 2025 14:19:21 -0000 1.54 +++ output-json.c 14 Jul 2025 21:35:25 -0000 @@ -24,7 +24,7 @@ #include "json.h" static void -outputheader_json(struct stats *st) +outputheader_json(struct validation_data *vd, struct stats *st) { char hn[NI_MAXHOST], tbuf[26]; struct tm *tp; @@ -85,6 +85,10 @@ outputheader_json(struct stats *st) json_do_int("cachedir_superfluous_files", st->repo_stats.extra_files); json_do_int("cachedir_del_superfluous_files", st->repo_stats.del_extra_files); + json_do_string("effective_vrp_set_hash", + vd->effective_vrps_hash); + json_do_string("transitive_window_view_hash", + vd->transitive_vrps_hash); json_do_end(); } @@ -153,7 +157,7 @@ output_json(FILE *out, struct validation struct nonfunc_ca *nca; json_do_start(out); - outputheader_json(st); + outputheader_json(vd, st); json_do_array("roas"); RB_FOREACH(v, vrp_tree, &vd->vrps) {