Internet-Draft CORE Authenticated Key Exchange (CAKE) August 2025
Schanzenbach & ch3 Expires 5 February 2026 [Page]
Workgroup:
Independent Stream
Internet-Draft:
draft-schanzen-cake-00
Published:
Intended Status:
Informational
Expires:
Authors:
M. Schanzenbach
Fraunhofer AISEC
ch3
GNUnet e.V.

CORE Authenticated Key Exchange (CAKE)

Abstract

This document contains the GNUnet CORE AKE (CAKE).

This document defines the normative wire format of the protocol, cryptographic routines and security considerations for use by implementers.

This specification was developed outside the IETF and does not have IETF consensus. It is published here to inform readers about the function of GNUnet communicators, guide future implementations, and ensure interoperability including with the pre-existing GNUnet implementation.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 5 February 2026.

Table of Contents

1. Introduction

This specification was developed outside the IETF and does not have IETF consensus. It is published here to guide implementers of GNS and to ensure interoperability among implementations.

1.1. Requirements Notation

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

2. Conventions and Terminology

While some of the terminology is explicitly re-defined here, the reader is expected to be familiar with TLS 1.3 ([RFC8446]), DTLS 1.3 ([RFC9147]) and HPKE ([RFC9180]).

inititator:
See client in [RFC9147] Section 2.
responder:
See server in [RFC9147] Section 2.
epoch:
See [RFC9147] Section 2.
IATS:
Initiator Application Traffic Secret Key
RATS:
Responder Application Traffic Secret Key
ES:
Early Secret Key
ETS:
Early Traffic Secret Key
HS:
Handshake Secret Key
MS:
Main Secret Key
ES:
Early Secret Key
IHTS:
Initiator Handshake Secret Key
RHTS:
Responder Handshake Secret Key
H(D):
A 512-bit hash over D. The hash function is TBD (Blake2b or SHA-512).
T(M):
means the transcript as a concatenation of received/sent messages starting from and including the InitiatorHello pk_e until and including M. Note that the transcript refers to everything that is seen on the wire, including potentially encrypted messages or fields and metadata.
'{}K'
indicates encryption with a handshake traffic key K and a modified [RFC8439], the XChaCha20-Poly1305 Authenticated Encryption with Associated Data (AEAD) construction.
'[]K'
indicates encryption with an application traffic key K using the XChaCha20-Poly1305 Authenticated Encryption with Associated Data (AEAD) construction.

3. Design Rationale

The design rationale for CAKE is similar to DTLS 1.3 (cf. Section 3 of [RFC9147]). Except that CAKE does not consider Fragmentation as this is expected to be provided by the transport underlay layer of GNUnet.

4. Protocol Flow

This protocol is heavily inspired by [KEMTLS].

We assume that the peers have semi-static (as opposed to ephemeral) key pairs. Let (pkA,skA) be the key pair of peer PIDA and (pkB,skB) the key pair of peer PIDB.

For any secure handshake protocol, we have to dermine an initiator and a responder in the protocol. We use GNUNET_CRYPTO_hash_cmp to determine which peer is the responder R and which peer the initiator I:


if (GNUNET_CRYPTO_hash_cmp (pk_A, pk_B))
{
  pk_I = pk_A
  pk_R = pk_B
}
else
{
  pk_I = pk_B
  pk_R = pk_A
}

It is possible that the designated initiator does not initiate the handshake. After a pre-determined timeout, the respective other peer may initiate. We assume that the initiator knows pkR (pre-distributed through HELLO, for example).

Below is a swimlane of the protocol messages. On the left and right side of the swimlanes the secrets known to the Initiator and Responder are shown respectively. If a private key of a key pair is known it is implied that the public key is also known. Messages in brackets are optional. Messages in braces are encrypted.

         Initiator                                Responder
sk_I     |                                               | sk_R
sk_e     |                                               |
r_I      |                                               |
pk_R     |                                               |
c_R      |                                               |
ss_e     |                                               |
ss_R     |                                               |
         |                                               |
         |               InitiatorHello:                 |
         |                 r_I                           |
         |                 pk_e                          |
         |                 c_R                           |
         |                 H(pk_R)                       |
         |                 {pk_I,pc_I,svcinfo_I}ETS      |
         +---------------------------------------------->|
         |                                               | r_R
         |                                               | r_I
         |                                               | pk_I
         |                                               | c_R
         |                                               | c_I
         |                                               | c_e
         |                                               | ss_R
         |                                               | ss_I
         |                                               | ss_e
         |               ResponderHello:                 |
         |                 r_R                           |
         |                 c_e                           |
         |                 {c_I,pc_R,svcinfo_R}RHTS      |
         |                 {finished_R}RHTS              |
