I'm going to present some ideas and patches to make OpenVPN more solid
in the area of versioning. The overall goal is to make OpenVPN more
robust with respect to differences between client and server in OpenVPN
version, TLS version, protocol capabilities, and config file grammar.
Specifically:
1. Allow one OpenVPN config file to be universally used by different
clients on different platforms, even if some of the directives target
capabilities only available in newer OpenVPN versions or specific kinds
of platforms (i.e. mobile).
2. Allow TLS versions higher than 1.0 to be negotiated when both client
and server support them.
3. Improve the flexibility of the OpenVPN protocol negotiation so that
parameters such as cipher and HMAC digest can be dynamically negotiated.
Versioning robustness is also a practical security feature, because it
allows gradual rollout of updated security parameters in a large network
without loss of service. Security parameters might include minimum TLS
version, allowed TLS ciphersuites, and OpenVPN cipher and HMAC digests.
Specific use case examples
--------------------------
(1) OpenVPN protocol negotiation: You are managing an OpenVPN server
configured with the BF-128-CBC cipher. You want to upgrade to
AES-128-CBC to take advantage of hardware AES acceleration on the
server, but not all clients in the field support AES yet. With cipher
negotiation, you could set up a mechanism where the server would
negotiate AES-128-CBC with clients that support it, but downgrade to
BF-128-CBC on clients that don't support either AES or the cipher
negotiation capability itself.
(2) TLS version negotiation: You are managing an OpenVPN network and
want to migrate to TLS 1.2 but still allow older clients to connect with
TLS 1.0 for some period of time until it is phased out.
(3) Config file grammar: You are a developer and want to add a new
OpenVPN config file directive, but don't want older clients to abort
because they don't understand the directive.
Within these use cases are three areas where versioning is an issue, and
I'll discuss each of them separately:
TLS Protocol
------------
Since day 1, OpenVPN has used TLS 1.0 as a control channel and key
exchange mechanism. But now we have TLS 1.1 and 1.2, each of which
addresses significant shortcomings in its predecessor. Fortunately,
SSL/TLS already includes dynamic version negotiation. So I've put
together a patch that leverages on this, by allowing OpenVPN client and
server to negotiate the TLS version and use the highest version
supported by both peers. The patch also adds a new directive
"tls-version-min" that allows either client or server to specify a
minimum required TLS version from the peer.
https://github.com/jamesyonan/openvpn/commit/6ee8faade224cc346d67a7f1716df4012782999a
OpenVPN Protocol
----------------
The OpenVPN protocol did not originally support handshaking, i.e. having
the client and server mutually communicate their capabilities and use
this info to dynamically select protocol parameters such as cipher, HMAC
digest, compression algorithm, etc.
Instead of handshaking, we would manually add compatible directives to
both client and server configs. But this approach is not very flexible,
because in the absence of a handshaked protocol negotiation, every time
a configuration change is made on the server that affects the protocol
(such as cipher type), all of the client configs must also be upgraded
or they will fail to connect.
To improve flexibility, we've gradually increased the amount of
handshaking that occurs in the OpenVPN protocol. For example, with the
recent addition of the Snappy compression algorithm, we now have a
mechanism where the compression algorithm can be dynamically negotiated
between client and server.
The basic mechanism for how this works is as follows:
After the TLS negotiation has succeeded but before the tunnel is active,
the client communicates its capabilities to the server using a list of
key/value pairs sent over the TLS control channel. We will call this
the Client Capabilities List (which itself was introduced in OpenVPN 2.1
and was added to the OpenVPN protocol in a way that older clients would
ignore it).
For example, running an OpenVPN 3.0 client on my mac, the following
key/value pairs are sent to the server.
IV_VER=3.0
IV_PLAT=mac
IV_SNAPPY=1
IV_LZO=1
This tells the server that the client supports both Snappy and LZO
compression protocols. Now that the server knows this, it can choose a
protocol that it also supports, and communicate this back to the client
via a pushed directive:
compress snappy
With this model, the client suggests a list of capabilities that it is
willing to support, and the server makes the final selection from that
list. But this also gives the client some discretion, as it can choose
to veto certain capabilities by not including them in the list sent to
the server. For example, suppose the client end-user chooses to disable
compression. In this case, the client will simply tell the server that
it doesn't support compression, i.e.
IV_VER=3.0
IV_PLAT=mac
IV_LZO_STUB=1
IV_COMP_STUB=1
The "STUB" parameters indicate support for null compression algorithms
that include the compression framing byte, but don't actually compress
any data.
So this basic model of the client sending a key/value list of protocol
capabilities to the server, and the server responding with push
directives to select the actual protocol feature can be a basic building
block for adding additional negotiated features to the OpenVPN protocol.
For example, this model could be used to negotiate other protocol
capabilities such as cipher and HMAC digest:
IV_CIPHER=BF-CBC,AES-128-CBC
IV_AUTH=SHA1,SHA256,SHA512
After receiving this info from the client, the server could choose a
cipher and auth parameter and push it back to the client. Among other
things, this requires that the client supports pushed "cipher" and
"auth" directives (currently only the 3.0 core supports this). There is
also the issue of the size of the Client Capabilities List. The
IV_CIPHER and IV_AUTH lists might grow to be quite long, and the current
Client Capabilities List is limited in size to 2048 bytes based on the
TLS_CHANNEL_BUF_SIZE constant in common.h. To reduce the size, we might
consider:
(a) use a single character designation for ciphers and HMAC digests,
such as:
Cipher Codes:
A : BF-CBC
B : AES-128-CBC
. . .
Then communicate IV_CIPHER using the cipher codes:
IV_CIPHER=AB
This would require that the OpenVPN Project standardize on set of codes
for ciphers and HMAC digests.
(b) remove the 2048 byte limitation on the Client Capabilities List.
The limitation arises because of the packetization of messages over the
OpenVPN control channel. One way to resolve this would be to
standardize on a continuation marker that tells the peer that another
packet of Client Capabilities List data is forthcoming. This would be
similar to how the same problem was solved with the pushed directives
list using continuations.
Rather than choosing either (a) or (b) I would suggest implementing them
both. The number of ciphers and digests is always increasing and using
a one or two-character code to denote a cipher or digest will make the
Client Capabilities List smaller and faster to pass over the wire. It
would also be good to fix (b) since it seems like an arbitrary
limitation that could cause issues as the Client Capabilities List grows.
Config file grammar
-------------------
Originally, OpenVPN was strict about config file directives that it
didn't recognize -- it was a fatal error to specify an unrecognized
directive.
In many cases this is beneficial for security in the sense that a
directive such as ns-cert-type or tls-remote, if not understood by the
client and ignored, could render the connection much less secure than
intended.
But it also reduces flexibility because it makes adding new directives
unnecessarily difficult if the new directive crashes an older client
that doesn't yet recognize it.
One solution that was introduced in OpenVPN 2.1 added a new directive
that would cause any unrecognized directive to be ignored.
setenv FORWARD_COMPATIBLE 1
In hindsight, this is probably too broad. It would be better to allow
individual directives to specify whether older clients that don't
recognize them should ignore them or abort.
I've put together a new (and very small) patch that I believe solves the
problem in a better way.
https://github.com/jamesyonan/openvpn/commit/037690df22a4604a556c9b470d1f5e891991834b
This patch defines a new directive prefix "setenv opt" that if prepended
to an existing directive, will cause it to be ignored by clients that
don't understand it (starting the directive with "setenv" is useful
because OpenVPN clients going back to 2.0 don't really care what comes
after the "setenv", so older clients should simply ignore the prefix).
To look at an example, the TLS versioning patch above adds a new
directive "tls-version-min" to set the minimum allowed TLS version used
by the peer. Example client usage might be:
tls-version-min 1.2
Suppose I want to put this directive in the config files I distribute to
clients, but have it be ignored by older clients that don't recognize
it. I could do this as follows on the client:
setenv opt tls-version-min 1.2
Suppose I also want to handle the case where some clients understand the
tls-version-min directive but are not linked with an SSL library that
supports TLS 1.2. To allow these clients to gracefully degrade to the
highest TLS version they can support, a variation of the directive could
be used on the client:
setenv opt tls-version-min 1.2 or-highest
Now suppose that after some upgrade period, we now want to require that
all clients connect at TLS 1.2 or higher, and that 1.1 and 1.0 are no
longer allowed. This can be done by limiting TLS to 1.2 or higher on
the server:
tls-version-min 1.2
James