Dear all,

I've authored an extension to rpki-client(8) to support validation of
Signed Objects containing Trust Anchor Keys (aka 'Signed TALs'). Signed
TALs provide a mechanism for RIRs to distribute and sign the next Trust
Anchor with the current Trust Anchor. This might be an improvement over
going to RIR websites and copy+pasting TAL data.

The Signed TAL specification is available here:
https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-signed-tal

A testbed is available here:
https://github.com/APNIC-net/rpki-signed-tal-demo/blob/master/testbed-tas.md

This implementation deviates from the specification in a small number of
ways:

* omitted to implement the notion of acceptance timers.
* omitted to check (and disallow) multiple TAK objects under a single
  TA.
* Because of the unveil() write barrier between the parser subprocess
  and the system operator 'owned' roots in /etc/rpki, this
  implementation does not use successor TAKeys for top-down validation.

Below is a terminal transcript of the workflow I envision distributors
of RPKI TALs would use to cryptographically verify a new TAL originated
from a given Trust Anchor operator. 

Kind regards,

Job

$ doas rpki-client -R -t two_taks.tal
Processing time 12 seconds (0 seconds user, 0 seconds system)
Skiplist entries: 0
Route Origin Authorizations: 0 (0 failed parse, 0 invalid)
AS Provider Attestations: 0 (0 failed parse, 0 invalid)
BGPsec Router Certificates: 0
Certificates: 2 (0 invalid)
Trust Anchor Locators: 1 (0 invalid)
Manifests: 2 (0 failed parse, 0 stale)
Certificate revocation lists: 2
Ghostbuster records: 0
Repositories: 2
Cleanup: removed 531 files, 228 directories, 0 superfluous
VRP Entries: 0 (0 unique)
VAP Entries: 0 (0 unique)

$ cd /var/cache/rpki-client/

$ find * -name '*.tak'
rpki-testbed.apnic.net/repository/ED9D6A424DA911EDB9AC0C5B9E174E93/05F53BCE4DAA11EDB9AC0C5B9E174E93.tak

$ cd rpki-testbed.apnic.net/repository/ED9D6A424DA911EDB9AC0C5B9E174E93/

$ rpki-client -t two_taks.tal -f 05F53BCE4DAA11EDB9AC0C5B9E174E93.tak
File: 05F53BCE4DAA11EDB9AC0C5B9E174E93.tak
Hash identifier: jTSYpCn4ceH4eSfpvehCtg9B4vr2pZYfyhAZg1z34L8=
Subject key identifier: 
37:AF:B3:64:3A:EA:0F:50:82:E9:20:D4:F2:72:C3:5B:88:A2:23:3A
Certificate serial: 05
Authority key identifier: 
08:C4:85:FC:A8:A3:59:F2:AD:09:47:E8:0F:CD:1F:48:52:93:4D:8F
Authority info access: 
rsync://rpki-testbed.apnic.net/repository/ED9D5F8E4DA911EDB9AC0C5B9E174E93/root.cer
TAK EE certificate valid until: 2037-01-01T00:00:00Z
TAL derived from the 'current' Trust Anchor Key:

        # Current key for original TAL
        
rsync://rpki-testbed.apnic.net/repository/ED9D5F8E4DA911EDB9AC0C5B9E174E93/root.cer

        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyYT3tLDtrBW4xeBnhNWM
        jgRkJJKlUyh3TodVDLxF+7r2CZOpTqSjkNO68G7HEmlRJnIxT6OyY+E4vg9kHmIU
        cYyy9Cf3AP1GN9PbgR2TD6BCZzinxhQZSvm8tN0D16jjWBLh23FXbNr0a0lhnbPa
        MGuLVIN2mHQ1eLyUSqhx6G40f+yVfOvmjW2vgkfiYkOSjbDPIGSVSn8jHzwDAVMY
        QZdqPgHniw3RHlCmJRJWFVGxgG4qBdjeZfnlCRZamv3/MA8HJMMu6knLc4xj7uFC
        kd/+I40pmZHrubsyWgLN4RBfx62hAOF/TTKzCqoncc45PYe8KOFHeEIR5fna0RjW
        sQIDAQAB

TAL derived from the 'successor' Trust Anchor Key:

        # Successor key for original TAL
        
rsync://rpki-testbed.apnic.net/repository/F785A7404DA911EDB9AC0C5B9E174E93/root.cer

        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1lGORSJFEjbf8t6zEANM
        F6ROAjSGs9mFMX5DnPUFoMzRcjc6mdgE1T4XsThm+oHc7lxlr4mhaZ08QPePHGuT
        UilXOBNZTyZS2Ed75fQTN12biVaOdP/XQCUEEllqpCiNpKt7+C0YjMmbzS1jEK76
        3WgaXQsKSiDolyHr4xfxmhf+/e3F8C6nU9OYaVSKU3grlr/rpfTZl7CSe7Gq1BNW
        A3BWuYHA/wmlGIQdV60GcvkHWZPzJTdxkRQLgeCQUHY22YHpiRtqckVCx9jHOU8v
        ygp62bERlCRT7PtGmgdUJE7Co40289/sgWWo2R0CncrvhPFdBEoaDNT/s+I+eTtx
        UQIDAQAB

Validation: OK

End of terminal transcript, proposed changeset follows below:

Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v
retrieving revision 1.26
diff -u -p -r1.26 Makefile
--- Makefile    30 Aug 2022 18:56:49 -0000      1.26
+++ Makefile    21 Oct 2022 13:07:29 -0000
@@ -5,7 +5,7 @@ SRCS=   as.c aspa.c cert.c cms.c crl.c enc
        ip.c log.c main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \
        output-csv.c output-json.c parser.c print.c repo.c roa.c rrdp.c \
        rrdp_delta.c rrdp_notification.c rrdp_snapshot.c rrdp_util.c \