r_R      |                                               |
c_I      |                                               |
c_e      |                                               |
ss_I     |                                               |
ss_e     |                                               |
         |               InitiatorDone:                  |
         |                 {finished_I}IHTS              |
         |                 [ACK]IATS                     |
         +---------------------------------------------->|
         |                                               |
         |                                               |
         |                 [ACK]RATS                     |
         |<----------------------------------------------+
         |                                               |
         |                                               |
         |               [Application Payload]           |
         |<--------------------------------------------->|
         |                                               |
         v                                               v
Figure 1: Overview over the Handshake Protocol Flow.

Notice how we do not need any acknowledgement messages until after finishedR (after 1 RTT). We use the ACKs in the handshake as explicit key confirmations. The InitiatorHello message is a single flight that is implicitly ack'ed with ResponderHello. ResponderHello is a single flight that is implicitly ack'ed with finishedI. The reason why this works is because CAKE groups the messages in row 3 of Table 1 in Section 5.7 of [RFC9147] into a single message (ResponderHello). Hence the only message that is sent without any expected response (and consequently requiring an explicit ACK) is finishedI (and Heartbeats). pcX are 16 bit fields that indicate the peer class (FIXME peer class section). NI is a nonce generated by the initiator. NR is a nonce generated by the responder.

The Initiator creates the InitiatorHello message which includes the encrypted tuple (pkI,svcinfo_I). The fields are encrypted using a key derived from the ETS according to Figure 1 and Figure 2. The so-called Responder KEM Challenge cR and the nonce rI are computed as:

  1. (ssR,cR) <- Encaps(pkR). Reciver KEM Challenge.
  2. rI <- RandomUInt64(). Initiator nonce.
  3. Encrypt pkI and svcinfoI using ETS (seq=0).

R processes the InitiatorHello as follows:

  1. Verify that the message type is CORE_INITIATOR_HELLO. See Message Header.
  2. Verify that H(pk_R) matches R's pk_R.
  3. (ssR,cR) <- Decaps(skR, cR). Response to Responder KEM Challenge.
  4. (sse,ce) <- Encaps(pke). Ephemeral shared secret.
  5. Generate ETS from Section 5 and decrypt pkI. pkI and svcinfo_I may be processed now.
  6. (ssI,cI) <- Encaps(pkI). Initiator KEM Challenge.
  7. Generate RHTS and RATS from Section 5.

The ETS, the Handshake and Master Secrets are generated according to Figure 2. Note that IATS cannot be derived (yet) at this point. R may now generate its ResponderHello message:

  1. rR <- RandomUInt64(). Responder nonce.
  2. Encrypt svcinfo_R and cI (Initiator KEM Challenge) using RHTS (seq=0).
  3. Create finishedR as per Section 6.
  4. Encrypt finishedR with RHTS (seq=1).
  5. Optionally, R may now already send application data encrypted with RATS.

I processes the message received by R:

  1. Verify that the message type is CORE_Responder_HELLO. See Message Header
  2. sse <- Decaps(ske,ce). Ephemeral shared secret.
  3. Generate IHTS and RHTS from Section 5 and decrypt svcinfo_R, cI and finishedR.
  4. ssI <- Decaps(skI,cI). Response to KEM Challenge
  5. Create finishedR as per Section 6 and check against decrypted payload.
  6. Create finishedI as per Section 6.
  7. Send finishedI message encrypted with the key derived from IHTS (seq=0) to R

At this point we have a secure channel.

Note that for the handshake we do not use epochs or sequence numbers. The reason for this is simple: DTLS uses epoch 0 for plaintext messages. Epoch 1 is reserved for payload encrypted with a key derived from ETS. However, we only have a single message that contains such a payload: InitiatorHello. Epoch 2 is reserved for payload encrypted with a key derived from *HTS. But we only have a single message that contains a payload encrypted with a key derived from RHTS: ResponderHello. We also only have a single message that contains a payload encrypted with a key derived from IHTS: finishedI. Consequently, we do not need any signalling of Epochs until we encrypt data using *ATS secrets. The optional application data that may already be sent by the responder after its first handshake message or by the initiator after its second handshake message, are already wrapped inside an EncryptedMessage and have both Epoch and sequence numbers set.

5. Key Schedule

The key schedule is very similar to [RFC8446] Section 7.1:

