mirror of
https://github.com/vacp2p/rfc-index.git
synced 2026-01-06 22:34:02 -05:00
Eth secpm splitted (#91)
This branch contains 2 new RFCs: 1. An RFC describing the secure 1-to-1 communication channels; 2. An RFC describing the MLS and the decentralized MLS. --------- Co-authored-by: Ekaterina Broslavskaya <seemenkina@gmail.com> Co-authored-by: kaiserd <1684595+kaiserd@users.noreply.github.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
1034
vac/raw/eth-demls.md
Normal file
1034
vac/raw/eth-demls.md
Normal file
File diff suppressed because it is too large
Load Diff
345
vac/raw/eth-secure-channel.md
Normal file
345
vac/raw/eth-secure-channel.md
Normal file
@@ -0,0 +1,345 @@
|
||||
---
|
||||
title: ETH-SECURE-CHANNEL
|
||||
name: Secure 1-to-1 channel setup using X3DH and the double ratchet
|
||||
status: raw
|
||||
category: Standards Track
|
||||
tags:
|
||||
editor: Ramses Fernandez <ramses@status.im>
|
||||
contributors:
|
||||
---
|
||||
|
||||
## Motivation
|
||||
|
||||
The need for secure communications has become paramount.
|
||||
This specification outlines a protocol describing a
|
||||
secure 1-to-1 comunication channel between 2 users. The
|
||||
main components are the X3DH key establishment mechanism,
|
||||
combined with the double ratchet. The aim of this
|
||||
combination of schemes is providing a protocol with both
|
||||
forward secrecy and post-compromise security.
|
||||
|
||||
## Theory
|
||||
|
||||
The specification is based on the noise protocol framework.
|
||||
It corresponds to the double ratchet scheme combined with
|
||||
the X3DH algorithm, which will be used to initialize the former.
|
||||
We chose to express the protocol in noise to be be able to use
|
||||
the noise streamlined implementation and proving features.
|
||||
The X3DH algorithm provides both authentication and forward
|
||||
secrecy, as stated in the
|
||||
[X3DH specification](https://signal.org/docs/specifications/x3dh/).
|
||||
|
||||
This protocol will consist of several stages:
|
||||
|
||||
1. Key setting for X3DH: this step will produce
|
||||
prekey bundles for Bob which will be fed into X3DH.
|
||||
It will also allow Alice to generate the keys required
|
||||
to run the X3DH algorithm correctly.
|
||||
2. Execution of X3DH: This step will output
|
||||
a common secret key `SK` together with an additional
|
||||
data vector `AD`. Both will be used in the double
|
||||
ratchet algorithm initialization.
|
||||
3. Execution of the double ratchet algorithm
|
||||
for forward secure, authenticated communications,
|
||||
using the common secret key `SK`, obtained from X3DH, as a root key.
|
||||
|
||||
The protocol assumes the following requirements:
|
||||
|
||||
- Alice knows Bob’s Ethereum address.
|
||||
- Bob is willing to participate in the protocol,
|
||||
and publishes his public key.
|
||||
- Bob’s ownership of his public key is verifiable,
|
||||
- Alice wants to send message M to Bob.
|
||||
- An eavesdropper cannot read M’s content
|
||||
even if she is storing it or relaying it.
|
||||
|
||||
## Syntax
|
||||
|
||||
### Cryptographic suite
|
||||
|
||||
The following cryptographic functions MUST be used:
|
||||
|
||||
- `X488` as Diffie-Hellman function `DH`.
|
||||
- `SHA256` as KDF.
|
||||
- `AES256-GCM` as AEAD algorithm.
|
||||
- `SHA512` as hash function.
|
||||
- `XEd448` for digital signatures.
|
||||
|
||||
### X3DH initialization
|
||||
|
||||
This scheme MUST work on the curve curve448.
|
||||
The X3DH algorithm corresponds to the IX pattern in Noise.
|
||||
|
||||
Bob and Alice MUST define personal key pairs
|
||||
`(ik_B, IK_B)` and `(ik_A, IK_A)` respectively where:
|
||||
|
||||
- The key `ik` must be kept secret,
|
||||
- and the key `IK` is public.
|
||||
|
||||
Bob MUST generate new keys using
|
||||
`(ik_B, IK_B) = GENERATE_KEYPAIR(curve = curve448)`.
|
||||
|
||||
Bob MUST also generate a public key pair
|
||||
`(spk_B, SPK_B) = GENERATE_KEYPAIR(curve = curve448)`.
|
||||
|
||||
`SPK` is a public key generated and stored at medium-term.
|
||||
Both signed prekey and the certificate MUST
|
||||
undergo periodic replacement.
|
||||
After replacing the key,
|
||||
Bob keeps the old private key of `SPK`
|
||||
for some interval, dependant on the implementation.
|
||||
This allows Bob to decrypt delayed messages.
|
||||
|
||||
Bob MUST sign `SPK` for authentication:
|
||||
`SigSPK = XEd448(ik, Encode(SPK))`
|
||||
|
||||
A final step requires the definition of
|
||||
`prekey_bundle = (IK, SPK, SigSPK, OPK_i)`
|
||||
|
||||
One-time keys `OPK` MUST be generated as
|
||||
`(opk_B, OPK_B) = GENERATE_KEYPAIR(curve = curve448)`.
|
||||
|
||||
Before sending an initial message to Bob,
|
||||
Alice MUST generate an AD: `AD = Encode(IK_A) || Encode(IK_B)`.
|
||||
|
||||
Alice MUST generate ephemeral key pairs
|
||||
`(ek, EK) = GENERATE_KEYPAIR(curve = curve448)`.
|
||||
|
||||
The function `Encode()` transforms a
|
||||
curve448 public key into a byte sequence.
|
||||
This is specified in the [RFC 7748](http://www.ietf.org/rfc/rfc7748.txt)
|
||||
on elliptic curves for security.
|
||||
|
||||
One MUST consider `q = 2^446 - 13818066809895115352007386748515426880336692474882178609894547503885`
|
||||
for digital signatures with `(XEd448_sign, XEd448_verify)`:
|
||||
|
||||
```text
|
||||
XEd448_sign((ik, IK), message):
|
||||
Z = randbytes(64)
|
||||
r = SHA512(2^456 - 2 || ik || message || Z )
|
||||
R = (r * convert_mont(5)) % q
|
||||
h = SHA512(R || IK || M)
|
||||
s = (r + h * ik) % q
|
||||
return (R || s)
|
||||
```
|
||||
|
||||
```text
|
||||
XEd448_verify(u, message, (R || s)):
|
||||
if (R.y >= 2^448) or (s >= 2^446): return FALSE
|
||||
h = (SHA512(R || 156326 || message)) % q
|
||||
R_check = s * convert_mont(5) - h * 156326
|
||||
if R == R_check: return TRUE
|
||||
return FALSE
|
||||
```
|
||||
|
||||
```text
|
||||
convert_mont(u):
|
||||
u_masked = u % mod 2^448
|
||||
inv = ((1 - u_masked)^(2^448 - 2^224 - 3)) % (2^448 - 2^224 - 1)
|
||||
P.y = ((1 + u_masked) * inv)) % (2^448 - 2^224 - 1)
|
||||
P.s = 0
|
||||
return P
|
||||
```
|
||||
|
||||
### Use of X3DH
|
||||
|
||||
This specification combines the double ratchet
|
||||
with X3DH using the following data as initialization for the former:
|
||||
|
||||
- The `SK` output from X3DH becomes the `SK`
|
||||
input of the double ratchet. See section 3.3 of
|
||||
[Signal Specification](https://signal.org/docs/specifications/doubleratchet/)
|
||||
for a detailed description.
|
||||
- The `AD` output from X3DH becomes the `AD`
|
||||
input of the double ratchet. See sections 3.4 and 3.5 of
|
||||
[Signal Specification](https://signal.org/docs/specifications/doubleratchet/)
|
||||
for a detailed description.
|
||||
- Bob’s signed prekey `SigSPKB` from X3DH is used as Bob’s
|
||||
initial ratchet public key of the double ratchet.
|
||||
|
||||
X3DH has three phases:
|
||||
|
||||
1. Bob publishes his identity key and prekeys to a server,
|
||||
a network, or dedicated smart contract.
|
||||
2. Alice fetches a prekey bundle from the server,
|
||||
and uses it to send an initial message to Bob.
|
||||
3. Bob receives and processes Alice's initial message.
|
||||
|
||||
Alice MUST perform the following computations:
|
||||
|
||||
```text
|
||||
dh1 = DH(IK_A, SPK_B, curve = curve448)
|
||||
dh2 = DH(EK_A, IK_B, curve = curve448)
|
||||
dh3 = DH(EK_A, SPK_B)
|
||||
SK = KDF(dh1 || dh2 || dh3)
|
||||
```
|
||||
|
||||
Alice MUST send to Bob a message containing:
|
||||
|
||||
- `IK_A, EK_A`.
|
||||
- An identifier to Bob's prekeys used.
|
||||
- A message encrypted with AES256-GCM using `AD` and `SK`.
|
||||
|
||||
Upon reception of the initial message, Bob MUST:
|
||||
|
||||
1. Perform the same computations above with the `DH()` function.
|
||||
2. Derive `SK` and construct `AD`.
|
||||
3. Decrypt the initial message encrypted with `AES256-GCM`.
|
||||
4. If decryption fails, abort the protocol.
|
||||
|
||||
### Initialization of the double datchet
|
||||
|
||||
In this stage Bob and Alice have generated key pairs
|
||||
and agreed a shared secret `SK` using X3DH.
|
||||
|
||||
Alice calls `RatchetInitAlice()` defined below:
|
||||
|
||||
```text
|
||||
RatchetInitAlice(SK, IK_B):
|
||||
state.DHs = GENERATE_KEYPAIR(curve = curve448)
|
||||
state.DHr = IK_B
|
||||
state.RK, state.CKs = HKDF(SK, DH(state.DHs, state.DHr))
|
||||
state.CKr = None
|
||||
state.Ns, state.Nr, state.PN = 0
|
||||
state.MKSKIPPED = {}
|
||||
```
|
||||
|
||||
The HKDF function MUST be the proposal by
|
||||
[Krawczyk and Eronen](http://www.ietf.org/rfc/rfc5869.txt).
|
||||
In this proposal `chaining_key` and `input_key_material`
|
||||
MUST be replaced with `SK` and the output of `DH` respectively.
|
||||
|
||||
Similarly, Bob calls the function `RatchetInitBob()` defined below:
|
||||
|
||||
```text
|
||||
RatchetInitBob(SK, (ik_B,IK_B)):
|
||||
state.DHs = (ik_B, IK_B)
|
||||
state.Dhr = None
|
||||
state.RK = SK
|
||||
state.CKs, state.CKr = None
|
||||
state.Ns, state.Nr, state.PN = 0
|
||||
state.MKSKIPPED = {}
|
||||
```
|
||||
|
||||
### Encryption
|
||||
|
||||
This function performs the symmetric key ratchet.
|
||||
|
||||
```text
|
||||
RatchetEncrypt(state, plaintext, AD):
|
||||
state.CKs, mk = HMAC-SHA256(state.CKs)
|
||||
header = HEADER(state.DHs, state.PN, state.Ns)
|
||||
state.Ns = state.Ns + 1
|
||||
return header, AES256-GCM_Enc(mk, plaintext, AD || header)
|
||||
```
|
||||
|
||||
The `HEADER` function creates a new message header
|
||||
containing the public key from the key pair output of the `DH`function.
|
||||
It outputs the previous chain length `pn`,
|
||||
and the message number `n`.
|
||||
The returned header object contains ratchet public key
|
||||
`dh` and integers `pn` and `n`.
|
||||
|
||||
### Decryption
|
||||
|
||||
The function `RatchetDecrypt()` decrypts incoming messages:
|
||||
|
||||
```text
|
||||
RatchetDecrypt(state, header, ciphertext, AD):
|
||||
plaintext = TrySkippedMessageKeys(state, header, ciphertext, AD)
|
||||
if plaintext != None:
|
||||
return plaintext
|
||||
if header.dh != state.DHr:
|
||||
SkipMessageKeys(state, header.pn)
|
||||
DHRatchet(state, header)
|
||||
SkipMessageKeys(state, header.n)
|
||||
state.CKr, mk = HMAC-SHA256(state.CKr)
|
||||
state.Nr = state.Nr + 1
|
||||
return AES256-GCM_Dec(mk, ciphertext, AD || header)
|
||||
```
|
||||
|
||||
Auxiliary functions follow:
|
||||
|
||||
```text
|
||||
DHRatchet(state, header):
|
||||
state.PN = state.Ns
|
||||
state.Ns = state.Nr = 0
|
||||
state.DHr = header.dh
|
||||
state.RK, state.CKr = HKDF(state.RK, DH(state.DHs, state.DHr))
|
||||
state.DHs = GENERATE_KEYPAIR(curve = curve448)
|
||||
state.RK, state.CKs = HKDF(state.RK, DH(state.DHs, state.DHr))
|
||||
```
|
||||
|
||||
```text
|
||||
SkipMessageKeys(state, until):
|
||||
if state.NR + MAX_SKIP < until:
|
||||
raise Error
|
||||
if state.CKr != none:
|
||||
while state.Nr < until:
|
||||
state.CKr, mk = HMAC-SHA256(state.CKr)
|
||||
state.MKSKIPPED[state.DHr, state.Nr] = mk
|
||||
state.Nr = state.Nr + 1
|
||||
```
|
||||
|
||||
```text
|
||||
TrySkippedMessageKey(state, header, ciphertext, AD):
|
||||
if (header.dh, header.n) in state.MKSKIPPED:
|
||||
mk = state.MKSKIPPED[header.dh, header.n]
|
||||
delete state.MKSKIPPED[header.dh, header.n]
|
||||
return AES256-GCM_Dec(mk, ciphertext, AD || header)
|
||||
else: return None
|
||||
```
|
||||
|
||||
## Information retrieval
|
||||
|
||||
### Static data
|
||||
|
||||
Some data, such as the key pairs `(ik, IK)` for Alice and Bob,
|
||||
MAY NOT be regenerated after a period of time.
|
||||
Therefore the prekey bundle MAY be stored in long-term
|
||||
storage solutions, such as a dedicated smart contract
|
||||
which outputs such a key pair when receiving an Ethereum wallet
|
||||
address.
|
||||
|
||||
Storing static data is done using a dedicated
|
||||
smart contract `PublicKeyStorage` which associates
|
||||
the Ethereum wallet address of a user with his public key.
|
||||
This mapping is done by `PublicKeyStorage`
|
||||
using a `publicKeys` function, or a `setPublicKey` function.
|
||||
This mapping is done if the user passed an authorization process.
|
||||
A user who wants to retrieve a public key associated
|
||||
with a specific wallet address calls a function `getPublicKey`.
|
||||
The user provides the wallet address as the only
|
||||
input parameter for `getPublicKey`.
|
||||
The function outputs the associated public key
|
||||
from the smart contract.
|
||||
|
||||
### Ephemeral data
|
||||
|
||||
Storing ephemeral data on Ethereum MAY be done using
|
||||
a combination of on-chain and off-chain solutions.
|
||||
This approach provides an efficient solution to
|
||||
the problem of storing updatable data in Ethereum.
|
||||
|
||||
1. Ethereum stores a reference or a hash
|
||||
that points to the off-chain data.
|
||||
2. Off-chain solutions can include systems like IPFS,
|
||||
traditional cloud storage solutions, or
|
||||
decentralized storage networks such as a
|
||||
[Swarm](https://www.ethswarm.org).
|
||||
|
||||
In any case, the user stores the associated
|
||||
IPFS hash, URL or reference in Ethereum.
|
||||
|
||||
The fact of a user not updating the ephemeral information
|
||||
can be understood as Bob not willing to participate in any
|
||||
communication.
|
||||
|
||||
## Copyright
|
||||
|
||||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
|
||||
## References
|
||||
|
||||
- [The Double Ratchet Algorithm](https://signal.org/docs/specifications/doubleratchet/)
|
||||
- [The X3DH Key Agreement Protocol](https://signal.org/docs/specifications/x3dh/)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 35 KiB |
Reference in New Issue
Block a user