In this section, we discuss and analyze the security properties of the rTLS protocol extension. We formally define the intruder model, and then present a formal model of the rTLS protocol extension itself. Various security properties are automatically verified by the OFMC software, thereby giving us high certainty that they hold for the protocol, as well.
4.1. Formal Verification
Now, we present a formalization and verification in OFMC [
7], a tool for formal verification of security protocols. It uses a symbolic Dolev-Yao-style model of cryptography, i.e., messages are represented in a term algebra where the algebraic properties of operators are represented (e.g., the properties of exponentiation needed for Diffie-Hellman). It formalizes a state-transition system through multi-set rewriting rules, and the main technique is a constraint-based representation of the intruder, dubbed the lazy intruder, which allow for verification without bounding the number of steps that the intruder can perform. However, the steps that the honest participants can perform needs to be bounded (or the tool will not terminate, in general). This choice of formal analysis software is motivated by the fact that most tools, such as ProVerif and Tamarin, run into problems with the ratchets since in an unbounded number of sessions, this creates structures for which the usual abstractions and bounding lemmata fail, but they do work in OFMC due to the bounds, allowing us to express the ratchets without problems. There are several input languages for OFMC, the native one being the AVISPA Intermediate Format IF [
15] based on set-rewriting (similar to the input language of Tamarin). This can be considered kind-of a “protocol assembly language”, i.e., it is hard to write by hand. The high-level languages available are Alice-and-Bob-style language AnB, but this language is too limited to express ratchets. There is also the AVISPA [
16] High-Level Protocol Specification Language HLPSL [
17] and its successor ASLan from the AVANTSSAR project [
18]. Both languages would be suitable for our purposes, but the updating of local states that we have to perform make them not much more easy for the specification than IF, so we directly relied on IF for an initial formal verification [
19]. We have, however, inspired by this work, developed a more high-level notation for protocols of this style and are currently working on a general compiler from this notation to IF to benefit in similar projects from it. We will use this high-level notation in the following presentation to explain our formal model.
4.1.1. Intruder Model
We define two roles, Client and Server. Each role can, in principle, be instantiated arbitrarily often by any number of clients and servers. We need to limit this for OFMC to two sessions, albeit symbolic ones, meaning that the name of the client and the server is a variable where the intruder can determine who is playing. Thus we include at all kinds of two-session scenarios, e.g., an honest Alice as client with the intruder as server in parallel with a session between honest Alice and Bob as client and server. Note that the intruder can play any of the roles under his real name, where he has access to appropriate initial key material shared with a client or server; the payload messages exchanged in such a session are of course not secret. To allow the intruder to participate as a “normal” agent is essential to capture attacks where an intruder is, for instance, a dishonest server contacted by an honest Alice, and uses part of the messages from this session to attack another session, as in the famous Needham-Schroeder PublicKey Protocol (NSPK) attack [
20].
In the style of the Dolev-Yao intruder model, the intruder also controls the network, i.e., every message an honest agent sends goes to the intruder, and every message an honest agent receives comes from the intruder. The intruder can perform normal cryptographic operations with keys he knows, just as any other agent.
The starting point is that a Client and Server have successfully established a secure TLS 1.3 session in the past and, thus, share a resumption master secret; moreover, the Client has obtained a session ticket containing a DH public key, as well as connection ID from the Server.
4.1.2. Resumption Handshake Model
Next, we present a detailed model of the resumption handshake protocol. This is effectively the standard TLS 1.3 0-RTT resumption protocol, with early data protected through a rotating (ratcheted) key.
First, every session of an agent is characterized by a number of state variables that are updated during the course of the session. These are shown in
Table 2. Both share the same resumption master secret (
RES_MASTER_SECRET) and connection ID (
CONN_ID). In OFMC, we model this by a secret function
resMasterSecret(C, S, CONN_ID) that, for a given client name, servername, and connection ID, returns a unique strong key; the intruder is given all keys where he is
C or
S. The root key
RK is derived from
RES_MASTER_SECRET. Note that
CONN_ID is simply a unique identifier.
Both the Client and Server store the latest DH public key received from the other side as ServerDHsPub and ClientDHsPub, respectively. They also store their own latest DH private key currPrivate. Because the Client has received a DH public key from the server during the first session in a NewSessionTicket, we assume that ServerDHsPub and the Server’s currPrivate are initially populated. In OFMC, we model the initial private key of the server again with a private function secret_exponent(S, CONN_ID) for the server (known to the intruder whenever ).
Finally, the Client needs to store its sending chain and the server needs to store its receiving chain. These consist of a chain key and a chain index, which are defined as ClientCKs and ClientNs for the Client, and ServerCKs and ServerNs for the server. However, these do not need to be initialized at the start. The Client will compute its private key before transmitting the first resumption message.
Similar to the session bounding in OFMC, we also need to bound how many ratchet turns each agent can make in each session. Again we have to limit ourselves to a quite low bound of 2 repetitions, but this should cover all likely scenarios. As a modeling trick, we just initialize both counters with 2, and, in each resumption, we decrease until it is 0.
4.1.3. Step 1: ClientHello
Now, we use the state variables to construct a detailed description of an execution of the 0-RTT protocol. Note that all steps come in two variants: with a new DH key exchange and without. In the OFMC implementation, the client can choose which variant. We describe only the variant in detail that does the DH key exchange, and we only mention the difference when no DH key exchange is done.
If a DH key exchange is to be included in the handshake, then, the first action is that the Client generates a new private key, as well as a shared DH key, together with the Server’s DH public key. When the Client has computed this DH secret, it passes the key into its inner ratchet, by applying the KDF function on the DH secret combined with a root key , and, finally, obtains the ClientCKs:
new currPrivate
RK := bkdf(RK,exp(ServerDHsPub,currPrivate))
ClientCKs := kdf(RK)
where we use and to model the corresponding key derivation functions.
Now, we can describe the initial message sent from a Client. First, it spins its ratchet and increases
ClientNs by one (i.e., actually in the OFMC model, decreases, if not yet zero). The key generated through this is used as input material for the Early Secret in the TLS key schedule. We focus only on the relevant parts of a resumption
ClientHello message, specifically, the early data itself (
MOUT, TLS session ID, client randomness and the relevant resumption parameters. The keys
, the
client_early_traffic_secret and
, the
binder_key are derived from the Early traffic secret as can be seen in
Figure 3. At this point, it is important to note that since the Client can choose to include an optional
key_share extension (DH handshake) in the ClientHello, the inclusion of
ClientDHsPub in the resumption handshake is also optional. The early data is encrypted with
client_early_traffic_secret. Additionally, the plaintext data is integrity protected through a MAC with key
binder_key. Both keys are derived from the master key conform the TLS standard:
let MSG1=step0(ClientNs,exp(g,currPrivate),CHR)
let K1=hkdf(ClientCKs,MSG1)
let K2=hkdf(ClientCKs,pair(exp(ServerDHsPub,currPrivate),pair(C,S)))
send(step1(scrypt(K2,MOUT),hmac(K1,MSG1),MSG1))
Here, and are message formats that represent how the cleartext data is serialized (i.e., every agent, including the intruder, can compose and decompose such messages without any keys). is another key-derivation function, stands for pure string concatenation, and stands for symmetric encryption of message m with key k, and stands for a hash-mac with key k of message m.
When the Server receives a ClientHello with early data indication, it first has to spin its inner ratchet to derive an early_secret identical to that of the Client. Included in this step is the incrementing of ServerNr. The Server can then derive the keys necessary to authenticate and decrypt the received early data. After this point, the Server proceeds differently based on whether the key_share extension was included by the Client. If the extension was not included, the Server continues using the current chain for future resumptions and can simply continue the current handshake as usual. If the extension was included, the Server will have to spin its DH ratchet, as well, which, in turn, leads to an update of the Server’s receiving chain root key. Note that this new DH secret is not just for future sessions and is already used in the remainder of this handshake as it normally would be in a TLS session, as we explain in the next paragraph.
In the high-level notation, we have:
receive (step1(SM2,HM1,M1))
try step0(SN,ClientDHsPub,CHR)==M1
let DH = exp(ClientDHsPub,currPrivate)
RK := bkdf(RK,DH)
ServerCKr := kdf(RK)
let K1=hkdf(ServerCKr,M1)
try HM1==hmac(K1,M1)
let K2=hkdf(ServerCKr,pair(DH,pair(C,S)))
try MIN==dscrypt(K2,SM2) style=beautIF
Note that the try is used to describe operations that might fail, such as trying to decrypt, parse, or check for equality. When it fails, the agent simply does aborts the transaction and rolls back to the state before the transaction. In particular, the first try in the above code snippet parses the received message M1 as the step0 format, extracting the three components of the message. The next try is checking that the received hmac HM1 is the same as constructing hmac(K1,M1), and the last try is trying to decrypt the message SM2. Note that we assume here symmetric encryption with MACs that tells us if decryption succeeded. Observe the contrast to the let x=t command, which simply means replacing all further occurrences of x with t, and the x:=t command, which means that the state variable x is set to t.
4.1.4. Step 2: ServerHello
Next, the Server will reply with a ServerHello message. If the Client included a key_share extension, then the server will reply with its newly generated DH public key from the previous step. Before the response can be sent, the Server has to compute all the remaining keys from the TLS key schedule. This starts with computing the handshake_secret. The KDF function for this secret takes two inputs, one being the hash product of the previous phase in the key schedule, and another being fresh Input Key Material (IKM). If a DH handshake occurred, then the resulting DH secret should be used as IKM here. If no DH handshake occurred, the IKM is simply set to 0. The ServerHello response itself includes a number of fields which are not relevant for our verification, so we leave them out. We do include EncryptedExtensions () as a representative message payload and the contents of the Finished message type, which has a field verify_data, containing an HMAC of the handshake context. This HMAC protects the integrity of ServerDHsPub and Server_rand, as well; therefore, we add these to the encrypted payload, while leaving other parts out to keep the model concise. We can do this, as the HMAC key is directly derived from the server_handshake_traffic_secret. We include Server_rand, as this is 32 bytes or randomness that is used for various cryptographic purposes and acts as a nonce. Finally, the Server has the opportunity to already send application data (App_Data) with its response.
Different parts of the transmission are encrypted with different keys derived from the master secret conform the TLS standard. The remainder of the handshake, i.e., most of the ServerHello message is encrypted with the server_handshake_traffic_secret. If the Server chooses to include a response payload, then this optional response can already be encrypted with the server_application_traffic_secret.
new currPrivate
new SHR
let DH = exp(ClientDHsPub,DHs)
RK := bkdf(RK,DH)
ServerCKr := kdf(RK)
let K2=serverK(hkdf(DH,pair(ServerCKr,pair(CHR,pair(C,S)))))
let MSG2=scrypt(K2,pair(exp(g,DHs),SHR))
let K1=serverK(hkdf(DH,pair(ServerCKr,pair(SHR,pair(CHR,pair(C,S))))))
let MSG1=scrypt(K1,MOUT)
send (step2(MSG1,MSG2,SHR,exp(g,DHs)))
When the Client receives the Server’s ServerHello, it first has to continue with its own execution of the TLS key schedule. If the Client initiated with a new DH public key and, thus, a key_share extension, the server replied with a fresh DH public key in its own key_share. This is then used by the Client as input for the handshake secret identically to how the server processed the DH secret. With this, the Client can continue the TLS key schedule until all keys are derived. Note that, for both the Server and Client, the newly computed Resumption Master Secret is assigned to the inner chain’s root key, but not necessarily included in the current chain; if no DH handshake was included, the inner chain is not reset. This does not matter, as no new entropy was introduced during the handshake either way. As is evident from the description of the operations of the rTLS resumption process given in this section, the optional DH exchanges feed into the TLS keyschedule and provide new entropy that gets propagated through to the inner chains and as a result future executions of the key schedule.
receive (step2(M1,M2,SHR,ExpgDHs))
let DH=exp(ExpgDHs,currPrivate)
ServerDHsPub :=ExpgDHs
RK :=bkdf(RK,DH)
ClientCKs:=kdf(RK)
ClientNs :=s(ClientNs)
let K2=serverK(hkdf(DH,pair(ClientCKs,pair(CHR,pair(C,S)))))
try pair(ExpgDHs,SHR)==dscrypt(K2,M2)
let K1=serverK(hkdf(DH,pair(ClientCKs,pair(SHR,pair(CHR,pair(C,S))))))
try MIN==dscrypt(K1,M1)
4.1.5. Step 3: Finished
The Client finishes the 0-RTT handshake with an EndOfEarlyData message and a Finished message. The EndOfEarlyData message is simply an indicator that the Client has no more early data to transmit and that all future data will be encrypted with the client_application_traffic_secret.
let TMP=pair(pair(C,S),pair(ExpgDHs,pair(CHR,SHR)))
let K3=clientK(hkdf(DH,pair(ClientCKs,TMP)))
send (scrypt(K3,MOUT))
4.1.6. Verification
We verify a number of security goals, the first of which is secrecy. We want the early data, i.e.,
MOUT/
MIN payloads, to be secret between Client and Server. The second security goal we verify is injective agreement [
21]. This means that, when an honest party
B receives a payload message apparently from
A, then, either
A is the intruder under his real name (no authentication guarantees) or
A indeed sent that payload message for
B (and they agree on all roles). Moreover, this is injective in the sense that
B does not accept the same payload more often than it was sent by
A, so there is no replay.
Using OFMC, we verify the described properties to hold for the rTLS resumption protocol. Due to an exponential increase of the search spaces with the number of sessions and resumptions, we bounded the number of sessions to 2, and the number of resumptions in each session also to 2. Note, however, that we have here symbolic sessions, i.e., they can be arbitrarily instantiated, including with the intruder as a client or server. Moreover, in each session and resumption, the client can decide to either perform a new DH key or not. We also extensively tested the specification, namely that all expected steps could be taken, in particular that honest agents can communicate, and the intruder can play each of its roles under his real name as a normal participant.
OFMC reported that no attacks were found in any runs which gives a high assurance that the rTLS session resumption protocol provides secrecy and injective agreement: While this is only proved for 2 sessions and with 2 resumptions each, it seems unlikely that further sessions and resumptions would allow for additional attacks because of the symmetry of all further repetitions.
Finally, we want to look at the so-called selfie attack [
22]: this is an attack that works on some pre-shared-key deployments of TLS 1.3, where a client
C and server
S use the same pre-shared key
in both directions of communication, allowing for reflection attacks. Similarly, if we allow in our rTLS model:
then we still do not get a selfie-attack because the setup of the Diffie-Hellman ratchet is different for client and server role. This is, however, looking only at the initial state of the resumption handshake rTLS, not at the preceding steps of the original TLS. This means that, if the setup of TLS is such that it does not allow for a selfie attack, then, by construction, rTLS cannot induce a selfie attack either.