noise-libp2p spec: rethink message data construction.#210
noise-libp2p spec: rethink message data construction.#210
Conversation
The current message data construction is not secure enough, as it is vulnerable to replay attacks by a MITM agent that records previous handshakes, and transplants old user data from those handshakes into new ones, as long as the static key hasn't changed. We fix this vulnerability by introducing a signature over the whole message payload against the ephemeral session key. This "seals" the payload so that it's only valid for that exchange. This PR simplifies protobuf field naming. Finally, we formalise in which Noise messages of IK and XX the message payload is to be shared, to guarantee secrecy, integrity and authentication.
434a452 to
3b2ef9f
Compare
| MUST rebuild the message payload with the new cryptographic material, | ||
| and resend it in the 3rd message. | ||
|
|
||
| To guard against replay attacks, these message payloads are signed with the |
There was a problem hiding this comment.
Shouldn't you be signing the HandshakeHash + the message payload to bind the signature to a specific channel.
Signing with an X25519 keys can get annoying. Signal protocol does it but X25519 to ed25519 is not provided by many implementations.
There was a problem hiding this comment.
Yeah easier to make the static keys Ed25519 if you want this.
|
Have you taken a look at https://github.com/noiseprotocol/noise_sig_spec/blob/master/output/noise_sig.pdf ? |
|
So this has been largely a never ending back and forth about how to include the application layer identity key in the handshake. I am pretty firmly in the sign the channel binding token camp vs the underutilized noise signature extensions. |
yusefnapora
left a comment
There was a problem hiding this comment.
It's a good point that we're vulnerable to a MITM replay here. I also like the protobuf name changes and being explicit about when we're sending handshake messages.
This solution might be hard to implement in some cases. rust-libp2p uses a Noise crate called snow; I skimmed the docs for the HandshakeState struct, and it doesn't expose the remote ephemeral key. Not that we should base the spec on a particular library, but it might be a common choice to make the remote ephemeral key private in other Noise implementations.
If we assume we do have access to the remote ephemeral key, we wouldn't necessarily need to use the ephemeral key to produce the signature. We could just include it in the data to be signed by the identity key, along with the static key. A MITM could replay the message, but they would need the original private ephemeral key to complete the handshake and wouldn't be able to substitute a new ephemeral key.
Signing the handshake hash (and possibly the entire handshake payload) as @zmanian suggests should work. The only downside is that we can't fully validate the connection until each side sends at least one transport message, since the handshake hash isn't available until after the handshake completes.
You don't need to verify the remote ephemeral key directly, or do anything more than sign the handshake hash. It's explicitly designed for this purpose. See: |
|
Thanks @tarcieri, that makes sense. It sounds like the channel binding token is the simplest way to solve this. Can you help me clarify my thinking about this? I'm worried that I'm missing something. As I understand it, the first But there is an ephemeral contribution from the sender, which seems like it should prevent @raulk's scenario. A MITM could extract the handshake payload from an initial Is that right, or have I been thinking about this wrong? |
The handshake hash is computed when the handshake is fully completed, and authenticates the entire message sequence of the handshake. From the table in the aforementioned payload security section: This means at the end of the handshake you have the following security properties, with channel binding to a digital signature key:
If there are any discrepancies in the transcript between either side, the handshake hash won't match. |
|
Thanks again @tarcieri! I realized that my thinking about this attack scenario was flawed. I was imagining a MITM extracting the encrypted payload and transplanting it directly into another message, which I don't think is directly possible thanks to the senders ephemeral key contributing to the encryption. However, an active attacker could initiate an @raulk what do you think about the channel binding solution? I was hoping to figure something else out to avoid requiring an exchange of transport messages before the connection is sound... IMO if we're going to require that, we might as well just send all the libp2p data in the first transport message instead of using handshake payloads at all. |
|
@yusefnapora there's an inherent tradeoff between 0-RTT and a replay defense. There are a few solutions, either exchanging an ephemeral key in advance ("pre-keys"), or fancy new research like puncturable encryption. I'd suggest just completing the 1-RTT handshake to begin with, and once you have that working, investigating various options for 0-RTT. |
|
Gah, I just re-read my last comment and am second guessing myself yet again! To use the cleartext payload obtained from a speculative XX handshake, the attacker would also need the private static Noise key, or the signature of the static in the payload would be invalid. This seems like a good time to get a cup of tea and think for a bit 😄 |
|
After a chat with @yusefnapora, I suggest this way forward:
|
|
@zmanian @tarcieri @noot @ansermino @wildmolasses – WDYT? |
|
I am confused by what you mean by This could either mean In either of these constructions, the |
Yeah I guess a MAC would suffice since the we're already authenticating a hash.
Is it? If we don't authenticate the handshake hash with our symmetric key, how do we protect against a MITM? Unless we encrypt the handshake hash. Since both ends will have already derived the symmetric key, this is feasible. |
|
What I would reccomend is you transmit the signature of the handshake hash but not handshake hash itself. The receiver then verifies the signature against their instance of handshake hash and the public key. if the signature doesn't verify, drop the connection. |
|
you don't need send the hashshake hash because the receiver already has it if the channel is secure. |
|
@zmanian how is that different to what I specified in #210 (comment)?
Forgive me for lack of precision (not a cryptographer, and glad that we have @tarcieri here!), but |
|
I think the most helpful thing I can do here is make a pull request to your branch that would bring the suggested spec in line with my thoughts. |
|
@zmanian the diff of this PR no longer matches the discussion we've had here. I'm gonna close this PR and submit a new one capturing the above. You can then suggest your changes on the incoming one. |
The important thing to remember is the sender and reciever will only generate an identical HandshakeHash if a secure channel and nonreplayable channel exists between the sender and receiver. If the signature in NoiseSignedHandshakePayload fails, the connection should be immediately dropped. |
|
My suggestion is that earliest we send a signature is after the handshake has been completed. If we want to send the rest of the Payload in earlier handshake message, this seems fine to me. |
|
@zmanian yes, and that's precisely what I had suggested above. Maybe it's worth re-reading point 3 in this comment: #210 (comment). We might be going around in circles now; it will all become clearer once I post the spec update reflecting the discussion. |
|
I think we are in alignment as well. I just wanted to document it. |
|
Superseded by #234. Thanks for your input here, it's been carried over to that PR. |
fix@8218153972 [skip fix]
The current message data construction is not secure enough, as it is vulnerable to replay attacks by a MITM agent that records previous handshakes, and transplants old user data from those handshakes into new ones, as long as the static key hasn't changed.
We fix this vulnerability by introducing a signature over the whole message payload against the ephemeral session key. This "seals" the payload so that it's only valid for that exchange.
Also, this PR simplifies protobuf field naming.
Finally, we formalise in which Noise messages of IK and XX the message payload is to be shared, to guarantee secrecy, integrity and authentication.
CC @noot @ansermino @wildmolasses @burdges @kirushik @zmanian