-       rsc.c rsync.c tal.c validate.c x509.c
+       rsc.c rsync.c tak.c tal.c validate.c x509.c
 MAN=   rpki-client.8
 
 LDADD+= -lexpat -ltls -lssl -lcrypto -lutil
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
retrieving revision 1.156
diff -u -p -r1.156 extern.h
--- extern.h    3 Sep 2022 21:24:02 -0000       1.156
+++ extern.h    21 Oct 2022 13:07:29 -0000
@@ -183,6 +183,7 @@ enum rtype {
        RTYPE_FILE,
        RTYPE_RSC,
        RTYPE_ASPA,
+       RTYPE_TAK,
 };
 
 enum location {
@@ -275,6 +276,33 @@ struct rsc {
 };
 
 /*
+ * Datastructure representing the TAKey sequence inside TAKs.
+ */
+struct takey {
+       char            **comments; /* Comments */
+       size_t           commentsz; /* number of Comments */
+       char            **uris; /* CertificateURI */
+       size_t           urisz; /* number of CertificateURIs */
+       unsigned char   *pubkey; /* DER encoded SubjectPublicKeyInfo */
+       size_t           pubkeysz;
+       char            *ski; /* hex encoded SubjectKeyIdentifier of pubkey */
+};
+
+/*
+ * A Signed TAL (TAK) draft-ietf-sidrops-signed-tal-12
+ */
+struct tak {
+       int              talid; /* TAK covered by what TAL */
+       struct takey    *current;
+       struct takey    *predecessor;
+       struct takey    *successor;
+       char            *aia; /* AIA */
+       char            *aki; /* AKI */
+       char            *ski; /* SKI */
+       time_t           expires; /* Not After of the TAK EE */
+};
+
+/*
  * A single Ghostbuster record
  */
 struct gbr {
@@ -474,6 +502,7 @@ struct stats {
        size_t   rrdp_fails; /* failed rrdp repositories */
        size_t   crls; /* revocation lists */
        size_t   gbrs; /* ghostbuster records */
+       size_t   taks; /* signed TAL objects */
        size_t   aspas; /* ASPA objects */
        size_t   aspas_fail; /* ASPA objects failing syntactic parse */
        size_t   aspas_invalid; /* ASPAs with invalid customerASID */
@@ -544,6 +573,11 @@ void                rsc_free(struct rsc *);
 struct rsc     *rsc_parse(X509 **, const char *, const unsigned char *,
                    size_t);
 
+void            tak_free(struct tak *);
+struct tak     *tak_parse(X509 **, const char *, const unsigned char *,
+                   size_t);
+struct tak     *tak_read(struct ibuf *);
+
 void            aspa_buffer(struct ibuf *, const struct aspa *);
 void            aspa_free(struct aspa *);
 void            aspa_insert_vaps(struct vap_tree *, struct aspa *, size_t *,
@@ -726,6 +760,7 @@ void                 roa_print(const X509 *, const str
 void            gbr_print(const X509 *, const struct gbr *);
 void            rsc_print(const X509 *, const struct rsc *);
 void            aspa_print(const X509 *, const struct aspa *);
+void            tak_print(const X509 *, const struct tak *);
 
 /* Output! */
 
Index: filemode.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/filemode.c,v
retrieving revision 1.14
diff -u -p -r1.14 filemode.c
--- filemode.c  6 Sep 2022 11:16:51 -0000       1.14
+++ filemode.c  21 Oct 2022 13:07:29 -0000
@@ -269,6 +269,7 @@ proc_parser_file(char *file, unsigned ch
        struct tal *tal = NULL;
        struct rsc *rsc = NULL;
        struct aspa *aspa = NULL;
+       struct tak *tak = NULL;
        char *aia = NULL, *aki = NULL;
        char filehash[SHA256_DIGEST_LENGTH];
        char *hash;
@@ -376,6 +377,14 @@ proc_parser_file(char *file, unsigned ch
                aia = aspa->aia;
                aki = aspa->aki;
                break;
+       case RTYPE_TAK:
+               tak = tak_parse(&x509, file, buf, len);
+               if (tak == NULL)
+                       break;
+               tak_print(x509, tak);
+               aia = tak->aia;
+               aki = tak->aki;
+               break;
        default:
                printf("%s: unsupported file type\n", file);
                break;
@@ -469,6 +478,7 @@ proc_parser_file(char *file, unsigned ch
        tal_free(tal);
        rsc_free(rsc);
        aspa_free(aspa);
+       tak_free(tak);
 }
 
 /*
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
retrieving revision 1.219
diff -u -p -r1.219 main.c
--- main.c      3 Sep 2022 09:22:25 -0000       1.219
+++ main.c      21 Oct 2022 13:07:30 -0000
@@ -612,6 +612,9 @@ entity_process(struct ibuf *b, struct st
                        st->aspas_invalid++;
                aspa_free(aspa);
                break;
+       case RTYPE_TAK:
+               st->taks++;
+               break;
        default:
                errx(1, "unknown entity type %d", type);
        }
@@ -1311,6 +1314,8 @@ main(int argc, char *argv[])
            stats.mfts, stats.mfts_fail, stats.mfts_stale);
        printf("Certificate revocation lists: %zu\n", stats.crls);
        printf("Ghostbuster records: %zu\n", stats.gbrs);
+       if (verbose)
+               printf("Trust Anchor Keys: %zu\n", stats.taks);
        printf("Repositories: %zu\n", stats.repos);
        printf("Cleanup: removed %zu files, %zu directories, %zu superfluous\n",
            stats.del_files, stats.del_dirs, stats.extra_files);
Index: mft.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/mft.c,v
retrieving revision 1.75
diff -u -p -r1.75 mft.c
--- mft.c       13 Oct 2022 04:43:32 -0000      1.75
+++ mft.c       21 Oct 2022 13:07:30 -0000
@@ -1,4 +1,4 @@
-/*     $OpenBSD: mft.c,v 1.75 2022/10/13 04:43:32 job Exp $ */
+/*     $OpenBSD: mft.c,v 1.74 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2022 Theo Buehler <t...@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <krist...@bsd.lv>
@@ -168,6 +168,8 @@ rtype_from_file_extension(const char *fn
                return RTYPE_RSC;
        if (strcasecmp(fn + sz - 4, ".asa") == 0)
                return RTYPE_ASPA;
+       if (strcasecmp(fn + sz - 4, ".tak") == 0)
+               return RTYPE_TAK;
 
        return RTYPE_INVALID;
 }
@@ -208,6 +210,7 @@ rtype_from_mftfile(const char *fn)
        case RTYPE_GBR:
        case RTYPE_ROA:
        case RTYPE_ASPA:
+       case RTYPE_TAK:
                return type;
        default:
                return RTYPE_INVALID;
Index: output-json.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-json.c,v
retrieving revision 1.28
diff -u -p -r1.28 output-json.c
--- output-json.c       30 Aug 2022 23:40:37 -0000      1.28
+++ output-json.c       21 Oct 2022 13:07:30 -0000
@@ -52,6 +52,7 @@ outputheader_json(FILE *out, struct stat
            "\t\t\"bgpsec_pubkeys\": %zu,\n"
            "\t\t\"certificates\": %zu,\n"
            "\t\t\"invalidcertificates\": %zu,\n"
+           "\t\t\"taks\": %zu,\n"
            "\t\t\"tals\": %zu,\n"
            "\t\t\"invalidtals\": %zu,\n"
            "\t\t\"talfiles\": [\n",
@@ -59,7 +60,7 @@ outputheader_json(FILE *out, struct stat
            (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
            st->roas, st->roas_fail, st->roas_invalid,
            st->aspas, st->aspas_fail, st->aspas_invalid,
-           st->brks, st->certs, st->certs_fail,
+           st->brks, st->certs, st->certs_fail, st->taks,
            st->tals, talsz - st->tals) < 0)
                return -1;
 
Index: parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
retrieving revision 1.77
diff -u -p -r1.77 parser.c
--- parser.c    3 Sep 2022 21:24:02 -0000       1.77
+++ parser.c    21 Oct 2022 13:07:30 -0000
@@ -521,6 +521,42 @@ proc_parser_aspa(char *file, const unsig
 }
 
 /*
+ * Parse a TAK object.
+ */
+static struct tak *
+proc_parser_tak(char *file, const unsigned char *der, size_t len)
+{
+       struct tak              *tak;
+       X509                    *x509;
+       struct crl              *crl;
+       struct auth             *a;
+       int                      rc = 0;
+
+       if ((tak = tak_parse(&x509, file, der, len)) == NULL)
+               return NULL;
+
+       a = valid_ski_aki(file, &auths, tak->ski, tak->aki);
+       crl = crl_get(&crlt, a);
+
+       /* TAK EE must be signed by self-signed CA */
+       if (a->parent != NULL)
+               goto out;
+
+       if (!valid_x509(file, ctx, x509, a, crl, 0))
+               goto out;
+
+       tak->talid = a->cert->talid;
+       rc = 1;
+ out:
+       if (rc == 0) {
+               tak_free(tak);
+               tak = NULL;
+       }
+       X509_free(x509);
+       return tak;
+}
+
+/*
  * Load the file specified by the entity information.
  */
 static char *
@@ -648,6 +684,11 @@ parse_entity(struct entityq *q, struct m
                        if (aspa != NULL)
                                aspa_buffer(b, aspa);
                        aspa_free(aspa);
+                       break;
+               case RTYPE_TAK:
+                       file = parse_load_file(entp, &f, &flen);
+                       io_str_buffer(b, file);
+                       proc_parser_tak(file, f, flen);
                        break;
                default:
                        errx(1, "unhandled entity type %d", entp->type);
Index: print.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/print.c,v
retrieving revision 1.16
diff -u -p -r1.16 print.c
--- print.c     30 Aug 2022 23:41:53 -0000      1.16
+++ print.c     21 Oct 2022 13:07:30 -0000
@@ -617,3 +617,95 @@ aspa_print(const X509 *x, const struct a
                }
        }
 }
+
+static void
+takey_print(char *name, const struct takey *t)
+{
+       char    *spki = NULL;
+       size_t   i, j = 0;
+
+       if (base64_encode(t->pubkey, t->pubkeysz, &spki) != 0)
+               errx(1, "base64_encode failed in %s", __func__);
+
+       if (outformats & FORMAT_JSON) {
+               printf("\t\t{\n\t\t\t\"name\": \"%s\",\n", name);
+               printf("\t\t\t\"comments\": [");
+               for (i = 0; i < t->commentsz; i++) {
+                       printf("\"%s\"", t->comments[i]);
+                       if (i + 1 < t->commentsz)
+                               printf(", ");
+               }
+               printf("],\n");
+               printf("\t\t\t\"uris\": [");
+               for (i = 0; i < t->urisz; i++) {
+                       printf("\"%s\"", t->uris[i]);
+                       if (i + 1 < t->urisz)
+                               printf(", ");
+               }
+               printf("],\n");
+               printf("\t\t\t\"spki\": \"%s\"\n\t\t}", spki);
+       } else {
+               printf("TAL derived from the '%s' Trust Anchor Key:\n\n", name);
+
+               for (i = 0; i < t->commentsz; i++) {
+                       printf("\t# %s\n", t->comments[i]);
+               }
+
+               for (i = 0; i < t->urisz; i++) {
+                       printf("\t%s\n\n\t", t->uris[i]);
+               }
+
+               for (i = 0; i < strlen(spki); i++) {
+                       printf("%c", spki[i]);
+                       j++;
+                       if (j == 64) {
+                               printf("\n\t");
+                               j = 0;
+                       }
+               }
+
+               printf("\n\n");
+       }
+
+       free(spki);
+}
+
+void
+tak_print(const X509 *x, const struct tak *p)
+{
+       char    tbuf[21];
+
+       if (outformats & FORMAT_JSON) {
+               printf("\t\"type\": \"tak\",\n");
+               printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
+               x509_print(x);
+               printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
+               printf("\t\"aia\": \"%s\",\n", p->aia);
+               printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
+               printf("\t\"takeys\": [\n");
+       } else {
+               strftime(tbuf, sizeof(tbuf), "%FT%TZ", gmtime(&p->expires));
+               printf("Subject key identifier: %s\n", pretty_key_id(p->ski));
+               x509_print(x);
+               printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
+               printf("Authority info access: %s\n", p->aia);
+               printf("TAK EE certificate valid until: %s\n", tbuf);
+       }
+
+       takey_print("current", p->current);
+
+       if (p->predecessor != NULL) {
+               if (outformats & FORMAT_JSON)
+                       printf(",\n");
+               takey_print("predecessor", p->predecessor);
+       }
+
+       if (p->successor != NULL) {
+               if (outformats & FORMAT_JSON)
+                       printf(",\n");
+               takey_print("successor", p->successor);
+       }
+
+       if (outformats & FORMAT_JSON)
+               printf("\n\t],\n");
+}
Index: rpki-client.8
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rpki-client.8,v
retrieving revision 1.73
diff -u -p -r1.73 rpki-client.8
--- rpki-client.8       5 Sep 2022 20:08:26 -0000       1.73
+++ rpki-client.8       21 Oct 2022 13:07:30 -0000
@@ -305,6 +305,8 @@ Resource Public Key Infrastructure (RPKI
 A profile for Resource Public Key Infrastructure (RPKI) Signed Checklists 
(RSC).
 .It draft-ietf-sidrops-aspa-profile-10
 A Profile for Autonomous System Provider Authorization (ASPA).
+.It draft-ietf-sidrops-signed-tal-12
+RPKI Signed Object for Trust Anchor Key.
 .El
 .Sh HISTORY
 .Nm
Index: rsync.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rsync.c,v
retrieving revision 1.43
diff -u -p -r1.43 rsync.c
--- rsync.c     2 Sep 2022 17:39:51 -0000       1.43
+++ rsync.c     21 Oct 2022 13:07:30 -0000
@@ -158,6 +158,7 @@ exec_rsync(const char *prog, const char 
                args[i++] = "--include=*.mft";
                args[i++] = "--include=*.roa";
                args[i++] = "--include=*.asa";
+               args[i++] = "--include=*.tak";
                args[i++] = "--exclude=*";
                if (bind_addr != NULL) {
                        args[i++] = "--address";
Index: tak.c
===================================================================
RCS file: tak.c
diff -N tak.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ tak.c       21 Oct 2022 13:07:30 -0000
@@ -0,0 +1,336 @@
+/*     $OpenBSD: tak.c,v 1.15 2022/09/03 14:40:09 job Exp $ */
+/*
+ * Copyright (c) 2022 Job Snijders <j...@fastly.com>
+ * Copyright (c) 2022 Theo Buehler <t...@openbsd.org>
+ * Copyright (c) 2019 Kristaps Dzonsons <krist...@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/safestack.h>
+#include <openssl/stack.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "extern.h"
+
+/*
+ * Parse results and data of the Trust Anchor Key file.
+ */
+struct parse {
+       const char      *fn; /* TAK file name */
+       struct tak      *res; /* results */
+};
+
+extern ASN1_OBJECT     *tak_oid;
+
+/*
+ * ASN.1 templates for Trust Anchor Keys (draft-ietf-sidrops-signed-tal-12)
+ */
+
+DECLARE_STACK_OF(ASN1_IA5STRING);
+
+#ifndef DEFINE_STACK_OF
+#define sk_ASN1_IA5STRING_num(st) SKM_sk_num(ASN1_IA5STRING, (st))
+#define sk_ASN1_IA5STRING_value(st, i) SKM_sk_value(ASN1_IA5STRING, (st), (i))
+#endif
+
+typedef struct {
+       STACK_OF(ASN1_UTF8STRING)       *comments;
+       STACK_OF(ASN1_IA5STRING)        *certificateURIs;
+       X509_PUBKEY                     *subjectPublicKeyInfo;
+} TAKey;
+
+typedef struct {
+       ASN1_INTEGER                    *version;
+       TAKey                           *current;
+       TAKey                           *predecessor;
+       TAKey                           *successor;
+} TAK;
+
+ASN1_SEQUENCE(TAKey) = {
+       ASN1_SEQUENCE_OF(TAKey, comments, ASN1_UTF8STRING),
+       ASN1_SEQUENCE_OF(TAKey, certificateURIs, ASN1_IA5STRING),
+       ASN1_SIMPLE(TAKey, subjectPublicKeyInfo, X509_PUBKEY),
+} ASN1_SEQUENCE_END(TAKey);
+
+ASN1_SEQUENCE(TAK) = {
+       ASN1_OPT(TAK, version, ASN1_INTEGER),
+       ASN1_SIMPLE(TAK, current, TAKey),
+       ASN1_EXP_OPT(TAK, predecessor, TAKey, 0),
+       ASN1_EXP_OPT(TAK, successor, TAKey, 1),
+} ASN1_SEQUENCE_END(TAK);
+
+DECLARE_ASN1_FUNCTIONS(TAK);
+IMPLEMENT_ASN1_FUNCTIONS(TAK);
+
+/*
+ * Return zero on failure, non-zero on success.
+ */
+static int
+parse_takey(const char *fn, const TAKey *takey, struct takey *out)
+{
+       const ASN1_UTF8STRING   *comment;
+       const ASN1_IA5STRING    *certURI;
+       EVP_PKEY                *pkey;
+       RSA                     *r;
+       unsigned char           *der = NULL, *rder = NULL;
+       unsigned char            md[SHA_DIGEST_LENGTH];
+       size_t                   i;
+       int                      rdersz, rc = 0;
+
+       if (takey == NULL)
+               goto out;
+
+       out->commentsz = sk_ASN1_UTF8STRING_num(takey->comments);
+       if ((out->comments = calloc(out->commentsz, sizeof(char *))) == NULL)
+               err(1, NULL);
+
+       for (i = 0; i < out->commentsz; i++) {
+               comment = sk_ASN1_UTF8STRING_value(takey->comments, i);
+               out->comments[i] = strndup(comment->data, comment->length);
+               if (out->comments[i] == NULL)
+                       err(1, NULL);
+       }
+
+       out->urisz = sk_ASN1_IA5STRING_num(takey->certificateURIs);
+       if (out->urisz == 0) {
+               warnx("%s: Signed TAL requires at least 1 CertificateURI", fn);
+               goto out;
+       }
+       if ((out->uris = calloc(out->urisz, sizeof(char *))) == NULL)
+               err(1, NULL);
+
+       for (i = 0; i < out->urisz; i++) {
+               certURI = sk_ASN1_IA5STRING_value(takey->certificateURIs, i);
+               out->uris[i] = strndup(certURI->data, certURI->length);
+               if (out->uris[i] == NULL)
+                       err(1, NULL);
+       }
+
+       if ((pkey = X509_PUBKEY_get0(takey->subjectPublicKeyInfo)) == NULL) {
+               warnx("%s: X509_PUBKEY_get0 failed", fn);
+               goto out;
+       }
+
+       if ((r = EVP_PKEY_get0_RSA(pkey)) == NULL) {
+               warnx("%s: EVP_PKEY_get0_RSA failed", fn);
+               goto out;
+       }
+
+       if ((rdersz = i2d_RSAPublicKey(r, &rder)) <= 0) {
+               warnx("%s: i2d_RSAPublicKey failed", fn);
+               goto out;
+       }
+
+       if (!EVP_Digest(rder, rdersz, md, NULL, EVP_sha1(), NULL)) {
+               warnx("%s: EVP_Digest failed", fn);
+               goto out;
+       }
+       out->ski = hex_encode(md, SHA_DIGEST_LENGTH);
+
+       if ((out->pubkeysz = i2d_PUBKEY(pkey, &der)) <= 0) {
+               warnx("%s: i2d_PUBKEY failed", fn);
+               goto out;
+       }
+
+       out->pubkey = der;
+       der = NULL;
+
+       rc = 1;
+ out:
+       free(der);
+       free(rder);
+       return rc;
+}
+
+/*
+ * Parses the eContent segment of an TAK file
+ * Returns zero on failure, non-zero on success.
+ */
+static int
+tak_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p)
+{
+       TAK             *tak;
+       const char      *fn;
+       int              rc = 0;
+
+       fn = p->fn;
+
+       if ((tak = d2i_TAK(NULL, &d, dsz)) == NULL) {
+               cryptowarnx("%s: failed to parse Trust Anchor Key", fn);
+               goto out;
+       }
+
+       if (!valid_econtent_version(fn, tak->version))
+               goto out;
+
+       p->res->current = calloc(1, sizeof(struct takey));
+       if (p->res->current == NULL)
+               err(1, NULL);
+
+       if (!parse_takey(fn, tak->current, p->res->current)) {
+               warnx("%s: invalid current TAKey", fn);
+               goto out;
+       }
+
+       if (tak->predecessor != NULL) {
+               p->res->predecessor = calloc(1, sizeof(struct takey));
+               if (p->res->predecessor == NULL)
+                       err(1, NULL);
+
+               if (!parse_takey(fn, tak->predecessor, p->res->predecessor)) {
+                       warnx("%s: invalid predecessor TAKey", fn);
+                       goto out;
+               }
+       }
+
+       if (tak->successor != NULL) {
+               p->res->successor = calloc(1, sizeof(struct takey));
+               if (p->res->successor == NULL)
+                       err(1, NULL);
+
+               if (!parse_takey(fn, tak->successor, p->res->successor)) {
+                       warnx("%s: invalid successor TAKey", fn);
+                       goto out;
+               }
+       }
+
+       rc = 1;
+ out:
+       TAK_free(tak);
+       return rc;
+}
+
+/*
+ * Parse a full draft-ietf-sidrops-signed-tal file.
+ * Returns the TAK or NULL if the object was malformed.
+ */
+struct tak *
+tak_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len)
+{
+       struct parse             p;
+       unsigned char           *cms;
+       size_t                   cmsz;
+       const ASN1_TIME         *at;
+       struct cert             *cert = NULL;
+       int                      rc = 0;
+
+       memset(&p, 0, sizeof(struct parse));
+       p.fn = fn;
+
+       cms = cms_parse_validate(x509, fn, der, len, tak_oid, &cmsz);
+       if (cms == NULL)
+               return NULL;
+
+       if ((p.res = calloc(1, sizeof(struct tak))) == NULL)
+               err(1, NULL);
+
+       if (!x509_get_aia(*x509, fn, &p.res->aia))
+               goto out;
+       if (!x509_get_aki(*x509, fn, &p.res->aki))
+               goto out;
+       if (!x509_get_ski(*x509, fn, &p.res->ski))
+               goto out;
+       if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) {
+               warnx("%s: RFC 6487 section 4.8: "
+                   "missing AIA, AKI or SKI X509 extension", fn);
+               goto out;
+       }
+
+       at = X509_get0_notAfter(*x509);
+       if (at == NULL) {
+               warnx("%s: X509_get0_notAfter failed", fn);
+               goto out;
+       }
+       if (x509_get_time(at, &p.res->expires) == -1) {
+               warnx("%s: ASN1_time_parse failed", fn);
+               goto out;
+       }
+
+       if (!x509_inherits(*x509)) {
+               warnx("%s: RFC 3779 extension not set to inherit", fn);
+               goto out;
+       }
+
+       if (!tak_parse_econtent(cms, cmsz, &p))
+               goto out;
+
+       if (strncmp(p.res->aki, p.res->current->ski, strlen(p.res->aki)) != 0) {
+               warnx("%s: current TAKey's SKI does not match EE AKI", fn);
+               goto out;
+       }
+
+       rc = 1;
+ out:
+       if (rc == 0) {
+               tak_free(p.res);
+               p.res = NULL;
+               X509_free(*x509);
+               *x509 = NULL;
+       }
+       cert_free(cert);
+       free(cms);
+       return p.res;
+}
+
+/*
+ * Free TAKey pointer.
+ */
+static void
+takey_free(struct takey *t)
+{
+       size_t  i;
+
+       if (t == NULL)
+               return;
+
+       for (i = 0; i < t->commentsz; i++)
+               free(t->comments[i]);
+
+       for (i = 0; i < t->urisz; i++)
+               free(t->uris[i]);
+
+       free(t->comments);
+       free(t->uris);
+       free(t->ski);
+       free(t->pubkey);
+       free(t);
+}
+
+/*
+ * Free an TAK pointer.
+ * Safe to call with NULL.
+ */
+void
+tak_free(struct tak *t)
+{
+       if (t == NULL)
+               return;
+
+       takey_free(t->current);
+       takey_free(t->predecessor);
+       takey_free(t->successor);
+
+       free(t->aia);
+       free(t->aki);
+       free(t->ski);
+       free(t);
+}
Index: x509.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/x509.c,v
retrieving revision 1.50
diff -u -p -r1.50 x509.c
--- x509.c      3 Sep 2022 14:40:09 -0000       1.50
+++ x509.c      21 Oct 2022 13:07:30 -0000
@@ -45,6 +45,7 @@ ASN1_OBJECT   *sign_time_oid; /* pkcs-9 id
 ASN1_OBJECT    *bin_sign_time_oid;     /* pkcs-9 id-aa-binarySigningTime */
 ASN1_OBJECT    *rsc_oid;       /* id-ct-signedChecklist */
 ASN1_OBJECT    *aspa_oid;      /* id-ct-ASPA */
+ASN1_OBJECT    *tak_oid;       /* id-ct-SignedTAL */
 
 void
 x509_init_oid(void)
@@ -85,6 +86,10 @@ x509_init_oid(void)
        if ((aspa_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.49", 1)) == NULL)
                errx(1, "OBJ_txt2obj for %s failed",
                    "1.2.840.113549.1.9.16.1.49");
+       if ((tak_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.50", 1)) == NULL)
+               errx(1, "OBJ_txt2obj for %s failed",
+                   "1.2.840.113549.1.9.16.1.50");
+
 }
 
 /*

Reply via email to