Skip to content

Commit

Permalink
Add support for checking and submitting HIP reports
Browse files Browse the repository at this point in the history
Unlike CSD, the HIP security checker runs during the connection	phase, not
during the authentication phase.

Therefore we need to build the CSD token (an MD5 digest identifying the
client) without relying on the authentication phase having run in the same
process.

We build it from the cookie containing authentication information,
but exclude the volatile field (which changes from session to session)
and the preferred-ip field (which may not be present in all cases, or may
change from session to session).

Signed-off-by: Daniel Lenski <dlenski@gmail.com>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
  • Loading branch information
dlenski authored and dwmw2 committed May 31, 2018
1 parent 1ad22fc commit 4e35d50
Show file tree
Hide file tree
Showing 6 changed files with 494 additions and 3 deletions.
212 changes: 211 additions & 1 deletion gpst.c
Expand Up @@ -26,6 +26,9 @@
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#ifndef _WIN32
#include <sys/wait.h>
#endif
#include <stdarg.h>
#ifdef HAVE_LZ4
#include <lz4.h>
Expand Down Expand Up @@ -717,6 +720,200 @@ static int gpst_connect(struct openconnect_info *vpninfo)
return ret;
}

static int parse_hip_report_check(struct openconnect_info *vpninfo, xmlNode *xml_node)
{
char *s;
int result = -EINVAL;

if (!xml_node || !xmlnode_is_named(xml_node, "response"))
goto out;

for (xml_node = xml_node->children; xml_node; xml_node=xml_node->next) {
if (!xmlnode_get_text(xml_node, "hip-report-needed", &s)) {
if (!strcmp(s, "no"))
result = 0;
else if (!strcmp(s, "yes"))
result = -EAGAIN;
else
result = -EINVAL;
free(s);
goto out;
}
}

out:
return result;
}

/* Unlike CSD, the HIP security checker runs during the connection
* phase, not during the authentication phase.
*
* The HIP security checker will (probably) ask us to resubmit the
* HIP report if either of the following changes:
* - Client IP address
* - Client HIP report md5sum
*
* I'm not sure what the md5sum is computed over in the official
* client, but it doesn't really matter.
*
* We just need an identifier for the combination of the local host
* and the VPN gateway which won't change when our IP address
* or authcookie are changed.
*/
static int build_csd_token(struct openconnect_info *vpninfo)
{
struct oc_text_buf *buf;
unsigned char md5[16];
int i;

if (vpninfo->csd_token)
return 0;

vpninfo->csd_token = malloc(MD5_SIZE * 2 + 1);
if (!vpninfo->csd_token)
return -ENOMEM;

/* use localname and cookie (excluding volatile authcookie and preferred-ip) to build md5sum */
buf = buf_alloc();
append_opt(buf, "computer", vpninfo->localname);
filter_opts(buf, vpninfo->cookie, "authcookie,preferred-ip", 0);
if (buf_error(buf))
goto out;

/* save as csd_token */
openconnect_md5(md5, buf->data, buf->pos);
for (i=0; i < MD5_SIZE; i++)
sprintf(&vpninfo->csd_token[i*2], "%02x", md5[i]);

out:
return buf_free(buf);
}

/* check if HIP report is needed (to ssl-vpn/hipreportcheck.esp) or submit HIP report contents (to ssl-vpn/hipreport.esp) */
static int check_or_submit_hip_report(struct openconnect_info *vpninfo, const char *report)
{
int result;

struct oc_text_buf *request_body = buf_alloc();
const char *request_body_type = "application/x-www-form-urlencoded";
const char *method = "POST";
char *xml_buf=NULL, *orig_path;

/* cookie gives us these fields: authcookie, portal, user, domain, and (maybe the unnecessary) preferred-ip */
buf_append(request_body, "client-role=global-protect-full&%s", vpninfo->cookie);
append_opt(request_body, "computer", vpninfo->localname);
append_opt(request_body, "client-ip", vpninfo->ip_info.addr);
if (report) {
/* XML report contains many characters requiring URL-encoding (%xx) */
buf_ensure_space(request_body, strlen(report)*3);
append_opt(request_body, "report", report);
} else {
result = build_csd_token(vpninfo);
if (result)
goto out;
append_opt(request_body, "md5", vpninfo->csd_token);
}
if ((result = buf_error(request_body)))
goto out;

orig_path = vpninfo->urlpath;
vpninfo->urlpath = strdup(report ? "ssl-vpn/hipreport.esp" : "ssl-vpn/hipreportcheck.esp");
result = do_https_request(vpninfo, method, request_body_type, request_body,
&xml_buf, 0);
free(vpninfo->urlpath);
vpninfo->urlpath = orig_path;

result = gpst_xml_or_error(vpninfo, result, xml_buf, report ? NULL : parse_hip_report_check, NULL, NULL);

out:
buf_free(request_body);
free(xml_buf);
return result;
}

