Skip to content
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

multi: refactor and dynamic auth data #27

Merged
merged 5 commits into from
Feb 15, 2022

Conversation

ellemouton
Copy link
Member

@ellemouton ellemouton commented Jan 11, 2022

This PR does the following:

  1. add a test for the current noise handshake logic
  2. refactor's the noise handshakestate logic to be more generic. This makes any changes a bit easier and also paves the way for us to add the second _KK pattern handshake when the time comes (a test for this is added too).
  3. Handshake versioning logic.
  4. Handshake version 1 which uses a dynamic, length prefixed payload in act 2.

mailbox/noise.go Outdated Show resolved Hide resolved
mailbox/noise.go Outdated
@@ -685,7 +713,10 @@ func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) {

authPayload := b.EncryptAndHash(payload[:])

actTwo[0] = HandshakeVersion
// The responder will always send its preferred handshake version in
// the hopes that the initiator will then upgrade to this version in
Copy link
Member

Choose a reason for hiding this comment

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

Upgrade to the version in act 2? I think this assumes a lot w.r.t how new versions will be rolled out all together.

Copy link
Member Author

Choose a reason for hiding this comment

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

The reasoning behind this flow is that in our case, the initiator (TW) will always be updated before the responder (Litd) . And since the initiator is also the one that sends the first message (act 1), it makes this negotiation a bit tricky. So we went with this approach:

  • Act 1: client sends its lowest acceptable version in act 1 (client cant send its max version here since then every new handshake version would be a breaking change and everyone would need to upgrade their Lit nodes)
  • Act 2: server sends its max version. Client will get this and will know which version the server will be happy with and will adapt to that version.
  • Act 3: client sends back what ever the server sent in act 2.

Let me know if there is a better way 👍

mailbox/noise.go Outdated Show resolved Hide resolved
mailbox/noise.go Outdated Show resolved Hide resolved
mailbox/noise.go Outdated Show resolved Hide resolved
@ellemouton ellemouton marked this pull request as draft January 27, 2022 16:24
@ellemouton ellemouton changed the title [WIP] Dynamic auth data method 2 [WIP] Dynamic auth data Jan 27, 2022
@lightninglabs-deploy
Copy link

@ellemouton, remember to re-request review from reviewers when ready

@ellemouton ellemouton marked this pull request as ready for review January 31, 2022 14:48
@ellemouton ellemouton changed the title [WIP] Dynamic auth data multi: refactor and dynamic auth data Jan 31, 2022
@ellemouton
Copy link
Member Author

Cool, I have changed this PR up quite a lot. Pls see the updated description 👍
basically first did a big refactor of the Noise logic so that the handshake patterns are executed in series. This also paves the way for us to easily use the KK_ pattern for the second handshake when the time comes. The refactor also made it easier to add the logic for handshake 1.
keen to hear some feedback on this approach.

Copy link
Member

@guggero guggero left a comment

Choose a reason for hiding this comment

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

Did a first round to familiarize myself with the refactor.
Amazing work on this, it really looks great! And I think I learned a thing or two about the noise handshake 💯

Will need a second round to catch all the details. But in the meantime I'll submit my comments.
The linter also has a few complaints by the way.

mailbox/noise_test.go Outdated Show resolved Hide resolved
mailbox/noise_test.go Show resolved Hide resolved
mailbox/grpc_noise_conn.go Outdated Show resolved Hide resolved
mailbox/grpc_noise_conn.go Outdated Show resolved Hide resolved
mailbox/noise_patterns.go Outdated Show resolved Hide resolved
mailbox/noise.go Outdated Show resolved Hide resolved
mailbox/noise_test.go Show resolved Hide resolved
mailbox/grpc_noise_conn.go Outdated Show resolved Hide resolved
mailbox/noise.go Show resolved Hide resolved
mailbox/noise.go Show resolved Hide resolved
Copy link
Member Author

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

Thanks for taking a look @guggero 🚀 updated 👍

mailbox/grpc_noise_conn.go Outdated Show resolved Hide resolved
mailbox/grpc_noise_conn.go Outdated Show resolved Hide resolved
Copy link
Member

@guggero guggero left a comment

Choose a reason for hiding this comment

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

Great refactor, LGTM 🎉

mailbox/grpc_noise_conn.go Outdated Show resolved Hide resolved
@ellemouton ellemouton force-pushed the dynamicActTwo branch 3 times, most recently from ffa3dab to 17d4d01 Compare February 9, 2022 07:47
This commit adds TestXXHandshake test which just ensures that the
current handshake logic works correctly. The reason for adding this test
now is so that we can use it to test a refactor too.
GBN reads an entire packet at once and so does not really care about the
number of bytes that the caller is expecting to read. To allow the
caller to read a payload byte by byte, we can use a bytes buffer which
we will populate with the full gbn payload and then subsequent read
calls will read from the buffer until it is empty.
@Roasbeef
Copy link
Member

Failing the linter rn:

Error: mailbox/noise_test.go:395:2: S1023: redundant `return` statement (gosimple)
level=info msg="Memory: 199 samples, avg is 1597.2MB, max is 3621.1MB"
level=info msg="Execution took 19.805837[26](https://github.com/lightninglabs/lightning-node-connect/runs/5121684068?check_suite_focus=true#step:6:26)3s"
	return
	^
Error: mailbox/noise_test.go:399:2: S1023: redundant `return` statement (gosimple)
	return
	^
make: *** [Makefile:83: lint] Error 1
Error: Process completed with exit code 2.

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

Excellent work!

Have always wanted to do such a refactor, but back then we didn't have any other handshakes we were using on the LN layer. Completed an initial pass, and have another queue'd up for tomorrow. Solid tests coverage, just have a few other things I want to examine more closely.

pk2, err := btcec.NewPrivateKey(btcec.S256())
require.NoError(t, err)

// Create a password that will be used to mask the first ephemeral key.
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

I guess this is from a slightly higher layer of abstraction though, the gRPC level.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah i wanted to write tests that actually use the ServerHandshake and ClientHandshake functions.
afaict, we dont use the code in tcp_noise_conn.go and tcp_noise_listener.go anywhere?

Copy link
Member

Choose a reason for hiding this comment

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

afaict, we dont use the code in tcp_noise_conn.go and tcp_noise_listener.go anywhere?

Correct, but we can use them when we eventually add the pure TCP version of LNC, for nodes that don't really need the NAT traversal component.

@@ -298,19 +303,38 @@ type handshakeState struct {

remoteStatic *btcec.PublicKey // nolint
remoteEphemeral *btcec.PublicKey // nolint

password []byte
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps this should live in something more PAKE specific?

Same comment re the payloads below.

Copy link
Member Author

Choose a reason for hiding this comment

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

meaning embed a new struct here? I would say only the password is PAKE specific? the payloads are part of any Noise handshake.

}

protocolName := fmt.Sprintf(
"Noise_%s_%s_%s_%s", pattern.Name, dhFn, cipherFn, hashFn,
Copy link
Member

Choose a reason for hiding this comment

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

Need to make sure we're not making a backwards incompatible change here, as this is used to generate the very first handshake digest.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah def. I have tested running a new client (which runs off the refactor) agains a server with the previous code and that works 👍

return fmt.Errorf("unknown act number: %d", mp.ActNum)
}

_, err := buff.Write(h.EncryptAndHash(payload))
Copy link
Member

Choose a reason for hiding this comment

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

Getting through the PR still, but marking here to ensure that the other party is actually able to read this information as is. As right now, I don't see the existing encrypted length prefix that we use (that includes a MAC) as normal.

Copy link
Member Author

Choose a reason for hiding this comment

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

for version 0, currently there is a fixed 500 bytes for the Act 2 payload that must be written/read by both sides (see the ActTwoPayloadSize variable on L395) and then we do write the encrypted length prefix on L404

mailbox/noise.go Outdated
// the MessagePattern Tokens are processed and then any extra cipher text is
// decrypted as defined by the handshake version.
func (h *handshakeState) readMsgPattern(r io.Reader, mp MessagePattern) error {

Copy link
Member

Choose a reason for hiding this comment

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

Stray new line.

mailbox/noise.go Outdated
for _, token := range tokens {
switch token {
case e:
e, err := btcec.NewPrivateKey(btcec.S256())
Copy link
Member

Choose a reason for hiding this comment

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

We still want that ephemeralGen function here, so we can do things like test vectors eventually and also make the tests more determinstic.

Copy link
Member Author

Choose a reason for hiding this comment

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

ok cool yeah that makes sense. Will add it back 👍

},
}

// KKPattern represents the KK Noise pattern in which both initiator
Copy link
Member

Choose a reason for hiding this comment

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

Noice, also this works as both sides already know which mailbox ID to be expecting, so they know which keys to expect.

me Token = "me"
)

// ActNum is the
Copy link
Member

Choose a reason for hiding this comment

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

Incomplete sentence.

Copy link
Member Author

Choose a reason for hiding this comment

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

haha will fix 👍

btw, what are your thoughts on having this ActNum variable added to the message patterns in the first place? cause technically we can just get the number from the index of the message pattern in the handshake pattern. So this isnt strictly necessary... But just added it to make the code a bit more readable

mailbox/noise.go Show resolved Hide resolved
mailbox/noise.go Show resolved Hide resolved
Refactor the noise handshakestate code. This commit makes the code more
reusable and takes advantage of the fact that the message pattern token
writing and reading are completely separate from the payload writing and
reading. With this commit, we also have all the cryto code necessary for
when we add the `KK` pattern for the second handshake. A test is added
for the KK pattern here too.
In this commit, the idea of a min supported and max supported handshake
version is introduced so that client and server can negotiate which
version they will use. The client will always send the minimum version
they support in act 1. The server will then send its maximum supported
version back in act 2. This is done due to the fact that for TW, the
client will always be the first to upgrade versions. What ever the
server sends back in act 2 is the version they will continue with.

This commit also adds a unit test to test if a handshake is successfull
given different version congigurations.
With handshake version 1, the auth data is sent in a dynamic field in
act 2 to allow for large authdata.
Copy link
Member Author

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

Thanks for the review @Roasbeef 🚀 updated 👍

pk2, err := btcec.NewPrivateKey(btcec.S256())
require.NoError(t, err)

// Create a password that will be used to mask the first ephemeral key.
Copy link
Member Author

Choose a reason for hiding this comment

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

yeah i wanted to write tests that actually use the ServerHandshake and ClientHandshake functions.
afaict, we dont use the code in tcp_noise_conn.go and tcp_noise_listener.go anywhere?

return fmt.Errorf("unknown act number: %d", mp.ActNum)
}

