akwizgran created page: BTP

akwizgran
2015-03-17 17:51:44 +00:00
parent 76ff7e8484
commit a14a68562f

@@ -1,110 +1,112 @@
BTP is a transport layer security protocol for delay-tolerant networks. It provides confidentiality, authenticity, integrity, forward secrecy and protocol obfuscation for simplex byte streams. It can operate over any transport that can deliver a stream of bytes from a sender to a recipient on a best-effort basis. A memory card strapped to a carrier pigeon is one example of such a transport. When operating over a duplex transport such as TCP, BTP treats each duplex connection as two independent simplex streams. BTP is a transport layer security protocol for delay-tolerant networks. It provides confidentiality, authenticity, integrity, forward secrecy and protocol obfuscation for simplex byte streams. It can operate over any transport that can deliver a stream of bytes from a sender to a recipient on a best-effort basis. A memory card strapped to a carrier pigeon is one example of such a transport.
When operating over a duplex transport such as TCP, BTP treats each duplex connection as two independent simplex streams.
The underlying transport is not required to provide any security properties. We assume the adversary can read, modify, delete and insert traffic on the underlying transport at will. The underlying transport is not required to provide any security properties. We assume the adversary can read, modify, delete and insert traffic on the underlying transport at will.
### Notation ### Notation
We use *||* to denote concatenation, double quotes to denote a UTF-8 string, *int64(x)* to denote *x* represented as a 64-bit two's complement big-endian integer, *len(m)* to denote the length of *m* in bytes, and *pack(m)* as shorthand for *int64(len(m)) || m*. We use || to denote concatenation, double quotes to denote a UTF-8 string, int64(x) to denote x represented as a 64-bit two's complement big-endian integer, len(x) to denote the length of x in bytes, and pack(x) as shorthand for int64(len(x)) || x.
### Crypto primitives ### Crypto primitives
BTP uses the following cryptographic primitives: BTP uses the following cryptographic primitives:
* A pseudo-random function, *MAC(k, m)* * A pseudo-random function, MAC(k, m)
* An authenticated cipher, *ENC(k, n, m)* and *DEC(k, n, m)*, where *n* is a nonce * An authenticated cipher, ENC(k, n, m) and DEC(k, n, m), where n is a nonce
All keys are *key_len* bytes and all nonces are *nonce_len* bytes. The output of *MAC* is *mac_len* bytes, and the output of *ENC(k, n, m)* is *auth_len* bytes longer than *m*. For simplicity we require that *mac_len == key_len*. All keys are key_len bytes and all nonces are nonce_len bytes. The output of MAC(k, m) is mac_len bytes, and the output of ENC(k, n, m) is auth_len bytes longer than m. For simplicity we require that mac_len == key_len.
> **Implementation note:** Previous versions of BTP used HMAC-SHA-256 as the pseudo-random function and AES-256-GCM as the authenticated cipher. Future versions will use BLAKE2s as the pseudo-random function and XSalsa20-Poly1305 as the authenticated cipher. In both cases, key_len == 32, nonce_len == 24, mac_len == 32, and auth_len == 16.
### Initial state ### Initial state
Before two devices can communicate using BTP they must establish the following state: Before two devices can communicate using BTP they must establish the following state:
* A shared secret, *S* * A shared secret key_len bytes long, S
* A timestamp, *T* * A timestamp in seconds since the Unix epoch, T
* The maximum expected difference between the devices' clocks, *D* * The maximum expected difference between the devices' clocks in seconds, D
* The maximum expected latency of the transport, *L* * The maximum expected latency of the transport in seconds, L
How this state is established is outside the scope of BTP. The devices must establish a separate *S* for each transport over which they wish to communicate. *T* is measured in seconds since the Unix epoch and must be in the past according to both devices' clocks. *D* and *L* may be hard-coded. How this state is established is outside the scope of BTP. The devices must establish a separate S for each transport over which they wish to communicate. T must be at least D + L seconds in the past according to both devices' clocks.
The devices must also agree which of them will play the role of Alice and which will play the role of Bob. The roles are identical except for some key derivation constants. The devices must also agree which of them will play the role of Alice and which will play the role of Bob. The roles are identical except for some key derivation constants.
> **Implementation notes:** Shared secrets for multiple transports may be derived from an initial shared secret established by a separate key agreement protocol. T may be hard-coded or negotiated by the key agreement protocol. D and L may be hard-coded.
### Key derivation ### Key derivation
BTP's key derivation function is based on a pseudo-random function. It is broadly similar to the counter mode KDF from NIST SP 800-108, but since we always require *key_len* bytes of output we can omit the counter and the output length argument. BTP's key derivation function is based on a pseudo-random function. The key derivation function takes an input key k, a label p, and zero or more optional arguments a_1 to a_n, and returns an output key. The label describes the purpose of the output key, and the additional arguments vary according to the purpose. The label and additional arguments are unambiguously concatenated using the pack function.
The key derivation function takes an input key *k*, a label *p*, and zero or more additional arguments *a_1* to *a_n*, and returns an output key. The label describes the purpose of the output key, and the additional arguments vary according to the purpose. KDF(k, p, a_1, ..., a_n) = MAC(k, pack(p) || pack(a_1) || ... || pack(a_n))
*KDF(k, p, a_1, ..., a_n) = MAC(k, pack(p) || pack(a_1) || ... || pack(a_n))* Each device derives four initial keys from S:
Each device derives four initial keys from *S*: * alice_tag = KDF(S, "ALICE_TAG_KEY")
* alice_cipher = KDF(S, "ALICE_CIPHER_KEY")
* bob_tag = KDF(S, "BOB_TAG_KEY")
* bob_cipher = KDF(S, "BOB_CIPHER_KEY")
* *ack = KDF(S, "ALICE_CIPHER_KEY")* Alice sets out_tag = alice_tag, out_cipher = alice_cipher, in_tag = bob_tag, and in_cipher = bob_cipher. Bob sets out_tag = bob_tag, out_cipher = bob_cipher, in_tag = alice_tag and in_cipher = alice_cipher. Thus Alice's outgoing keys (out_tag and out_cipher) are the same as Bob's incoming keys (in_tag and in_cipher), and vice versa. Both devices then erase S.
* *bck = KDF(S, "BOB_CIPHER_KEY")*
* *atk = KDF(S, "ALICE_TAG_KEY")*
* *btk = KDF(S, "BOB_TAG_KEY")*
Alice sets *ock = ack*, *ick = bck*, *otk = atk*, and *itk = btk*. Bob sets *ock = bck*, *ick = ack*, *otk = btk*, and *itk = atk*. Thus Alice's outgoing keys (*ock* and *otk*) are the same as Bob's incoming keys (*ick* and *itk*), and vice versa.
### Key rotation ### Key rotation
BTP achieves forward secrecy by rotating keys periodically. The key rotation function is deterministic, so devices that start from the same *S* will have matching keys in each rotation period. BTP achieves forward secrecy by rotating keys periodically. The key rotation function is deterministic, so devices that start from the same S will have matching keys in each rotation period.
The length of each rotation period is *R = D + L*. Rotation periods are aligned with the Unix epoch. The timestamp *T* falls in rotation period *P = floor(T / R)*, where all times are measured in seconds. The length of each rotation period is R = D + L seconds. Rotation periods are aligned with the Unix epoch. The timestamp T falls in rotation period P = floor(T / R).
The initial keys derived from *S* are the keys for period *P - 1*. The keys for each subsequent period *i* are derived from the previous period's keys as follows: The initial keys derived from S are the keys for period P. The keys for each subsequent period i are derived from the previous period's keys as follows:
* *next_ock = KDF(ock, "ROTATE_OUTGOING_CIPHER_KEY", int64(i))* * next_out_tag = KDF(out_tag, "ROTATE_OUTGOING_TAG_KEY", int64(i))
* *next_ick = KDF(ick, "ROTATE_INCOMING_CIPHER_KEY", int64(i))* * next_out_cipher = KDF(out_cipher, "ROTATE_OUTGOING_CIPHER_KEY", int64(i))
* *next_otk = KDF(otk, "ROTATE_OUTGOING_TAG_KEY", int64(i))* * next_in_tag = KDF(in_tag, "ROTATE_INCOMING_TAG_KEY", int64(i))
* *next_itk = KDF(itk, "ROTATE_INCOMING_TAG_KEY", int64(i))* * next_in_cipher = KDF(in_cipher, "ROTATE_INCOMING_CIPHER_KEY", int64(i))
If a sender starts sending a stream at time *t* according to the sender's clock, the recipient may start receiving it at any time between *t - D* and *t + D + L* according to the recipient's clock. Therefore each device must retain the incoming keys for the previous, current and next rotation periods, along with the outgoing keys for the current rotation period. If the sender starts sending a stream at time t according to the sender's clock, the recipient may start receiving it at any time between t - D and t + D + L according to the recipient's clock. Therefore each device must retain the incoming keys for the previous, current and next rotation periods, along with the outgoing keys for the current rotation period. Keys are erased when they are no longer needed.
### Tags ### Tags
Each stream starts with a pseudo-random tag. The recipient calculates the tag in advance and uses it to recognise which sender the stream comes from and which key should be used to authenticate and decrypt it. Each stream starts with a pseudo-random tag, which is tag_len bytes long. The recipient calculates the tag in advance and uses it to recognise which sender the stream comes from and which incoming cipher key should be used.
The tag for the *i^th* stream from a given sender to a given recipient in a given rotation period is equal to the first *tag_len* bytes of *MAC(k, int64(i))*, where *k* is *otk* for the sender and *itk* for the recipient. We require that *mac_len >= tag_len*. The tag for the i^th stream from the sender to the recipient in a given rotation period is the first tag_len bytes of MAC(k, int64(i)), where k is the sender's outgoing tag key. We require that mac_len >= tag_len.
For each sender, the recipient keeps three reordering windows: one for each of the previous, current and next rotation periods. Reordering windows allow streams to be recognised if they are received out of order due to reordering or loss by the underlying transport. BTP uses reordering windows to allow the recipient to recognise streams that are received out of order due to reordering or loss by the underlying transport. The recipient maintains reordering windows for the previous, current and next rotation periods. Each window contains *W* tags, each of which is marked as seen or unseen. When a previously unseen tag is marked as seen, the window slides according to the following rules:
Each window contains *W* tags, each of which is marked as expected or received. When an expected tag is marked as received, the window slides according to the following rules: 1. Slide the window until all tags in the top half of the window are unseen.
2. Slide the window until the lowest tag in the window is unseen.
1. Slide the window until all tags in the top half of the window are expected. If the window slides past a tag that has not been seen, the recipient can no longer recognise the corresponding stream. Larger values of *W* make it possible to tolerate more reordering and loss by the underlying transport, but require the recipient to maintain larger windows.
2. Slide the window until the lowest tag in the window is expected.
If the window slides past a tag before it is received, the tag can no longer be recognised. Larger values of *W* make it possible to tolerate more reordering and loss by the underlying transport, but require the recipient to store larger windows. To avoid reusing tags, which would allow the adversary to distinguish BTP traffic from random, the sender must persistently store the number of streams sent to the recipient in the current rotation period. To avoid accepting replayed streams, the recipient must persistently store the reordering windows for the previous, current and next rotation periods.
To avoid reusing tags, which would allow the adversary to identify BTP traffic, the sender must persistently store the number of streams sent to each recipient in the current rotation period. To avoid accepting replayed streams, the recipient must persistently store the reordering window for each sender in the previous, current and next rotation periods. > **Implementation note:** The current implementation uses tag_length == 16 and W == 32. Different values of W may be suitable for different transports.
### Stream header ### Stream header
The pseudo-random tag is followed by the stream header, which contains a random nonce and an ephemeral cipher key encrypted and authenticated with the rotating cipher key and the nonce. The length of the stream header is *nonce_len + key_len + auth_len* bytes. The ephemeral cipher key is used for encrypting and authenticating the rest of the stream. The pseudo-random tag is followed by the stream header, which consists of a random nonce and an ephemeral cipher key encrypted and authenticated with the sender's outgoing cipher key and the nonce. The stream header is nonce_len + key_len + auth_len bytes long. The ephemeral cipher key is used for encrypting and authenticating the rest of the stream.
### Frames ### Frames
The remainder of the stream consists of one or more frames. Each frame has a fixed-length header and a variable-length body that may contain data, padding, neither or both. The frames are numbered starting from zero. No more than 2^63 frames may be sent in a single stream. The remainder of the stream consists of one or more frames. Each frame has a fixed-length header and a variable-length body that may contain data, padding, neither or both. The frames are numbered starting from zero. A stream may not contain more than 2^63 frames.
The plaintext frame header is 4 bytes long with the following format: The plaintext frame header is 4 bytes long with the following format:
* Bit 0: Final frame flag, set to one if this is the last frame in the stream * Bit 0: Final frame flag, set to one if this is the last frame in the stream
* Bits 1-15: Length of the data in bytes as a big-endian integer * Bits 1-15: Length of the data in bytes, as a big-endian integer
* Bit 16: Zero * Bit 16: Zero
* Bits 17-31: Length of the padding in bytes as a big-endian integer * Bits 17-31: Length of the padding in bytes, as a big-endian integer
The total length of the data and padding must be less than 2^15 bytes. The plaintext frame body contains the data and padding. The total length of the data and padding must be less than 2^15 bytes. If any padding is present it must all be zeroes.
The plaintext frame body contains the data and padding. If any padding is present it must all be zeroes.
The header and body are encrypted and authenticated separately using the ephemeral cipher key and deterministic nonces, which are not sent. The header and body are encrypted and authenticated separately using the ephemeral cipher key and deterministic nonces, which are not sent.
The nonce for the frame header is *nonce_len* bytes long with the following format: The nonce for the frame header is nonce_len bytes long with the following format:
* Bit 0: Header flag, set to one * Bit 0: Header flag, set to one
* Bits 1-63: Frame number as a big-endian integer * Bits 1-63: Frame number as a big-endian integer
* Remaining bits: Zero * Remaining bits: Zero
The nonce for the frame body is *nonce_len* bytes long with the following format: The nonce for the frame body is nonce_len bytes long with the following format:
* Bit 0: Header flag, set to zero * Bit 0: Header flag, set to zero
* Bits 1-63: Frame number as a big-endian integer * Bits 1-63: Frame number as a big-endian integer