static int run_hip_script(struct openconnect_info *vpninfo)
{
#if !defined(_WIN32) && !defined(__native_client__)
int pipefd[2];
int ret;
pid_t child;
#endif

if (!vpninfo->csd_wrapper) {
vpn_progress(vpninfo, PRG_ERR,
_("WARNING: Server asked us to submit HIP report with md5sum %s.\n"
"VPN connectivity may be disabled or limited without HIP report submission.\n"
"You need to provide a --csd-wrapper argument with the HIP report submission script.\n"),
vpninfo->csd_token);
/* XXX: Many GlobalProtect VPNs work fine despite allegedly requiring HIP report submission */
return 0;
}

#if defined(_WIN32) || defined(__native_client__)
vpn_progress(vpninfo, PRG_ERR,
_("Error: Running the 'HIP Report' script on this platform is not yet implemented.\n"));
return -EPERM;
#else
if (pipe(pipefd) == -1)
goto out;
child = fork();
if (child == -1) {
goto out;
} else if (child > 0) {
/* in parent: read report from child */
struct oc_text_buf *report_buf = buf_alloc();
char b[256];
int i, status;
close(pipefd[1]);

buf_truncate(report_buf);
while ((i = read(pipefd[0], b, sizeof(b))) > 0)
buf_append_bytes(report_buf, b, i);

waitpid(child, &status, 0);
if (status != 0) {
vpn_progress(vpninfo, PRG_ERR,
_("HIP script returned non-zero status: %d\n"), status);
ret = -EINVAL;
} else {
ret = check_or_submit_hip_report(vpninfo, report_buf->data);
if (ret < 0)
vpn_progress(vpninfo, PRG_ERR, _("HIP report submission failed.\n"));
else {
vpn_progress(vpninfo, PRG_INFO, _("HIP report submitted successfully.\n"));
ret = 0;
}
}
buf_free(report_buf);
return ret;
} else {
/* in child: run HIP script */
char *hip_argv[32];
int i = 0;
close(pipefd[0]);
dup2(pipefd[1], 1);

hip_argv[i++] = openconnect_utf8_to_legacy(vpninfo, vpninfo->csd_wrapper);
hip_argv[i++] = (char *)"--cookie";
hip_argv[i++] = vpninfo->cookie;
hip_argv[i++] = (char *)"--computer";
hip_argv[i++] = vpninfo->localname;
hip_argv[i++] = (char *)"--client-ip";
hip_argv[i++] = (char *)vpninfo->ip_info.addr;
hip_argv[i++] = (char *)"--md5";
hip_argv[i++] = vpninfo->csd_token;
hip_argv[i++] = NULL;
execv(hip_argv[0], hip_argv);

out:
vpn_progress(vpninfo, PRG_ERR,
_("Failed to exec HIP script %s\n"), hip_argv[0]);
exit(1);
}

#endif /* !_WIN32 && !__native_client__ */
}

int gpst_setup(struct openconnect_info *vpninfo)
{
int ret;
Expand All @@ -728,7 +925,19 @@ int gpst_setup(struct openconnect_info *vpninfo)
/* Get configuration */
ret = gpst_get_config(vpninfo);
if (ret)
return ret;
goto out;

/* Check HIP */
ret = check_or_submit_hip_report(vpninfo, NULL);
if (ret == -EAGAIN) {
vpn_progress(vpninfo, PRG_DEBUG,
_("Gateway says HIP report submission is needed.\n"));
ret = run_hip_script(vpninfo);
if (ret != 0)
goto out;
} else if (ret == 0)
vpn_progress(vpninfo, PRG_DEBUG,
_("Gateway says no HIP report submission is needed.\n"));

/* We do NOT actually start the HTTPS tunnel yet if we want to
* use ESP, because the ESP tunnel won't work if the HTTPS tunnel
Expand All @@ -737,6 +946,7 @@ int gpst_setup(struct openconnect_info *vpninfo)
if (vpninfo->dtls_state == DTLS_DISABLED || vpninfo->dtls_state == DTLS_NOSECRET)
ret = gpst_connect(vpninfo);

out:
return ret;
}

Expand Down

0 comments on commit 4e35d50

Please sign in to comment.