Add support to verify X509 chain from CERT payloads.
authortobhe <tobhe@openbsd.org>
Wed, 28 Jun 2023 14:10:24 +0000 (14:10 +0000)
committertobhe <tobhe@openbsd.org>
Wed, 28 Jun 2023 14:10:24 +0000 (14:10 +0000)
Encode cert and intermediate CAs in new cert bundle object,
so the information can be passed to the ca process in one step.
Pass untrusted intermediates to X509_verify_cert().

From markus@

sbin/iked/ca.c
sbin/iked/iked.h
sbin/iked/ikev2.c
sbin/iked/ikev2.h
sbin/iked/ikev2_msg.c
sbin/iked/ikev2_pld.c

index 93ccda1..7f3f51d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ca.c,v 1.94 2023/06/25 08:07:04 op Exp $      */
+/*     $OpenBSD: ca.c,v 1.95 2023/06/28 14:10:24 tobhe Exp $   */
 
 /*
  * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
@@ -62,7 +62,7 @@ int    ca_x509_subject_cmp(X509 *, struct iked_static_id *);
 int     ca_validate_pubkey(struct iked *, struct iked_static_id *,
            void *, size_t, struct iked_id *);
 int     ca_validate_cert(struct iked *, struct iked_static_id *,
-           void *, size_t, X509 **);
+           void *, size_t, STACK_OF(X509) *, X509 **);
 EVP_PKEY *
         ca_bytes_to_pkey(uint8_t *, size_t);
 int     ca_privkey_to_method(struct iked_id *);
@@ -203,6 +203,130 @@ ca_reset(struct privsep *ps)
                fatal("ca_reset: reload");
 }
 
+int
+ca_certbundle_add(struct ibuf *buf, struct iked_id *id)
+{
+       uint8_t          type = id->id_type;
+       size_t           len = ibuf_length(id->id_buf);
+       void            *val = ibuf_data(id->id_buf);
+
+       if (id == NULL ||
+           buf == NULL ||
+           ibuf_add(buf, &type, sizeof(type)) != 0 ||
+           ibuf_add(buf, &len, sizeof(len)) != 0 ||
+           ibuf_add(buf, val, len) != 0)
+               return -1;
+       return 0;
+}
+
+/*
+ * decode cert bundle to cert and untrusted intermediate CAs.
+ * datap/lenp point to bundle on input and to decoded cert output
+ */
+static int
+ca_decode_cert_bundle(struct iked *env, struct iked_sahdr *sh,
+  uint8_t **datap, size_t *lenp, STACK_OF(X509)        **untrustedp)
+{
+       STACK_OF(X509)          *untrusted = NULL;
+       X509                    *cert;
+       BIO                     *rawcert = NULL;
+       uint8_t                 *certdata = NULL;
+       size_t                   certlen = 0;
+       uint8_t                  datatype;
+       size_t                   datalen = 0;
+       uint8_t                 *ptr;
+       size_t                   len;
+       int                      ret = -1;
+
+       log_debug("%s: decoding cert bundle", SPI_SH(sh, __func__));
+
+       ptr = *datap;
+       len = *lenp;
+       *untrustedp = NULL;
+
+       /* allocate stack for intermediate CAs */
+       if ((untrusted = sk_X509_new_null()) == NULL)
+               goto done;
+
+       /* parse TLV, see ca_certbundle_add() */
+       while (len > 0) {
+               /* Type */
+               if (len < sizeof(datatype)) {
+                       log_debug("%s: short data (type)",
+                           SPI_SH(sh, __func__));
+                       goto done;
+               }
+               memcpy(&datatype, ptr, sizeof(datatype));
+               ptr += sizeof(datatype);
+               len -= sizeof(datatype);
+
+               /* Only X509 certs/CAs are supported */
+               if (datatype != IKEV2_CERT_X509_CERT) {
+                       log_info("%s: unsupported data type: %s",
+                           SPI_SH(sh, __func__),
+                           print_map(datatype, ikev2_cert_map));
+                       goto done;
+               }
+
+               /* Length */
+               if (len < sizeof(datalen)) {
+                       log_info("%s: short data (len)",
+                           SPI_SH(sh, __func__));
+                       goto done;
+               }
+               memcpy(&datalen, ptr, sizeof(datalen));
+               ptr += sizeof(datalen);
+               len -= sizeof(datalen);
+
+               /* Value */
+               if (len < datalen) {
+                       log_info("%s: short len %zu < datalen %zu",
+                           SPI_SH(sh, __func__), len, datalen);
+                       goto done;
+               }
+
+               if (certdata == NULL) {
+                       /* First entry is cert */
+                       certdata = ptr;
+                       certlen = datalen;
+               } else {
+                       /* All other entries are intermediate CAs */
+                       rawcert = BIO_new_mem_buf(ptr, datalen);
+                       if (rawcert == NULL)
+                               goto done;
+                       cert = d2i_X509_bio(rawcert, NULL);
+                       BIO_free(rawcert);
+                       if (cert == NULL) {
+                               log_warnx("%s: cannot parse CA",
+                                   SPI_SH(sh, __func__));
+                               ca_sslerror(__func__);
+                               goto done;
+                       }
+                       if (!sk_X509_push(untrusted, cert)) {
+                               log_warnx("%s: cannot store CA",
+                                   SPI_SH(sh, __func__));
+                               X509_free(cert);
+                               goto done;
+                       }
+               }
+               ptr += datalen;
+               len -= datalen;
+       }
+       log_debug("%s: decoded cert bundle", SPI_SH(sh, __func__));
+       *datap = certdata;
+       *lenp = certlen;
+       *untrustedp = untrusted;
+       untrusted = NULL;
+       ret = 0;
+
+ done:
+       if (ret != 0)
+               log_info("%s: failed to decode cert bundle",
+                   SPI_SH(sh, __func__));
+       sk_X509_free(untrusted);
+       return ret;
+}
+
 int
 ca_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
 {
@@ -470,6 +594,7 @@ ca_getcert(struct iked *env, struct imsg *imsg)
 {
        struct ca_store         *store = env->sc_priv;
        X509                    *issuer = NULL, *cert;
+       STACK_OF(X509)          *untrusted = NULL;
        EVP_PKEY                *certkey;
        struct iked_sahdr        sh;
        uint8_t                  type;
@@ -498,6 +623,10 @@ ca_getcert(struct iked *env, struct imsg *imsg)
 
        bzero(&key, sizeof(key));
 
+       if (type == IKEV2_CERT_BUNDLE &&
+           ca_decode_cert_bundle(env, &sh, &ptr, &len, &untrusted) == 0)
+               type = IKEV2_CERT_X509_CERT;
+
        switch (type) {
        case IKEV2_CERT_X509_CERT:
                /* Look in local cert storage first */
@@ -515,15 +644,17 @@ ca_getcert(struct iked *env, struct imsg *imsg)
                        }
                }
                if (env->sc_ocsp_url == NULL)
-                       ret = ca_validate_cert(env, &id, ptr, len, NULL);
+                       ret = ca_validate_cert(env, &id, ptr, len, untrusted, NULL);
                else {
-                       ret = ca_validate_cert(env, &id, ptr, len, &issuer);
+                       ret = ca_validate_cert(env, &id, ptr, len, untrusted, &issuer);
                        if (ret == 0) {
                                ret = ocsp_validate_cert(env, ptr, len, sh,
                                    type, issuer);
                                X509_free(issuer);
-                               if (ret == 0)
+                               if (ret == 0) {
+                                       sk_X509_free(untrusted);
                                        return (0);
+                               }
                        } else
                                X509_free(issuer);
                }
@@ -561,6 +692,8 @@ ca_getcert(struct iked *env, struct imsg *imsg)
 
        ret = proc_composev(&env->sc_ps, PROC_IKEV2, cmd, iov, iovcnt);
        ibuf_free(key.id_buf);
+       sk_X509_free(untrusted);
+
        return (ret);
 }
 
