On Fri, May 14, 2021 at 09:23:02PM +0100, Stuart Henderson wrote:
> On 2021/05/14 21:14, Tobias Heider wrote:
> > On Thu, May 13, 2021 at 02:39:37PM +0900, Katsuhiro Ueno wrote:
> > > Hi,
> > > 
> > > I would be happy if iked(8) supports intermediate CAs and sends the
> > > entire certificate chain to the clients. The diff attached adds
> > > supports for intermediate CAs and multiple CERT payloads to iked(8).
> > > 
> > > What I would like to do is to use a LetsEncrypt certificate as a
> > > server certificate of IKEv2 EAP and establish VPN connections with
> > > Windows clients. However, I could not complete it because of the
> > > following reasons.
> > > * LetsEncrypt server certificate is issued by an intermediate CA
> > >   and therefore the certificate of the intermediate CA is needed to
> > >   check the validity of the server certificate.
> > > * Windows expects the IKEv2 server to send the intermediate CA's
> > >   certificate in addition to the server certificate to check the
> > >   validity.
> > > * On the other hand, iked(8) is not capable of dealing with
> > >   certificate chains and sending multiple certificates (multiple
> > >   CERT payloads) to the clients.
> > > Consequently, Windows fails to verify the certificate and therefore
> > > VPN connection cannot be established.
> > > 
> > > To overcome this, I added an (ad-hoc) support for certificate chain
> > > and multiple CERT payloads. The diff attached is the changes that I
> > > made. It works fine for me but I am not sure whether or not it works
> > > for everyone and everywhere. Tests and comments are greatly
> > > appreciated.
> > > 
> > > Many thanks,
> > > Katsuhiro Ueno
> > 
> > Hi Katsuhiro,
> > 
> > thank you for the diff!
> > As Stuart said this is a very welcome addition and the diff looks good to 
> > me.
> > 
> > Unfortunately I don't have a Windows machine here to test with, so it would 
> > be
> > nice if anyone reading this could give it a try on their Windows setup.
> > 
> > I will try running a few more tests with Strongswan clients and commit it
> > once I'm sure everything still works.
> > 
> > - Tobias
> > 
> 
> Tested with Android strongswan. If I write the intermediate and
> root certificates to ca/ca.crt and the server certificate to
> certs/$HOSTNAME.crt then I'm able to connect.
> 
> I haven't tested Windows yet, I'll try to locate a machine to test with
> at the weekend.
> 
> The certificate arrangement is a little awkward to work with typical
> ACME infrastructure used with standard TLS servers:
> 
> For a standard server the root certificate would not normally need to
> be present at all; only server and intermediate certs are needed (though
> possibly this may be different for IKEv2).  As such, ACME clients don't
> normally fetch this certificate so it must be retrieved separately.
> 
> Also typically for a TLS server, any intermediates would be stored
> alongside the server certificate (fullchain.pem in the usual acme-client
> config). Storing that in a separate file isn't difficult ("chain" rather
> than "full chain" for acme-client) but, combined with the above need
> to store the CA in that same file, it does mean some extra fiddling is
> required.
> 
> So to use this diff as-is with acme-client, something like this is needed
> (I haven't tested the whole thing put together but I think it's right),
> 
> domain key "/etc/iked/private/local.key"
> domain certificate "/etc/iked/certs/hostname.crt"
> domain chain certificate "/etc/iked/ca/chain.crt"
> 
> <request cert>
> 
> ftp -o /etc/iked/ca/ca.p7c $(openssl x509 -in /etc/iked/ca/chain.crt -text 
> -noout | grep 'CA Issuers' | cut -d: -f2-)
> 
> (cat /etc/iked/ca/chain.crt; openssl pkcs7 -inform DER -in 
> /etc/iked/ca/ca.p7c -print_certs) > /etc/iked/ca/ca.crt
> 
> At least it's going to need some doc to show what goes where. But it
> would be much simpler to work with if it would at least accept "full
> chain" in the single certs/hostname.crt file.
> 

Here is an updated version of the diff working with -current iked since
I was reminded of this missing feature lately.
Stuart is probably right in that we should support fullchain.pem style
chains in a single file, but this is something we can fix in-tree.

It would be nice if we could get someone to test if it works with
Windows.

Index: ca.c
===================================================================
RCS file: /cvs/src/sbin/iked/ca.c,v
retrieving revision 1.87
diff -u -p -r1.87 ca.c
--- ca.c        14 Dec 2021 13:44:36 -0000      1.87
+++ ca.c        19 May 2022 14:28:21 -0000
@@ -328,6 +328,32 @@ ca_setcert(struct iked *env, struct iked
        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
        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 
        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 
                        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 
                                break;
                        }
                }
+
+               for (i = chain_len; i >= 2; i--)
+                       ca_setscert(env, &sh, type, chain[i - 2]);
+
                /* Fallthrough */
        case IKEV2_CERT_NONE:
  fallback:
Index: config.c
===================================================================
RCS file: /cvs/src/sbin/iked/config.c,v
retrieving revision 1.85
diff -u -p -r1.85 config.c
--- config.c    8 May 2022 20:26:31 -0000       1.85
+++ config.c    19 May 2022 14:28:21 -0000
@@ -110,6 +110,8 @@ config_free_fragments(struct iked_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 
        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: eap.c
===================================================================
RCS file: /cvs/src/sbin/iked/eap.c,v
retrieving revision 1.20
diff -u -p -r1.20 eap.c
--- eap.c       28 Jan 2022 05:24:15 -0000      1.20
+++ eap.c       19 May 2022 14:28:21 -0000
@@ -90,6 +90,7 @@ eap_identity_request(struct iked *env, s
        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, s
                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: iked.h
===================================================================
RCS file: /cvs/src/sbin/iked/iked.h,v
retrieving revision 1.204
diff -u -p -r1.204 iked.h
--- iked.h      14 Mar 2022 12:58:55 -0000      1.204
+++ iked.h      19 May 2022 14:28:21 -0000
@@ -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: ikev2.c
===================================================================
RCS file: /cvs/src/sbin/iked/ikev2.c,v
retrieving revision 1.346
diff -u -p -r1.346 ikev2.c
--- ikev2.c     14 Mar 2022 12:58:55 -0000      1.346
+++ ikev2.c     19 May 2022 14:28:22 -0000
@@ -315,6 +315,7 @@ ikev2_dispatch_cert(int fd, struct privs
        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 privs
                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, st
        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, st
                        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, st
        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, st
                        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, str
        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, str
                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: types.h
===================================================================
RCS file: /cvs/src/sbin/iked/types.h,v
retrieving revision 1.48
diff -u -p -r1.48 types.h
--- types.h     13 Apr 2022 20:54:55 -0000      1.48
+++ types.h     19 May 2022 14:28:22 -0000
@@ -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,

Reply via email to