def test_official_auth_header_packet_preparation(tag, auth_tag, id_nonce, encoded_message, local_private_key, auth_response_key, encryption_key, ephemeral_public_key, auth_message_rlp): message_type_id = encoded_message[0] message_type = default_message_type_registry[message_type_id] message = rlp.decode(encoded_message[1:], message_type) assert message.to_bytes() == encoded_message id_nonce_signature = V4IdentityScheme.create_id_nonce_signature( id_nonce=id_nonce, ephemeral_public_key=ephemeral_public_key, private_key=local_private_key, ) packet = AuthHeaderPacket.prepare( tag=tag, auth_tag=auth_tag, id_nonce=id_nonce, message=message, initiator_key=encryption_key, id_nonce_signature=id_nonce_signature, auth_response_key=auth_response_key, enr=None, ephemeral_public_key=ephemeral_public_key, ) packet_wire_bytes = packet.to_wire_bytes() assert packet_wire_bytes == auth_message_rlp
def test_auth_header_preparation_without_enr(tag, auth_tag, id_nonce, initiator_key, auth_response_key, ephemeral_public_key): message = PingMessage( request_id=5, enr_seq=1, ) id_nonce_signature = b"\x00" * 32 packet = AuthHeaderPacket.prepare( tag=tag, auth_tag=auth_tag, id_nonce=id_nonce, message=message, initiator_key=initiator_key, id_nonce_signature=id_nonce_signature, auth_response_key=auth_response_key, enr=None, ephemeral_public_key=ephemeral_public_key ) decrypted_auth_response = aesgcm_decrypt( key=auth_response_key, nonce=ZERO_NONCE, cipher_text=packet.auth_header.encrypted_auth_response, authenticated_data=b"", ) decoded_auth_response = rlp.decode(decrypted_auth_response) assert is_list_like(decoded_auth_response) and len(decoded_auth_response) == 3 assert decoded_auth_response[0] == int_to_big_endian(AUTH_RESPONSE_VERSION) assert decoded_auth_response[1] == id_nonce_signature assert decoded_auth_response[2] == []
def test_oversize_auth_header_packet_encoding(): auth_header = AuthHeader( auth_tag=b"\x00" * NONCE_SIZE, auth_scheme_name=AUTH_SCHEME_NAME, ephemeral_pubkey=b"\x00" * 32, encrypted_auth_response=32, ) header_size = len(rlp.encode(auth_header)) encrypted_message_size = MAX_PACKET_SIZE - TAG_SIZE - header_size + 1 encrypted_message = b"\x00" * encrypted_message_size packet = AuthHeaderPacket( tag=b"\x00" * TAG_SIZE, auth_header=auth_header, encrypted_message=encrypted_message, ) with pytest.raises(ValidationError): packet.to_wire_bytes()
def test_auth_header_preparation(tag, auth_tag, id_nonce, initiator_key, auth_response_key, ephemeral_public_key): enr = ENR( sequence_number=1, signature=b"", kv_pairs={ b"id": b"v4", b"secp256k1": b"\x02" * 33, } ) message = PingMessage( request_id=5, enr_seq=enr.sequence_number, ) id_nonce_signature = b"\x00" * 32 packet = AuthHeaderPacket.prepare( tag=tag, auth_tag=auth_tag, id_nonce=id_nonce, message=message, initiator_key=initiator_key, id_nonce_signature=id_nonce_signature, auth_response_key=auth_response_key, enr=enr, ephemeral_public_key=ephemeral_public_key ) assert packet.tag == tag assert packet.auth_header.auth_tag == auth_tag assert packet.auth_header.id_nonce == id_nonce assert packet.auth_header.auth_scheme_name == AUTH_SCHEME_NAME assert packet.auth_header.ephemeral_public_key == ephemeral_public_key decrypted_auth_response = aesgcm_decrypt( key=auth_response_key, nonce=ZERO_NONCE, cipher_text=packet.auth_header.encrypted_auth_response, authenticated_data=b"", ) decoded_auth_response = rlp.decode(decrypted_auth_response) assert is_list_like(decoded_auth_response) and len(decoded_auth_response) == 3 assert decoded_auth_response[0] == int_to_big_endian(AUTH_RESPONSE_VERSION) assert decoded_auth_response[1] == id_nonce_signature assert ENR.deserialize(decoded_auth_response[2]) == enr decrypted_message = aesgcm_decrypt( key=initiator_key, nonce=auth_tag, cipher_text=packet.encrypted_message, authenticated_data=tag, ) assert decrypted_message[0] == message.message_type assert rlp.decode(decrypted_message[1:], PingMessage) == message
def complete_handshake(self, response_packet: Packet) -> HandshakeResult: if not self.is_response_packet(response_packet): raise ValueError( f"Packet {response_packet} is not the expected response packet" ) if not isinstance(response_packet, WhoAreYouPacket): raise TypeError( "Invariant: Only WhoAreYou packets are valid responses") who_are_you_packet = response_packet # compute session keys ( ephemeral_private_key, ephemeral_public_key, ) = self.identity_scheme.create_handshake_key_pair() remote_public_key_object = PublicKey.from_compressed_bytes( self.remote_enr.public_key) remote_public_key_uncompressed = remote_public_key_object.to_bytes() session_keys = self.identity_scheme.compute_session_keys( local_private_key=ephemeral_private_key, remote_public_key=remote_public_key_uncompressed, local_node_id=self.local_enr.node_id, remote_node_id=self.remote_node_id, id_nonce=who_are_you_packet.id_nonce, is_locally_initiated=True, ) # prepare response packet id_nonce_signature = self.identity_scheme.create_id_nonce_signature( id_nonce=who_are_you_packet.id_nonce, ephemeral_public_key=ephemeral_public_key, private_key=self.local_private_key, ) if who_are_you_packet.enr_sequence_number < self.local_enr.sequence_number: enr = self.local_enr else: enr = None auth_header_packet = AuthHeaderPacket.prepare( tag=self.tag, auth_tag=get_random_auth_tag(), id_nonce=who_are_you_packet.id_nonce, message=self.initial_message, initiator_key=session_keys.encryption_key, id_nonce_signature=id_nonce_signature, auth_response_key=session_keys.auth_response_key, enr=enr, ephemeral_public_key=ephemeral_public_key, ) return HandshakeResult( session_keys=session_keys, enr=None, message=None, auth_header_packet=auth_header_packet, )
def decrypt_and_validate_auth_response( self, auth_header_packet: AuthHeaderPacket, auth_response_key: AES128Key, id_nonce: IDNonce, ) -> Optional[ENR]: try: id_nonce_signature, enr = auth_header_packet.decrypt_auth_response( auth_response_key) except DecryptionError as error: raise HandshakeFailure( "Unable to decrypt auth response") from error except ValidationError as error: raise HandshakeFailure("Invalid auth response content") from error # validate ENR if present if enr is None: if self.remote_enr is None: raise HandshakeFailure("Peer failed to send their ENR") else: current_remote_enr = self.remote_enr else: try: enr.validate_signature() except ValidationError as error: raise HandshakeFailure( "ENR in auth response contains invalid signature" ) from error if self.remote_enr is not None: if enr.sequence_number <= self.remote_enr.sequence_number: raise HandshakeFailure( "ENR in auth response is not newer than what we already have" ) if enr.node_id != self.remote_node_id: raise HandshakeFailure( f"ENR received from peer belongs to different node ({encode_hex(enr.node_id)} " f"instead of {encode_hex(self.remote_node_id)})") current_remote_enr = enr try: self.identity_scheme.validate_id_nonce_signature( signature=id_nonce_signature, id_nonce=id_nonce, ephemeral_public_key=auth_header_packet.auth_header. ephemeral_public_key, public_key=current_remote_enr.public_key, ) except ValidationError as error: raise HandshakeFailure( "Invalid id nonce signature in auth response") from error return enr
def decrypt_and_validate_message(self, auth_header_packet: AuthHeaderPacket, decryption_key: AES128Key) -> BaseMessage: try: return auth_header_packet.decrypt_message(decryption_key) except DecryptionError as error: raise HandshakeFailure( "Failed to decrypt message in AuthHeader packet with newly established session keys" ) from error except ValidationError as error: raise HandshakeFailure("Received invalid message") from error
def test_auth_header_packet_encoding_decoding(tag, auth_tag, ephemeral_pubkey, encrypted_auth_response, encrypted_message_size): auth_header = AuthHeader( auth_tag=auth_tag, auth_scheme_name=AUTH_SCHEME_NAME, ephemeral_pubkey=ephemeral_pubkey, encrypted_auth_response=encrypted_auth_response, ) encrypted_message = b"\x00" * encrypted_message_size original_packet = AuthHeaderPacket( tag=tag, auth_header=auth_header, encrypted_message=encrypted_message, ) encoded_packet = original_packet.to_wire_bytes() decoded_packet = decode_message_packet(encoded_packet) assert isinstance(decoded_packet, AuthHeaderPacket) assert decoded_packet == original_packet
def test_auth_header_message_decryption(tag, auth_tag, id_nonce, initiator_key, auth_response_key, ephemeral_public_key, enr, message): id_nonce_signature = b"\x00" * 32 packet = AuthHeaderPacket.prepare( tag=tag, auth_tag=auth_tag, id_nonce=id_nonce, message=message, initiator_key=initiator_key, id_nonce_signature=id_nonce_signature, auth_response_key=auth_response_key, enr=enr, ephemeral_public_key=ephemeral_public_key) decrypted_message = packet.decrypt_message(initiator_key) assert decrypted_message == message
def test_invalid_auth_header_decryption_with_wrong_key(tag, auth_tag, id_nonce, initiator_key, ephemeral_public_key, message): id_nonce_signature = b"\x00" * 32 encryption_key = b"\x00" * AES128_KEY_SIZE decryption_key = b"\x11" * AES128_KEY_SIZE packet = AuthHeaderPacket.prepare( tag=tag, auth_tag=auth_tag, id_nonce=id_nonce, message=message, initiator_key=initiator_key, id_nonce_signature=id_nonce_signature, auth_response_key=encryption_key, enr=None, ephemeral_public_key=ephemeral_public_key) with pytest.raises(DecryptionError): packet.decrypt_auth_response(decryption_key)
def test_auth_header_decryption_without_enr(tag, auth_tag, id_nonce, initiator_key, auth_response_key, ephemeral_public_key, message): id_nonce_signature = b"\x00" * 32 packet = AuthHeaderPacket.prepare( tag=tag, auth_tag=auth_tag, id_nonce=id_nonce, message=message, initiator_key=initiator_key, id_nonce_signature=id_nonce_signature, auth_response_key=auth_response_key, enr=None, ephemeral_public_key=ephemeral_public_key) recovered_id_nonce_signature, recovered_enr = packet.decrypt_auth_response( auth_response_key) assert recovered_id_nonce_signature == id_nonce_signature assert recovered_enr is None