> > 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
This command no longer works as-is, perhaps due to letsencrypt's (ab)use
of the old java/android bug providing a dodgy chain to let it work with
old devices.
However I have been successful in connecting with a ca.crt file with
the standard contents of "chain certificate" as fetched for a letsencrypt
cert by acme-client without the extra messing about. Also before I tried
that I had a manually generated one with just the following certificate
which also worked::
Subject: C=US, O=Let's Encrypt, CN=R3
Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1
No problem with StrongSwan on recent Android.
For Windows this works provided that ISRG Root X1 is already in the
computer trust store. It seems this is present on some but not other
Windows installations - if you get "IKE authentication credentials
are unacceptable" this is the likely failure. Or you can check in
MMC > certificates (local computer) > trusted root CAs. If you don't
have ISRG Root X1 shown there then open a browser (I have had
success with Internet Explorer and Chrome but not Edge, YMMV) and
open https://valid-isrgrootsx1.letsencrypt.org/, which will silently
add the new root CA to the store, at which point the VPN should work.
It is likely to work lower-hassle with Buypass as they are already
in the Windows CA store and don't rely on the automatic fetching.
> 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.
I have connected successfully with Windows 20H2.
OK
> 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,