Daniel Lenski [Mon, 22 Feb 2021 02:44:22 +0000 (18:44 -0800)]
make F5 and Fortinet tests go through config-pulling (up to the point of tunnel connection), rather than stopping after authentication
The fake servers don't actually implement the tunnel, but they do implement
the pre-tunnel configuration endpoints, and then simulate a "cookie rejected"
response upon tunnel connection.
OpenConnect distinguishes “cookie rejected” errors via an exit status of 2,
whereas any other failure results in an exit status of 1.
Daniel Lenski [Sun, 21 Feb 2021 23:02:44 +0000 (15:02 -0800)]
add auth-f5 tests
This tests OpenConnect's ability to authenticate with a (fake) F5
server, using username+password (the only option that OpenConnect
currently supports).
The fake F5 authentication server requires python3 and Flask.
Daniel Lenski [Sun, 21 Feb 2021 23:02:44 +0000 (15:02 -0800)]
add auth-fortinet tests
This tests OpenConnect's ability to authenticate with a (fake) Fortinet
server, using all of the options that OpenConnect currently supports
(username+password, username+password+token, non-default realm).
The fake Fortinet authentication server requires Python 3.6 and Flask.
Daniel Lenski [Fri, 19 Feb 2021 04:14:00 +0000 (20:14 -0800)]
Fortinet: parse <split-dns> domains and DNS servers from config
Chimped config containing these settings from
https://github.com/adrienverge/openfortivpn/issues/824#issuecomment-764641406.
This doesn't actually *do* anything with the settings yet.
See https://github.com/dlenski/openconnect/issues/151 and
https://gitlab.com/openconnect/openconnect/-/merge_requests/132 for
discussion about split-DNS.
Daniel Lenski [Thu, 18 Feb 2021 03:27:10 +0000 (19:27 -0800)]
attempt to implement Fortinet challenge-based 2FA (ping #225)
2FA involves reading a bunch of values from the initial response and
parroting them back, along with changing the 'credential' field to 'code'.
This is based on Openfortivpn's implementation
(https://github.com/adrienverge/openfortivpn/blob/master/src/http.c#L711-L754),
and has been tested for basic correctness against a toy HTTPS server that
gives responses with the right format.
Still to do:
- Another one-time password mode: https://github.com/adrienverge/openfortivpn/blob/master/src/http.c#L672-L679, https://github.com/adrienverge/openfortivpn/commit/61dd4fcbbfe1dd89b95081e7d5f5c3e933d7897a
- There's a mobile-app-based "push" mode: https://github.com/adrienverge/openfortivpn/commit/2f16d964560d9b00acbf7bfc7131a0ac40c688f2
Daniel Lenski [Mon, 8 Feb 2021 01:49:10 +0000 (17:49 -0800)]
implement fortinet_obtain_cookie
Fortinet uses HTML-based login forms, similar to Juniper's, resulting in an SVPNCOOKIE.
Believe it or not, they're even more badly designed than Juniper's forms:
1. Inconsistent use of HTTP redirects ('Location' header) vs. Javascript-only redirects
2. Significant <input> fields that aren't actually nested within the <form> tag.
3. Misleading HTTP 405 status ("Method Not Allowed") after incorrect credentials are entered.
4. No apparent way to determine the allowed values for the realm field.
(Though various articles floating around suggest it comes from the URL-path entry point,
from which I assume it's triggered by JavaScript.)
Daniel Lenski [Thu, 4 Feb 2021 19:31:20 +0000 (11:31 -0800)]
Fortinet: server rejects asyncmap and header compression options
This appears to be a "feature" of all Fortinet servers, not just the one I have
access to. Openfortivpn calls pppd with the 'noaccomp nopfcomp default-asyncmap'
options: https://github.com/adrienverge/openfortivpn/blob/ba44ce1/src/tunnel.c#L233-L245
We should avoid offering these options to save an unnecessary round-trip in the
LCP stage of PPP configuration.
Don't blame me. I didn't design this.
For that matter, we don't need to include the asyncmap option with *any*
encapsulation that doesn't use HDLC.
Daniel Lenski [Wed, 13 May 2020 21:29:41 +0000 (14:29 -0700)]
add test-fortinet-login.py
Often easier to prototype HTTPS-based authentication flows in Python, since
they're so fiddly and arbitary. So I copied `test-f5-login.py` to
`test-fortinet-login.py`. Currently only handles basic
username-and-password auth, no 2FA:
positional arguments:
endpoint Fortinet server (or complete URL, e.g.
https://forti.vpn.com/remote/login)
extra Extra field to pass to include in the login query
string (e.g. "foo=bar")
optional arguments:
-h, --help show this help message and exit
-v, --verbose
--no-verify Ignore invalid server certificate
Login credentials:
-u USERNAME, --username USERNAME
Username (will prompt if unspecified)
-p PASSWORD, --password PASSWORD
Password (will prompt if unspecified)
-r REALM, --realm REALM
Realm (empty if unspecified)
-c CERT, --cert CERT PEM file containing client certificate (and optionally
private key)
--key KEY PEM file containing client private key (if not
included in same file as certificate)
```
Daniel Lenski [Mon, 8 Feb 2021 20:43:07 +0000 (12:43 -0800)]
F5: implement f5_obtain_cookie
Like Fortinet, F5's authentication interface is very Javascript-heavy.
Unlike with Fortinet, I didn't even both to try to parse and "follow" HTML forms.
For now, we just create a static form (with username and password fields),
ask the user to fill it out, submit it, and attempt to detect successful login
by a length-0 response including F5_ST and MRHSession cookies.
The F5_ST cookie appears to be a reliable(?) indicator of successful login.
It's a "session timeout" cookie (https://support.f5.com/csp/article/K15387),
which looks like '1z1z1z1612808487z604800'. The 4th field appears to be the
Unix epoch timestamp of session activation, and the 5th appears to be the
duration in seconds until it expires.
Daniel Lenski [Mon, 29 Mar 2021 02:55:18 +0000 (19:55 -0700)]
ppp-over-tls tests: fix PPP-over-IPv6 tests on Ubuntu
For reasons that are unclear, but probably also unimportant, IPv6 is disabled by default on this CI
image (verified in https://gitlab.com/openconnect/openconnect/-/jobs/1135199323#L335), and this will
cause PPP tests using IPv6 to fail.
Explicitly enabling IPv6 with sysctl resolves this.
Daniel Lenski [Fri, 26 Feb 2021 02:39:45 +0000 (18:39 -0800)]
ppp-over-tls tests: give up on CentOS 6
We should be able to --enable-ppp-tests on CentOS 6, but they simply aren't working.
For reasons that are not clear, OpenConnect fails to handshake a TLS
connection to socat 1.7.2 (from CentOS 6 EPEL), and I don't want to install
an 11-year-old distribution on a VM just to figure out why.
Some CI is still failing because pppd can't successfully execute the
/etc/ppp/* scripts after configuring the interfaces. Let's just move these
out of the way so that pppd won't try to execute them.
Side rant: pppd is the most appallingly bad program in terms of separation
of concerns.
Daniel Lenski [Wed, 24 Feb 2021 05:03:47 +0000 (21:03 -0800)]
ppp-over-tls tests: try to keep CentOS 6 CI working, and improve flaky startup of pppd
Even with EPEL, CentOS has an old version of socat which doesn't support the
'rawer' option, so let's use the older 'raw,echo=0' combination to keep it
limping along.
More carefully try to verify that socat and pppd start up and connect to each other:
- Wait for socat to create PTY in 1-second increments, and keep going until PTY
actually exists (up to 15 seconds).
- Wait for ppp to connect to PTY in 1-second increments, and keep going until pppd
creates a "UUCP-style lockfile" for the PTY.
- Log how long it takes for the above process to complete (socat and pppd combined
startup) in the test output.
Daniel Lenski [Tue, 9 Feb 2021 00:08:17 +0000 (16:08 -0800)]
CI: re-enable PPP tests for CentOS7, Fedora, and Ubuntu
Still to-do:
1) Get socat+pppd working in CentOS8 and CentOS6 CI
2) Figure out why PPP tests are so slow (added log retention for 1 week, even on success, in Ubuntu18.04/GnuTLS build)
Daniel Lenski [Tue, 9 Feb 2021 01:07:10 +0000 (17:07 -0800)]
improve ppp-over-tls tests
- Cleanup ugly pppd syntax
- Always set 'nodefaultroute' and negotiate link-local IPv4 addresses
- Don't rely on non-root to cause OpenConnect to terminate
- More tests with HDLC, only one without
Daniel Lenski [Fri, 22 May 2020 03:47:59 +0000 (20:47 -0700)]
add ppp-over-tls tests (with pppd as the reference peer implementation)
These test OpenConnect's ability to communicate with the standard pppd using
PPP-over-TLS, with a variety of PPP negotiation options:
± IPv4
± IPv6
± DNS and NBNS server negotiation
± PPP header protocol/address field compression
± Van Jacobson header compression (always rejected by OpenConnect)
These tests use socat to create TLS socket pairs, connecting `openconnect
--protocol=nullppp` to one end and `pppd` to the other.
I tried and failed to combine socat and pppd invocations, but pppd seemingly
cannot handle being wrapped by libsocket_wrapper.so (nor libuid_wrapper.so;
it must run as root).
`pppd sync` (non-HDLC framing) appears to have trouble reacting to incoming
packets in this configuration, so OpenConnect has to invoke retry timers during
negotiation. This seems to be a bug in pppd, rather than a flaw in OpenConnect's
implementation of PPP. Added logging of the time that each run takes.
Connect OpenConnect to the TLS socket, and watch it negotiate LCP/IPCP/IP6CP with its peer, and reject CCP:
# Add noipv4,noipv6 to cookie to try those
./openconnect --protocol=nullppp --cookie hdlc --servercert=d66b507ae074d03b02eafca40d35f87dd81049d3 --dump localhost:5556
David Woodhouse [Wed, 6 May 2020 21:10:42 +0000 (22:10 +0100)]
add support for PPP-based protocols
This started out as the 'f5' branch, which was then rebased (by DL) onto a
more recent master as 'ppp_rebased'.
This was then squashed down into a single commit, with all of the bits
depending on "real" PPP-based VPN protocols removed, so that only the shell
of generic PPP support remains, including PPP protocol negotation (LCP,
IPCP, IP6CP) and mainloop.
Almost all of this code was done by David Woodhouse and Daniel Lenski in
May 2020, with a few more recent bugfixes by DL in early February 2021.
The remaining shell supports two different encapsulations of PPP:
- PPP_ENCAP_RFC1661: Plain PPP. “Synchronous” in the ’90s-era language,
because the start and end of the frame are known from external means.
In modern instances, this is because they arrive in a complete packet with
a known length from its lower-level encapsulation. (TLS or DTLS records in
our expected use cases.)
This is what `pppd sync` does.
- PPP_ENCAP_RFC1662: “PPP in HDLC-like framing.”
This is what `pppd` without `sync` does.
The following commits will add tests to demonstrate the functionality of
OpenConnect's PPP implementation at the level of this generic encapsulation.
Daniel Lenski [Mon, 8 Feb 2021 19:38:19 +0000 (11:38 -0800)]
factor out internal_split_cookies from auth-juniper.c
This is useful for other protocols that use HTTP cookies for authentication, and may
need a way to handoff >1 cookie from the authentication phase to the connection phase.
Improve it slightly by allowing it to set a "default" HTTP cookie if the
authcookie string doesn't contain '=', since most protocols really only NEED
one cookie for the connection phase to work.
This allows shortcuts (like `openconnect --protocol=nc -C 'foobar'` → 'Cookie: DSID=foobar'
or `openconnect --protocol=fortinet -C 'ABCD123456=='` → 'Cookie: SVPNCOOKIE=ABCD123456==') without limiting
the ability to store multiple cookies if/when useful.
Daniel Lenski [Mon, 11 May 2020 22:34:34 +0000 (15:34 -0700)]
oncp_control_queue → tcp_control_queue
This is a queue for outgoing packets which must be sent over the
TCP-based transport; that is, they cannot be sent over the
UDP-based transport.
This queue was initially used by oNCP protocol for ESP enable/disable
packets, and it is now also used by Pulse. It will likely be used for
control packets by some PPP-based protocols as well.
Renaming it to TCP control queue to emphasize its cross-protocol
nature (cf. https://gitlab.com/openconnect/openconnect/-/merge_requests/151).
David Woodhouse [Sat, 27 Mar 2021 15:00:34 +0000 (15:00 +0000)]
Cast GetVolumeInformationByHandleW to (void *)
The 64-bit Windows build was complaining of pointer type mismatches but
I'm fairly sure it was a false positive:
../ssl.c: In function 'openconnect_passphrase_from_fsid':
../ssl.c:598:9: warning: cast between incompatible function types from 'FARPROC' {aka 'long long int (*)()'} to 'BOOL (*)(void *, WCHAR *, DWORD, DWORD *, DWORD *, DWORD *, WCHAR *, DWORD)' {aka 'int (*)(void *, short unsigned int *, long unsigned int, long unsigned int *, long unsigned int *, long unsigned int *, short unsigned int *, long unsigned int)'} [-Wcast-function-type]
598 | func = (GVIBH)GetProcAddress(kernlib, "GetVolumeInformationByHandleW");
| ^
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
David Woodhouse [Fri, 26 Mar 2021 15:54:39 +0000 (15:54 +0000)]
Fix obsolete-server-crypto in the GnuTLS build not the OpenSSL one.
We're clearing OpenSSL capabilities in the GnuTLS tests to work around
a SoftHSM bug, which is what confused me into putting the XFAIL in the
wrong case.
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
David Woodhouse [Fri, 26 Mar 2021 15:38:16 +0000 (15:38 +0000)]
Add obsolete-server-crypto to XFAIL tests in Fedora package
The Fedora specfile explicitly sets the GnuTLS priority string to
include @OPENCONNECT but the test sets GNUTLS_SYSTEM_PRIORITY_FILE
to point to /dev/null so that stops working. Just XFAIL the test for
now so the builds start working again. More thinking required...
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
David Woodhouse [Fri, 26 Mar 2021 15:34:17 +0000 (15:34 +0000)]
Fix up string handling for ciphersuite_config
Sure it isn't C++ and std::string, but we *have* a method for appending
strings to a dynamic buffer. We don't need this snprintf("%s%s%s%s%s")
nonsense.
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
David Woodhouse [Fri, 26 Mar 2021 14:52:40 +0000 (14:52 +0000)]
Fix pfs test for out-of-tree builds
Originally we generated config files from autoconf, so we had:
• ${srcdir}/tests/configs/test-foo.in
• ${builddir}/tests/configs/test-foo
Later, we wanted to generate files more dynamically at runtime with
different contents, so we added the update_config() function in
tests/common.sh which did its own substitution to a temporary file,
from
• $(srcdir}/tests/configs/test-foo
The pfs and obsolete-server-crypto tests appear to use a broken hybrid
of the two, first creating ${builddir}/tests/configs/test-foo from
autoconf and then attempting to use ${srcdir}/tests/configs/test-foo
at runtime. The latter isn't going to exist if ${srcdir} != ${builddir}.
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
David Woodhouse [Fri, 26 Mar 2021 14:03:10 +0000 (14:03 +0000)]
Drop web page handling
The web site is handled by a cron job; if we wanted to flip that
over to pull from gitlab instead of git.infradead.org it would
be simple enough to it. Let's keep it in the same place for now.
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Daniel Lenski [Tue, 23 Mar 2021 17:55:19 +0000 (10:55 -0700)]
MingW32 builds: generate NSIS installers for Windows
nsiswrapper is a rather dodgy Perl script (looks like it hasn't actually
been updated since ~2009) with one indispensable function: it can
automatically find required DLLs and bundle them into the installer.
1. The `PATH` variable must be set to include DLL locations (why `PATH`?️)
2. It mixes up its stdout and stderr, so `--verbose` can't be used without
`--run`. (Argh…)
3. It doesn't try to normalize the paths of the bundled files, so
`./x` and `$PWD/x` result in different directory structures.
Daniel Lenski [Tue, 16 Mar 2021 21:40:18 +0000 (14:40 -0700)]
cstp: don't send X-AnyConnect-Platform header
1. Cisco AnyConnect 4.8+ no longer sends it, and some newer servers reject
any client which sends it (see #101)… including older versions of Cisco's
own client. (Great job, Cisco 🤦🏻♂️.)
2. We can't find any evidence of older Cisco servers which *do* require this
header to be present in order to authenticate the client.
3. It's redundant. Any server that wants to know the client's platform as
soon as it receives the initial XML POST already has it. (It's in the
<device-id> tag in addition to the header.)
If there actually are any servers that *do* require this header to identify
and authenticate the client/platform, then the `--local-id` mechanism of
!103 is probably the right way to ensure that it is sent.