Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand OpenVPN data key usage #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 101 additions & 36 deletions openvpn-wire-protocol.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1187,15 +1187,27 @@ 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 renegotiation happens, the new TLS session will use
the current key-id increased by one. When the renegotiation
succeeds, the key-id of the renegotion will also be used for all
data channel packets and further control channel packet that
belong/derive from this TLS session. If a renegotiation fails
and is reattempted, the key-id from the failed attempt will be
reused for a new renegotiation attempt.
Only the intial TLS session will use the key-id 0. If the key-id
reaches 7, the following key-id is 1 when increasing the key-id.
</t>
</section>

Expand Down Expand Up @@ -1533,15 +1545,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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if renegotiation_time is 1? :D Is this then half a second? I ask because I use --reneg-sec 1 in a test setup, and I am curious if sub second values are used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the implementation becomes broken and directly uses the new key since 1/2 = 0 in integer math:

From ssl.c:

static int
auth_deferred_expire_window(const struct tls_options *o)
{
    int ret = o->handshake_window;
    const int r2 = o->renegotiate_seconds / 2;

    if (o->renegotiate_seconds && r2 < ret)
    {
        ret = r2;
    }
    return ret;
}

I didn't want to include this that are more implementation bugs in corner cases into the RFC.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that makes sense. Thanks for the code snippet. I'll modify my test setup!

</artwork>
set by the --hand-window and --reneg-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 +1847,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