From: Job Snijders Subject: rpki-client: add support for router keys in CCR To: tech@openbsd.org Date: Sun, 14 Sep 2025 10:36:33 +0000 This changeset implements support for router keys in the CCR format. The objective here is to have full coverage for RFC8210/RFC8210bis. OK? Index: ccr.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/ccr.c,v diff -u -p -r1.9 ccr.c --- ccr.c 11 Sep 2025 09:25:05 -0000 1.9 +++ ccr.c 14 Sep 2025 10:34:56 -0000 @@ -46,6 +46,9 @@ ASN1_ITEM_EXP ASPAPayloadSets_it; ASN1_ITEM_EXP ASPAPayloadSet_it; ASN1_ITEM_EXP SubjectKeyIdentifiers_it; ASN1_ITEM_EXP SubjectKeyIdentifier_it; +ASN1_ITEM_EXP RouterKeySets_it; +ASN1_ITEM_EXP RouterKeySet_it; +ASN1_ITEM_EXP RouterKey_it; /* * Can't use CMS_ContentInfo since it is not backed by a public struct @@ -67,6 +70,7 @@ ASN1_SEQUENCE(CanonicalCacheRepresentati ASN1_EXP_OPT(CanonicalCacheRepresentation, vrps, ROAPayloadState, 2), ASN1_EXP_OPT(CanonicalCacheRepresentation, vaps, ASPAPayloadState, 3), ASN1_EXP_OPT(CanonicalCacheRepresentation, tas, TrustAnchorState, 4), + ASN1_EXP_OPT(CanonicalCacheRepresentation, rks, RouterKeyState, 5), } ASN1_SEQUENCE_END(CanonicalCacheRepresentation); IMPLEMENT_ASN1_FUNCTIONS(CanonicalCacheRepresentation); @@ -157,6 +161,34 @@ ASN1_ITEM_TEMPLATE_END(SubjectKeyIdentif IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(SubjectKeyIdentifiers, SubjectKeyIdentifiers, SubjectKeyIdentifiers); +ASN1_SEQUENCE(RouterKeyState) = { + ASN1_SEQUENCE_OF(RouterKeyState, rksets, RouterKeySet), + ASN1_SIMPLE(RouterKeyState, hash, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(RouterKeyState); + +IMPLEMENT_ASN1_FUNCTIONS(RouterKeyState); + +ASN1_ITEM_TEMPLATE(RouterKeySets) = + ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, rks, RouterKeySet) +ASN1_ITEM_TEMPLATE_END(RouterKeySets); + +IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(RouterKeySets, RouterKeySets, + RouterKeySets); + +ASN1_SEQUENCE(RouterKeySet) = { + ASN1_SIMPLE(RouterKeySet, asID, ASN1_INTEGER), + ASN1_SEQUENCE_OF(RouterKeySet, routerKeys, RouterKey), +} ASN1_SEQUENCE_END(RouterKeySet); + +IMPLEMENT_ASN1_FUNCTIONS(RouterKeySet); + +ASN1_SEQUENCE(RouterKey) = { + ASN1_SIMPLE(RouterKey, ski, SubjectKeyIdentifier), + ASN1_SIMPLE(RouterKey, spki, X509_PUBKEY), +} ASN1_SEQUENCE_END(RouterKey); + +IMPLEMENT_ASN1_FUNCTIONS(RouterKey); + static void hash_asn1_item(ASN1_OCTET_STRING *astr, const ASN1_ITEM *it, void *val) { @@ -471,6 +503,71 @@ generate_trustanchorstate(struct validat return tas; } +static RouterKeyState * +generate_routerkeystate(struct validation_data *vd) +{ + RouterKeyState *rks; + RouterKeySet *rkset; + RouterKey *rk; + struct brk *brk, *prev; + unsigned char *pk_der = NULL; + size_t pk_len; + const unsigned char *der; + + if ((rks = RouterKeyState_new()) == NULL) + errx(1, "RouterKeyState_new"); + + prev = NULL; + RB_FOREACH(brk, brk_tree, &vd->brks) { + static char hbuf[SHA_DIGEST_LENGTH] = { 0 }; + + if (prev == NULL || prev->asid != brk->asid) { + if ((rkset = RouterKeySet_new()) == NULL) + errx(1, "RouterKeySet_new"); + + if (!ASN1_INTEGER_set_uint64(rkset->asID, brk->asid)) + errx(1, "ASN1_INTEGER_set_uint64"); + + if (sk_RouterKeySet_push(rks->rksets, rkset) <= 0) + errx(1, "sk_RouterKeySet_push"); + } + + if ((rk = RouterKey_new()) == NULL) + errx(1, "RouterKey_new"); + + if (hex_decode(brk->ski, hbuf, sizeof(hbuf)) == -1) + errx(1, "hex_decode brk pubkey corrupted"); + + if (!ASN1_OCTET_STRING_set(rk->ski, hbuf, sizeof(hbuf))) + errx(1, "ASN1_OCTET_STRING_set"); + + if (base64_decode(brk->pubkey, strlen(brk->pubkey), &pk_der, + &pk_len) == -1) + errx(1, "base64_decode"); + + X509_PUBKEY_free(rk->spki); + der = pk_der; + if ((rk->spki = d2i_X509_PUBKEY(NULL, &der, pk_len)) == NULL) + errx(1, "d2i_X509_PUBKEY"); + if (der != pk_der + pk_len) + errx(1, "brk pubkey corrupted"); + free(pk_der); + pk_der = NULL; + + if ((sk_RouterKey_push(rkset->routerKeys, rk)) <= 0) + errx(1, "sk_RouterKey_push"); + + prev = brk; + } + + hash_asn1_item(rks->hash, ASN1_ITEM_rptr(RouterKeySets), rks->rksets); + + if (!base64_encode_asn1str(rks->hash, &vd->ccr.brks_hash)) + errx(1, "base64_encode_asn1str"); + + return rks; +} + static CanonicalCacheRepresentation * generate_ccr(struct validation_data *vd) { @@ -499,6 +596,9 @@ generate_ccr(struct validation_data *vd) if ((ccr->tas = generate_trustanchorstate(vd)) == NULL) errx(1, "generate_trustanchorstate"); + if ((ccr->rks = generate_routerkeystate(vd)) == NULL) + errx(1, "generate_routerkeystate"); + return ccr; } @@ -743,6 +843,28 @@ ccr_tas_free(struct ccr_tas_tree *tas) } } +static void +brk_free(struct brk *brk) +{ + if (brk == NULL) + return; + + free(brk->ski); + free(brk->pubkey); + free(brk); +} + +static void +ccr_brks_free(struct brk_tree *brks) +{ + struct brk *brk, *tmp_brk; + + RB_FOREACH_SAFE(brk, brk_tree, brks, tmp_brk) { + RB_REMOVE(brk_tree, brks, brk); + brk_free(brk); + } +} + void ccr_free(struct ccr *ccr) { @@ -753,6 +875,7 @@ ccr_free(struct ccr *ccr) ccr_vrps_free(&ccr->vrps); ccr_vaps_free(&ccr->vaps); ccr_tas_free(&ccr->tas); + ccr_brks_free(&ccr->brks); free(ccr->mfts_hash); free(ccr->vrps_hash); @@ -1216,6 +1339,127 @@ parse_tastate(const char *fn, struct ccr return rc; } +static int +parse_routerkeys(const char *fn, struct ccr *ccr, uint32_t asid, + STACK_OF(RouterKey) *routerkeys) +{ + RouterKey *rk; + struct brk *brk, *prev; + int i, rk_num, rc = 0; + + if ((rk_num = sk_RouterKey_num(routerkeys)) <= 0) { + warnx("%s: missing RouterKey", fn); + goto out; + } + + prev = NULL; + for (i = 0; i < rk_num; i++) { + unsigned char *der = NULL; + size_t der_len; + + if ((brk = calloc(1, sizeof(*brk))) == NULL) + err(1, NULL); + + brk->asid = asid; + + rk = sk_RouterKey_value(routerkeys, i); + + if (rk->ski->length != SHA_DIGEST_LENGTH) { + warnx("%s: AS%d RouterKey SKI corrupted", fn, asid); + goto out; + } + + brk->ski = hex_encode(rk->ski->data, rk->ski->length); + if (brk->ski == NULL) + goto out; + + if ((der_len = i2d_X509_PUBKEY(rk->spki, &der)) <= 0) { + warnx("%s: AS%d RouterKey SPKI corrupted", fn, asid); + goto out; + } + + if (base64_encode(der, der_len, &brk->pubkey) == -1) + errx(1, "base64_encode"); + + free(der); + + if (prev != NULL) { + if (strcmp(brk->ski, prev->ski) <= 0) { + warnx("%s: misordered RouterKey", fn); + goto out; + } + } + + if ((RB_INSERT(brk_tree, &ccr->brks, brk)) != NULL) { + warnx("%s; duplicate RouterKey", fn); + goto out; + } + + prev = brk; + brk = NULL; + } + + rc = 1; + out: + return rc; +} + +static int +parse_rksets(const char *fn, struct ccr *ccr, STACK_OF(RouterKeySet) *rksets) +{ + RouterKeySet *rkset; + uint32_t asid, prev = 0; + int i, rc = 0, rksets_num; + + rksets_num = sk_RouterKeySet_num(rksets); + + RB_INIT(&ccr->brks); + + for (i = 0; i < rksets_num; i++) { + rkset = sk_RouterKeySet_value(rksets, i); + + if (!as_id_parse(rkset->asID, &asid)) { + warnx("%s: malformed asID in RouterKeySet", fn); + goto out; + } + + if (i > 0) { + if (asid <= prev) { + warnx("%s: AS %d misordered routerkeyset", fn, + asid); + goto out; + } + } + + if (!parse_routerkeys(fn, ccr, asid, rkset->routerKeys)) + goto out; + + prev = asid; + } + + rc = 1; + out: + return rc; +} + +static int +parse_rkstate(const char *fn, struct ccr *ccr, const RouterKeyState *state) +{ + int rc = 0; + + ccr->brks_hash = validate_asn1_hash(fn, "RouterKeyState", state->hash, + ASN1_ITEM_rptr(RouterKeySets), state->rksets); + if (ccr->brks_hash == NULL) + goto out; + + if (!parse_rksets(fn, ccr, state->rksets)) + goto out; + + rc = 1; + out: + return rc; +} + struct ccr * ccr_parse(const char *fn, const unsigned char *der, size_t len) { @@ -1278,7 +1522,8 @@ ccr_parse(const char *fn, const unsigned goto out; if (ccr_asn1->mfts == NULL && ccr_asn1->vrps == NULL && - ccr_asn1->vaps == NULL && ccr_asn1->tas == NULL) { + ccr_asn1->vaps == NULL && ccr_asn1->tas == NULL && + ccr_asn1->rks == NULL) { warnx("%s: must contain at least one state component", fn); goto out; } @@ -1300,6 +1545,11 @@ ccr_parse(const char *fn, const unsigned if (ccr_asn1->tas != NULL) { if (!parse_tastate(fn, ccr, ccr_asn1->tas)) + goto out; + } + + if (ccr_asn1->rks != NULL) { + if (!parse_rkstate(fn, ccr, ccr_asn1->rks)) goto out; } Index: extern.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v diff -u -p -r1.263 extern.h --- extern.h 11 Sep 2025 08:21:00 -0000 1.263 +++ extern.h 14 Sep 2025 10:34:56 -0000 @@ -490,10 +490,12 @@ struct ccr { struct ccr_vrp_tree vrps; struct vap_tree vaps; /* only used in filemode */ struct ccr_tas_tree tas; + struct brk_tree brks; /* only used in filemode */ char *mfts_hash; char *vrps_hash; char *vaps_hash; char *tas_hash; + char *brks_hash; time_t producedat; time_t most_recent_update; unsigned char *der; Index: print.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/print.c,v diff -u -p -r1.66 print.c --- print.c 9 Sep 2025 08:23:24 -0000 1.66 +++ print.c 14 Sep 2025 10:34:56 -0000 @@ -1073,6 +1073,42 @@ print_ccr_tastate(struct ccr *ccr) } } +static void +print_ccr_rkstate(struct ccr *ccr) +{ + char *hash; + struct brk *brk; + + if (base64_encode(ccr->brks_hash, SHA256_DIGEST_LENGTH, &hash) == -1) + errx(1, "base64_encode"); + + if (outformats & FORMAT_JSON) { + json_do_object("routerkey_state", 0); + json_do_string("hash", hash); + json_do_array("routerkeys"); + RB_FOREACH(brk, brk_tree, &ccr->brks) { + json_do_object("brk", 0); + json_do_int("asn", brk->asid); + json_do_string("ski", brk->ski); + json_do_string("pubkey", brk->pubkey); + json_do_end(); // brk + } + json_do_end(); // routerkeys + json_do_end(); // routerkey_state + } else { + printf("Router key state hash: %s\n", hash); + printf("Router keys:\n"); + RB_FOREACH(brk, brk_tree, &ccr->brks) { + printf("%26s", ""); + printf("asid:%d ", brk->asid); + printf("ski:%s ", brk->ski); + printf("pubkey:%s\n", brk->pubkey); + } + } + + free(hash); +} + void ccr_print(struct ccr *ccr) { @@ -1095,4 +1131,7 @@ ccr_print(struct ccr *ccr) if (ccr->tas_hash != NULL) print_ccr_tastate(ccr); + + if (ccr->brks_hash != NULL) + print_ccr_rkstate(ccr); } Index: rpki-asn1.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/rpki-asn1.h,v diff -u -p -r1.5 rpki-asn1.h --- rpki-asn1.h 11 Sep 2025 08:21:00 -0000 1.5 +++ rpki-asn1.h 14 Sep 2025 10:34:56 -0000 @@ -23,6 +23,7 @@ #include #include +#include /* * Autonomous System Provider Authorization (ASPA) @@ -55,6 +56,9 @@ extern ASN1_ITEM_EXP ASPAPayloadSets_it; extern ASN1_ITEM_EXP ASPAPayloadSet_it; extern ASN1_ITEM_EXP SubjectKeyIdentifiers_it; extern ASN1_ITEM_EXP SubjectKeyIdentifier_it; +extern ASN1_ITEM_EXP RouterKeySets_it; +extern ASN1_ITEM_EXP RouterKeySet_it; +extern ASN1_ITEM_EXP RouterKey_it; typedef struct { ASN1_OCTET_STRING *hash; @@ -164,6 +168,47 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(TrustAnchorState); typedef struct { + STACK_OF(RouterKeySet) *rksets; + ASN1_OCTET_STRING *hash; +} RouterKeyState; + +DECLARE_ASN1_FUNCTIONS(RouterKeyState); + +typedef struct { + ASN1_INTEGER *asID; + STACK_OF(RouterKey) *routerKeys; +} RouterKeySet; + +DECLARE_STACK_OF(RouterKeySet); + +#ifndef DEFINE_STACK_OF +#define sk_RouterKeySet_num(st) SKM_sk_num(RouterKeySet, (st)) +#define sk_RouterKeySet_push(st, i) SKM_sk_push(RouterKeySet, (st), (i)) +#define sk_RouterKeySet_value(st, i) SKM_sk_value(RouterKeySet, (st), (i)) +#endif + +DECLARE_ASN1_FUNCTIONS(RouterKeySet); + +typedef STACK_OF(RouterKeySet) RouterKeySets; + +DECLARE_ASN1_FUNCTIONS(RouterKeySets); + +typedef struct { + SubjectKeyIdentifier *ski; + X509_PUBKEY *spki; +} RouterKey; + +DECLARE_STACK_OF(RouterKey); + +#ifndef DEFINE_STACK_OF +#define sk_RouterKey_num(st) SKM_sk_num(RouterKey, (st)) +#define sk_RouterKey_push(st, i) SKM_sk_push(RouterKey, (st), (i)) +#define sk_RouterKey_value(st, i) SKM_sk_value(RouterKey, (st), (i)) +#endif + +DECLARE_ASN1_FUNCTIONS(RouterKey); + +typedef struct { ASN1_INTEGER *version; ASN1_OBJECT *hashAlg; ASN1_GENERALIZEDTIME *producedAt; @@ -171,6 +216,7 @@ typedef struct { ROAPayloadState *vrps; ASPAPayloadState *vaps; TrustAnchorState *tas; + RouterKeyState *rks; } CanonicalCacheRepresentation; DECLARE_ASN1_FUNCTIONS(CanonicalCacheRepresentation);