Files
specs/secio/README.md
2018-10-24 17:35:24 -04:00

4.9 KiB

SECIO 1.0.0

A stream security transport for libp2p. Streams wrapped by SECIO use secure sessions to encrypt all traffic.

Authors

Editors

Implementations

Table of Contents

Algorithm Support

SECIO allows participating peers to support a subset of the following algorithms.

Exhchanges

  • P-256
  • P-384
  • P-521

Ciphers

  • AES-256
  • AES-128
  • Blowfish

Hashes

  • SHA-256
  • SHA-512

Data Structures

The SECIO wire protocol features two message types defined in the protobuf description language. These two messages, Propose and Exchange are the only serialized types required to implement SECIO.

Protocol

Proposal Generation

SECIO channel negotiation begins with a proposal phase. Both sides generate a proposal as follows:

Note: the public key should be serialized per the Bytes method from go-libp2p-crypto.

Propose{
  Rand: <16 secure random bytes>,
  Pubkey: <public key bytes>,
  Exchanges: <comma separated string of supported key exchanges>,
  Ciphers: <comma separated string of supported ciphers>,
  Hashes: <comma separated string of supported hashes>,
}

Both peers serialize this message and send it over the wire. Upon deserializing their peer's message, they verify that the pubkey matches that described by the peer's peer ID (NOTE: this is sometimes only possible for the peer initiating the connection.) If the key doesn't match the peer ID, the peer can close the connection. If the peer doesn't know the remote peer's ID, they can compute and store the remote peer ID for later use.

Determining Roles and Algorithms

Next, the peers use a deterministic formula to compute their roles in the coming exchanges. Each peer computes:

oh1 := sha256(concat(remotePeerPubKeyBytes, myNonce))
oh2 := sha256(concat(myPubKeyBytes, remotePeerNonce))

With these hashes, determine which peer's preferences to favor. This peer will be referred to as the "preferred peer". If oh1 == oh2, then the peer is communicating with itself and should return an error. If oh1 < oh2, use the remote peer's preferences. If oh1 > oh2, prefer the local peer's preferences.

Given our preference, we now sort through each of the Exchanges, Ciphers, and Hashes provided by both peers, selecting the first item from our preferred peer's set that is also shared by the other peer.

Key Exchange

Now the peers prepare a key exchange. Both peers generate an ephemeral key based on the agreed upon exchange (currently support is only available for elliptic curve algorithms). Ephemeral keys are generated via go-libp2p-crypto.

With keys generated, both peers create a Exchange message. First, they start by generating a "corpus" that they will sign.

corpus := concat(myProposalBytes, remotePeerProposalBytes, ephemeralPubKey)

Then, generate the Exchange:

Exchange{
  Epubkey: <ephemeral pubkey>,
  Signature: <sign corpus with local private key>,
}

The peers serialize these and write them over the wire. Upon receiving the remote peer's Exchange, validate the signature by computing the corpus you expect them to have generated with their public key. Peers should close the connection if the signature does not validate.

Key Stretching

Peers now generate their shared secret based on the function generated by the ephemeral key generation, passing it the remote peer's ephemeral key. With the shared secret generated, both peers stretch the key using the algorithm described by go-libp2p-crypto.

k1, k2 := KeyStretcher(sharedSecret)

With k1 and k2 computed, swap the two values if the remote peer is the preferred peer. After swapping if necessary, k1 becomes the local peer's key and k2 the remote peer's key.

Each peer now generates a MAC key and cipher for the remote and local keys generated in the previous step using the MacKey and CipherKey from the generated StretchedKeys objects.

Initiate Secure Channel

With the cipher and HMAC signer created, the secure channel is ready to be opened. Each packet is of the form:

[uint32 length of packet | encrypted body | hmac signature of encrypted body]

The first packet transmitted by each peer must be the remote peer's nonce. Peers validate that the remote peer sent them their nonce, closing if unsuccessful. Otherwise, a secure channel has been successfully opened.