-/* $OpenBSD: cert.c,v 1.36 2021/10/07 12:59:29 job Exp $ */
+/* $OpenBSD: cert.c,v 1.37 2021/10/11 16:50:03 job Exp $ */
/*
+ * Copyright (c) 2021 Job Snijders <job@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
* Permission to use, copy, modify, and distribute this software for any
/* Validation on required fields. */
+ switch (p.res->purpose) {
+ case CERT_PURPOSE_CA:
+ if (p.res->mft == NULL) {
+ warnx("%s: RFC 6487 section 4.8.8: missing SIA", p.fn);
+ goto out;
+ }
+ if (p.res->asz == 0 && p.res->ipsz == 0) {
+ warnx("%s: missing IP or AS resources", p.fn);
+ goto out;
+ }
+ break;
+ case CERT_PURPOSE_BGPSEC_ROUTER:
+ p.res->bgpsec_pubkey = x509_get_bgpsec_pubkey(x, p.fn);
+ if (p.res->bgpsec_pubkey == NULL) {
+ warnx("%s: x509_get_bgpsec_pubkey failed", p.fn);
+ goto out;
+ }
+ if (p.res->ipsz > 0) {
+ warnx("%s: unexpected IP resources in BGPsec cert", p.fn);
+ goto out;
+ }
+ if (sia_present) {
+ warnx("%s: unexpected SIA extension in BGPsec cert", p.fn);
+ goto out;
+ }
+ break;
+ default:
+ warnx("%s: x509_get_purpose failed in %s", p.fn, __func__);
+ goto out;
+ }
+
if (p.res->ski == NULL) {
warnx("%s: RFC 6487 section 8.4.2: missing SKI", p.fn);
goto out;
goto out;
}
- if (p.res->asz == 0 && p.res->ipsz == 0) {
- warnx("%s: RFC 6487 section 4.8.10 and 4.8.11: "
- "missing IP or AS resources", p.fn);
- goto out;
- }
-
- if (p.res->ipsz > 0 &&
- p.res->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
- warnx("%s: BGPsec Router Certificate must not have RFC 3779 IP "
- "Addresses", p.fn);
- goto out;
- }
-
- if (p.res->purpose == CERT_PURPOSE_BGPSEC_ROUTER && sia_present) {
- warnx("%s: BGPsec Router Certificate must not have SIA", p.fn);
- goto out;
- }
-
- if (p.res->purpose == CERT_PURPOSE_CA && p.res->mft == NULL) {
- warnx("%s: RFC 6487 section 4.8.8: missing SIA", p.fn);
- goto out;
- }
-
if (X509_up_ref(x) == 0)
errx(1, "%s: X509_up_ref failed", __func__);
free(p->aia);
free(p->aki);
free(p->ski);
+ free(p->tal);
+ free(p->bgpsec_pubkey);
X509_free(p->x509);
free(p);
}
size_t i;
io_simple_buffer(b, &p->valid, sizeof(int));
+ io_simple_buffer(b, &p->expires, sizeof(time_t));
io_simple_buffer(b, &p->purpose, sizeof(enum cert_purpose));
io_simple_buffer(b, &p->ipsz, sizeof(size_t));
for (i = 0; i < p->ipsz; i++)
io_str_buffer(b, p->aia);
io_str_buffer(b, p->aki);
io_str_buffer(b, p->ski);
+ io_str_buffer(b, p->tal);
+ io_str_buffer(b, p->bgpsec_pubkey);
}
static void
err(1, NULL);
io_simple_read(fd, &p->valid, sizeof(int));
+ io_simple_read(fd, &p->expires, sizeof(time_t));
io_simple_read(fd, &p->purpose, sizeof(enum cert_purpose));
io_simple_read(fd, &p->ipsz, sizeof(size_t));
p->ips = calloc(p->ipsz, sizeof(struct cert_ip));
io_str_read(fd, &p->aki);
io_str_read(fd, &p->ski);
assert(p->ski);
+ io_str_read(fd, &p->tal);
+ io_str_read(fd, &p->bgpsec_pubkey);
return p;
}
}
RB_GENERATE(auth_tree, auth, entry, authcmp);
+
+/*
+ * Extract
+ */
+static void
+insert_brk(struct brk_tree *tree, struct cert *cert, int asid)
+{
+ struct brk *b, *found;
+
+ if ((b = calloc(1, sizeof(*b))) == NULL)
+ err(1, NULL);
+
+ b->asid = asid;
+ b->expires = cert->expires;
+ if ((b->key = strdup(cert->bgpsec_pubkey)) == NULL)
+ err(1, NULL);
+ if ((b->tal = strdup(cert->tal)) == NULL)
+ err(1, NULL);
+
+ /*
+ * Check if a similar BRK already exists in the tree. If the found BRK
+ * expires sooner, update it to this BRK's later expiry moment.
+ */
+ if ((found = RB_INSERT(brk_tree, tree, b)) != NULL) {
+ /* already exists */
+ if (found->expires < b->expires) {
+ /* update found with preferred data */
+ found->expires = b->expires;
+ free(found->tal);
+ found->tal = b->tal;
+ b->tal = NULL;
+ }
+ free(b->key);
+ free(b->tal);
+ free(b);
+ }
+}
+
+/*
+ * Add each BGPsec Router Key into the BRK tree.
+ */
+void
+cert_insert_brks(struct brk_tree *tree, struct cert *cert)
+{
+ size_t i, asid;
+
+ for (i = 0; i < cert->asz; i++) {
+ switch (cert->as[i].type) {
+ case CERT_AS_ID:
+ insert_brk(tree, cert, cert->as[i].id);
+ break;
+ case CERT_AS_RANGE:
+ for (asid = cert->as[i].range.min;
+ asid <= cert->as[i].range.max; asid++)
+ insert_brk(tree, cert, asid);
+ break;
+ default:
+ warnx("invalid AS identifier type");
+ continue;
+ }
+ }
+}
+
+static inline int
+brkcmp(struct brk *a, struct brk *b)
+{
+ if (a->asid > b->asid)
+ return 1;
+ if (a->asid < b->asid)
+ return -1;
+
+ return strcmp(a->key, b->key);
+}
+
+RB_GENERATE(brk_tree, brk, entry, brkcmp);
-/* $OpenBSD: extern.h,v 1.70 2021/10/10 21:57:43 job Exp $ */
+/* $OpenBSD: extern.h,v 1.71 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
};
enum cert_purpose {
- CERT_PURPOSE_CA = 1,
+ CERT_PURPOSE_INVALID,
+ CERT_PURPOSE_CA,
CERT_PURPOSE_BGPSEC_ROUTER
};
char *aia; /* AIA (or NULL, for trust anchor) */
char *aki; /* AKI (or NULL, for trust anchor) */
char *ski; /* SKI */
+ char *tal; /* basename of TAL for this cert */
enum cert_purpose purpose; /* Certificate Purpose (BGPSec or CA) */
+ char *bgpsec_pubkey; /* BGPsec Router Key */
int valid; /* validated resources */
X509 *x509; /* the cert */
time_t expires; /* do not use after */
RB_HEAD(vrp_tree, vrp);
RB_PROTOTYPE(vrp_tree, vrp, entry, vrpcmp);
+/*
+ * A single BGPsec Router Key (including ASID)
+ */
+struct brk {
+ RB_ENTRY(brk) entry;
+ uint32_t asid;
+ char *tal; /* basename of TAL for this key */
+ uint8_t *key; /* raw P-256 ECDSA public key */
+ time_t expires; /* transitive expiry moment */
+};
+/*
+ * Tree of BRK sorted by asid
+ */
+RB_HEAD(brk_tree, brk);
+RB_PROTOTYPE(brk_tree, brk, entry, brkcmp);
+
/*
* A single CRL
*/
size_t uniqs; /* number of unique vrps */
size_t del_files; /* number of files removed in cleanup */
size_t del_dirs; /* number of directories removed in cleanup */
- size_t bgpsec_routers; /* number of BGPsec Router certs */
- size_t bgpsec_invalids; /* invalid bgpsec router certs */
+ size_t brks; /* number of BGPsec Router Key (BRK) certificates */
+ size_t brks_invalids; /* invalid BGPsec certs */
char *talnames;
struct timeval elapsed_time;
struct timeval user_time;
struct cert *cert_parse(X509 **, const char *);
struct cert *ta_parse(X509 **, const char *, const unsigned char *, size_t);
struct cert *cert_read(int);
+void cert_insert_brks(struct brk_tree *, struct cert *);
void mft_buffer(struct ibuf *, const struct mft *);
void mft_free(struct mft *);
/* X509 helpers. */
-char *hex_encode(const unsigned char *, size_t);
char *x509_get_aia(X509 *, const char *);
char *x509_get_aki(X509 *, int, const char *);
char *x509_get_ski(X509 *, const char *);
time_t x509_get_expire(X509 *, const char *);
char *x509_get_crl(X509 *, const char *);
char *x509_crl_get_aki(X509_CRL *, const char *);
+char *x509_get_bgpsec_pubkey(X509 *, const char *);
enum cert_purpose x509_get_purpose(X509 *, const char *);
/* Output! */
#define FORMAT_CSV 0x04
#define FORMAT_JSON 0x08
-int outputfiles(struct vrp_tree *v, struct stats *);
+int outputfiles(struct vrp_tree *v, struct brk_tree *b,
+ struct stats *);
int outputheader(FILE *, struct stats *);
-int output_bgpd(FILE *, struct vrp_tree *, struct stats *);
-int output_bird1v4(FILE *, struct vrp_tree *, struct stats *);
-int output_bird1v6(FILE *, struct vrp_tree *, struct stats *);
-int output_bird2(FILE *, struct vrp_tree *, struct stats *);
-int output_csv(FILE *, struct vrp_tree *, struct stats *);
-int output_json(FILE *, struct vrp_tree *, struct stats *);
+int output_bgpd(FILE *, struct vrp_tree *, struct brk_tree *,
+ struct stats *);
+int output_bird1v4(FILE *, struct vrp_tree *, struct brk_tree *,
+ struct stats *);
+int output_bird1v6(FILE *, struct vrp_tree *, struct brk_tree *,
+ struct stats *);
+int output_bird2(FILE *, struct vrp_tree *, struct brk_tree *,
+ struct stats *);
+int output_csv(FILE *, struct vrp_tree *, struct brk_tree *,
+ struct stats *);
+int output_json(FILE *, struct vrp_tree *, struct brk_tree *,
+ struct stats *);
void logx(const char *fmt, ...)
__attribute__((format(printf, 1, 2)));
-/* $OpenBSD: main.c,v 1.148 2021/10/10 22:04:33 job Exp $ */
+/* $OpenBSD: main.c,v 1.149 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
* In all cases, we gather statistics.
*/
static void
-entity_process(int proc, struct stats *st, struct vrp_tree *tree)
+entity_process(int proc, struct stats *st, struct vrp_tree *tree,
+ struct brk_tree *brktree)
{
- enum rtype type;
+ enum rtype type;
struct tal *tal;
struct cert *cert;
struct mft *mft;
} else
st->certs_invalid++;
} else if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
- if (cert->valid)
- st->bgpsec_routers++;
- else
- st->bgpsec_invalids++;
+ if (cert->valid) {
+ cert_insert_brks(brktree, cert);
+ st->brks++;
+ } else
+ st->brks_invalids++;
} else
st->certs_invalid++;
cert_free(cert);
const char *cachedir = NULL, *outputdir = NULL;
const char *tals[TALSZ_MAX], *errs, *name;
struct vrp_tree v = RB_INITIALIZER(&v);
+ struct brk_tree b = RB_INITIALIZER(&b);
struct rusage ru;
struct timeval start_time, now_time;
*/
if ((pfd[0].revents & POLLIN)) {
- entity_process(proc, &stats, &v);
+ entity_process(proc, &stats, &v, &b);
}
}
if (fchdir(outdirfd) == -1)
err(1, "fchdir output dir");
- if (outputfiles(&v, &stats))
+ if (outputfiles(&v, &b, &stats))
rc = 1;
logx("Certificate revocation lists: %zu", stats.crls);
logx("Ghostbuster records: %zu", stats.gbrs);
logx("BGPsec Router Certificates: %zu (%zu invalid)",
- stats.bgpsec_routers, stats.bgpsec_invalids);
+ stats.brks, stats.brks_invalids);
logx("Repositories: %zu", stats.repos);
logx("Cleanup: removed %zu files, %zu directories",
stats.del_files, stats.del_dirs);
-/* $OpenBSD: output-bgpd.c,v 1.22 2021/09/01 15:21:10 job Exp $ */
+/* $OpenBSD: output-bgpd.c,v 1.23 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
#include "extern.h"
int
-output_bgpd(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_bgpd(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+ struct stats *st)
{
struct vrp *v;
-/* $OpenBSD: output-bird.c,v 1.11 2021/04/19 17:04:35 deraadt Exp $ */
+/* $OpenBSD: output-bird.c,v 1.12 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2020 Robert Scheck <robert@fedoraproject.org>
#include "extern.h"
int
-output_bird1v4(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_bird1v4(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+ struct stats *st)
{
extern const char *bird_tablename;
struct vrp *v;
}
int
-output_bird1v6(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_bird1v6(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+ struct stats *st)
{
extern const char *bird_tablename;
struct vrp *v;
}
int
-output_bird2(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_bird2(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+ struct stats *st)
{
extern const char *bird_tablename;
struct vrp *v;
-/* $OpenBSD: output-csv.c,v 1.10 2021/05/06 17:03:57 job Exp $ */
+/* $OpenBSD: output-csv.c,v 1.11 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
*
#include "extern.h"
int
-output_csv(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_csv(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+ struct stats *st)
{
struct vrp *v;
-/* $OpenBSD: output-json.c,v 1.17 2021/05/06 17:03:57 job Exp $ */
+/* $OpenBSD: output-json.c,v 1.18 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
*
"\t\t\"roas\": %zu,\n"
"\t\t\"failedroas\": %zu,\n"
"\t\t\"invalidroas\": %zu,\n"
+ "\t\t\"bgpsec_router_keys\": %zu,\n"
+ "\t\t\"invalidbgpsec_router_keys\": %zu,\n"
"\t\t\"certificates\": %zu,\n"
"\t\t\"failcertificates\": %zu,\n"
"\t\t\"invalidcertificates\": %zu,\n"
hn, tbuf, (long long)st->elapsed_time.tv_sec,
(long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
st->roas, st->roas_fail, st->roas_invalid,
+ st->brks, st->brks_invalids,
st->certs, st->certs_fail, st->certs_invalid,
st->tals, st->talnames,
st->mfts, st->mfts_fail, st->mfts_stale,
}
int
-output_json(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+ struct stats *st)
{
char buf[64];
struct vrp *v;
+ struct brk *b;
int first = 1;
if (outputheader_json(out, st) < 0)
return -1;
RB_FOREACH(v, vrp_tree, vrps) {
- if (first)
- first = 0;
- else {
+ if (!first) {
if (fprintf(out, ",\n") < 0)
return -1;
}
+ first = 0;
ip_addr_print(&v->addr, v->afi, buf, sizeof(buf));
return -1;
}
+ if (fprintf(out, "\n\t],\n\n\t\"bgpsec_keys\": [\n") < 0)
+ return -1;
+
+ first = 1;
+ RB_FOREACH(b, brk_tree, brks) {
+ if (!first) {
+ if (fprintf(out, ",\n") < 0)
+ return -1;
+ }
+ first = 0;
+
+ if (fprintf(out, "\t\t{ \"asn\": %u, \"key\": \"%s\", \"ta\": "
+ "\"%s\", \"expires\": %lld }", b->asid, b->key, b->tal,
+ (long long)b->expires) < 0)
+ return -1;
+ }
+
if (fprintf(out, "\n\t]\n}\n") < 0)
return -1;
return 0;
-/* $OpenBSD: output.c,v 1.21 2021/03/02 09:08:59 claudio Exp $ */
+/* $OpenBSD: output.c,v 1.22 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Theo de Raadt <deraadt@openbsd.org>
*
static const struct outputs {
int format;
char *name;
- int (*fn)(FILE *, struct vrp_tree *, struct stats *);
+ int (*fn)(FILE *, struct vrp_tree *, struct brk_tree *,
+ struct stats *);
} outputs[] = {
{ FORMAT_OPENBGPD, "openbgpd", output_bgpd },
{ FORMAT_BIRD, "bird1v4", output_bird1v4 },
static void set_signal_handler(void);
int
-outputfiles(struct vrp_tree *v, struct stats *st)
+outputfiles(struct vrp_tree *v, struct brk_tree *b, struct stats *st)
{
int i, rc = 0;
rc = 1;
continue;
}
- if ((*outputs[i].fn)(fout, v, st) != 0) {
+ if ((*outputs[i].fn)(fout, v, b, st) != 0) {
warn("output for %s format failed", outputs[i].name);
fclose(fout);
output_cleantmp();
"# Generated on host %s at %s\n"
"# Processing time %lld seconds (%lld seconds user, %lld seconds system)\n"
"# Route Origin Authorizations: %zu (%zu failed parse, %zu invalid)\n"
+ "# BGPsec Router Certificates: %zu (%zu invalid)\n"
"# Certificates: %zu (%zu failed parse, %zu invalid)\n"
"# Trust Anchor Locators: %zu (%s)\n"
"# Manifests: %zu (%zu failed parse, %zu stale)\n"
hn, tbuf, (long long)st->elapsed_time.tv_sec,
(long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
st->roas, st->roas_fail, st->roas_invalid,
+ st->brks, st->brks_invalids,
st->certs, st->certs_fail, st->certs_invalid,
st->tals, st->talnames,
st->mfts, st->mfts_fail, st->mfts_stale,
-/* $OpenBSD: parser.c,v 1.12 2021/10/07 08:36:17 claudio Exp $ */
+/* $OpenBSD: parser.c,v 1.13 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
X509 *x509;
int c;
struct auth *a = NULL, *na;
- char *tal;
STACK_OF(X509) *chain;
STACK_OF(X509_CRL) *crls;
if (na == NULL)
err(1, NULL);
- tal = a->tal;
+ cert->tal = strdup(a->tal);
+ if (cert->tal == NULL)
+ err(1, NULL);
na->parent = a;
na->cert = cert;
- na->tal = tal;
+ na->tal = a->tal;
na->fn = strdup(entp->file);
if (na->fn == NULL)
err(1, NULL);
-.\" $OpenBSD: rpki-client.8,v 1.47 2021/09/01 08:17:37 claudio Exp $
+.\" $OpenBSD: rpki-client.8,v 1.48 2021/10/11 16:50:03 job Exp $
.\"
.\" Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
.\"
.\" 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 1 2021 $
+.Dd $Mdocdate: October 11 2021 $
.Dt RPKI-CLIENT 8
.Os
.Sh NAME
Resource Public Key Infrastructure (RPKI) Trust Anchor Locator.
.It RFC 8182
The RPKI Repository Delta Protocol (RRDP).
+.It RFC 8209
+A Profile for BGPsec Router Certificates, Certificate Revocation Lists, and
+Certification Requests.
.El
.\" .Sh HISTORY
.Sh AUTHORS
-/* $OpenBSD: validate.c,v 1.15 2021/08/16 10:38:57 jsg Exp $ */
+/* $OpenBSD: validate.c,v 1.16 2021/10/11 16:50:03 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
return 0;
for (i = 0; i < cert->asz; i++) {
- if (cert->as[i].type == CERT_AS_INHERIT)
+ if (cert->as[i].type == CERT_AS_INHERIT) {
+ if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER)
+ return 0; /* BGPsec doesn't permit inheriting */
continue;
+ }
min = cert->as[i].type == CERT_AS_ID ?
cert->as[i].id : cert->as[i].range.min;
max = cert->as[i].type == CERT_AS_ID ?
-/* $OpenBSD: x509.c,v 1.23 2021/10/07 08:30:39 claudio Exp $ */
+/* $OpenBSD: x509.c,v 1.24 2021/10/11 16:50:04 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
#include <string.h>
#include <unistd.h>
+#include <openssl/evp.h>
#include <openssl/x509v3.h>
#include "extern.h"
}
res = hex_encode(d, dsz);
+
out:
AUTHORITY_KEYID_free(akid);
return res;
return purpose;
}
+/*
+ * Extract ECDSA key from a BGPsec Router Certificate.
+ * Returns NULL on failure, on success return public key,
+ */
+char *
+x509_get_bgpsec_pubkey(X509 *x, const char *fn)
+{
+ EVP_PKEY *pubkey;
+ EC_KEY *ec;
+ int nid;
+ const char *cname;
+ int keylen;
+ uint8_t *key = NULL;
+ char *res = NULL;
+
+ pubkey = X509_get0_pubkey(x);
+ if (pubkey == NULL) {
+ warnx("%s: X509_get_pubkey failed in %s", fn, __func__);
+ goto out;
+ }
+ if (EVP_PKEY_base_id(pubkey) != EVP_PKEY_EC) {
+ warnx("%s: Expected EVP_PKEY_EC, got %d", fn,
+ EVP_PKEY_base_id(pubkey));
+ goto out;
+ }
+
+ ec = EVP_PKEY_get0_EC_KEY(pubkey);
+ if (ec == NULL) {
+ warnx("%s: Incorrect key type", fn);
+ goto out;
+ }
+
+ nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
+ if (nid != NID_X9_62_prime256v1) {
+ if ((cname = EC_curve_nid2nist(nid)) == NULL)
+ cname = OBJ_nid2sn(nid);
+ warnx("%s: Expected P-256, got %s", fn, cname);
+ goto out;
+ }
+
+ if (!EC_KEY_check_key(ec)) {
+ warnx("%s: EC_KEY_check_key failed in %s", fn, __func__);
+ goto out;
+ }
+
+ keylen = i2o_ECPublicKey(ec, &key);
+ if (keylen <= 0) {
+ warnx("%s: i2o_ECPublicKey failed in %s", fn, __func__);
+ goto out;
+ }
+
+ if (base64_encode(key, keylen, &res) == -1)
+ errx(1, "base64_encode failed in %s", __func__);
+
+ out:
+ free(key);
+ return res;
+}
/*
* Parse the Authority Information Access (AIA) extension