]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Support AnyConnect single-sign-on-v2
authorSteven Walter <stevenrwalter@gmail.com>
Wed, 18 Mar 2020 16:36:25 +0000 (12:36 -0400)
committerLuca Boccassi <bluca@debian.org>
Wed, 23 Feb 2022 19:22:53 +0000 (19:22 +0000)
Advertise support for auth-method single-sign-on-v2.  This, combined
with not sending X-AnyConnect-Platform and X-Support-HTTP-Auth, allows
one to complete a web-based SSO authentication.  Server replies with a
response like:

    <?xml version="1.0" encoding="UTF-8"?>
    <config-auth client="vpn" type="auth-request" aggregate-auth-version="2">
    <opaque is-for="sg">
    <tunnel-group>standard-group</tunnel-group>
    <auth-method>single-sign-on-v2</auth-method>
    <group-alias>Anyconnect</group-alias>
    <config-hash>1584128676139</config-hash>
    </opaque>
    <auth id="main">
    <title>Login</title>
    <message>Please complete the authentication process in the AnyConnect Login window.</message>
    <banner></banner>
    <sso-v2-login>https://SERVER_NAME/+CSCOE+/saml/sp/login?tgname=standard-group&#x26;acsamlcap=v2</sso-v2-login>
    <sso-v2-login-final>https://SERVER_NAME/+CSCOE+/saml_ac_login.html</sso-v2-login-final>
    <sso-v2-token-cookie-name>acSamlv2Token</sso-v2-token-cookie-name>
    <sso-v2-error-cookie-name>acSamlv2Error</sso-v2-error-cookie-name>
    <form>
    <input type="sso" name="sso-token"></input>
    <select name="group_list" label="GROUP:">
    <option selected="true">Anyconnect</option>
    </select>
    </form>
    </auth>
    </config-auth>

If either X-AnyConnect-Platform or X-Support-HTTP-Auth is present, then
this response is not received, and a 302 redirect to the "standard" auth
flow is given.  However, this auth flow does not work on my VPN server;
presumably it is administratively disabled.

Once you get the above response, you can open the URL from sso-v2-login
in an HTML viewer and complete the steps.  This requires an openconnect
front-end that supports the open_webview callback.  Eventually you end
up at the URL from sso-v2-login-final, and then you can extract the
cookie with the name in sso-v2-token-cookie-name (acSamlv2Token, in this
case).

Neither samlwebcookie nor openconnect-sso, mentioned in #84, worked for
me.

Signed-off-by: Steven Walter <steven@stevenwalter.org>
Co-authored-by: Luca Boccassi <luca.boccassi@microsoft.com>
auth.c
library.c
openconnect-internal.h
openconnect.h