@@ -979,7 +1112,7 @@ ca_reload(struct iked *env)
 
                x509 = X509_OBJECT_get0_X509(xo);
 
-               (void)ca_validate_cert(env, NULL, x509, 0, NULL);
+               (void)ca_validate_cert(env, NULL, x509, 0, NULL, NULL);
        }
 
        if (!env->sc_certreqtype)
@@ -1690,7 +1823,7 @@ ca_validate_pubkey(struct iked *env, struct iked_static_id *id,
 
 int
 ca_validate_cert(struct iked *env, struct iked_static_id *id,
-    void *data, size_t len, X509 **issuerp)
+    void *data, size_t len, STACK_OF(X509) *untrusted, X509 **issuerp)
 {
        struct ca_store         *store = env->sc_priv;
        X509_STORE_CTX          *csc = NULL;
@@ -1754,7 +1887,7 @@ ca_validate_cert(struct iked *env, struct iked_static_id *id,
                errstr = "failed to alloc csc";
                goto done;
        }
-       X509_STORE_CTX_init(csc, store->ca_cas, cert, NULL);
+       X509_STORE_CTX_init(csc, store->ca_cas, cert, untrusted);
        param = X509_STORE_get0_param(store->ca_cas);
        if (X509_VERIFY_PARAM_get_flags(param) & X509_V_FLAG_CRL_CHECK) {
                X509_STORE_CTX_set_flags(csc, X509_V_FLAG_CRL_CHECK);
index b220f18..dc97f65 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.h,v 1.219 2023/06/25 08:07:04 op Exp $   */
+/*     $OpenBSD: iked.h,v 1.220 2023/06/28 14:10:24 tobhe Exp $        */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -642,6 +642,7 @@ struct iked_message {
        struct iked_id           msg_peerid;
        struct iked_id           msg_localid;
        struct iked_id           msg_cert;
+       struct iked_id           msg_scert[IKED_SCERT_MAX]; /* supplemental certs */
        struct ibuf             *msg_cookie;
        uint16_t                 msg_group;
        uint16_t                 msg_cpi;
@@ -1176,6 +1177,7 @@ int        ca_setcert(struct iked *, struct iked_sahdr *, struct iked_id *,
 int     ca_setauth(struct iked *, struct iked_sa *,
            struct ibuf *, enum privsep_procid);
 void    ca_getkey(struct privsep *, struct iked_id *, enum imsg_type);
+int     ca_certbundle_add(struct ibuf *, struct iked_id *);
 int     ca_privkey_serialize(EVP_PKEY *, struct iked_id *);
 int     ca_pubkey_serialize(EVP_PKEY *, struct iked_id *);
 void    ca_sslerror(const char *);
index dd3ac45..88e7ad5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2.c,v 1.371 2023/06/14 14:09:29 claudio Exp $     */
+/*     $OpenBSD: ikev2.c,v 1.372 2023/06/28 14:10:24 tobhe Exp $       */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -940,11 +940,12 @@ ikev2_ike_auth_recv(struct iked *env, struct iked_sa *sa,
     struct iked_message *msg)
 {
        struct iked_id          *id;
-       struct ibuf             *authmsg;
+       struct ibuf             *authmsg, *buf;
        struct iked_policy      *old;
        uint8_t                 *cert = NULL;
        size_t                   certlen = 0;
        int                      certtype = IKEV2_CERT_NONE;
+       int                      i;
 
        /* The AUTH payload indicates if the responder wants EAP or not */
        if (msg->msg_auth.id_type != IKEV2_AUTH_NONE &&
@@ -1047,6 +1048,30 @@ ikev2_ike_auth_recv(struct iked *env, struct iked_sa *sa,
                }
        }
 
+       /* Encode all received certs as single blob */
+       if (msg->msg_cert.id_type != IKEV2_CERT_BUNDLE &&
+           msg->msg_scert[0].id_type != IKEV2_CERT_NONE) {
+               if ((buf = ibuf_new(NULL, 0)) == NULL)
+                       return (-1);
+               /* begin with certificate */
+               if (ca_certbundle_add(buf, &msg->msg_cert) != 0) {
+                       ibuf_free(buf);
+                       return (-1);
+               }
+               /* add intermediate CAs */
+               for (i = 0; i < IKED_SCERT_MAX; i++) {
+                       if (msg->msg_scert[i].id_type == IKEV2_CERT_NONE)
+                               break;
+                       if (ca_certbundle_add(buf, &msg->msg_scert[i]) != 0) {
+                               ibuf_free(buf);
+                               return (-1);
+                       }
+               }
+               ibuf_free(msg->msg_cert.id_buf);
+               msg->msg_cert.id_buf = buf;
+               msg->msg_cert.id_type = IKEV2_CERT_BUNDLE;
+       }
+
        if (!TAILQ_EMPTY(&msg->msg_proposals)) {
                if (proposals_negotiate(&sa->sa_proposals,
                    &sa->sa_policy->pol_proposals, &msg->msg_proposals,
index bb8d178..48d2bcf 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2.h,v 1.34 2021/05/28 18:01:39 tobhe Exp $        */
+/*     $OpenBSD: ikev2.h,v 1.35 2023/06/28 14:10:24 tobhe Exp $        */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -433,6 +433,7 @@ struct ikev2_cert {
  * use range (201-255, same RFC) for ECDSA.
  */
 #define IKEV2_CERT_ECDSA               201     /* Private */
+#define IKEV2_CERT_BUNDLE              254     /* Private */
 
 extern struct iked_constmap ikev2_cert_map[];
 
index 338ef13..e694f8a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2_msg.c,v 1.95 2023/06/13 12:34:12 tb Exp $       */
+/*     $OpenBSD: ikev2_msg.c,v 1.96 2023/06/28 14:10:24 tobhe Exp $    */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -189,6 +189,7 @@ void
 ikev2_msg_cleanup(struct iked *env, struct iked_message *msg)
 {
        struct iked_certreq     *cr;
+       int                      i;
 
        if (msg == msg->msg_parent) {
                ibuf_free(msg->msg_nonce);
@@ -197,6 +198,8 @@ ikev2_msg_cleanup(struct iked *env, struct iked_message *msg)
                ibuf_free(msg->msg_peerid.id_buf);
                ibuf_free(msg->msg_localid.id_buf);
                ibuf_free(msg->msg_cert.id_buf);
+               for (i = 0; i < IKED_SCERT_MAX; i++)
+                       ibuf_free(msg->msg_scert[i].id_buf);
                ibuf_free(msg->msg_cookie);
                ibuf_free(msg->msg_cookie2);
                ibuf_free(msg->msg_del_buf);
@@ -211,6 +214,8 @@ ikev2_msg_cleanup(struct iked *env, struct iked_message *msg)
                msg->msg_peerid.id_buf = NULL;
                msg->msg_localid.id_buf = NULL;
                msg->msg_cert.id_buf = NULL;
+               for (i = 0; i < IKED_SCERT_MAX; i++)
+                       msg->msg_scert[i].id_buf = NULL;
                msg->msg_cookie = NULL;
                msg->msg_cookie2 = NULL;
                msg->msg_del_buf = NULL;
index b176bf5..8d3662a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2_pld.c,v 1.130 2023/06/14 14:09:29 claudio Exp $ */
+/*     $OpenBSD: ikev2_pld.c,v 1.131 2023/06/28 14:10:24 tobhe Exp $   */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -810,6 +810,7 @@ ikev2_pld_cert(struct iked *env, struct ikev2_payload *pld,
        struct iked_id                  *certid;
        uint8_t                         *msgbuf = ibuf_data(msg->msg_data);
        const struct iked_sa            *sa = msg->msg_sa;
+       int                              i;
 
        if (ikev2_validate_cert(msg, offset, left, &cert))
                return (-1);
@@ -826,13 +827,28 @@ ikev2_pld_cert(struct iked *env, struct ikev2_payload *pld,
        if (!ikev2_msg_frompeer(msg))
                return (0);
 
-       certid = &msg->msg_parent->msg_cert;
-       if (certid->id_type) {
-               log_debug("%s: multiple cert payloads, ignoring",
+       /* do not accept internal encoding in the wire */
+       if (cert.cert_type == IKEV2_CERT_BUNDLE) {
+               log_debug("%s: ignoring IKEV2_CERT_BUNDLE",
                   SPI_SA(sa, __func__));
                return (0);
        }
 
+       certid = &msg->msg_parent->msg_cert;
+       if (certid->id_type) {
+               /* try to set supplemental certs */
+               for (i = 0; i < IKED_SCERT_MAX; i++) {
+                       certid = &msg->msg_parent->msg_scert[i];
+                       if (!certid->id_type)
+                               break;
+               }
+               if (certid->id_type) {
+                       log_debug("%s: too many cert payloads, ignoring",
+                          SPI_SA(sa, __func__));
+                       return (0);
+               }
+       }
+
        if ((certid->id_buf = ibuf_new(buf, len)) == NULL) {
                log_debug("%s: failed to save cert", __func__);
                return (-1);