Skip to content
This repository has been archived by the owner on May 26, 2022. It is now read-only.

use a stateless reset key derived from the private key #122

Merged
merged 4 commits into from
Mar 25, 2020

Conversation

marten-seemann
Copy link
Collaborator

@marten-seemann marten-seemann commented Mar 14, 2020

Fixes #121.

When a QUIC endpoint loses state (e.g. due to a reboot or a crash), it won't be able to decrypt packets that arrive for connection that it previously handled. QUIC uses a stateless reset to allow the peer to quickly detect this condition (and not having to rely on a lengthy connection timeout).

From the outside, a QUIC stateless reset packet looks like any other (Short Header) QUIC packet. An endpoint recognizes a stateless reset by comparing the last 16 bytes of the encrypted packet to a stateless reset token communicated earlier during the connection.

This token can't be tied to any connection state. Instead, it needs to be derived from values that survive a crash of the node. It therefore makes sense to derive it from the node's private key.

transport.go Outdated
@@ -2,9 +2,13 @@ package libp2pquic

import (
"context"
"crypto/sha256"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually use https://github.com/minio/sha256-simd for performance.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think performance is critical here, since the only time we calculate a hash is when NewTransport is called. I'm ok with changing it to keep things consistent though.

This will be obsoleted with libp2p/go-libp2p-core#131 anyway.

transport.go Outdated
if err != nil {
return nil, err
}
keyReader := hkdf.Expand(sha256.New, keyBytes, []byte("libp2p quic stateless reset key"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's define the info as a constant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I don't think we can do this as the key may not be uniformly random. I think we need the "extract" step (or need to call New)

I wonder if we should just define some form of "DeriveSecretKey(info)` function on our secret key interface. That way, we have a standard way to do this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Let me prepare a PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Stebalien Can we move forward with this PR without resolving libp2p/go-libp2p-core#131 first? For the stateless reset token, the only thing that matter is that the peer has any was of deriving a key. No interoperability required.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: if we do that, you'll need to update this patch to call hkdf.New instead of hkdf.Expand.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Stebalien Done.

transport.go Outdated
if err != nil {
return nil, err
}
keyReader := hkdf.New(sha256.New, keyBytes, nil, []byte("libp2p quic stateless reset key"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Let's make the "info" a variable/constant (private).
  2. Let's add a test to make sure we don't accidentally change it. That will cause silent breakage that won't be caught except in interop tests (which we need anyways but still).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Done.
  2. I'm not convinced that's necessary. Stateless resets are an optimization, and the worst case behavior in the absence of one is a connection timeout.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's even worse. That's a performance bug that we won't catch in any tests because it'll "just work" even if we break it.

@marten-seemann marten-seemann merged commit 506057f into master Mar 25, 2020
@Stebalien Stebalien deleted the set-stateless-reset-key branch March 25, 2020 19:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

set a stateless reset token key
2 participants