Program journey
Each rotator passes through various high-level logical stages that are summarized in the state diagram bellow. The overall workflow of a rotator is described next.
---
config:
theme: 'dark'
---
stateDiagram-v2
up: Up command
ke: Establish shared key
pull: Pull key from key buffer
push: Push key to key buffer
th: Get seconds since last tunnel handshake
kbf: IsKeyBufferFull
kbe: IsKeyBufferEmpty
sleep0: Sleep for a moment
sleep1: Sleep for a moment
sleep2: Sleep for a moment
rotate: Rotate tunnel PSK
state if_state0 <<choice>>
state if_state1 <<choice>>
state if_state2 <<choice>>
state fork_state <<fork>>
up --> fork_state
fork_state --> kbe
fork_state --> Hold
state KeyGenerator {
kbe --> if_state0
if_state0 --> pull : False
if_state0 --> sleep0 : True
sleep0 --> kbe
pull --> th
th --> if_state2
if_state2 --> rotate : < 30
if_state2 --> sleep2 : > 30
sleep2 --> th
rotate --> kbe
}
state KeyRotationScheduler {
Hold --> Bootstrap
Bootstrap --> ke
ke --> push
push --> kbf
kbf --> if_state1
if_state1 --> ke : False
sleep1 --> kbf
if_state1 --> sleep1 : True
}
Even though the workflow described in this page is true to the implementation. Known (and also unknown) implementation specificities still apply that can make the program deviate from its normal path or get stuck in a state due to an exception.
1. The rotator has started
By starting a rotator with the up command, a new process is created and detached from the user’s terminal session. For each peer indicated in the configuration file, two threads are created - one for the KeyRotationScheduler and another for the KeyGenerator. For example, if three peers were indicated in the configuration file, six new threads are created.
One can look at the KeyGenerator as the producer of keys and the KeyRotationScheduler as the consumer. They share a key buffer that for each pair of KeyGenerator and KeyRotationScheduler, the KeyGenerator ensures that it is always full and the KeyRotationScheduler retrieves keys from it has necessary.
2. Bootstrap
To each rotator is assigned one role, server or client. Consequently, to each KeyGenerator managing one tunnel is assign a role. The server awaits for its client to send an initialization message containing information important parameters to the bootstrap of the rotator. If the server agrees with these parameters, it sends an acknowledgement. After this, they are ready to start establishing keys.
3. Key establishment
The KeyGenerator with the client role is always assessing whether the key buffer is full. If it notices that is not full, it starts the key establishment procedure.
The key establishment can be divided into three stages:
- QKD key establishment;
- post-quantum key establishment;
- key verification.
QKD key establishment
After detecting that the buffer is not full, the client retrieves a 32 byte key from the KMS and send the respective ID of the key to its peer. The server upon the receiving of this ID, also requests the key to its respective KMS. If everything goes as expected, the server answers with an acknowledgement.
Post-quantum key establishment
Before performing the key exchange, both the client and server generate an ephemeral key-pair for the selected KEM and send the respective public key to the peer.
For each KEM, the client encapsulates a 32 byte random secret and sends it to the server. The server decapsulates the secret and also encapsulates a random secret of its own and sends to the client. After the client performing the decapsulation, both have two 32 byte values that are XORed, this way constituting the shared key for the given KEM.
If more than one KEM is configured, this procedure is performed as many times as needed. The final key established using post-quantum cryptography is the result of the XOR of all keys established.
Since PQ-KEs are considered optional, this step is also optional.
Key verification
The final key that was generated is the result of the XOR of the QKD and post-quantum keys. To ensure its integrity, the client hashes the key using SHA-512 with a combination of the SAE IDs as the salt. The client sends the digest to the server that verifies it by performing the same hashing procedure. If the verification passes successfully, it answers with an acknowledgement.
After a successful key establishment, the freshly generated key is pushed to the key buffer, and loops back to the starting condition of whether the key buffer is full or not.
4. Key rotation
The KeyRotationScheduler is continuously checking if the key buffer was keys. If it was, it retrieves it, and starts monitoring the underlying WireGuard tunnel to check if it is safe to rotate the key or not. When the right time comes, it sets the key as the tunnel’s PSK, and returns to the initial starting condition.
A key is only rotated within the first 30 seconds after the tunnel’s handshake. Knowing that WireGuard only performs two consecutive handshakes at minimum every two minutes, this ensures that a PSK is only set when there’s no chance of WireGuard triggering a handshake.