diff options
Diffstat (limited to 'sbin/iked/ikev2_pld.c')
-rw-r--r-- | sbin/iked/ikev2_pld.c | 1115 |
1 files changed, 1115 insertions, 0 deletions
diff --git a/sbin/iked/ikev2_pld.c b/sbin/iked/ikev2_pld.c new file mode 100644 index 00000000000..e75d81a6c91 --- /dev/null +++ b/sbin/iked/ikev2_pld.c @@ -0,0 +1,1115 @@ +/* $OpenBSD: ikev2_pld.c,v 1.1 2010/06/10 08:29:47 reyk Exp $ */ +/* $vantronix: ikev2.c,v 1.101 2010/06/03 07:57:33 reyk Exp $ */ + +/* + * Copyright (c) 2010 Reyk Floeter <reyk@vantronix.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <netinet/ip_ipsp.h> +#include <arpa/inet.h> + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <getopt.h> +#include <signal.h> +#include <errno.h> +#include <err.h> +#include <pwd.h> +#include <event.h> + +#include <openssl/sha.h> +#include <openssl/evp.h> + +#include "iked.h" +#include "ikev2.h" +#include "eap.h" +#include "dh.h" + +int ikev2_pld_payloads(struct iked *, struct iked_message *, + off_t, size_t, u_int, int); +int ikev2_pld_sa(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_xform(struct iked *, struct ikev2_sa_proposal *, + struct iked_message *, off_t); +int ikev2_pld_attr(struct iked *, struct ikev2_transform *, + struct iked_message *, off_t, int); +int ikev2_pld_ke(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_id(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t, u_int); +int ikev2_pld_cert(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_certreq(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_nonce(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_notify(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_delete(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_ts(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t, u_int); +int ikev2_pld_auth(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_e(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_cp(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); +int ikev2_pld_eap(struct iked *, struct ikev2_payload *, + struct iked_message *, off_t); + +int +ikev2_pld_parse(struct iked *env, struct ike_header *hdr, + struct iked_message *msg, off_t offset) +{ + log_debug("%s: header ispi %s rspi %s" + " nextpayload %s version 0x%02x exchange %s flags 0x%02x" + " msgid %d length %d response %d", __func__, + print_spi(betoh64(hdr->ike_ispi), 8), + print_spi(betoh64(hdr->ike_rspi), 8), + print_map(hdr->ike_nextpayload, ikev2_payload_map), + hdr->ike_version, + print_map(hdr->ike_exchange, ikev2_exchange_map), + hdr->ike_flags, + betoh32(hdr->ike_msgid), + betoh32(hdr->ike_length), + msg->msg_response); + + if (ibuf_size(msg->msg_data) < betoh32(hdr->ike_length)) { + log_debug("%s: short message", __func__); + return (-1); + } + + offset += sizeof(*hdr); + + return (ikev2_pld_payloads(env, msg, offset, + betoh32(hdr->ike_length), hdr->ike_nextpayload, 0)); +} + +int +ikev2_pld_payloads(struct iked *env, struct iked_message *msg, + off_t offset, size_t length, u_int payload, int quick) +{ + struct ikev2_payload pld; + u_int e; + int ret; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + + /* Check if message was decrypted in an E payload */ + e = msg->msg_decrypted ? IKED_E : 0; + + if (quick) + print_debug("%s: %spayloads", __func__, + e ? "decrypted " : ""); + else + ikev2_pld_payloads(env, msg, offset, length, payload, 1); + + while (payload != 0 && offset < (off_t)length) { + memcpy(&pld, msgbuf + offset, sizeof(pld)); + + if (quick) + print_debug(" %s", + print_map(payload, ikev2_payload_map)); + else + log_debug("%s: %spayload %s" + " nextpayload %s critical 0x%02x length %d", + __func__, e ? "decrypted " : "", + print_map(payload, ikev2_payload_map), + print_map(pld.pld_nextpayload, ikev2_payload_map), + pld.pld_reserved & IKEV2_CRITICAL_PAYLOAD, + betoh16(pld.pld_length)); + + offset += sizeof(pld); + ret = 0; + + if (quick) + goto next; + + switch (payload | e) { + case IKEV2_PAYLOAD_SA: + case IKEV2_PAYLOAD_SA | IKED_E: + ret = ikev2_pld_sa(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_KE: + ret = ikev2_pld_ke(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_IDi | IKED_E: + case IKEV2_PAYLOAD_IDr | IKED_E: + ret = ikev2_pld_id(env, &pld, msg, offset, payload); + break; + case IKEV2_PAYLOAD_CERT | IKED_E: + ret = ikev2_pld_cert(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_CERTREQ: + case IKEV2_PAYLOAD_CERTREQ | IKED_E: + ret = ikev2_pld_certreq(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_AUTH | IKED_E: + ret = ikev2_pld_auth(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_NONCE: + case IKEV2_PAYLOAD_NONCE | IKED_E: + ret = ikev2_pld_nonce(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_NOTIFY: + case IKEV2_PAYLOAD_NOTIFY | IKED_E: + ret = ikev2_pld_notify(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_DELETE | IKED_E: + ret = ikev2_pld_delete(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_TSi | IKED_E: + case IKEV2_PAYLOAD_TSr | IKED_E: + ret = ikev2_pld_ts(env, &pld, msg, offset, payload); + break; + case IKEV2_PAYLOAD_E: + ret = ikev2_pld_e(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_CP | IKED_E: + ret = ikev2_pld_cp(env, &pld, msg, offset); + break; + case IKEV2_PAYLOAD_EAP | IKED_E: + ret = ikev2_pld_eap(env, &pld, msg, offset); + break; + default: + print_hex(msgbuf, offset, + betoh16(pld.pld_length) - sizeof(pld)); + break; + } + + if (ret != 0 && !msg->msg_response) { + (void)ikev2_send_informational(env, msg); + return (-1); + } + + /* Encrypted payload must appear last */ + if (payload == IKEV2_PAYLOAD_E) + return (0); + + next: + payload = pld.pld_nextpayload; + offset += betoh16(pld.pld_length) - sizeof(pld); + } + + if (quick) + print_debug("\n"); + + return (0); +} + +int +ikev2_pld_sa(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ikev2_sa_proposal sap; + struct iked_proposal *prop = NULL; + u_int32_t spi32; + u_int64_t spi = 0, spi64; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + struct iked_sa *sa = msg->msg_sa; + + memcpy(&sap, msgbuf + offset, sizeof(sap)); + offset += sizeof(sap); + + if (sap.sap_spisize) { + switch (sap.sap_spisize) { + case 4: + memcpy(&spi32, msgbuf + offset, 4); + spi = betoh32(spi32); + break; + case 8: + memcpy(&spi64, msgbuf + offset, 8); + spi = betoh64(spi64); + break; + default: + log_debug("%s: unsupported SPI size %d", + __func__, sap.sap_spisize); + return (-1); + } + + offset += sap.sap_spisize; + } + + log_debug("%s: more %d reserved %d length %d" + " proposal #%d protoid %s spisize %d xforms %d spi %s", + __func__, sap.sap_more, sap.sap_reserved, + betoh16(sap.sap_length), sap.sap_proposalnr, + print_map(sap.sap_protoid, ikev2_saproto_map), sap.sap_spisize, + sap.sap_transforms, print_spi(spi, sap.sap_spisize)); + + if (!msg->msg_response) { + if ((msg->msg_prop = config_add_proposal(&msg->msg_proposals, + sap.sap_proposalnr, sap.sap_protoid)) == NULL) { + log_debug("%s: invalid proposal", __func__); + return (-1); + } + prop = msg->msg_prop; + prop->prop_localspi.spi_size = sap.sap_spisize; + prop->prop_peerspi.spi = spi; + } + + /* + * Parse the attached transforms + */ + if (ikev2_pld_xform(env, &sap, msg, offset) != 0) { + log_debug("%s: invalid proposal transforms", __func__); + return (-1); + } + + if (msg->msg_response) + return (0); + + /* XXX we need a better way to get this */ + if (ikev2_sa_negotiate(sa, + &msg->msg_policy->pol_proposals, + &msg->msg_proposals, msg->msg_decrypted ? + IKEV2_SAPROTO_ESP : IKEV2_SAPROTO_IKE) != 0) { + log_debug("%s: no proposal chosen", __func__); + msg->msg_error = IKEV2_N_NO_PROPOSAL_CHOSEN; + return (-1); + } else if (sa_stateok(sa, IKEV2_STATE_SA_INIT)) + sa_stateflags(sa, IKED_REQ_SA); + + return (0); +} + +int +ikev2_pld_xform(struct iked *env, struct ikev2_sa_proposal *sap, + struct iked_message *msg, off_t offset) +{ + struct ikev2_transform xfrm; + char id[BUFSIZ]; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + + memcpy(&xfrm, msgbuf + offset, sizeof(xfrm)); + + switch (xfrm.xfrm_type) { + case IKEV2_XFORMTYPE_ENCR: + strlcpy(id, print_map(betoh16(xfrm.xfrm_id), + ikev2_xformencr_map), sizeof(id)); + break; + case IKEV2_XFORMTYPE_PRF: + strlcpy(id, print_map(betoh16(xfrm.xfrm_id), + ikev2_xformprf_map), sizeof(id)); + break; + case IKEV2_XFORMTYPE_INTEGR: + strlcpy(id, print_map(betoh16(xfrm.xfrm_id), + ikev2_xformauth_map), sizeof(id)); + break; + case IKEV2_XFORMTYPE_DH: + strlcpy(id, print_map(betoh16(xfrm.xfrm_id), + ikev2_xformdh_map), sizeof(id)); + break; + case IKEV2_XFORMTYPE_ESN: + strlcpy(id, print_map(betoh16(xfrm.xfrm_id), + ikev2_xformesn_map), sizeof(id)); + break; + default: + snprintf(id, sizeof(id), "<%d>", betoh16(xfrm.xfrm_id)); + break; + } + + log_debug("%s: more %d reserved %d length %d" + " type %s id %s", + __func__, xfrm.xfrm_more, xfrm.xfrm_reserved, + betoh16(xfrm.xfrm_length), + print_map(xfrm.xfrm_type, ikev2_xformtype_map), id); + + /* + * Parse transform attributes, if available + */ + msg->msg_attrlength = 0; + if ((u_int)betoh16(xfrm.xfrm_length) > sizeof(xfrm)) + ikev2_pld_attr(env, &xfrm, msg, offset + sizeof(xfrm), + betoh16(xfrm.xfrm_length) - sizeof(xfrm)); + + if (!msg->msg_response) { + if (config_add_transform(msg->msg_prop, xfrm.xfrm_type, + betoh16(xfrm.xfrm_id), msg->msg_attrlength, + msg->msg_attrlength) == NULL) { + log_debug("%s: failed to add transform", __func__); + return (-1); + } + } + + /* Next transform */ + offset += betoh16(xfrm.xfrm_length); + if (xfrm.xfrm_more == IKEV2_XFORM_MORE) + ikev2_pld_xform(env, sap, msg, offset); + + return (0); +} + +int +ikev2_pld_attr(struct iked *env, struct ikev2_transform *xfrm, + struct iked_message *msg, off_t offset, int total) +{ + struct ikev2_attribute attr; + u_int type; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + + memcpy(&attr, msgbuf + offset, sizeof(attr)); + + type = betoh16(attr.attr_type) & ~IKEV2_ATTRAF_TV; + + log_debug("%s: attribute type %s length %d total %d", + __func__, print_map(type, ikev2_attrtype_map), + betoh16(attr.attr_length), total); + + if (betoh16(attr.attr_type) & IKEV2_ATTRAF_TV) { + /* Type-Value attribute */ + offset += sizeof(attr); + total -= sizeof(attr); + + if (type == IKEV2_ATTRTYPE_KEY_LENGTH) + msg->msg_attrlength = betoh16(attr.attr_length); + } else { + /* Type-Length-Value attribute */ + print_hex(msgbuf, offset + sizeof(attr), + betoh16(attr.attr_length) - sizeof(attr)); + offset += betoh16(attr.attr_length); + total -= betoh16(attr.attr_length); + } + + if (total > 0) { + /* Next attribute */ + ikev2_pld_attr(env, xfrm, msg, offset, total); + } + + return (0); +} + +int +ikev2_pld_ke(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ikev2_keyexchange kex; + u_int8_t *buf; + size_t len; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + + memcpy(&kex, msgbuf + offset, sizeof(kex)); + + log_debug("%s: dh group %s reserved %d", + __func__, + print_map(betoh16(kex.kex_dhgroup), ikev2_xformdh_map), + betoh16(kex.kex_reserved)); + + buf = msgbuf + offset + sizeof(kex); + len = betoh16(pld->pld_length) - sizeof(*pld) - sizeof(kex); + + print_hex(buf, 0, len); + + if (!msg->msg_response) { + if ((msg->msg_sa->sa_dhiexchange = + ibuf_new(buf, len)) == NULL) { + log_debug("%s: failed to get exchange", __func__); + return (-1); + } + } + + return (0); +} + +int +ikev2_pld_id(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset, u_int payload) +{ + u_int8_t *ptr; + struct ikev2_id id; + size_t len; + struct iked_id *idp, idb; + struct iked_sa *sa = msg->msg_sa; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + struct ibuf *authmsg; + char idstr[IKED_ID_SIZE]; + + memcpy(&id, msgbuf + offset, sizeof(id)); + bzero(&idb, sizeof(idb)); + + /* Don't strip the Id payload header */ + ptr = msgbuf + offset; + len = betoh16(pld->pld_length) - sizeof(*pld); + + idb.id_type = id.id_type; + if ((idb.id_buf = ibuf_new(ptr, len)) == NULL) + return (-1); + + if (print_id(&idb, sizeof(id), idstr, sizeof(idstr)) == -1) { + log_debug("%s: malformed id", __func__); + return (-1); + } + + log_debug("%s: id %s/%s length %d", + __func__, print_map(id.id_type, ikev2_id_map), idstr, len); + + if (msg->msg_response) { + ibuf_release(idb.id_buf); + return (0); + } + + if (sa->sa_hdr.sh_initiator && payload == IKEV2_PAYLOAD_IDr) { + idp = &sa->sa_rid; + } else if (!sa->sa_hdr.sh_initiator && payload == IKEV2_PAYLOAD_IDi) { + idp = &sa->sa_iid; + } else { + log_debug("%s: unexpected id payload", __func__); + return (0); + } + + ibuf_release(idp->id_buf); + idp->id_buf = idb.id_buf; + idp->id_type = idb.id_type; + + if ((authmsg = ikev2_msg_auth(env, sa, + !sa->sa_hdr.sh_initiator)) == NULL) { + log_debug("%s: failed to get response auth data", __func__); + return (-1); + } + + ca_setauth(env, sa, authmsg, PROC_CERT); + + return (0); +} + +int +ikev2_pld_cert(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ikev2_cert cert; + u_int8_t *buf; + size_t len; + struct iked_sa *sa = msg->msg_sa; + struct iked_id *certid, *id; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + + memcpy(&cert, msgbuf + offset, sizeof(cert)); + offset += sizeof(cert); + + buf = msgbuf + offset; + len = betoh16(pld->pld_length) - sizeof(*pld) - sizeof(cert); + + log_debug("%s: type %s length %d", + __func__, print_map(cert.cert_type, ikev2_cert_map), len); + + print_hex(buf, 0, len); + + if (msg->msg_response) + return (0); + + if (!sa->sa_hdr.sh_initiator && !msg->msg_response) { + certid = &sa->sa_icert; + id = &sa->sa_iid; + } else if (sa->sa_hdr.sh_initiator && msg->msg_response) { + certid = &sa->sa_rcert; + id = &sa->sa_rid; + } else + return (0); /* ignore */ + + if ((certid->id_buf = ibuf_new(buf, len)) == NULL) { + log_debug("%s: failed to save cert", __func__); + return (-1); + } + certid->id_type = cert.cert_type; + + ca_setcert(env, &msg->msg_sa->sa_hdr, id, cert.cert_type, + buf, len, PROC_CERT); + + return (0); +} + +int +ikev2_pld_certreq(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ikev2_cert cert; + u_int8_t *buf; + size_t len; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + + memcpy(&cert, msgbuf + offset, sizeof(cert)); + offset += sizeof(cert); + + buf = msgbuf + offset; + len = betoh16(pld->pld_length) - sizeof(*pld) - sizeof(cert); + + log_debug("%s: type %s signatures length %d", + __func__, print_map(cert.cert_type, ikev2_cert_map), len); + print_hex(buf, 0, len); + + if (msg->msg_response) + return (0); + + if (!len || (len % SHA_DIGEST_LENGTH) != 0) { + log_debug("%s: invalid certificate request", __func__); + return (-1); + } + + if (msg->msg_sa == NULL) + return (-1); + + /* Optional certreq for PSK */ + msg->msg_sa->sa_staterequire |= IKED_REQ_CERT; + + ca_setreq(env, &msg->msg_sa->sa_hdr, cert.cert_type, + buf, len, PROC_CERT); + + return (0); +} + +int +ikev2_pld_auth(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ikev2_auth auth; + struct iked_auth ikeauth; + u_int8_t *buf; + size_t len; + struct ibuf *authmsg; + struct iked_sa *sa = msg->msg_sa; + struct iked_policy *policy = sa->sa_policy; + int ret = -1; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + + memcpy(&auth, msgbuf + offset, sizeof(auth)); + offset += sizeof(auth); + + buf = msgbuf + offset; + len = betoh16(pld->pld_length) - sizeof(*pld) - sizeof(auth); + + log_debug("%s: method %s length %d", + __func__, print_map(auth.auth_method, ikev2_auth_map), len); + + print_hex(buf, 0, len); + + if (msg->msg_response) + return (0); + + memcpy(&ikeauth, &policy->pol_auth, sizeof(ikeauth)); + + if (policy->pol_auth.auth_eap && sa->sa_eapmsk != NULL) { + /* The initiator EAP auth is a PSK derived from the MSK */ + ikeauth.auth_method = IKEV2_AUTH_SHARED_KEY_MIC; + + /* Copy session key as PSK */ + memcpy(ikeauth.auth_data, ibuf_data(sa->sa_eapmsk), + ibuf_size(sa->sa_eapmsk)); + ikeauth.auth_length = ibuf_size(sa->sa_eapmsk); + } + if (auth.auth_method != ikeauth.auth_method) { + log_debug("%s: method %s required", __func__, + print_map(ikeauth.auth_method, ikev2_auth_map)); + return (-1); + } + + /* The AUTH payload indicates if the responder wants EAP or not */ + if (!sa_stateok(sa, IKEV2_STATE_EAP)) + sa_state(env, sa, IKEV2_STATE_AUTH_REQUEST); + + if ((authmsg = ikev2_msg_auth(env, sa, + sa->sa_hdr.sh_initiator)) == NULL) { + log_debug("%s: failed to get auth data", __func__); + return (-1); + } + + ret = ikev2_msg_authverify(env, sa, &ikeauth, buf, len, + authmsg); + + ibuf_release(authmsg); + authmsg = NULL; + + if (ret != 0) + goto done; + + if (sa->sa_eapmsk != NULL) { + if ((authmsg = ikev2_msg_auth(env, sa, + !sa->sa_hdr.sh_initiator)) == NULL) { + log_debug("%s: failed to get auth data", __func__); + return (-1); + } + + /* 2nd AUTH for EAP messages */ + if ((ret = ikev2_msg_authsign(env, sa, + &ikeauth, authmsg)) != 0) + goto done; + + sa_state(env, sa, IKEV2_STATE_EAP_VALID); + } + + done: + ibuf_release(authmsg); + + return (ret); +} + +int +ikev2_pld_nonce(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + size_t len; + u_int8_t *buf; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + struct iked_sa *sa = msg->msg_sa; + struct ibuf *localnonce, *peernonce; + + buf = msgbuf + offset; + len = betoh16(pld->pld_length) - sizeof(*pld); + print_hex(buf, 0, len); + + if (!msg->msg_response) { + if ((peernonce = ibuf_new(buf, len)) == NULL) { + log_debug("%s: failed to get peer nonce", __func__); + return (-1); + } + if ((localnonce = + ibuf_random(IKED_NONCE_SIZE)) == NULL) { + log_debug("%s: failed to get local nonce", __func__); + ibuf_release(peernonce); + return (-1); + } + + ibuf_release(sa->sa_inonce); + ibuf_release(sa->sa_rnonce); + + log_debug("%s: updating nonces", __func__); + + if (sa->sa_hdr.sh_initiator) { + sa->sa_inonce = localnonce; + sa->sa_rnonce = peernonce; + } else { + sa->sa_inonce = peernonce; + sa->sa_rnonce = localnonce; + } + } + + return (0); +} + +int +ikev2_pld_notify(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ikev2_notify *n; + u_int8_t *buf, md[SHA_DIGEST_LENGTH]; + size_t len; + u_int16_t type; + u_int32_t spi32; + u_int64_t spi64; + struct iked_spi *rekey; + + if ((n = ibuf_seek(msg->msg_data, offset, sizeof(*n))) == NULL) + return (-1); + type = betoh16(n->n_type); + + log_debug("%s: protoid %s spisize %d type %s", + __func__, + print_map(n->n_protoid, ikev2_saproto_map), n->n_spisize, + print_map(type, ikev2_n_map)); + + len = betoh16(pld->pld_length) - sizeof(*pld) - sizeof(*n); + if ((buf = ibuf_seek(msg->msg_data, offset + sizeof(*n), len)) == NULL) + return (-1); + + print_hex(buf, 0, len); + + switch (type) { + case IKEV2_N_NAT_DETECTION_SOURCE_IP: + case IKEV2_N_NAT_DETECTION_DESTINATION_IP: + if (ikev2_nat_detection(msg, md, sizeof(md), type, + msg->msg_response) == -1) + return (-1); + if (len != sizeof(md) || memcmp(buf, md, len) != 0) { + log_debug("%s: %s detected NAT, enabling " + "UDP encapsulation", __func__, + print_map(type, ikev2_n_map)); + + /* + * Enable UDP encapsulation of ESP packages if + * the check detected NAT. + */ + if (msg->msg_sa != NULL) + msg->msg_sa->sa_udpencap = 1; + } + print_hex(md, 0, sizeof(md)); + break; + case IKEV2_N_REKEY_SA: + if (len != n->n_spisize) { + log_debug("%s: malformed notification", __func__); + return (-1); + } + if (msg->msg_decrypted) + rekey = &msg->msg_decrypted->msg_rekey; + else + rekey = &msg->msg_rekey; + if (rekey->spi != 0) { + log_debug("%s: rekeying of multiple SAs not supported", + __func__); + return (-1); + } + switch (n->n_spisize) { + case 4: + memcpy(&spi32, buf, len); + rekey->spi = betoh32(spi32); + break; + case 8: + memcpy(&spi64, buf, len); + rekey->spi = betoh64(spi64); + break; + default: + log_debug("%s: invalid spi size %d", __func__, + n->n_spisize); + return (-1); + } + rekey->spi_size = n->n_spisize; + rekey->spi_protoid = n->n_protoid; + + log_debug("%s: rekey %s spi %s", __func__, + print_map(n->n_protoid, ikev2_saproto_map), + print_spi(rekey->spi, n->n_spisize)); + break; + } + + return (0); +} + +int +ikev2_pld_delete(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ikev2_delete *del, *localdel; + u_int64_t spi64, spi = 0, *localspi = NULL; + u_int32_t spi32; + size_t len, i, cnt, sz, found = 0, failed = 0; + u_int8_t *buf, firstpayload = 0; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + struct iked_sa *sa = msg->msg_sa; + struct ibuf *resp = NULL; + int ret = -1; + + if ((del = ibuf_seek(msg->msg_data, offset, sizeof(*del))) == NULL) + return (-1); + cnt = betoh16(del->del_nspi); + sz = del->del_spisize; + + log_debug("%s: protoid %s spisize %d nspi %d", + __func__, print_map(del->del_protoid, ikev2_saproto_map), + sz, cnt); + + buf = msgbuf + offset + sizeof(*del); + len = betoh16(pld->pld_length) - sizeof(*pld) - sizeof(*del); + + print_hex(buf, 0, len); + + switch (sz) { + case 4: + case 8: + break; + default: + if (!msg->msg_response && + del->del_protoid == IKEV2_SAPROTO_IKE) { + sa_state(env, sa, IKEV2_STATE_DELETE); + return (0); + } + log_debug("%s: invalid SPI size", __func__); + return (-1); + } + + if ((len / sz) != cnt) { + log_debug("%s: invalid payload length %d/%d != %d", + __func__, len, sz, cnt); + return (-1); + } + + if (!msg->msg_response && + (localspi = calloc(cnt, sizeof(u_int64_t))) == NULL) { + log_warn("%s", __func__); + return (-1); + } + + for (i = 0; i < cnt; i++) { + /* XXX delete SAs */ + switch (sz) { + case 4: + memcpy(&spi32, buf + (i * sz), sizeof(spi32)); + spi = betoh32(spi32); + break; + case 8: + memcpy(&spi64, buf + (i * sz), sizeof(spi64)); + spi = betoh64(spi64); + break; + } + if (msg->msg_response) { + log_debug("%s: spi %s", __func__, print_spi(spi, sz)); + continue; + } + + if (ikev2_childsa_delete(env, sa, + del->del_protoid, spi, &localspi[i], 0) == -1) + failed++; + else + found++; + } + + if (msg->msg_response) + return (0); + + if ((resp = ibuf_static()) == NULL) + goto done; + + if (found) { + if ((localdel = ibuf_advance(resp, sizeof(*localdel))) == NULL) + goto done; + + firstpayload = IKEV2_PAYLOAD_DELETE; + localdel->del_protoid = del->del_protoid; + localdel->del_spisize = del->del_spisize; + localdel->del_nspi = htobe16(found); + + for (i = 0; i < cnt; i++) { + if (!localspi[i]) + continue; + + switch (sz) { + case 4: + spi32 = htobe32(localspi[i]); + if (ibuf_add(resp, &spi32, sizeof(spi32)) != 0) + goto done; + break; + case 8: + spi64 = htobe64(localspi[i]); + if (ibuf_add(resp, &spi64, sizeof(spi64)) != 0) + goto done; + break; + } + } + + } + + if (found) { + ret = ikev2_send_ike_e(env, sa, resp, + firstpayload, IKEV2_EXCHANGE_INFORMATIONAL, 1); + } else { + /* XXX should we send an INVALID_SPI notification? */ + ret = 0; + } + + done: + if (localspi != NULL) + free(localspi); + ibuf_release(resp); + return (ret); +} + +int +ikev2_pld_ts(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset, u_int payload) +{ + u_int8_t *ptr; + struct ikev2_tsp tsp; + struct ikev2_ts ts; + size_t len, i; + struct sockaddr_in s4; + struct sockaddr_in6 s6; + u_int8_t buf[2][128]; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + + memcpy(&tsp, msgbuf + offset, sizeof(tsp)); + offset += sizeof(tsp); + + ptr = msgbuf + offset; + len = betoh16(pld->pld_length) - sizeof(*pld) - sizeof(tsp); + + log_debug("%s: count %d length %d", __func__, + tsp.tsp_count, len); + + for (i = 0; i < tsp.tsp_count; i++) { + memcpy(&ts, msgbuf + offset, sizeof(ts)); + + log_debug("%s: type %s protoid %u length %d " + "startport %u endport %u", __func__, + print_map(ts.ts_type, ikev2_ts_map), + ts.ts_protoid, betoh16(ts.ts_length), + betoh16(ts.ts_startport), + betoh16(ts.ts_endport)); + + switch (ts.ts_type) { + case IKEV2_TS_IPV4_ADDR_RANGE: + bzero(&s4, sizeof(s4)); + s4.sin_family = AF_INET; + s4.sin_len = sizeof(s4); + memcpy(&s4.sin_addr.s_addr, + msgbuf + offset + sizeof(ts), 4); + print_host((struct sockaddr_storage *)&s4, + (char *)buf[0], sizeof(buf[0])); + memcpy(&s4.sin_addr.s_addr, + msgbuf + offset + sizeof(ts) + 4, 4); + print_host((struct sockaddr_storage *)&s4, + (char *)buf[1], sizeof(buf[1])); + log_debug("%s: start %s end %s", __func__, + buf[0], buf[1]); + break; + case IKEV2_TS_IPV6_ADDR_RANGE: + bzero(&s6, sizeof(s6)); + s6.sin6_family = AF_INET6; + s6.sin6_len = sizeof(s6); + memcpy(&s6.sin6_addr, + msgbuf + offset + sizeof(ts), 16); + print_host((struct sockaddr_storage *)&s6, + (char *)buf[0], sizeof(buf[0])); + memcpy(&s6.sin6_addr, + msgbuf + offset + sizeof(ts) + 16, 16); + print_host((struct sockaddr_storage *)&s6, + (char *)buf[1], sizeof(buf[1])); + log_debug("%s: start %s end %s", __func__, + buf[0], buf[1]); + break; + default: + break; + } + + offset += betoh16(ts.ts_length); + } + + return (0); +} + +int +ikev2_pld_e(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ibuf *e = NULL; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + struct iked_message emsg; + u_int8_t *buf; + size_t len; + int ret = -1; + + buf = msgbuf + offset; + len = betoh16(pld->pld_length) - sizeof(*pld); + + if ((e = ibuf_new(buf, len)) == NULL) + goto done; + + if ((e = ikev2_msg_decrypt(env, msg->msg_sa, + msg->msg_data, e)) == NULL) + goto done; + + /* + * Parse decrypted payload + */ + bzero(&emsg, sizeof(emsg)); + memcpy(&emsg, msg, sizeof(*msg)); + emsg.msg_data = e; + emsg.msg_decrypted = msg; + TAILQ_INIT(&emsg.msg_proposals); + + ret = ikev2_pld_payloads(env, &emsg, 0, ibuf_size(e), + pld->pld_nextpayload, 0); + + done: + ibuf_release(e); + + return (ret); +} + +int +ikev2_pld_cp(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct ikev2_cp cp; + struct ikev2_cfg *cfg; + u_int8_t *buf; + size_t len, i; + u_int8_t *msgbuf = ibuf_data(msg->msg_data); + struct iked_sa *sa = msg->msg_sa; + + memcpy(&cp, msgbuf + offset, sizeof(cp)); + offset += sizeof(cp); + + buf = msgbuf + offset; + len = betoh16(pld->pld_length) - sizeof(*pld) - sizeof(cp); + + log_debug("%s: type %s", + __func__, print_map(cp.cp_type, ikev2_cp_map), len); + print_hex(buf, 0, len); + + for (i = 0; i < len;) { + cfg = (struct ikev2_cfg *)(buf + i); + + log_debug("%s: %s 0x%04x length %d", __func__, + print_map(betoh16(cfg->cfg_type), ikev2_cfg_map), + betoh16(cfg->cfg_type), + betoh16(cfg->cfg_length)); + + i += betoh16(cfg->cfg_length) + sizeof(*cfg); + } + + if (msg->msg_response) + return (0); + + if (sa) + sa->sa_cp = cp.cp_type; + + return (0); +} + +int +ikev2_pld_eap(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, off_t offset) +{ + struct eap_header *hdr; + struct eap_message *eap = NULL; + struct iked_sa *sa = msg->msg_sa; + size_t len; + + if ((hdr = ibuf_seek(msg->msg_data, offset, sizeof(*hdr))) == NULL) { + log_debug("%s: failed to get EAP header", __func__); + return (-1); + } + + len = betoh16(hdr->eap_length); + + if (len < sizeof(*eap)) { + log_info("%s: %s id %d length %d", __func__, + print_map(hdr->eap_code, eap_code_map), + hdr->eap_id, betoh16(hdr->eap_length)); + } else { + /* Now try to get the indicated length */ + if ((eap = ibuf_seek(msg->msg_data, offset, len)) == NULL) { + log_debug("%s: invalid EAP length", __func__); + return (-1); + } + + log_info("%s: %s id %d length %d EAP-%s", __func__, + print_map(eap->eap_code, eap_code_map), + eap->eap_id, betoh16(eap->eap_length), + print_map(eap->eap_type, eap_type_map)); + } + + if (eap_parse(env, sa, hdr, msg->msg_response) == -1) + return (-1); + + return (0); +} |