Skip to content

Commit

Permalink
Expand OpenVPN data key usage
Browse files Browse the repository at this point in the history
  • Loading branch information
schwabe committed Sep 30, 2024
1 parent 07de450 commit 7b1e45d
Showing 1 changed file with 97 additions and 36 deletions.
133 changes: 97 additions & 36 deletions openvpn-wire-protocol.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1187,15 +1187,23 @@ available) to drop revoked keys.)</li>
renegotiation can start using the CONTROL_V1 OPCODE.
</t>
<t>
When the ACK_V1 packet is being sent, the key_id field
MUST be incremented to ensure the connection can still
use the old keys for a shorter time until the transition
has completed. Any following packets need to use the new
key-id until the old key has been removed and the new
key-id replaces it completely. [FIXME/schwabe:THIS IS WRONG)
This session will be negotiated the same as the inital
session up to the point that the control channel is
establish. At this point the control channel of the new
session will replace the control channel of the old channel.
</t>
<t>
[FIXME: Elaborate more on how re-keying happens]
Unlike in the initial TLS session wih the key-id 0, the client
will not send PUSH_REQUEST messages to request server to query
its configuration but continue to use the configuration
negotiated in the initial TLS session.
</t>
<t>
Whenever a new TLS session is renegotiated the key-id is increased
by one. However, only succesful renegotiation will increase the
key-id. If a renegotiation fails and is reattempted, the key-id
will not be increased. Only the intial TLS session will use the key-id
0. If the key-id reaches 7, the following key-id is 1.
</t>
</section>

Expand Down Expand Up @@ -1533,15 +1541,95 @@ struct datakeys {
key_c2s and auth_c2s are used to encrypt/authenticate data from client to server
and key_s2c and auth_s2c are used to encrypt/authenticate from server to client.
</t>
<section title="Data channel key generation">
<t>
Every time a OpenVPN control channel session is succesfully established, a data channel key in the above format is
generated and assigned to the key-id of the control channel.
</t>
<t>
This key structure is normally generated by using <xref target="RFC5705">RFC 5705 key material exporter</xref>
from the Control Channel session with the label <tt>EXPORTER-OpenVPN-datakeys</tt>
and using the 256 bytes length of the structure.
</t>
<t>
When a client or server lack the support for generating data channel keys using TLS Key material
export and do not annoounce the support in IV_CIPHER, the server may reject a client. At the same
time a client that does not get signalled by the server by `key-derivation tls-ekm` or
`protocol-flags tls-ekm` to use TLS Key material can abort the connection.
</t>
<t>
If support for older server or clients is desired that do not support TLS EKM, client and
server can fall back to the old mechanism of using the
<xref target="openvpnprf"> OpenVPN data channel PRF</xref>.
</t>
</section>
<section anchor="openvpnprf" title="OpenVPN data channel PRF">
<t>
This is a deprecated way of deriving the data channel keys. It should be only implemented if
compatibility with older OpenVPN versions is required that do not support key derivation via
the RFC5705 key material exporter. It also requires the MD5 algorithm which often
modern crypto libraries do not readily support anymore.</t>

<t>This uses the <tt>key_random</tt>, <tt>random1</tt>, and <tt>random2</tt> from the key
exchange messages. These will be prefixed with client and server here to indicate from which
packet these field are coming. Also the session id of the control channel will be used here</t>

<t>
Here TLS_PRF(seed, secret) is the TLS PRF function according to RFC 2246 with
MD5 and SHA1 as used in TLS 1.0/1.1.

<figure>
<sourcecode>
seed = "OpenVPN master secret" | client.random1 | server.random1
secret = client.key_random
master_secret - TLS_PRF(seed, secret)

Older clients use the mechanism described in the section OpenVPN data channel
PRF.
key_seed = "OpenVPN key expansion " | client.random2 | server.random2
datakeys = TLS_PRF(key_seed, key_seed)
</sourcecode>
</figure>
</t>
</section>
</section>
<section title="Data channel key rotation">
<t>
Whenever a reneogiation of a key has finshed, a new data channel key is generated.
This new key will consider the pending key. A pending key is considered valid for
decryption of packets with that key-id but will not be used for encryption yet.
</t>
<t>
After a set time period the pending key is promoted to being the active key and
the active key is set to be the retired key (sometimes also referred to as lame
duck key). The retired key will be used only for decryption but no longer for
encryption.
</t>
<t>
In the default configuration of OpenVPN, the time period of promoting a key from
pending to active is calucated as
<artwork>
min(handshake_window, renegotiation_time/2)
</artwork>
set by the --hand-window and --renog-sec options, which default to 60 and 3600
respectively. So the default time to promote a key from pending to active is 60s.
</t>
<t>
This approach to defer using the key with a timeout is done to avoid key
desynchronisation. It allows installing the new keys with enough lead time
that at the point that any side switches from pending to active key, the
other side will have the key already installed as pending key and is able
to decrypt the packets. Likewise, keeping the old key as retired key for
some time after the pending key is switched to active ensures that the
if the peer takes longer to switch the pending key to active key, the
receiver will still be able to decrypt the packets using the retiring key.
</t>
<t>
Should a renegotiation complete before a pending key is promoted to an active
key, the pending key will replace the active key and the new pending key will
be the pending key. Note that that implementations are allowed to
limit themselves just two key slots with either active/pending or active/retired
and in this scenario will drop active key without moving it to retired state first.
</t>
</section>
<section title="Peer-ID">
<t>
The purpose of this feature is to allow a client to float between various client
Expand Down Expand Up @@ -1755,33 +1843,6 @@ struct data_packet_xfb {
<t>For unencrypted data packets the same format as CBC without IV is used.</t>
</t>
</section>
<section title="OpenVPN data channel PRF">
<t>
This is a deprecated way of deriving the data channel keys. It should be only implemented if
compatibility with older OpenVPN versions is required that do not support key derivation via
the RFC5705 key material exporter. It also requires the MD5 algorithm which often
modern crypto libraries do not readily support anymore.</t>

<t>This uses the <tt>key_random</tt>, <tt>random1</tt>, and <tt>random2</tt> from the key
exchange messages. These will be prefixed with client and server here to indicate from which
packet these field are coming. Also the session id of the control channel will be used here</t>

<t>
Here TLS_PRF(seed, secret) is the TLS PRF function according to RFC 2246 with
MD5 and SHA1 as used in TLS 1.0/1.1.

<figure>
<sourcecode>
seed = "OpenVPN master secret" | client.random1 | server.random1
secret = client.key_random
master_secret - TLS_PRF(seed, secret)

key_seed = "OpenVPN key expansion " | client.random2 | server.random2
datakeys = TLS_PRF(key_seed, key_seed)
</sourcecode>
</figure>
</t>
</section>
</section>
<section title="Control channel messages">
<section title="Message format">
Expand Down

0 comments on commit 7b1e45d

Please sign in to comment.