def signature_validator(msg: rpc_pb2.Message) -> bool: """ Verify the message against the given public key. :param pubkey: the public key which signs the message. :param msg: the message signed. """ # Check if signature is attached if msg.signature == b"": logger.debug("Reject because no signature attached for msg: %s", msg) return False # Validate if message sender matches message signer, # i.e., check if `msg.key` matches `msg.from_id` msg_pubkey = deserialize_public_key(msg.key) if ID.from_pubkey(msg_pubkey) != msg.from_id: logger.debug( "Reject because signing key does not match sender ID for msg: %s", msg ) return False # First, construct the original payload that's signed by 'msg.key' msg_without_key_sig = rpc_pb2.Message( data=msg.data, topicIDs=msg.topicIDs, from_id=msg.from_id, seqno=msg.seqno ) payload = PUBSUB_SIGNING_PREFIX.encode() + msg_without_key_sig.SerializeToString() try: return msg_pubkey.verify(payload, msg.signature) except Exception: return False
def __init__( self, local_key_pair: KeyPair, secure_bytes_provider: Callable[[int], bytes] = default_secure_bytes_provider, ) -> None: self.local_private_key = local_key_pair.private_key self.local_peer = ID.from_pubkey(local_key_pair.public_key) self.secure_bytes_provider = secure_bytes_provider
async def handshake_outbound(self, conn: IRawConnection, remote_peer: ID) -> ISecureConn: noise_state = self.create_noise_state() read_writer = NoiseHandshakeReadWriter(conn, noise_state) noise_state.set_as_initiator() noise_state.start_handshake() handshake_state = noise_state.noise_protocol.handshake_state # Send msg#1, which is *not* encrypted. msg_1 = b"" await read_writer.write_msg(msg_1) # Read msg#2 from the remote, which contains the public key of the peer. msg_2 = await read_writer.read_msg() peer_handshake_payload = NoiseHandshakePayload.deserialize(msg_2) if handshake_state.rs is None: raise NoiseStateError( "something is wrong in the underlying noise `handshake_state`: " "we received and consumed msg#3, which should have included the" " remote static public key, but it is not present in the handshake_state" ) remote_pubkey = self._get_pubkey_from_noise_keypair(handshake_state.rs) if not verify_handshake_payload_sig(peer_handshake_payload, remote_pubkey): raise InvalidSignature remote_peer_id_from_pubkey = ID.from_pubkey( peer_handshake_payload.id_pubkey) if remote_peer_id_from_pubkey != remote_peer: raise PeerIDMismatchesPubkey( "peer id does not correspond to the received pubkey: " f"remote_peer={remote_peer}, " f"remote_peer_id_from_pubkey={remote_peer_id_from_pubkey}") # Send msg#3, which includes our encrypted payload and our noise static key. our_payload = self.make_handshake_payload() msg_3 = our_payload.serialize() await read_writer.write_msg(msg_3) if not noise_state.handshake_finished: raise HandshakeHasNotFinished( "handshake is done but it is not marked as finished in `noise_state`" ) transport_read_writer = NoiseTransportReadWriter(conn, noise_state) return SecureSession( local_peer=self.local_peer, local_private_key=self.libp2p_privkey, remote_peer=remote_peer_id_from_pubkey, remote_permanent_pubkey=remote_pubkey, is_initiator=True, conn=transport_read_writer, )
def test_id_from_public_key(): key_pair = create_new_key_pair() public_key = key_pair.public_key key_bin = public_key.serialize() algo = multihash.Func.sha2_256 mh_digest = multihash.digest(key_bin, algo) expected = ID(mh_digest.encode()) actual = ID.from_pubkey(public_key) assert actual == expected
async def run_handshake(self) -> None: """Raise `HandshakeFailure` when handshake failed.""" msg = make_exchange_message(self.local_private_key.get_public_key()) msg_bytes = msg.SerializeToString() encoded_msg_bytes = encode_fixedint_prefixed(msg_bytes) try: await self.write(encoded_msg_bytes) except RawConnError: raise HandshakeFailure("connection closed") try: remote_msg_bytes = await read_fixedint_prefixed(self.conn) except RawConnError: raise HandshakeFailure("connection closed") remote_msg = plaintext_pb2.Exchange() remote_msg.ParseFromString(remote_msg_bytes) received_peer_id = ID(remote_msg.id) # Verify if the receive `ID` matches the one we originally initialize the session. # We only need to check it when we are the initiator, because only in that condition # we possibly knows the `ID` of the remote. if self.is_initiator and self.remote_peer_id != received_peer_id: raise HandshakeFailure( "remote peer sent unexpected peer ID. " f"expected={self.remote_peer_id} received={received_peer_id}" ) # Verify if the given `pubkey` matches the given `peer_id` try: received_pubkey = deserialize_public_key( remote_msg.pubkey.SerializeToString() ) except ValueError: raise HandshakeFailure( f"unknown `key_type` of remote_msg.pubkey={remote_msg.pubkey}" ) except MissingDeserializerError as error: raise HandshakeFailure(error) peer_id_from_received_pubkey = ID.from_pubkey(received_pubkey) if peer_id_from_received_pubkey != received_peer_id: raise HandshakeFailure( "peer id and pubkey from the remote mismatch: " f"received_peer_id={received_peer_id}, remote_pubkey={received_pubkey}, " f"peer_id_from_received_pubkey={peer_id_from_received_pubkey}" ) # Nothing is wrong. Store the `pubkey` and `peer_id` in the session. self.remote_permanent_pubkey = received_pubkey # Only need to set peer's id when we don't know it before, # i.e. we are not the connection initiator. if not self.is_initiator: self.remote_peer_id = received_peer_id
async def test_create_secure_session(nursery): local_nonce = b"\x01" * NONCE_SIZE local_key_pair = create_new_key_pair(b"a") local_peer = ID.from_pubkey(local_key_pair.public_key) remote_nonce = b"\x02" * NONCE_SIZE remote_key_pair = create_new_key_pair(b"b") remote_peer = ID.from_pubkey(remote_key_pair.public_key) async with raw_conn_factory(nursery) as conns: local_conn, remote_conn = conns local_secure_conn, remote_secure_conn = None, None async def local_create_secure_session(): nonlocal local_secure_conn local_secure_conn = await create_secure_session( local_nonce, local_peer, local_key_pair.private_key, local_conn, remote_peer, ) async def remote_create_secure_session(): nonlocal remote_secure_conn remote_secure_conn = await create_secure_session( remote_nonce, remote_peer, remote_key_pair.private_key, remote_conn) async with trio.open_nursery() as nursery_1: nursery_1.start_soon(local_create_secure_session) nursery_1.start_soon(remote_create_secure_session) msg = b"abc" await local_secure_conn.write(msg) received_msg = await remote_secure_conn.read(MAX_READ_LEN) assert received_msg == msg
def test_peer_id_interop(): private_key_protobuf_bytes = base64.b64decode( PRIVATE_KEY_PROTOBUF_SERIALIZATION) private_key_protobuf = pb.PrivateKey() private_key_protobuf.ParseFromString(private_key_protobuf_bytes) private_key_data = private_key_protobuf.data private_key_impl = RSA.import_key(private_key_data) private_key = RSAPrivateKey(private_key_impl) public_key = private_key.get_public_key() peer_id = ID.from_pubkey(public_key) assert peer_id == EXPECTED_PEER_ID
async def test_create_secure_session(): local_nonce = b"\x01" * NONCE_SIZE local_key_pair = create_new_key_pair(b"a") local_peer = ID.from_pubkey(local_key_pair.public_key) remote_nonce = b"\x02" * NONCE_SIZE remote_key_pair = create_new_key_pair(b"b") remote_peer = ID.from_pubkey(remote_key_pair.public_key) local_conn = InMemoryConnection(local_peer, is_initiator=True) remote_conn = InMemoryConnection(remote_peer) local_pipe_task = asyncio.ensure_future( create_pipe(local_conn, remote_conn)) remote_pipe_task = asyncio.ensure_future( create_pipe(remote_conn, local_conn)) local_session_builder = create_secure_session(local_nonce, local_peer, local_key_pair.private_key, local_conn, remote_peer) remote_session_builder = create_secure_session(remote_nonce, remote_peer, remote_key_pair.private_key, remote_conn) local_secure_conn, remote_secure_conn = await asyncio.gather( local_session_builder, remote_session_builder) msg = b"abc" await local_secure_conn.write(msg) received_msg = await remote_secure_conn.read() assert received_msg == msg await asyncio.gather(local_secure_conn.close(), remote_secure_conn.close()) local_pipe_task.cancel() remote_pipe_task.cancel() await local_pipe_task await remote_pipe_task
async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn: noise_state = self.create_noise_state() noise_state.set_as_responder() noise_state.start_handshake() handshake_state = noise_state.noise_protocol.handshake_state read_writer = NoiseHandshakeReadWriter(conn, noise_state) # Consume msg#1. await read_writer.read_msg() # Send msg#2, which should include our handshake payload. our_payload = self.make_handshake_payload() msg_2 = our_payload.serialize() await read_writer.write_msg(msg_2) # Receive and consume msg#3. msg_3 = await read_writer.read_msg() peer_handshake_payload = NoiseHandshakePayload.deserialize(msg_3) if handshake_state.rs is None: raise NoiseStateError( "something is wrong in the underlying noise `handshake_state`: " "we received and consumed msg#3, which should have included the" " remote static public key, but it is not present in the handshake_state" ) remote_pubkey = self._get_pubkey_from_noise_keypair(handshake_state.rs) if not verify_handshake_payload_sig(peer_handshake_payload, remote_pubkey): raise InvalidSignature remote_peer_id_from_pubkey = ID.from_pubkey( peer_handshake_payload.id_pubkey) if not noise_state.handshake_finished: raise HandshakeHasNotFinished( "handshake is done but it is not marked as finished in `noise_state`" ) transport_read_writer = NoiseTransportReadWriter(conn, noise_state) return SecureSession( local_peer=self.local_peer, local_private_key=self.libp2p_privkey, remote_peer=remote_peer_id_from_pubkey, remote_permanent_pubkey=remote_pubkey, is_initiator=False, conn=transport_read_writer, )
def calculate_peer_id(self) -> PeerID: return PeerID.from_pubkey(self.public_key)
def node_peer_id(node_key_pair): return PeerID.from_pubkey(node_key_pair.public_key)
def peer_id(self) -> ID: return ID.from_pubkey(self.node_privkey.get_public_key())
def make_exchange_message(pubkey: PublicKey) -> plaintext_pb2.Exchange: pubkey_pb = crypto_pb2.PublicKey( key_type=pubkey.get_type().value, data=pubkey.to_bytes() ) id_bytes = ID.from_pubkey(pubkey).to_bytes() return plaintext_pb2.Exchange(id=id_bytes, pubkey=pubkey_pb)
async def run_handshake( local_peer: ID, local_private_key: PrivateKey, conn: IRawConnection, is_initiator: bool, remote_peer_id: ID, ) -> ISecureConn: """Raise `HandshakeFailure` when handshake failed.""" msg = make_exchange_message(local_private_key.get_public_key()) msg_bytes = msg.SerializeToString() read_writer = PlaintextHandshakeReadWriter(conn) try: await read_writer.write_msg(msg_bytes) except RawConnError as e: raise HandshakeFailure("connection closed") from e try: remote_msg_bytes = await read_writer.read_msg() except RawConnError as e: raise HandshakeFailure("connection closed") from e remote_msg = plaintext_pb2.Exchange() remote_msg.ParseFromString(remote_msg_bytes) received_peer_id = ID(remote_msg.id) # Verify if the receive `ID` matches the one we originally initialize the session. # We only need to check it when we are the initiator, because only in that condition # we possibly knows the `ID` of the remote. if is_initiator and remote_peer_id != received_peer_id: raise HandshakeFailure( "remote peer sent unexpected peer ID. " f"expected={remote_peer_id} received={received_peer_id}" ) # Verify if the given `pubkey` matches the given `peer_id` try: received_pubkey = deserialize_public_key(remote_msg.pubkey.SerializeToString()) except ValueError as e: raise HandshakeFailure( f"unknown `key_type` of remote_msg.pubkey={remote_msg.pubkey}" ) from e except MissingDeserializerError as error: raise HandshakeFailure() from error peer_id_from_received_pubkey = ID.from_pubkey(received_pubkey) if peer_id_from_received_pubkey != received_peer_id: raise HandshakeFailure( "peer id and pubkey from the remote mismatch: " f"received_peer_id={received_peer_id}, remote_pubkey={received_pubkey}, " f"peer_id_from_received_pubkey={peer_id_from_received_pubkey}" ) secure_conn = InsecureSession( local_peer=local_peer, local_private_key=local_private_key, remote_peer=received_peer_id, remote_permanent_pubkey=received_pubkey, is_initiator=is_initiator, conn=conn, ) # TODO: Store `pubkey` and `peer_id` to `PeerStore` return secure_conn
def generate_peer_id_from(key_pair: KeyPair) -> ID: public_key = key_pair.public_key return ID.from_pubkey(public_key)
def __init__( self, local_node_key: PrivateKey, eth2_config: Eth2Config, clock: Clock, chain: BaseBeaconChain, validator_api_port: int, client_identifier: str, p2p_maddr: Multiaddr, preferred_nodes: Collection[Multiaddr], bootstrap_nodes: Collection[Multiaddr], ) -> None: self._local_key_pair = create_new_key_pair(local_node_key.to_bytes()) self._eth2_config = eth2_config self._clock = clock self._chain = chain self._block_pool: Set[SignedBeaconBlock] = set() self._slashable_block_pool: Set[SignedBeaconBlock] = set() # FIXME: can we provide `p2p_maddr` as a default listening interface for `_mk_host`? peer_id = PeerID.from_pubkey(self._local_key_pair.public_key) if "p2p" in p2p_maddr: existing_peer_id = p2p_maddr.value_for_protocol("p2p") existing_p2p_maddr = Multiaddr(f"/p2p/{existing_peer_id}") self.logger.warning( "peer identity derived from local key pair %s overriding given identity %s", peer_id, existing_peer_id, ) p2p_maddr = p2p_maddr.decapsulate(existing_p2p_maddr) self._p2p_maddr = p2p_maddr.encapsulate(Multiaddr(f"/p2p/{peer_id}")) # TODO: persist metadata and handle updates... self._metadata_provider = lambda: MetaData.create() self._peer_updater, self._peer_updates = trio.open_memory_channel[ Tuple[PeerID, Any]](0) self._host = Host( self._local_key_pair, peer_id, self._accept_peer_updates, self._get_status, self._get_finalized_root_by_epoch, self._get_block_by_slot, self._get_block_by_root, self._metadata_provider, self._get_fork_digest, self._eth2_config, ) self._preferred_nodes = preferred_nodes self._bootstrap_nodes = bootstrap_nodes self._sync_notifier, self._sync_requests = trio.open_memory_channel[ SyncRequest](0) self._syncer = _mk_syncer() api_context = Context( client_identifier, eth2_config, self._syncer, self._chain, self._clock, _mk_block_broadcaster(self), ) self.validator_api_port = validator_api_port self._validator_api_server = _mk_validator_api_server( self.validator_api_port, api_context)