HKDF-Extract(ss_R,0) = Early Secret (ES)
         |
         +-----> HKDF-Expand-Label(.,
         |                         "early data",
         |                         H(T(H(pk_R))))
         |       = Early Transport Secret (ETS)
         |
         v
HKDF-Expand-Label(.,
         |        "derived",
         |        "") = derived Early Secret (dES)
         |
         v
HKDF-Extract(ss_e,.) = Handshake Secret (HS)
         |
         +-----> HKDF-Expand-Label(.,
         |                         "i hs traffic",
         |                         H(T(r_R)))
         |       = IHTS
         |
         +-----> HKDF-Expand-Label(.,
         |                         "r hs traffic",
         |                         H(T(r_R)))
         |       = RHTS
         v
HKDF-Expand-Label(.,
         |        "derived",
         |        "") = derived Handshake Secret (dHS)
         |
         v
HKDF-Extract(ss_I,.) = Master Secret (MS)
         |
         +-----> HKDF-Expand-Label(.,
         |                         "i ap traffic",
         |                         H(T({finished_I})))
         |       = IATS_0
         |
         +-----> HKDF-Expand-Label(.,
                                   "r ap traffic",
                                   H(T({finished_R})))
                 = RATS_0
Figure 2: The Key Schedule.

"." shows the argument position of the input variable from the incoming arrow.

In general the transcript hashes are part of the HKDF-Expand calls. The transcript hash is defined as the hash over the message parts sent (or to be sent) and received on the wire up until that point.

Notice that from the very beginning ssR is required for the key schedule. This means that R must be able to solve the Responder KEM Challenge cR. Similarly, the master secret (MS) requires knowledge of ssI. This means that I must be able to solve the Initiator KEM Challenge cI. The KEM Challenges provide the underlying public key authentication mechanism.

When a traffic secret ([I,R][A,H]TS or ETS) is used to encrypt data, the respective encryption key and starting nonce is generated as follows:

key = HKDF-Expand-Label [I,R][A,H]TS, "key", 32)
nonce = HKDF-Expand-Label ([I,R][A,H]TS, "iv", 24)
Figure 3: Traffic Key Generation.

Notice that the per-message nonce is generated from the nonce above as defined in Section 5.3 of [RFC8446] from the sequence number.

After a successful initial handshake, both initiator and responder may update the application traffic secrets ([A,I]ATS) and generate new keys. Let [I,R]ATS0 be the initial secrets with index 0. The next secret is derived as:

[I,R]ATS_N+1 = HKDF-Expand-Label ([I,R]ATS_N,
                                  "traffic_upd",
                                  secret_len)
Figure 4: Traffic Secret Update.

When a peer wants to update keys, it sends a key update message Section 6.10. Implementations SHOULD delete old traffic secrets and their derived keys.

6. The CAKE Handshake Protocol

This section is named and structured to mimic Section 5 of [RFC9147].

6.1. Timeout and Retransmission

CAKE reuses the logic for timeout and retransmission from Section 5.8 of [RFC9147]. It differs in that large flight sizes are not of concern for CAKE. Similarly, the only Post-Handshake message relevant for CAKE is the KeyUpdate message.

6.2. Cryptographic Label Prefix

Section 7.1 of [RFC8446] specifies that HKDF-Expand-Label uses a label prefix of "tls13 ". For CAKE, that label SHALL be "cake10". This ensures key separation between CAKE, DTLS 1.3 and TLS 1.3.

6.3. Services Info Field

The svcinfo field is a string consisting of key-value pairs separated by a separator indicating supported services and their versions. E.g. "dht:1.1;cadet:0.4". The field is zero terminated.

6.4. Finished Field

The HandshakeFinished field contains either finishedI or finishedR value:

  1. fkI <- HKDF-Expand-Label(MS, "i finished", NULL)
  2. finishedI <- HMAC(fkI, H(T({finishedR})))
  1. fkR <- HKDF-Expand-Label(MS, "r finished", NULL)
  2. finishedR <- HMAC(fkR, H(T({svcinfo_R,c_I}))

6.5. CAKE Message Format

Any sent message starts with a MessageHeader:

          0     8     16    24    32    40    48    56
          +-----+-----+-----+-----+-----+-----+-----+-----+
          |         Size          |       Type (0xXX)     |
          +-----+-----+-----+-----+-----+-----+-----+-----+
Figure 5: The Wire Format of the Message Header.
size:
The size of the message including the Size and Type fields.
type:
The message type.

The possible types of messages are:

  • InitiatorHello
  • ResponderHello
  • InitiatorDone
  • EncryptedMessage

An encrypted message also always starts with a MessageHeader and the allowed types are:

  • Hearbeat
  • Ack
  • ApplicationData

6.6. InitiatorHello Message

The InitiatorHello:

          0     8     16    24    32    40    48    56
          +-----+-----+-----+-----+-----+-----+-----+-----+
          |                  r_I                          |
          +-----+-----+-----+-----+-----+-----+-----+-----+
          |                  pk_e                         |
          |                                               |
          |                                               |
          +-----+-----+-----+-----+-----+-----+-----+-----+
          |                  c_R                          |
          |                                               |
          |                                               |
          |                                               |
          +-----+-----+-----+-----+-----+-----+-----+-----+
          |                  H(pk_R) (512 bit)            |
          /                                               /
          |                                               |
          +-----+-----+-----+-----+-----+-----+-----+-----+
          /             {pk_I,pc_I,svcinfo_I}             /
Figure 6: The Wire Format of the InitiatorHello.

The initiator kem challenge cR is generated according to Figure 2 using:

  1. (ssR,cR) <- Encaps(pkR)

The pkI and ServiceInfo are encrypted using XChaCha20-Poly1305 [RFC8439] with key and IV derived from the ETS.

6.7. ResponderHello Message

The ResponderHello:

          0     8     16    24    32    40    48    56
          +-----+-----+-----+-----+-----+-----+-----+-----+
          |                     r_R                       |
          +-----+-----+-----+-----+-----+-----+-----+-----+
          |                     c_e                       |
          |                                               |
          |                                               |
          |                                               |
          +-----+-----+-----+-----+-----+-----+-----+-----+
          /           {c_I,pc_I,svcinfo_R}{finished_R}    /
Figure 7: The Wire Format of the ResponderHello.

The protected fields after the nonce are encrypted using a key derived from AHTS. The finishedR is encrypted individually. This is because the transcript of the ResponderHello to generate the finishedR must end before it.

6.8. InitiatorDone Message

The InitiatorDone message contains the finishedI field encrypted with a key derived from the IHTS. The message type MUST be CORE_INITIATOR_DONE.

6.9. EncryptedMessage

The EncryptedMessage follows a message header with type CORE_ENCRYPTED_MESSAGE:

0     8     16    24    32    40    48    56
+-----+-----+-----+-----+-----+-----+-----+-----+
|                     Epoch                     |
+-----+-----+-----+-----+-----+-----+-----+-----+
|                 Sequence Number               |
+-----+-----+-----+-----+-----+-----+-----+-----+
|                     Tag                       |
|                                               |
+-----+-----+-----+-----+-----+-----+-----+-----+
Figure 8: The Wire Format of the EncryptedMessage header.

The epoch starts at 0 after the handshake with the first *ATS secret. Any peer may at any time update to a new epoch. Peers may keep a history of secrets for respective epochs at their own discretion in order to handle out-of-order message deliveries. Unlike DTLS1.3 in CAKE does not truncate the epoch and no reconstruction is necessary, hence the epoch may be bumped by peers at their own discretion without explicit key update mechanisms. A peer may consider the epoch too old or too far in the future and reject description. For this purpose, a peer may manage a sliding window of epochs that can be used by the other peer.

The sequence number is encrypted as defined in Section 4.2.3 of [RFC9147] for ChaCha20-based AEAD schemes. For clarity, the XOR-based encryption using the 64 byte output of ChaCha20 is as follows: The Tag is divided into a 32-bit counter and 96-bit nonce for use with ChaCha20. The key is derived from the *ats as follows:

sn_secret = HKDF-Expand-Label [I,R]ATS_N, "sn", 32)
Figure 9: Traffic Key Generation.

Where [I,R]ATS_N is the respective ATS secret of epoch N. The leading 8 bytes of the 64 byte output of ChaCha20 are then XORed with the sequence number in network byte order:

sn_key = HKDF-Expand-Label(Secret, "sn", "", 8)
mask = ChaCha20(sn_key, Ciphertext[0..3], Ciphertext[4..15])
sn_enc = mask[0..8] XOR sn_nbo
Figure 10: Traffic Key Generation.

Notice how this requires a ciphertext of at least 16 bytes. But our ciphertexts are always at least 16 bytes due to the Poly1305 atuthentication tag. In fact, since the authentication tag is considered part of the ciphertext, and is prepended in front of the encrypted plaintext, the mask is always computed on the authentication tag only.

The Tag is followed by encrypted application data. The length of the data is included in the size field of the MessageHeader preceeding the EncryptedMessage header.

