Tinverse logo Tinverse LLC

Part 11 of 14

Key distribution

Every key in Part 9 had to reach the other side. The channel that carries them is itself encrypted, using the bootstrap material derived from the handshake, and it is reliable — a lost key is not something a peer can work around.

That makes this a small reliable protocol running inside an encrypted envelope, and it inherits the problems of both. It needs the acknowledgement and repair of Part 3, but its control records must not be readable or forgeable by anyone outside the association, and its history holds secrets that must be erased the moment they are no longer needed.

Encoding

Wrapping material as a token

Runs when

key material is prepared for sending or unwrapped on receipt

How often

Once per key exchange.

Material does not travel as bare bytes. It travels inside a generic container — a class name and a list of named properties — which is the shape DDS Security uses for everything it exchanges.

That container is encoded in whatever byte order the surrounding message used. The material nested inside it is always big-endian regardless.

This is the kind of detail that produces an implementation which works perfectly against itself and fails against everyone else. Two implementations on the same architecture agree; the mismatch appears only when one of them runs on a machine of the other order, at which point key exchange fails while everything else works.

Cost

One pass to encode, one to decode, over a fixed number of properties and a bounded material extent. The secrets are copied once, into the storage that owns them.

Routing

Addressing a token

Runs when

a token message is built or received

How often

Once per token.

A token message names its source, its destination, its class, and carries exactly one token. Each of those four is checked on receipt, and it is worth being explicit about what each check prevents.

Worked example

CheckWhat it stops
Source is the peer this channel belongs tomaterial from one peer installed in another's association
Destination is this participanta token observed in transit being replayed at a third party
Route is from the remote volatile writer to the local participanta token arriving on a path that was never established
Class is the expected one, and there is exactly one tokena message carrying several tokens, where only the first is validated

The last row is the one that reads as pedantry and is not. A container holding a list invites an implementation that validates the list's first element and installs all of them, and cardinality is checked here so that a well-formed-looking message with two tokens is refused rather than half-inspected.

Reliability

Keeping a token until acknowledged

Runs when

a token is sent, and when the peer acknowledges it

How often

Once each per token.

Participant tokens and endpoint tokens share one reliable writer, so they share one sequence space. An unacknowledged token has to stay available for repair, exactly as in Part 3.

Two things differ from an ordinary history. What is retained is a secret, so acknowledgement must erase it rather than merely release it. And the history is keep-all, which means a full history refuses new work — but it must never evict a token that has not been acknowledged, because that token is a key some association is waiting for and nothing else will supply it.

Acknowledge-Token(h, n)

h the token history · n the sequence the peer has acknowledged up to

  1. for each entry e in h where e.seq ≤ n
  2. Erase(e.material) // not merely dropped
  3. Remove(h, e)

Erasing at line 2 rather than at some later collection is what bounds how long a superseded key exists in memory. The acknowledgement is the moment it is known to be unnecessary, and that is the moment it goes.

Transmission

Building a protected record

Runs when

a token is first sent, and on every repair of it

How often

Once per transmission, including repeats.

A token is sent, possibly several times if the first attempt is lost. Those transmissions carry the same sequence number — the reliability protocol of Part 3 requires it, since a repair must be identifiable as the sample that was asked for.

But each transmission is separately encrypted, and Part 10 requires that each encryption use a fresh nonce. So the two numbers come apart: one semantic sequence, several cryptographic identities.

one token, sequence 7 key material seq 7 · nonce (s, 41) first attempt, lost seq 7 · nonce (s, 42) repair seq 7 · nonce (s, 43) repair, delivered Same sequence, so the reader knows which sample this is. Different nonce each time, so no key is ever used twice on one nonce — and the reader's replay window accepts only the first copy to arrive.
Figure 11.1 One sequence number, a fresh nonce per transmission.

The record is built directly into the outgoing buffer rather than assembled and then copied. Staging a complete message first would mean the plaintext of a key existing in a second place, and would add a copy to a path that repeats under loss.

Reliability

