Support sending certificate chains with intermediate CAs in multiple CERT
authortobhe <tobhe@openbsd.org>
Fri, 8 Jul 2022 19:51:11 +0000 (19:51 +0000)
committertobhe <tobhe@openbsd.org>
Fri, 8 Jul 2022 19:51:11 +0000 (19:51 +0000)
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
sbin/iked/config.c
sbin/iked/eap.c
sbin/iked/iked.h
sbin/iked/ikev2.c
sbin/iked/types.h

index 561570d..58b6050 100644 (file)
@@ -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 <reyk@openbsd.org>
@@ -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:
index 1f9cee7..25caf3d 100644 (file)
@@ -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 <tobias.heider@stusta.de>
@@ -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);
 
index f85d1f4..28119fa 100644 (file)
@@ -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 <reyk@openbsd.org>
@@ -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)
index ff6e66c..17d42da 100644 (file)
@@ -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 <tobias.heider@stusta.de>
@@ -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)
 
index e3a51fc..783d9cb 100644 (file)
@@ -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 <tobias.heider@stusta.de>
@@ -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) {
index 8e06512..7800b17 100644 (file)
@@ -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 <tobias.heider@stusta.de>
@@ -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,