-
Notifications
You must be signed in to change notification settings - Fork 280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SECIO spec #106
SECIO spec #106
Changes from 2 commits
0dc2992
2194c68
3497da6
029798f
0c0a2aa
c73bc5d
a727256
9264cea
2c7fe93
1443ebf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
# SECIO 1.0.0 | ||
|
||
> A stream security transport for libp2p. Streams wrapped by SECIO use secure | ||
> sessions to encrypt all traffic. | ||
|
||
## Authors | ||
|
||
- [Juan Benet](https://github.com/jbenet) (October, 2015) | ||
|
||
## Editors | ||
|
||
- [Cole Brown](https://github.com/bigs) | ||
- [Lars Gierth](https://github.com/lgierth) | ||
|
||
## Implementations | ||
|
||
- [js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio) | ||
- [go-secio](https://github.com/libp2p/go-libp2p-secio) | ||
|
||
## Table of Contents | ||
|
||
- [Algorithm Support](#algorithm-support) | ||
|
||
## Algorithm Support | ||
|
||
SECIO allows participating peers to support a subset of the following | ||
algorithms. | ||
|
||
### Exhchanges | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Elliptic curves used for ephemeral key generation. |
||
|
||
- P-256 | ||
- P-384 | ||
- P-521 | ||
|
||
### Ciphers | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ciphers used for key stretching and for message encryption once SECIO channel is established. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is a deprecated cipher (Blowfish) supported? From the Go package docs:
Since the spec is not yet finalized, it would be cool if support was removed. If there is some mitigating circumstance (I haven't gone through the source very thoroughly!), a note about it would be helpful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would also be worth noting what mode the AES ciphers should use. From the Go implementation, it looks like it's CTR! Also, more generally, is this an implementation of any well-reviewed specification? If not, is there a reason why? I would imagine there are a number of specs for low-overhead, encrypted channels which may prevent future pitfalls. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is definitely not well-reviewed and will be will be deprecated in favor of TLS quite soon. See: https://github.com/libp2p/go-libp2p-tls/. (go-ipfs now has experimental support which we'll likely upgrade to default support after a release or two). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha, makes sense! And that answers my follow-up questions as well :) |
||
|
||
- AES-256 | ||
- AES-128 | ||
- Blowfish | ||
|
||
### Hashes | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hashes used for key stretching, and for HMACs once SECIO channel is established. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What mode and padding is AES-* using? Are there any parameters for Blowfish? |
||
|
||
- SHA-256 | ||
- SHA-512 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Current go implementation does not use dashes in the hash name; see al.go. |
||
|
||
## Data Structures | ||
|
||
The SECIO wire protocol features two message types defined in the | ||
[protobuf description language](https://github.com/libp2p/go-libp2p-secio/blob/master/pb/spipe.proto). | ||
These two messages, `Propose` and `Exchange` are the only serialized types | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's dump the current state of the protobufs here. Relying on a reference that can mutate can render the spec incoherent at a later time. Also, we're seeking to version specs in general, so capturing the current state and versioning the spec as it evolves is fair play. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nevermind, I see this feedback is recurrent below. |
||
required to implement SECIO. | ||
|
||
## Protocol | ||
|
||
### Proposal Generation | ||
|
||
SECIO channel negotiation begins with a proposal phase. Both sides generate a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a prerequisite for SECIO, we need a dedicated channel where both parties have agreed to proceed with SECIO handshake by means of a multiplexer or else. |
||
proposal as follows: | ||
|
||
*Note: the public key should be serialized per the `Bytes` method from | ||
[go-libp2p-crypto](https://godoc.org/github.com/libp2p/go-libp2p-crypto#Key).* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's point at #100 (once it's merged) |
||
|
||
``` | ||
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>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's put the actual protobuf here (and say that it's a protobuf). |
||
} | ||
``` | ||
|
||
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* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that this is just the spec, I'd just say:
Or something like that. We don't need to explicitly tell the programmer how to implement the protocol step-by-step (i.e., "calculate the peer ID and store it for later"). |
||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the first use of the word "nonce". Does it refer to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. |
||
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](https://godoc.org/github.com/libp2p/go-libp2p-crypto#GenerateEKeyPair). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should actually spec this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's standard ECDHE if I'm not mistaken. |
||
|
||
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>, | ||
} | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, let's just use the protobuf definition. |
||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC, that function is referred to in literature as the KDF (key derivation function), and the overall process is called ECDH key agreement. Let's use these terms to evoke familiar concepts. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think its official name is ECSVDP-DH |
||
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](https://godoc.org/github.com/libp2p/go-libp2p-crypto#KeyStretcher). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, we need to spec this. |
||
|
||
``` | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are not generated here. The MAC key is already generated during key stretching above. The cipher was selected above when comparing Propose messages. What the Go implementation does at this point is prepare the constructs for HMAC and encryption/decryption. I think that's what needs to be stated, i.e. the implementation now has the required parameters to initialise the cryptographic constructs. |
||
generated in the previous step using the `MacKey` and `CipherKey` from the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to specify the order within the result of the stretching. |
||
generated | ||
[`StretchedKeys`](https://godoc.org/github.com/libp2p/go-libp2p-crypto#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] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the padding style for encrypt need to be specified? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really, we should specify everything. Ideally, we'd point to an RFC. However, we should optimize for merging something that's correct rather than waiting for something that's perfect. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm assuming that the length includes the encrypted body and the hmac signature. |
||
``` | ||
|
||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The target of validation is actually the (d)encryption and HMAC'ing logic. We use a known value (nonce) to send a predictable message so we can validate that both parties have set up the encrypted channels correctly. |
||
Otherwise, a secure channel has been successfully opened. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ToC is missing entries.