Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Improve Fortinet auth
Use the same 'auth_id' values as GlobalProtect uses ('_login' for the normal
username/password form, and '_challenge' for the MFA challenge form). This
is a follow-on to 613fa87.

This also fixes:

- A potential double-free() issue if the challenge form is loaded repeatedly,
  adding a test of 2 rounds of challenge 2FA to verify it.
- Warnings about the unused 'invalid_cookie' label.

Signed-off-by: Daniel Lenski <dlenski@gmail.com>
  • Loading branch information
dlenski committed May 5, 2021
1 parent fc2a10a commit 684f6db
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 6 deletions.
10 changes: 9 additions & 1 deletion fortinet.c
Expand Up @@ -144,7 +144,7 @@ int fortinet_obtain_cookie(struct openconnect_info *vpninfo)
ret = -ENOMEM;
goto out;
}
form->auth_id = strdup("fortinet_auth");
form->auth_id = strdup("_login");
if (!form->auth_id)
goto nomem;
opt = form->opts = calloc(1, sizeof(*opt));
Expand Down Expand Up @@ -219,6 +219,7 @@ int fortinet_obtain_cookie(struct openconnect_info *vpninfo)
opt->type = OC_FORM_OPT_HIDDEN;
free(opt2->label);
free(opt2->_value);
opt2->label = opt2->_value = NULL;

/* Change 'credential' field to 'code'. */
opt2->_value = NULL;
Expand All @@ -229,6 +230,11 @@ int fortinet_obtain_cookie(struct openconnect_info *vpninfo)
else
opt2->type = OC_FORM_OPT_PASSWORD;

/* Change 'auth_id' to '_challenge'. */
free(form->auth_id);
if (!(form->auth_id = strdup("_challenge")))
goto nomem;

/* Save a bunch of values to parrot back */
filter_opts(action_buf, resp_buf, "reqid,polid,grp,portal,peer,magic", 1);
if ((ret = buf_error(action_buf)))
Expand Down Expand Up @@ -564,7 +570,9 @@ static int fortinet_configure(struct openconnect_info *vpninfo)
* XX: See do_https_request() for why ret==0 can only happen
* if there was a successful-but-unfetched redirect.
*/
#if 0
invalid_cookie:
#endif
ret = -EPERM;
goto out;
}
Expand Down
15 changes: 10 additions & 5 deletions tests/fake-fortinet-server.py
Expand Up @@ -34,7 +34,8 @@
# values via a (cookie-based) session.
#
# In order to test with 2FA, the initial 'GET /' request should include
# the query string '?want_2fa=1'.
# the query string '?want_2fa=1'. If >1, multiple rounds of 2FA token entry
# will be required.
########################################

import sys
Expand Down Expand Up @@ -86,7 +87,7 @@ def wrapped(*args, **kwargs):
@app.route('/')
@app.route('/<realm>')
def realm(realm=None):
session.update(step='GET-realm', want_2fa='want_2fa' in request.args)
session.update(step='GET-realm', want_2fa=int(request.args.get('want_2fa', 0)))
# print(session)
if realm:
return redirect(url_for('login', realm=realm))
Expand All @@ -108,8 +109,12 @@ def login():
def logincheck():
want_2fa = session.get('want_2fa')

if (want_2fa and request.form.get('code')):
return complete_2fa()
if want_2fa and request.form.get('username') and request.form.get('code'):
if want_2fa == 1:
return complete_2fa()
else:
session.update(want_2fa=want_2fa - 1)
return send_2fa_challenge()
elif (want_2fa and request.form.get('username') and request.form.get('credential')):
return send_2fa_challenge()
elif (request.form.get('username') and request.form.get('credential')):
Expand Down Expand Up @@ -141,7 +146,7 @@ def send_2fa_challenge():
# print(session)

return ('ret=2,reqid={reqid},polid={polid},grp={grp},portal={portal},magic={magic},'
'tokeninfo=,chal_msg=Please enter your token code'.format(**session),
'tokeninfo=,chal_msg=Please enter your token code ({want_2fa} remaining)'.format(**session),
{'content-type': 'text/plain'})


Expand Down
6 changes: 6 additions & 0 deletions tests/fortinet-auth-and-config
Expand Up @@ -51,6 +51,12 @@ echo -n "Authenticating with username/password/token and DEFAULT path... "
( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=fortinet -q $ADDRESS:443/?want_2fa=1 -u test --token-mode=totp --token-secret=FAKE $FINGERPRINT --cookieonly >/dev/null 2>&1) ||
fail $PID "Could not receive cookie from fake Fortinet server"

ok

echo -n "Authenticating with username/password/(2 round of token) and DEFAULT path... "
( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=fortinet -q $ADDRESS:443/?want_2fa=2 -u test --token-mode=totp --token-secret=FAKE $FINGERPRINT --cookieonly >/dev/null 2>&1) ||
fail $PID "Could not receive cookie from fake Fortinet server"

echo ok

echo -n "Authenticating with username/password/token and NON-DEFAULT path... "
Expand Down

0 comments on commit 684f6db

Please sign in to comment.