Reliable control on a protected channel

Runs when

the token channel announces or requests

How often

As Part 3, on this channel only.

The channel needs the control records of Part 3 — announcements and requests — and they must be protected too. Unprotected control on a protected channel would let anyone request a repair, and repairs carry keys.

The writer also has to distinguish three things that an ordinary writer can blur: the interval it still holds, the subset the reader has acknowledged, and the subset the reader is asking for. It cannot expand any of them into allocated storage, so all three use the fixed 256-position bitmap of Part 3.

The control records travel inside the same three-record envelope as the tokens, so a receiver authenticates the envelope before it learns whether what it contains is a token or a request. That is the same ordering as Part 10's selection step: structure, then authentication, then meaning.

Receipt

Receiving a participant token

Runs when

a protected participant token arrives

How often

Once per token, plus once per duplicate.

A token crosses several independent boundaries before it may change anything, and the order they are crossed in is the algorithm.

Receive-Participant-Token(a, msg)

a the participant association · msg the arriving protected message

  1. inner = Authenticate-Envelope(a, msg)
  2. if inner == error
  3. return reject // nothing beyond this point sees unverified bytes
  4. t = Decode-Token-Message(inner)
  5. if t == error or t is misaddressed
  6. return reject
  7. permit = Admit-Material(a, t.seq, t.material)
  8. if permit == stale
  9. return Acknowledge(a, t.seq) // a duplicate is still received
  10. if not Receipt-Window-Has-Room(a, t.seq)
  11. return reject // do not acknowledge what cannot be tracked
  12. if Install(a, permit) == error
  13. return reject // old material still installed; not acknowledged
  14. return Acknowledge(a, t.seq)

Line 9 acknowledges a duplicate rather than ignoring it. The peer resent because it did not hear the first acknowledgement, and staying silent makes it resend forever.

Lines 10 and 11 refuse before acknowledging. Acknowledging a sequence the receipt window cannot record would tell the peer to stop resending something the receiver cannot prove it has — and the peer will erase its retained copy on the strength of it.

Line 13 does the same for a failed installation. Acknowledgement means the material is installed; sending it because the message arrived would leave an association whose peer believes it has a key it does not.

Receipt

Receiving an endpoint token

Runs when

a protected endpoint token arrives

How often

Once per token.

Endpoint tokens arrive on the same reader as participant tokens, and the procedure above applies with one addition: the token names an endpoint match, and it may change that match and no other.

So a missing match, an invalid route, a failed session activation, or an exhausted receipt window each cause the same two things — the sequence is not acknowledged, and no other match is touched.

The second half of that is what a shared reader makes easy to get wrong. A failure while handling one match must not leave the reader's receipt state, or any other match's material, changed. Since a rejected token acknowledges nothing, the peer resends it, and a transient failure resolves itself.

Discovery

Secure discovery

Runs when

secure endpoint announcements are exchanged

How often

As ordinary discovery, once the participant channel is established.

Endpoint announcements themselves can be protected, so that which topics a participant publishes is not visible to observers. Those are reliable discovery endpoints, and they raise a bootstrapping question: which key protects them?

Not the handshake bootstrap material. That protects the token channel only. Secure discovery uses the participant material, which means it becomes readable only after the token channel has delivered that material — establishing an order between the two that is not otherwise obvious.

Two prohibitions follow, and both are about accepting a weaker path. Plaintext input on a secure discovery endpoint is refused rather than treated as ordinary discovery, and unauthenticated control records for it are refused rather than processed. An implementation that accepted either would offer an attacker a way to bypass the protection entirely by simply not using it.

Cost

The same reliability machinery as Part 3, over the announcements of Part 1, inside the envelope of Part 10. Nothing new per sample; the cost is that discovery cannot complete until key distribution has.

Next: teleoperation

A command that must reach a robot once, in time, over a link that may fail, with nothing allocating.

Part 12 →

These descriptions follow the implementations in Tin DDS and Retina. Evaluation access and the full algorithm notes are available from engineering@tinverse.com.