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

Implement P2P auth handshake #96

Merged
merged 1 commit into from
Sep 27, 2017
Merged

Conversation

gsalgado
Copy link
Contributor

@gsalgado gsalgado commented Sep 22, 2017

I tried to use coincurve for the ecdh exchange, but apparently it is not compatible with geth's implementation, so had to resort to pyca/cryptography.

Copy link
Member

@pipermerriam pipermerriam left a comment

Choose a reason for hiding this comment

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

My brain hurts a little trying to ingest all of this stuff.

evm/p2p/auth.py Outdated
initiator = HandshakeInitiator(remote, privkey)
reader, writer = yield from initiator.connect()

initiator_nonce = keccak(int_to_big_endian(random.randint(0, 2 ** 256 - 1)))
Copy link
Member

Choose a reason for hiding this comment

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

Is it safe for us to be using random.random? My understanding has always been no.

With pycryptodome we can use Crypto.Random.get_random_bytes(n) which I understand to be appropriate for key generation. thoughts?

evm/p2p/auth.py Outdated

ephemeral_pubkey, responder_nonce, auth_ack = initiator.decode_auth_ack_message(ack_data)
aes_secret, mac_secret, egress_mac, ingress_mac = initiator.derive_secrets(
initiator_nonce, responder_nonce, ephemeral_pubkey, auth_init, auth_ack)
Copy link
Member

Choose a reason for hiding this comment

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

formatting nitpick

I find this style hard to read. Can you drop these all onto their own lines and maybe even call it with keyword arguments to ensure that argument ordering doesn't ever get subtly borked.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, but as I said below I'm moving this to another branch

evm/p2p/auth.py Outdated
aes_secret, mac_secret, egress_mac, ingress_mac = initiator.derive_secrets(
initiator_nonce, responder_nonce, ephemeral_pubkey, auth_init, auth_ack)