The per-message nonce is not transmitted and instead generated as defined in Section 5.3 of [RFC8446].

6.10. Heartbeat

The HEARTBEAT message is a simple MessageHeader inside an EncryptedMessage with type CORE_HEARTBEAT followed by an UpdateRequested indicator. This means that for every received EncryptedMessage the peer MUST check if this is a Heartbeat. A Heartbeat message may implicitlyindicate that the sender has switched its traffic secrets according to the key schedule in Section 5. If any bit in the UpdateRequested field is set, this means that the responder of the Heartbeat SHOULD increment its epoch. Any bytes following the UpdateRequested field are updated services info svcinfo (Section 6.3). Services info updates are optional.

0     8     16    24    32
+-----+-----+-----+-----+
|      UpdateRequested  |
+-----+-----+-----+-----+
|        svcinfo        /
/                       /
Figure 11: The Wire Format of the EncryptedMessage header.

6.11. ACK

The ACK message is a simple MessageHeader inside an EncryptedMessage with type CORE_ACK. This means that for every received EncryptedMessage the peer MUST check if this is an ACK message after decryption. The peer may use the ACK in order to ensure liveliness of connected peers in combination with Heartbeats.

7. Open Issues

We must discuss EdDSA vs X25519 KEM usage. Maybe see Communicator draft for this. Basically, since we also use the EdDSA peer ID key for signing (HELLOs), we offer any attacker a signing oracle against our X25519 public keys. This should be safe, but it is not clean. To solve this, we would have to separate HELLO signing keys and PID KX keys. This issue did not arise before, because we used signed semi-statix DH keys.

The IETF ChaCha20 version is basically only recommended for use when a specification or compatibility specifically requires it. With ChaCha20, we would have to increment the nonce as it cannot be chosen securely at random (not long enough). XChaCha20 is the generally recommended cipher for any use case and we use it. The only downside seems to be that XChaCha20 is practically not specified anywhere (although it can be trivially defined in this document based on HChaCha) and only really implemented in libsodium.

We must define which KEM is to be used. We may want to use our HPKE Elligator KEM [LSD0011]. Using Elligator on this level may not be useful unless we also get rid of the plaintext message headers since those definitely do not look random. Ergo, the use of elligator probably makes more sense on the communicator level.

The Initiator/Responder selection logic may require a timed fallback: The designates Initiator may never initiate (NAT, already has sufficient connections, learns about responder later than responder about initiator etc.). This may result in edge cases where the Initiator initiates a handshake and the Responder also initiates a handshake at the same time switching roles. In such cases we may simply do both key exchanges. If both succeed, we drop the key exchange that was not initiated by the designated initiator on both peers. Otherwise we use the successful key exchange and the roles are swapped.

8. Security and Privacy Considerations

TODO

9. GANA Considerations

-

10. Implementation and Deployment Status

The CAKE handshake is currently implemented in a branch. Open tasks include:

  1. Requesting key updates
  2. Use KDF/Hash Function, SHA2 vs Blake2b
  3. Services info transmission (currently empty/unused)
  4. Integrate and test PILS PID changes (potentially requires in-band signalling of new PIDs in CORE, not really that much related to CAKE)

11. Normative References

[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8439]
Nir, Y. and A. Langley, "ChaCha20 and Poly1305 for IETF Protocols", RFC 8439, DOI 10.17487/RFC8439, , <https://www.rfc-editor.org/info/rfc8439>.
[RFC8446]
Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, , <https://www.rfc-editor.org/info/rfc8446>.
[RFC9147]
Rescorla, E., Tschofenig, H., and N. Modadugu, "The Datagram Transport Layer Security (DTLS) Protocol Version 1.3", RFC 9147, DOI 10.17487/RFC9147, , <https://www.rfc-editor.org/info/rfc9147>.
[RFC9180]
Barnes, R., Bhargavan, K., Lipp, B., and C. Wood, "Hybrid Public Key Encryption", RFC 9180, DOI 10.17487/RFC9180, , <https://www.rfc-editor.org/info/rfc9180>.
[LSD0011]
Schanzenbach, M. and P. Fardzadeh, "The HPKE Elligator KEM", , <https://lsd.gnunet.org/lsd0011>.

12. Informative References

[KEMTLS]
Wiggers, T., "Post-Quantum TLS", , <https://thomwiggers.nl/publication/thesis/>.

Authors' Addresses

Martin Schanzenbach
Fraunhofer AISEC
Lichtenbergstrasse 11
85748 Garching
Germany
ch3
GNUnet e.V.
Lenggries
Germany