Add support for draft-ietf-sidrops-signed-tal-12
authorjob <job@openbsd.org>
Wed, 2 Nov 2022 12:43:02 +0000 (12:43 +0000)
committerjob <job@openbsd.org>
Wed, 2 Nov 2022 12:43:02 +0000 (12:43 +0000)
Add support validation of Signed Objects containing Trust Anchor Keys
(TAKs - 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 visiting RIR websites and
copy+pasting TAL data by hand.

OK tb@

12 files changed:
usr.sbin/rpki-client/Makefile
usr.sbin/rpki-client/extern.h
usr.sbin/rpki-client/filemode.c
usr.sbin/rpki-client/main.c
usr.sbin/rpki-client/mft.c
usr.sbin/rpki-client/output-json.c
usr.sbin/rpki-client/parser.c
usr.sbin/rpki-client/print.c
usr.sbin/rpki-client/rpki-client.8
usr.sbin/rpki-client/rsync.c
usr.sbin/rpki-client/tak.c [new file with mode: 0644]
usr.sbin/rpki-client/x509.c

index a7198aa..37393fb 100644 (file)
@@ -1,11 +1,11 @@
-#      $OpenBSD: Makefile,v 1.26 2022/08/30 18:56:49 job Exp $
+#      $OpenBSD: Makefile,v 1.27 2022/11/02 12:43:02 job Exp $
 
 PROG=  rpki-client
 SRCS=  as.c aspa.c cert.c cms.c crl.c encoding.c filemode.c gbr.c http.c io.c \
        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 77eee1d..96d0025 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: extern.h,v 1.156 2022/09/03 21:24:02 job Exp $ */
+/*     $OpenBSD: extern.h,v 1.157 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -183,6 +183,7 @@ enum rtype {
        RTYPE_FILE,
        RTYPE_RSC,
        RTYPE_ASPA,
+       RTYPE_TAK,
 };
 
 enum location {
@@ -274,6 +275,33 @@ struct rsc {
        time_t           expires; /* Not After of the RSC EE */
 };
 
+/*
+ * 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
  */
@@ -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,12 @@ void                rsc_free(struct rsc *);
 struct rsc     *rsc_parse(X509 **, const char *, const unsigned char *,
                    size_t);
 
+void            takey_free(struct takey *);
+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 +761,7 @@ void                 roa_print(const X509 *, const struct roa *);
 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 d26ea22..2982f2d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: filemode.c,v 1.14 2022/09/06 11:16:51 job Exp $ */
+/*     $OpenBSD: filemode.c,v 1.15 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -269,6 +269,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
        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 char *buf, size_t len)
                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 char *buf, size_t len)
        tal_free(tal);
        rsc_free(rsc);
        aspa_free(aspa);
+       tak_free(tak);
 }
 
 /*
index 9d2cb34..51f4211 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: main.c,v 1.219 2022/09/03 09:22:25 claudio Exp $ */
+/*     $OpenBSD: main.c,v 1.220 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -612,6 +612,9 @@ entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree,
                        st->aspas_invalid++;
                aspa_free(aspa);
                break;
+       case RTYPE_TAK:
+               st->taks++;
+               break;
        default:
                errx(1, "unknown entity type %d", type);
        }
@@ -1311,6 +1314,7 @@ 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);
+       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 69f14bb..7fde67f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: mft.c,v 1.75 2022/10/13 04:43:32 job Exp $ */
+/*     $OpenBSD: mft.c,v 1.76 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@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 69f8413..51b0c83 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: output-json.c,v 1.28 2022/08/30 23:40:37 tb Exp $ */
+/*     $OpenBSD: output-json.c,v 1.29 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  *
@@ -52,6 +52,7 @@ outputheader_json(FILE *out, struct stats *st)
            "\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 stats *st)
            (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 a613966..6f84e18 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parser.c,v 1.77 2022/09/03 21:24:02 job Exp $ */
+/*     $OpenBSD: parser.c,v 1.78 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -520,6 +520,42 @@ proc_parser_aspa(char *file, const unsigned char *der, size_t len)
        return aspa;
 }
 
+/*
+ * 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);
+
+       if (!valid_x509(file, ctx, x509, a, crl, 0))
+               goto out;
+
+       /* TAK EE must be signed by self-signed CA */
+       if (a->parent != NULL)
+               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.
  */
@@ -649,6 +685,11 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
                                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 607d748..b56bd99 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: print.c,v 1.16 2022/08/30 23:41:53 tb Exp $ */
+/*     $OpenBSD: print.c,v 1.17 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -617,3 +617,95 @@ aspa_print(const X509 *x, const struct aspa *p)
                }
        }
 }
+
+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 bcb85b2..c5d5cd0 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: rpki-client.8,v 1.73 2022/09/05 20:08:26 job Exp $
+.\"    $OpenBSD: rpki-client.8,v 1.74 2022/11/02 12:43:02 job Exp $
 .\"
 .\" Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
 .\"
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: September 5 2022 $
+.Dd $Mdocdate: November 2 2022 $
 .Dt RPKI-CLIENT 8
 .Os
 .Sh NAME
@@ -305,6 +305,8 @@ Resource Public Key Infrastructure (RPKI) Trust Anchor Locator.
 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 afcf3db..fbf9daf 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rsync.c,v 1.43 2022/09/02 17:39:51 claudio Exp $ */
+/*     $OpenBSD: rsync.c,v 1.44 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -158,6 +158,7 @@ exec_rsync(const char *prog, const char *bind_addr, char *uri, char *dst,
                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";
diff --git a/usr.sbin/rpki-client/tak.c b/usr.sbin/rpki-client/tak.c
new file mode 100644 (file)
index 0000000..d621aca
--- /dev/null
@@ -0,0 +1,335 @@
+/*     $OpenBSD: tak.c,v 1.1 2022/11/02 12:43:02 job Exp $ */
+/*
+ * Copyright (c) 2022 Job Snijders <job@fastly.com>
+ * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
+ * Copyright (c) 2019 Kristaps Dzonsons <kristaps@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_EXP_OPT(TAK, version, ASN1_INTEGER, 0),
+       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);
+
+/*
+ * On success return pointer to allocated & valid takey structure,
+ * on failure return NULL.
+ */
+static struct takey *
+parse_takey(const char *fn, const TAKey *takey)
+{
+       const ASN1_UTF8STRING   *comment;
+       const ASN1_IA5STRING    *certURI;
+       EVP_PKEY                *pkey;
+       RSA                     *r;
+       struct takey            *res = NULL;
+       unsigned char           *der = NULL, *rder = NULL;
+       unsigned char            md[SHA_DIGEST_LENGTH];
+       size_t                   i;
+       int                      rdersz, rc = 0;
+
+       if ((res = calloc(1, sizeof(struct takey))) == NULL)
+               err(1, NULL);
+
+       res->commentsz = sk_ASN1_UTF8STRING_num(takey->comments);
+       if (res->commentsz > 0) {
+               res->comments = calloc(res->commentsz, sizeof(char *));
+               if (res->comments == NULL)
+                       err(1, NULL);
+
+               for (i = 0; i < res->commentsz; i++) {
+                       comment = sk_ASN1_UTF8STRING_value(takey->comments, i);
+                       res->comments[i] = strndup(comment->data, comment->length);
+                       if (res->comments[i] == NULL)
+                               err(1, NULL);
+               }
+       }
+
+       res->urisz = sk_ASN1_IA5STRING_num(takey->certificateURIs);
+       if (res->urisz == 0) {
+               warnx("%s: Signed TAL requires at least 1 CertificateURI", fn);
+               goto out;
+       }
+       if ((res->uris = calloc(res->urisz, sizeof(char *))) == NULL)
+               err(1, NULL);
+
+       for (i = 0; i < res->urisz; i++) {
+               certURI = sk_ASN1_IA5STRING_value(takey->certificateURIs, i);
+               if (!valid_uri(certURI->data, certURI->length, NULL)) {
+                       warnx("%s: invalid TA URI", fn);
+                       goto out;
+               }
+
+               /* XXX: enforce that protocol is rsync or https. */
+
+               res->uris[i] = strndup(certURI->data, certURI->length);
+               if (res->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;
+       }
+       res->ski = hex_encode(md, SHA_DIGEST_LENGTH);
+
+       if ((res->pubkeysz = i2d_PUBKEY(pkey, &der)) <= 0) {
+               warnx("%s: i2d_PUBKEY failed", fn);
+               goto out;
+       }
+
+       res->pubkey = der;
+       der = NULL;
+
+       rc = 1;
+ out:
+       if (rc == 0) {
+               takey_free(res);
+               res = NULL;
+       }
+       free(der);
+       free(rder);
+       return res;
+}
+
+/*
+ * 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 = parse_takey(fn, tak->current);
+       if (p->res->current == NULL)
+               goto out;
+
+       if (tak->predecessor != NULL) {
+               p->res->predecessor = parse_takey(fn, tak->predecessor);
+               if (p->res->predecessor == NULL)
+                       goto out;
+       }
+
+       if (tak->successor != NULL) {
+               p->res->successor = parse_takey(fn, tak->successor);
+               if (p->res->successor == NULL)
+                       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;
+       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)) {
+               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 (strcmp(p.res->aki, p.res->current->ski) != 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;
+       }
+       free(cms);
+       return p.res;
+}
+
+/*
+ * Free TAKey pointer.
+ */
+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 05d0edd..7ebf9e8 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: x509.c,v 1.52 2022/11/02 10:04:41 tb Exp $ */
+/*     $OpenBSD: x509.c,v 1.53 2022/11/02 12:43:02 job Exp $ */
 /*
  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
@@ -45,6 +45,7 @@ ASN1_OBJECT   *sign_time_oid; /* pkcs-9 id-signingTime */
 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 */
 
 static const struct {
        const char       *oid;
@@ -106,6 +107,10 @@ static const struct {
                .oid = "1.2.840.113549.1.9.16.1.49",
                .ptr = &aspa_oid,
        },
+       {
+               .oid = "1.2.840.113549.1.9.16.1.50",
+               .ptr = &tak_oid,
+       },
 };
 
 void