diff --git a/gpst.c b/gpst.c
index 0097c955..bd6af62d 100644
--- a/gpst.c
+++ b/gpst.c
@@ -26,6 +26,9 @@
#include
#include
#include
+#ifndef _WIN32
+#include
+#endif
#include
#ifdef HAVE_LZ4
#include
@@ -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;
@@ -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
@@ -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;
}
diff --git a/hipreport.sh b/hipreport.sh
new file mode 100755
index 00000000..a25cf139
--- /dev/null
+++ b/hipreport.sh
@@ -0,0 +1,185 @@
+#!/bin/sh
+
+# openconnect will call this script with the follow command-line
+# arguments, which are needed to populate the contents of the
+# HIP report:
+#
+# --cookie: a URL-encoded string, as output by openconnect
+# --authenticate --protocol=gp, which includes parameters
+# --from the /ssl-vpn/login.esp response
+#
+# --computer: local hostname, which can be overriden with
+# --openconnect local-hostname=HOSTNAME
+#
+# --client-ip: IPv4 address allocated by the GlobalProtect VPN for
+# this client (included in /ssl-vpn/getconfig.esp
+# response)
+#
+# --md5: The md5 digest to encode into this HIP report. I'm not sure
+# exactly what this is the md5 digest *of*, but all that
+# really matters is that the value in the HIP report
+# submission should match the value in the HIP report check.
+
+# Read command line arguments into variables
+COOKIE=
+COMPUTER=
+IP=
+MD5=
+
+while [ "$1" ]; do
+ if [ "$1" = "--cookie" ]; then shift; COOKIE="$1"; fi
+ if [ "$1" = "--computer" ]; then shift; COMPUTER="$1"; fi
+ if [ "$1" = "--client-ip" ]; then shift; IP="$1"; fi
+ if [ "$1" = "--md5" ]; then shift; MD5="$1"; fi
+ shift
+done
+
+if [ -z "$COOKIE" -o -z "$COMPUTER" -o -z "$IP" -o -z "$MD5" ]; then
+ echo "Parameters --cookie, --computer, --client-ip, and --md5 are required" >&2
+ exit 1;
+fi
+
+# Extract username and domain from cookie
+USER=$(echo "$COOKIE" | sed -rn 's/(.+&|^)user=([^&]+)(&.+|$)/\2/p')
+DOMAIN=$(echo "$COOKIE" | sed -rn 's/(.+&|^)domain=([^&]+)(&.+|$)/\2/p')
+
+# Timestamp in the format expected by GlobalProtect server
+NOW=$(date +'%m/%d/%Y %H:%M:%S')
+
+# This value may need to be extracted from the official HIP report, if a made-up value is not accepted.
+HOSTID="deadbeef-dead-beef-dead-beefdeadbeef"
+
+cat <
+ $MD5
+ $USER
+ $DOMAIN
+ $COMPUTER
+ $HOSTID
+ $IP
+
+ $NOW
+
+
+ 4.0.2-19
+ Microsoft Windows 10 Pro , 64-bit
+ Microsoft
+ $DOMAIN.internal
+ $COMPUTER
+ $HOSTID
+
+
+ PANGP Virtual Ethernet Adapter #2
+ 01-02-03-00-00-01
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ yes
+ 10/11/2017 15:23:41
+
+
+
+
+
+
+ no
+ n/a
+
+
+
+
+
+
+
+
+
+
+ yes
+ 10/11/2017 15:23:41
+
+
+
+
+
+
+ no
+ n/a
+
+
+
+
+
+
+
+
+
+
+ n/a
+
+
+
+
+
+
+
+
+
+
+
+
+ C:
+ full
+
+
+
+
+
+
+
+
+
+
+
+
+ yes
+
+
+
+
+
+
+
+
+
+
+ yes
+
+
+
+
+
+
+ yes
+
+
+
+
+
+
+
+
+
+
+EOF
diff --git a/www/Makefile.am b/www/Makefile.am
index f791a004..a6de6ac2 100644
--- a/www/Makefile.am
+++ b/www/Makefile.am
@@ -3,7 +3,7 @@
SUBDIRS = styles inc images
CONV = "$(srcdir)/html.py"
-FTR_PAGES = csd.html charset.html token.html pkcs11.html tpm.html features.html gui.html nonroot.html
+FTR_PAGES = csd.html charset.html token.html pkcs11.html tpm.html features.html gui.html nonroot.html hip.html
START_PAGES = building.html connecting.html manual.html vpnc-script.html
INDEX_PAGES = changelog.html download.html index.html packages.html platforms.html
PROTO_PAGES = anyconnect.html juniper.html globalprotect.html
diff --git a/www/features.xml b/www/features.xml
index 92457dc4..cbe91447 100644
--- a/www/features.xml
+++ b/www/features.xml
@@ -24,7 +24,7 @@
Automatic update of VPN server list / configuration.
Roaming support, allowing reconnection when the local IP address changes.
Run without root privileges (see here).
- "Cisco Secure Desktop" support (see here).
+ Support for "Cisco Secure Desktop" (see here) and "GlobalProtect HIP report" (see here).
Graphical connection tools for various environments (see here).
diff --git a/www/globalprotect.xml b/www/globalprotect.xml
index ee458199..655db9a2 100644
--- a/www/globalprotect.xml
+++ b/www/globalprotect.xml
@@ -16,15 +16,22 @@
href="https://tools.ietf.org/html/rfc3948">ESP, with routing and
configuration information distributed in XML format.
+Authentication
+
To authenticate, you connect to the secure web server (POST
/ssl-vpn/login.esp), provide a username, password, and (optionally) a
certificate, and receive an authcookie. The username, authcookie, and a
couple other bits of information obtained at login are combined into the
OpenConnect cookie.
+Tunnel configuration
+
To connect to the secure tunnel, the cookie is used to read routing and
tunnel configuration information (POST /ssl-vpn/getconfig.esp).
+Next, a HIP report (security scanner report) is
+generated by the client and submitted to the server, if required.
+
Finally, either an HTTPS-based or ESP-based tunnel is setup:
diff --git a/www/hip.xml b/www/hip.xml
new file mode 100644
index 00000000..0009c320
--- /dev/null
+++ b/www/hip.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+PAN GlobalProtect HIP
+
+The HIP ('Host Integrity Protection') mechanism is a security
+scanner for the PAN GlobalProtect
+VPNs, in the same vein as Cisco's CSD and Juniper's Host Checker (tncc.jar).
+
+How it works
+
+It is somewhat less intrusive than CSD or TNCC, because it
+does not appear to work by downloading a trojan binary from the VPN
+server. Instead, it runs a HIP report generator (built-in as part of
+the official GlobalProtect VPN client software), which generates an
+"HIP report" XML file.
+
+HIP flow used in the official clients:
+
+ - Client authenticates and fetches the tunnel configuration from the GlobalProtect gateway.
+ - Client runs HIP report generator and computes MD5 digest of report.
+ - Client checks whether a HIP report is required (
/ssl-vpn/hipreportcheck.esp
), including its MD5 digest and gateway-assigned IP address in the report.
+ - Gateway responds whether or not a HIP report is required (normally, it doesn't require a new one if a report with the same MD5 digest and same IP address have been submitted recently).
+ - Client uploads the complete HIP report to (
/ssl-vpn/hipreport.esp
).
+ - Server confirms acceptance of HIP report with a success message.
+
+
+If all goes well, the client should have the expected level of
+access to resources on the network after these steps are
+complete. However, two things can go wrong:
+
+
+ - Many GlobalProtect servers report that they require HIP reports
+ (#3 above), but don't actually enforce this requirement. (For this
+ reason, OpenConnect does not currently fail if a HIP report is
+ required but no HIP report script is provided.)
+ - Many GlobalProtect servers will claim that the HIP report was
+ accepted successfully (#6 above) but silently fail to enable the
+ expected network access, presumably because some aspect of the
+ HIP report contents were not approved.
+
+
+HIP support in openconnect
+
+OpenConnect supports HIP report generation and submission by passing the --csd-wrapper=SCRIPT
argument with a shell script to generate a HIP report in the format expected by the
+server. This shell script must output the HIP report to standard output and exit successfully (status code 0). The HIP script is called with the following command-line arguments:
+
+
+ --cookie: a URL-encoded string, as output by openconnect
+ --authenticate --protocol=gp, which includes parameters
+ --from the /ssl-vpn/login.esp response
+
+ --computer: local hostname, which can be overriden with
+ --openconnect local-hostname=HOSTNAME
+
+ --client-ip: IPv4 address allocated by the GlobalProtect VPN for
+ this client (included in /ssl-vpn/getconfig.esp
+ response)
+
+ --md5: The md5 digest to encode into this HIP report. All that
+ really matters is that the value in the HIP report
+ submission should match the value in the HIP report check.
+
+
+Generating/spoofing a HIP report
+
+An example hipreport.sh
script is included in the
+openconnect distribution.
+
+Depending on how picky your GlobalProtect
+VPN is, it may be necessary to spoof or alter some of the parameters
+of the HIP report to match the output of one of the official
+clients. In order to capture the contents of the official Windows
+client's HIP reports, enable the highest logging level for the "PanGPS
+Service", and then sift through the giant PanGPS.log
file
+(which should be in the same directory as the executables, normally
+c:\Program Files\PaloAlto Networks\GlobalProtect
) to find
+the HIP report submission.
+
+
+