peer = Peer(remote, privkey, reader, writer, aes_secret,
Copy link
Member

Choose a reason for hiding this comment

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

I've always been a bit frightened of function calls and object instantaition that uses more than 3-4 positional arguments given the ease of transposing them or just plain getting the order wrong. Thoughts on converting this to use keyword arguments?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'm also a bit weary of them so I'm happy to do that. But I'll move this whole function and the creation of the Peer class to another branch I have, as this really belongs there and will mean less conflicts when I rebase the other branch after we're done with the code review

evm/p2p/auth.py Outdated
except AuthenticationError:
eph_pubkey, nonce, version = decode_ack_eip8(ciphertext, self.privkey)
self.got_eip8_auth = True
auth_ack = ciphertext
Copy link
Member

Choose a reason for hiding this comment

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

this assignment struck me as strange.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch; it makes no sense for this method to do this and then return it afterwards, as this is the very argument passed into the method. Fixed so it no longer returns auth_ack and dropped the assignment

evm/p2p/auth.py Outdated
# S || H(ephemeral-pubk) || pubk || nonce || 0x0
flag = 0x0
auth_message = S + keccak(self.ephemeral_pubkey) + self.pubkey + \
nonce + ascii_chr(flag)
Copy link
Member

Choose a reason for hiding this comment

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

The ascii_chr(flag seems like strange indirection for the literal b'\x00'.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, fixed

evm/p2p/auth.py Outdated
if self.got_eip8_auth:
# The EIP-8 version has an authenticated length prefix.
prefix = struct.pack('>H', len(ack_message) + ecies.encrypt_overhead_length)
auth_ack = ecies.encrypt(ack_message, self.remote.pubkey, shared_mac_data=prefix)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe rename to suffix to avoid the overwriting of this value on the next line.

evm/p2p/ecies.py Outdated
mode = modes.CTR
curve = ec.SECP256K1()
# ECIES using AES256 and HMAC-SHA-256-32
key_len = 32
Copy link
Member

Choose a reason for hiding this comment

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

Thoughts on renaming this and the others above it to KEY_LEN?

I find that casing helps distinguish top level module variables from vars in the local context.

encrypted_auth_msg_len = auth_msg_len + encrypt_overhead_length

# Length of encrypted pre-EIP-8 handshake reply
encrypted_auth_ack_len = auth_ack_len + encrypt_overhead_length
Copy link
Member

Choose a reason for hiding this comment

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

Can all of these be changed to CAP_CASE so that it's a bit easier to tell they are constants when they are used in other modules.

@@ -81,7 +81,7 @@ def __init__(self, pubkey, address):
@classmethod
def from_uri(cls, uri):
ip, port, pubkey = host_port_pubkey_from_uri(uri)
return cls(pubkey, Address(ip.decode(), int(port)))
return cls(pubkey, Address(ip.decode(), int(port), int(port)))
Copy link
Member

Choose a reason for hiding this comment

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

Just one line but this seems like it could get extracted and merged separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was left by accident, I'll remove it

evm/p2p/utils.py Outdated
def sxor(s1, s2):
if len(s1) != len(s2):
raise ValueError("Cannot sxor strings of different length")
return b''.join(ascii_chr(safe_ord(a) ^ safe_ord(b)) for a, b in zip(s1, s2))
Copy link
Member

Choose a reason for hiding this comment

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

I don't know if this is much better in terms of readability. but...

bytes((itertools.starmap(operator.xor, zip(a, b))))
# or just
bytes(x ^ y for x, y in zip(a, b))

The ascii_chr and safe_ord are python2.7 compat right? Those can be dropped?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, leftover from stuff I copied from pydevp2p

@pipermerriam
Copy link
Member

This should be rebased now that https://github.com/pipermerriam/py-evm/pull/95 is merged.

@gsalgado
Copy link
Contributor Author

Rebased and I think I've addressed all your comments, so should be ready for another pass

@ethereum ethereum deleted a comment from gsalgado Sep 27, 2017
@ethereum ethereum deleted a comment from gsalgado Sep 27, 2017
Copy link
Member

@pipermerriam pipermerriam left a comment

Choose a reason for hiding this comment

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

Good to merge as is, but I did leave a few more comments.

evm/p2p/auth.py Outdated
S = self.ephemeral_privkey.sign_msg_hash(secret_xor_nonce).to_bytes()

# S || H(ephemeral-pubk) || pubk || nonce || 0x0
auth_message = S + keccak(self.ephemeral_pubkey.to_bytes()) + self.pubkey.to_bytes() + \
Copy link
Member

Choose a reason for hiding this comment

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

I've found this format a bit more readable than using \ to split across multiple lines.

auth_message = (
    S +
    keccak(self.ephemeral_pubkey.to_bytes()) +
    self.pubkey.to_bytes() + 
    nonce +
    b'\x00'
)

Or:

auth_message = b''.join((
    S,
    keccak(self.ephemeral_pubkey.to_bytes()),
    self.pubkey.to_bytes(),
    nonce,
    b'\x00',
))

privkey = ecies.generate_privkey()
ciphertext = ecies.encrypt(msg, privkey.public_key)
decrypted = ecies.decrypt(ciphertext, privkey)
assert decrypted == msg
Copy link
Member

Choose a reason for hiding this comment

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

This is the type of test that I've historically used hypothesis for (things that have a round trip operation like encrypt/decrypt, encode/decode)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I see!

assert hmac == tag_expected


# FIXME: Document those values; this test was lifted from pydevp2p
Copy link
Member

Choose a reason for hiding this comment

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

Couple of remaining FIXME left in. Fine to fix them later but it might be good to at least provide a link here for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Included the links in the comments

@@ -0,0 +1,4 @@
def sxor(s1, s2):
if len(s1) != len(s2):
Copy link
Member

Choose a reason for hiding this comment

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

Since this is used in cryptographic operations, should this use a constant time compare?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Isn't that the case already, since we're comparing their lengths?

Copy link
Member

Choose a reason for hiding this comment

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

I should not review code late at night. 👍

@@ -10,4 +10,9 @@ def keccak(value):
return keccak_256(value).digest()


# FIXME: this is a terrible name.
def keccak_updateable(value):
Copy link
Member

Choose a reason for hiding this comment

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

Thinking that it might be better to just use keccak_256 directly from the sha3 library instead of this proxy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, I'll do that

@gsalgado gsalgado merged commit 52409ec into ethereum:master Sep 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants