From 055943a192f18e1315df27bcd3114dacc5842dc7 Mon Sep 17 00:00:00 2001 From: tobhe Date: Fri, 8 Jul 2022 19:51:11 +0000 Subject: [PATCH] Support sending certificate chains with intermediate CAs in multiple CERT payloads. Local certificate chains as required with LetsEncrypt certs will work between iked and other IKEv2 implementations, iked to iked connections won't work yet because of missing support to receive multiple CERT payloads. from Katsuhiro Ueno tested by and ok sthen@ --- sbin/iked/ca.c | 85 +++++++++++++++++++++++++++++++++++++++++-- sbin/iked/config.c | 6 +++- sbin/iked/eap.c | 19 +++++++++- sbin/iked/iked.h | 4 ++- sbin/iked/ikev2.c | 89 +++++++++++++++++++++++++++++++++++++++++++++- sbin/iked/types.h | 3 +- 6 files changed, 198 insertions(+), 8 deletions(-) diff --git a/sbin/iked/ca.c b/sbin/iked/ca.c index 561570d4a1c..58b6050b607 100644 --- a/sbin/iked/ca.c +++ b/sbin/iked/ca.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ca.c,v 1.87 2021/12/14 13:44:36 tobhe Exp $ */ +/* $OpenBSD: ca.c,v 1.88 2022/07/08 19:51:11 tobhe Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -328,6 +328,32 @@ ca_setcert(struct iked *env, struct iked_sahdr *sh, struct iked_id *id, return (0); } +static int +ca_setscert(struct iked *env, struct iked_sahdr *sh, uint8_t type, X509 *cert) +{ + struct iovec iov[3]; + int iovcnt = 0; + struct ibuf *buf; + int ret; + + if ((buf = ca_x509_serialize(cert)) == NULL) + return (-1); + + iov[iovcnt].iov_base = sh; + iov[iovcnt].iov_len = sizeof(*sh); + iovcnt++; + iov[iovcnt].iov_base = &type; + iov[iovcnt].iov_len = sizeof(type); + iovcnt++; + iov[iovcnt].iov_base = ibuf_data(buf); + iov[iovcnt].iov_len = ibuf_size(buf); + iovcnt++; + + ret = proc_composev(&env->sc_ps, PROC_IKEV2, IMSG_SCERT, iov, iovcnt); + ibuf_release(buf); + return (ret); +} + int ca_setreq(struct iked *env, struct iked_sa *sa, struct iked_static_id *localid, uint8_t type, uint8_t more, uint8_t *data, @@ -541,6 +567,51 @@ ca_getcert(struct iked *env, struct imsg *imsg) return (0); } +static unsigned int +ca_chain_by_issuer(struct ca_store *store, X509_NAME *subject, + struct iked_static_id *id, X509 **dst, size_t dstlen) +{ + STACK_OF(X509_OBJECT) *h; + X509_OBJECT *xo; + X509 *cert; + int i; + unsigned int n; + X509_NAME *issuer, *subj; + + if (subject == NULL || dstlen == 0) + return (0); + + if ((cert = ca_by_issuer(store->ca_certs, subject, id)) != NULL) { + *dst = cert; + return (1); + } + + h = X509_STORE_get0_objects(store->ca_cas); + for (i = 0; i < sk_X509_OBJECT_num(h); i++) { + xo = sk_X509_OBJECT_value(h, i); + if (X509_OBJECT_get_type(xo) != X509_LU_X509) + continue; + cert = X509_OBJECT_get0_X509(xo); + if ((issuer = X509_get_issuer_name(cert)) == NULL) + continue; + if (X509_NAME_cmp(subject, issuer) == 0) { + if ((subj = X509_get_subject_name(cert)) == NULL) + continue; + /* Skip root CAs */ + if (X509_NAME_cmp(subj, issuer) == 0) + continue; + n = ca_chain_by_issuer(store, subj, id, + dst + 1, dstlen - 1); + if (n > 0) { + *dst = cert; + return (n + 1); + } + } + } + + return (0); +} + int ca_getreq(struct iked *env, struct imsg *imsg) { @@ -551,6 +622,8 @@ ca_getreq(struct iked *env, struct imsg *imsg) size_t len; unsigned int i; X509 *ca = NULL, *cert = NULL; + X509 *chain[IKED_SCERT_MAX + 1]; + size_t chain_len = 0; struct ibuf *buf; struct iked_static_id id; char idstr[IKED_ID_SIZE]; @@ -612,8 +685,10 @@ ca_getreq(struct iked *env, struct imsg *imsg) log_debug("%s: found CA %s", __func__, subj_name); free(subj_name); - if ((cert = ca_by_issuer(store->ca_certs, - subj, &id)) != NULL) { + chain_len = ca_chain_by_issuer(store, subj, &id, + chain, nitems(chain)); + if (chain_len > 0) { + cert = chain[chain_len - 1]; if (!ca_cert_local(env, cert)) { log_info("%s: found cert with matching " "ID but without matching key.", @@ -623,6 +698,10 @@ ca_getreq(struct iked *env, struct imsg *imsg) break; } } + + for (i = chain_len; i >= 2; i--) + ca_setscert(env, &sh, type, chain[i - 2]); + /* Fallthrough */ case IKEV2_CERT_NONE: fallback: diff --git a/sbin/iked/config.c b/sbin/iked/config.c index 1f9cee7c8b4..25caf3d3e15 100644 --- a/sbin/iked/config.c +++ b/sbin/iked/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.85 2022/05/08 20:26:31 tobhe Exp $ */ +/* $OpenBSD: config.c,v 1.86 2022/07/08 19:51:11 tobhe Exp $ */ /* * Copyright (c) 2019 Tobias Heider @@ -110,6 +110,8 @@ config_free_fragments(struct iked_frag *frag) void config_free_sa(struct iked *env, struct iked_sa *sa) { + int i; + timer_del(env, &sa->sa_timer); timer_del(env, &sa->sa_keepalive); timer_del(env, &sa->sa_rekey); @@ -165,6 +167,8 @@ config_free_sa(struct iked *env, struct iked_sa *sa) ibuf_release(sa->sa_rid.id_buf); ibuf_release(sa->sa_icert.id_buf); ibuf_release(sa->sa_rcert.id_buf); + for (i = 0; i < IKED_SCERT_MAX; i++) + ibuf_release(sa->sa_scert[i].id_buf); ibuf_release(sa->sa_localauth.id_buf); ibuf_release(sa->sa_peerauth.id_buf); diff --git a/sbin/iked/eap.c b/sbin/iked/eap.c index f85d1f4e3fa..28119fa841c 100644 --- a/sbin/iked/eap.c +++ b/sbin/iked/eap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: eap.c,v 1.20 2022/01/28 05:24:15 guenther Exp $ */ +/* $OpenBSD: eap.c,v 1.21 2022/07/08 19:51:11 tobhe Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -90,6 +90,7 @@ eap_identity_request(struct iked *env, struct iked_sa *sa) uint8_t firstpayload; int ret = -1; ssize_t len = 0; + int i; /* Responder only */ if (sa->sa_hdr.sh_initiator) @@ -128,6 +129,22 @@ eap_identity_request(struct iked *env, struct iked_sa *sa) if (ibuf_cat(e, certid->id_buf) != 0) goto done; len = ibuf_size(certid->id_buf) + sizeof(*cert); + + for (i = 0; i < IKED_SCERT_MAX; i++) { + if (sa->sa_scert[i].id_type == IKEV2_CERT_NONE) + break; + if (ikev2_next_payload(pld, len, + IKEV2_PAYLOAD_CERT) == -1) + goto done; + if ((pld = ikev2_add_payload(e)) == NULL) + goto done; + if ((cert = ibuf_advance(e, sizeof(*cert))) == NULL) + goto done; + cert->cert_type = sa->sa_scert[i].id_type; + if (ibuf_cat(e, sa->sa_scert[i].id_buf) != 0) + goto done; + len = ibuf_size(sa->sa_scert[i].id_buf) + sizeof(*cert); + } } if (ikev2_next_payload(pld, len, IKEV2_PAYLOAD_AUTH) == -1) diff --git a/sbin/iked/iked.h b/sbin/iked/iked.h index ff6e66cd6ef..17d42dae73c 100644 --- a/sbin/iked/iked.h +++ b/sbin/iked/iked.h @@ -1,4 +1,4 @@ -/* $OpenBSD: iked.h,v 1.204 2022/03/14 12:58:55 tobhe Exp $ */ +/* $OpenBSD: iked.h,v 1.205 2022/07/08 19:51:11 tobhe Exp $ */ /* * Copyright (c) 2019 Tobias Heider @@ -469,11 +469,13 @@ struct iked_sa { struct iked_id sa_localauth; /* local AUTH message */ struct iked_id sa_peerauth; /* peer AUTH message */ int sa_sigsha2; /* use SHA2 for signatures */ +#define IKED_SCERT_MAX 3 /* max # of supplemental cert payloads */ struct iked_id sa_iid; /* initiator id */ struct iked_id sa_rid; /* responder id */ struct iked_id sa_icert; /* initiator cert */ struct iked_id sa_rcert; /* responder cert */ + struct iked_id sa_scert[IKED_SCERT_MAX]; /* supplemental certs */ #define IKESA_SRCID(x) ((x)->sa_hdr.sh_initiator ? &(x)->sa_iid : &(x)->sa_rid) #define IKESA_DSTID(x) ((x)->sa_hdr.sh_initiator ? &(x)->sa_rid : &(x)->sa_iid) diff --git a/sbin/iked/ikev2.c b/sbin/iked/ikev2.c index e3a51fcbb90..783d9cb5dfa 100644 --- a/sbin/iked/ikev2.c +++ b/sbin/iked/ikev2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ikev2.c,v 1.348 2022/07/04 08:39:55 tobhe Exp $ */ +/* $OpenBSD: ikev2.c,v 1.349 2022/07/08 19:51:11 tobhe Exp $ */ /* * Copyright (c) 2019 Tobias Heider @@ -315,6 +315,7 @@ ikev2_dispatch_cert(int fd, struct privsep_proc *p, struct imsg *imsg) size_t len; struct iked_id *id = NULL; int ignore = 0; + int i; switch (imsg->hdr.type) { case IMSG_CERTREQ: @@ -416,6 +417,51 @@ ikev2_dispatch_cert(int fd, struct privsep_proc *p, struct imsg *imsg) if (ikev2_ike_auth(env, sa) != 0) log_debug("%s: failed to send ike auth", __func__); break; + case IMSG_SCERT: + if ((sa = ikev2_getimsgdata(env, imsg, + &sh, &type, &ptr, &len)) == NULL) { + log_debug("%s: invalid supplemental cert reply", + __func__); + break; + } + + if (sa->sa_stateflags & IKED_REQ_CERT || + type == IKEV2_CERT_NONE) + ignore = 1; + + log_debug("%s: supplemental cert type %s length %zu, %s", + __func__, + print_map(type, ikev2_cert_map), len, + ignore ? "ignored" : "ok"); + + if (ignore) + break; + + for (i = 0; i < IKED_SCERT_MAX; i++) { + id = &sa->sa_scert[i]; + if (id->id_type == IKEV2_CERT_NONE) + break; + id = NULL; + } + + if (id == NULL) { + log_debug("%s: too many supplemental cert. ignored", + __func__); + break; + } + + id->id_type = type; + id->id_offset = 0; + ibuf_release(id->id_buf); + id->id_buf = NULL; + + if (len <= 0 || (id->id_buf = ibuf_new(ptr, len)) == NULL) { + log_debug("%s: failed to get supplemental cert payload", + __func__); + break; + } + + break; case IMSG_AUTH: if ((sa = ikev2_getimsgdata(env, imsg, &sh, &type, &ptr, &len)) == NULL) { @@ -1490,6 +1536,7 @@ ikev2_init_ike_auth(struct iked *env, struct iked_sa *sa) uint8_t firstpayload; int ret = -1; ssize_t len; + int i; if (!sa_stateok(sa, IKEV2_STATE_SA_INIT)) return (0); @@ -1544,6 +1591,22 @@ ikev2_init_ike_auth(struct iked *env, struct iked_sa *sa) goto done; len = ibuf_size(certid->id_buf) + sizeof(*cert); + for (i = 0; i < IKED_SCERT_MAX; i++) { + if (sa->sa_scert[i].id_type == IKEV2_CERT_NONE) + break; + if (ikev2_next_payload(pld, len, + IKEV2_PAYLOAD_CERT) == -1) + goto done; + if ((pld = ikev2_add_payload(e)) == NULL) + goto done; + if ((cert = ibuf_advance(e, sizeof(*cert))) == NULL) + goto done; + cert->cert_type = sa->sa_scert[i].id_type; + if (ibuf_cat(e, sa->sa_scert[i].id_buf) != 0) + goto done; + len = ibuf_size(sa->sa_scert[i].id_buf) + sizeof(*cert); + } + /* CERTREQ payload(s) */ if ((len = ikev2_add_certreq(e, &pld, len, env->sc_certreq, env->sc_certreqtype)) == -1) @@ -3722,6 +3785,7 @@ ikev2_resp_ike_auth(struct iked *env, struct iked_sa *sa) uint8_t firstpayload; int ret = -1; ssize_t len; + int i; if (sa == NULL) return (-1); @@ -3781,6 +3845,24 @@ ikev2_resp_ike_auth(struct iked *env, struct iked_sa *sa) if (ibuf_cat(e, certid->id_buf) != 0) goto done; len = ibuf_size(certid->id_buf) + sizeof(*cert); + + for (i = 0; i < IKED_SCERT_MAX; i++) { + if (sa->sa_scert[i].id_type == IKEV2_CERT_NONE) + break; + if (ikev2_next_payload(pld, len, + IKEV2_PAYLOAD_CERT) == -1) + goto done; + if ((pld = ikev2_add_payload(e)) == NULL) + goto done; + if ((cert = ibuf_advance(e, + sizeof(*cert))) == NULL) + goto done; + cert->cert_type = sa->sa_scert[i].id_type; + if (ibuf_cat(e, sa->sa_scert[i].id_buf) != 0) + goto done; + len = ibuf_size(sa->sa_scert[i].id_buf) + + sizeof(*cert); + } } if (ikev2_next_payload(pld, len, IKEV2_PAYLOAD_AUTH) == -1) @@ -4458,6 +4540,7 @@ ikev2_ikesa_enable(struct iked *env, struct iked_sa *sa, struct iked_sa *nsa) struct iked_childsa *csa, *csatmp, *ipcomp; struct iked_flow *flow, *flowtmp; struct iked_proposal *prop, *proptmp; + int i; log_debug("%s: IKE SA %p ispi %s rspi %s replaced" " by SA %p ispi %s rspi %s ", @@ -4535,11 +4618,15 @@ ikev2_ikesa_enable(struct iked *env, struct iked_sa *sa, struct iked_sa *nsa) nsa->sa_icert = sa->sa_rcert; nsa->sa_rcert = sa->sa_icert; } + for (i = 0; i < IKED_SCERT_MAX; i++) + nsa->sa_scert[i] = sa->sa_scert[i]; /* duplicate the actual buffer */ nsa->sa_iid.id_buf = ibuf_dup(nsa->sa_iid.id_buf); nsa->sa_rid.id_buf = ibuf_dup(nsa->sa_rid.id_buf); nsa->sa_icert.id_buf = ibuf_dup(nsa->sa_icert.id_buf); nsa->sa_rcert.id_buf = ibuf_dup(nsa->sa_rcert.id_buf); + for (i = 0; i < IKED_SCERT_MAX; i++) + nsa->sa_scert[i].id_buf = ibuf_dup(nsa->sa_scert[i].id_buf); /* Transfer sa_addrpool address */ if (sa->sa_addrpool) { diff --git a/sbin/iked/types.h b/sbin/iked/types.h index 8e06512aa8d..7800b179d75 100644 --- a/sbin/iked/types.h +++ b/sbin/iked/types.h @@ -1,4 +1,4 @@ -/* $OpenBSD: types.h,v 1.48 2022/04/13 20:54:55 deraadt Exp $ */ +/* $OpenBSD: types.h,v 1.49 2022/07/08 19:51:11 tobhe Exp $ */ /* * Copyright (c) 2019 Tobias Heider @@ -115,6 +115,7 @@ enum imsg_type { IMSG_CERTVALID, IMSG_CERTINVALID, IMSG_CERT_PARTIAL_CHAIN, + IMSG_SCERT, IMSG_IF_ADDADDR, IMSG_IF_DELADDR, IMSG_VROUTE_ADD, -- 2.20.1