Download raw body.
rpki-client: normalize & hash the ROA validated payloads
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) {
rpki-client: normalize & hash the ROA validated payloads