From: tobhe Date: Wed, 28 Jun 2023 14:10:24 +0000 (+0000) Subject: Add support to verify X509 chain from CERT payloads. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=a30a01d6baebec8e8d53611d304541518caaf92a;p=openbsd Add support to verify X509 chain from CERT payloads. 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@ --- diff --git a/sbin/iked/ca.c b/sbin/iked/ca.c index 93ccda1e698..7f3f51d46c1 100644 --- a/sbin/iked/ca.c +++ b/sbin/iked/ca.c @@ -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 @@ -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); diff --git a/sbin/iked/iked.h b/sbin/iked/iked.h index b220f189041..dc97f6560f9 100644 --- a/sbin/iked/iked.h +++ b/sbin/iked/iked.h @@ -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 @@ -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 *); diff --git a/sbin/iked/ikev2.c b/sbin/iked/ikev2.c index dd3ac450dd8..88e7ad54318 100644 --- a/sbin/iked/ikev2.c +++ b/sbin/iked/ikev2.c @@ -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 @@ -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, diff --git a/sbin/iked/ikev2.h b/sbin/iked/ikev2.h index bb8d17816c1..48d2bcf8c05 100644 --- a/sbin/iked/ikev2.h +++ b/sbin/iked/ikev2.h @@ -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 @@ -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[]; diff --git a/sbin/iked/ikev2_msg.c b/sbin/iked/ikev2_msg.c index 338ef13d864..e694f8a1e75 100644 --- a/sbin/iked/ikev2_msg.c +++ b/sbin/iked/ikev2_msg.c @@ -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 @@ -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; diff --git a/sbin/iked/ikev2_pld.c b/sbin/iked/ikev2_pld.c index b176bf54951..8d3662a2cb6 100644 --- a/sbin/iked/ikev2_pld.c +++ b/sbin/iked/ikev2_pld.c @@ -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 @@ -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);