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,