f8739268e2
TODO: Add a buffer to read only `n` bytes in `read(n)`
168 lines
6.1 KiB
Python
168 lines
6.1 KiB
Python
from abc import ABC, abstractmethod
|
|
|
|
from noise.connection import Keypair as NoiseKeypair
|
|
from noise.connection import NoiseConnection as NoiseState
|
|
|
|
from libp2p.crypto.ed25519 import Ed25519PublicKey
|
|
from libp2p.crypto.keys import PrivateKey
|
|
from libp2p.network.connection.raw_connection_interface import IRawConnection
|
|
from libp2p.peer.id import ID
|
|
from libp2p.security.secure_conn_interface import ISecureConn
|
|
|
|
from .connection import NoiseConnection
|
|
from .exceptions import (
|
|
HandshakeHasNotFinished,
|
|
InvalidSignature,
|
|
NoiseStateError,
|
|
PeerIDMismatchesPubkey,
|
|
)
|
|
from .io import NoiseHandshakeReadWriter
|
|
from .messages import (
|
|
NoiseHandshakePayload,
|
|
make_handshake_payload_sig,
|
|
verify_handshake_payload_sig,
|
|
)
|
|
|
|
|
|
class IPattern(ABC):
|
|
@abstractmethod
|
|
async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn:
|
|
...
|
|
|
|
@abstractmethod
|
|
async def handshake_outbound(
|
|
self, conn: IRawConnection, remote_peer: ID
|
|
) -> ISecureConn:
|
|
...
|
|
|
|
|
|
class BasePattern(IPattern):
|
|
protocol_name: bytes
|
|
noise_static_key: PrivateKey
|
|
local_peer: ID
|
|
libp2p_privkey: PrivateKey
|
|
early_data: bytes
|
|
|
|
def create_noise_state(self) -> NoiseState:
|
|
noise_state = NoiseState.from_name(self.protocol_name)
|
|
noise_state.set_keypair_from_private_bytes(
|
|
NoiseKeypair.STATIC, self.noise_static_key.to_bytes()
|
|
)
|
|
return noise_state
|
|
|
|
def make_handshake_payload(self) -> NoiseHandshakePayload:
|
|
signature = make_handshake_payload_sig(
|
|
self.libp2p_privkey, self.noise_static_key.get_public_key()
|
|
)
|
|
return NoiseHandshakePayload(self.libp2p_privkey.get_public_key(), signature)
|
|
|
|
|
|
class PatternXX(BasePattern):
|
|
def __init__(
|
|
self,
|
|
local_peer: ID,
|
|
libp2p_privkey: PrivateKey,
|
|
noise_static_key: PrivateKey,
|
|
early_data: bytes = None,
|
|
) -> None:
|
|
self.protocol_name = b"Noise_XX_25519_ChaChaPoly_SHA256"
|
|
self.local_peer = local_peer
|
|
self.libp2p_privkey = libp2p_privkey
|
|
self.noise_static_key = noise_static_key
|
|
self.early_data = early_data
|
|
|
|
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"
|
|
)
|
|
# Use `Ed25519PublicKey` since 25519 is used in our pattern.
|
|
remote_pubkey = Ed25519PublicKey.from_bytes(handshake_state.rs.public_bytes)
|
|
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`"
|
|
)
|
|
|
|
return NoiseConnection(
|
|
self.local_peer,
|
|
self.libp2p_privkey,
|
|
remote_peer_id_from_pubkey,
|
|
conn,
|
|
False,
|
|
noise_state,
|
|
)
|
|
|
|
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"
|
|
)
|
|
# Use `Ed25519PublicKey` since 25519 is used in our pattern.
|
|
remote_pubkey = Ed25519PublicKey.from_bytes(handshake_state.rs.public_bytes)
|
|
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`"
|
|
)
|
|
|
|
return NoiseConnection(
|
|
self.local_peer, self.libp2p_privkey, remote_peer, conn, False, noise_state
|
|
)
|