diff --git a/auth.c b/auth.c
index f76b4b80bc0b3585a608fa31ec9c85fe7ec3b34f..e5d975c56bebfebda3173402db6cb6599e3d7b2d 100644 (file)
--- a/auth.c
+++ b/auth.c
@@ -227,6 +227,8 @@ static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *for
                        opt->_value = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
                } else if (!strcmp(input_type, "text")) {
                        opt->type = OC_FORM_OPT_TEXT;
+               } else if (!strcmp(input_type, "sso")) {
+                       opt->type = OC_FORM_OPT_SSO;
                } else if (!strcmp(input_type, "password")) {
                        if (!cstp_can_gen_tokencode(vpninfo, form, opt))
                                opt->type = OC_FORM_OPT_TOKEN;
@@ -422,6 +424,10 @@ static int parse_auth_node(struct openconnect_info *vpninfo, xmlNode *xml_node,
                xmlnode_get_text(xml_node, "banner", &form->banner);
                xmlnode_get_text(xml_node, "message", &form->message);
                xmlnode_get_text(xml_node, "error", &form->error);
+               xmlnode_get_text(xml_node, "sso-v2-login", &vpninfo->sso_login);
+               xmlnode_get_text(xml_node, "sso-v2-login-final", &vpninfo->sso_login_final);
+               xmlnode_get_text(xml_node, "sso-v2-token-cookie-name", &vpninfo->sso_token_cookie);
+               xmlnode_get_text(xml_node, "sso-v2-error-cookie-name", &vpninfo->sso_error_cookie);
 
                if (xmlnode_is_named(xml_node, "form")) {
 
@@ -753,7 +759,7 @@ static xmlDocPtr xmlpost_new_query(struct openconnect_info *vpninfo, const char
                                   xmlNodePtr *rootp)
 {
        xmlDocPtr doc;
-       xmlNodePtr root, node;
+       xmlNodePtr root, node, capabilities;
 
        doc = xmlNewDoc(XCAST("1.0"));
        if (!doc)
@@ -768,6 +774,8 @@ static xmlDocPtr xmlpost_new_query(struct openconnect_info *vpninfo, const char
                goto bad;
        if (!xmlNewProp(root, XCAST("type"), XCAST(type)))
                goto bad;
+       if (!xmlNewProp(root, XCAST("aggregate-auth-version"), XCAST("2")))
+               goto bad;
 
        node = xmlNewTextChild(root, NULL, XCAST("version"),
                               XCAST(vpninfo->version_string ? : openconnect_version_str));
@@ -786,6 +794,21 @@ static xmlDocPtr xmlpost_new_query(struct openconnect_info *vpninfo, const char
                        goto bad;
        }
 
+       capabilities = xmlNewNode(NULL, XCAST("capabilities"));
+       if (!capabilities)
+               goto bad;
+       capabilities = xmlAddChild(root, capabilities);
+       if (!capabilities)
+               goto bad;
+
+       node = xmlNewTextChild(capabilities, NULL, XCAST("auth-method"), XCAST("single-sign-on"));
+       if (!node)
+               goto bad;
+
+       node = xmlNewTextChild(capabilities, NULL, XCAST("auth-method"), XCAST("single-sign-on-v2"));
+       if (!node)
+               goto bad;
+
        *rootp = root;
        return doc;
 
index 77e4712656b964072ac421cec9b987f702871e39..9e21cab92293a22c9d1c1e8e015eef935de2b69f 100644 (file)
--- a/library.c
+++ b/library.c
@@ -1572,6 +1572,13 @@ retry:
                int second_auth = opt->flags & OC_FORM_OPT_SECOND_AUTH;
                opt->flags &= ~OC_FORM_OPT_IGNORE;
 
+               if (opt->type == OC_FORM_OPT_SSO && vpninfo->open_webview) {
+                   vpninfo->sso_cookie_value = NULL;
+                   vpninfo->open_webview(vpninfo, vpninfo->sso_login, NULL);
+                   opt->_value = vpninfo->sso_cookie_value;
+                   vpninfo->sso_cookie_value = NULL;
+               }
+
                if (!auth_choice ||
                    (opt->type != OC_FORM_OPT_TEXT && opt->type != OC_FORM_OPT_PASSWORD))
                        continue;
@@ -1610,11 +1617,28 @@ retry:
 void openconnect_set_webview_callback(struct openconnect_info *vpninfo,
                                      openconnect_open_webview_vfn webview_fn)
 {
-       return;
+       vpninfo->open_webview = webview_fn;
+       vpninfo->try_http_auth = 0;
 }
 
 int openconnect_webview_load_changed(struct openconnect_info *vpninfo,
                                      const struct oc_webview_result *result)
 {
-       return -EOPNOTSUPP;
+    int i;
+
+    // If we're not at the final URI, tell the webview to keep going
+    if (strcmp(result->uri, vpninfo->sso_login_final)) {
+        return 1;
+    }
+
+    for (i=0; result->cookies[i] != NULL; i+=2) {
+        if (!strcmp(vpninfo->sso_token_cookie, result->cookies[i]))
+        {
+            vpninfo->sso_cookie_value = strdup(result->cookies[i+1]);
+            break;
+        }
+    }
+
+    // Tell the webview to terminate
+    return 0;
 }
index 5db87638854d240d663f10ecb55767dc4d859977..8b644cbe4dbbd816614342530587e004fd122305 100644 (file)
@@ -762,10 +762,17 @@ struct openconnect_info {
                DELAY_CLOSE_IMMEDIATE_CALLBACK,
        } delay_close;                          /* Delay close of mainloop */
 
+       char *sso_login;
+       char *sso_login_final;
+       char *sso_token_cookie;
+       char *sso_error_cookie;
+       char *sso_cookie_value;
+
        int verbose;
        void *cbdata;
        openconnect_validate_peer_cert_vfn validate_peer_cert;
        openconnect_write_new_config_vfn write_new_config;
+       openconnect_open_webview_vfn open_webview;
        openconnect_process_auth_form_vfn process_auth_form;
        openconnect_progress_vfn progress;
        openconnect_protect_socket_vfn protect_socket;
index 5abb05128be439be8e8cf8a6f78964c44e77c807..168400dd62988f4309a060f07b6a97354550c29a 100644 (file)
@@ -214,6 +214,7 @@ struct oc_vpn_proto {
 #define OC_FORM_OPT_SELECT     3
 #define OC_FORM_OPT_HIDDEN     4
 #define OC_FORM_OPT_TOKEN      5
+#define OC_FORM_OPT_SSO        6
 
 #define OC_FORM_RESULT_ERR             -1
 #define OC_FORM_RESULT_OK              0