_, err := buff.Write(h.EncryptAndHash(payload))
Copy link
Member Author

Choose a reason for hiding this comment

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

for version 0, currently there is a fixed 500 bytes for the Act 2 payload that must be written/read by both sides (see the ActTwoPayloadSize variable on L395) and then we do write the encrypted length prefix on L404

mailbox/noise.go Outdated
for _, token := range tokens {
switch token {
case e:
e, err := btcec.NewPrivateKey(btcec.S256())
Copy link
Member Author

Choose a reason for hiding this comment

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

ok cool yeah that makes sense. Will add it back 👍

me Token = "me"
)

// ActNum is the
Copy link
Member Author

Choose a reason for hiding this comment

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

haha will fix 👍

btw, what are your thoughts on having this ActNum variable added to the message patterns in the first place? cause technically we can just get the number from the index of the message pattern in the handshake pattern. So this isnt strictly necessary... But just added it to make the code a bit more readable

@@ -298,19 +303,38 @@ type handshakeState struct {

remoteStatic *btcec.PublicKey // nolint
remoteEphemeral *btcec.PublicKey // nolint

password []byte
Copy link
Member Author

Choose a reason for hiding this comment

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

meaning embed a new struct here? I would say only the password is PAKE specific? the payloads are part of any Noise handshake.

}

protocolName := fmt.Sprintf(
"Noise_%s_%s_%s_%s", pattern.Name, dhFn, cipherFn, hashFn,
Copy link
Member Author

Choose a reason for hiding this comment

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

yeah def. I have tested running a new client (which runs off the refactor) agains a server with the previous code and that works 👍

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM 🍭

@Roasbeef
Copy link
Member

With the new handshake (Noise_KK), we'll need to do a later update to abstract away storage/retrieval for long term public key, and also to have the client detect when it no longer needs to use the pass phrase for pairing.

@ellemouton ellemouton merged commit 44032fe into lightninglabs:master Feb 15, 2022
@ellemouton ellemouton deleted the dynamicActTwo branch February 15, 2022 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants