Index | Thread | Search

From:
Job Snijders <job@openbsd.org>
Subject:
rpki-client: normalize & hash the ROA validated payloads
To:
tech@openbsd.org
Date:
Mon, 14 Jul 2025 22:07:21 +0000

Download raw body.

Thread
  • Job Snijders:

    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) {