py-libp2p/libp2p/security/noise/patterns.py

184 lines
7.1 KiB
Python
Raw Normal View History

2020-02-09 00:33:26 +08:00
from abc import ABC, abstractmethod
2020-02-19 23:54:12 +08:00
from noise.backends.default.keypairs import KeyPair as NoiseKeyPair
from noise.connection import Keypair as NoiseKeypairEnum
2020-02-09 00:33:26 +08:00
from noise.connection import NoiseConnection as NoiseState
from libp2p.crypto.ed25519 import Ed25519PublicKey
2020-02-19 23:54:12 +08:00
from libp2p.crypto.keys import PrivateKey, PublicKey
2020-02-09 00:33:26 +08:00
from libp2p.network.connection.raw_connection_interface import IRawConnection
from libp2p.peer.id import ID
from libp2p.security.secure_conn_interface import ISecureConn
from libp2p.security.secure_session import SecureSession
2020-02-09 00:33:26 +08:00
from .exceptions import (
HandshakeHasNotFinished,
InvalidSignature,
NoiseStateError,
PeerIDMismatchesPubkey,
)
from .io import NoiseHandshakeReadWriter, NoiseTransportReadWriter
from .messages import (
NoiseHandshakePayload,
make_handshake_payload_sig,
verify_handshake_payload_sig,
)
2020-02-09 00:33:26 +08:00
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
2020-02-09 00:33:26 +08:00
def create_noise_state(self) -> NoiseState:
noise_state = NoiseState.from_name(self.protocol_name)
noise_state.set_keypair_from_private_bytes(
2020-02-19 23:54:12 +08:00
NoiseKeypairEnum.STATIC, self.noise_static_key.to_bytes()
2020-02-09 00:33:26 +08:00
)
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)
2020-02-09 00:33:26 +08:00
class PatternXX(BasePattern):
def __init__(
self,
local_peer: ID,
libp2p_privkey: PrivateKey,
noise_static_key: PrivateKey,
early_data: bytes = None,
2020-02-09 00:33:26 +08:00
) -> 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
2020-02-09 00:33:26 +08:00
async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn:
noise_state = self.create_noise_state()
noise_state.set_as_responder()
noise_state.start_handshake()
2020-02-16 18:23:16 +08:00
handshake_state = noise_state.noise_protocol.handshake_state
read_writer = NoiseHandshakeReadWriter(conn, noise_state)
2020-02-09 00:33:26 +08:00
2020-02-16 18:23:16 +08:00
# 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)
2020-02-09 00:33:26 +08:00
2020-02-16 18:23:16 +08:00
# Receive and consume msg#3.
msg_3 = await read_writer.read_msg(prefix_encoded=True)
peer_handshake_payload = NoiseHandshakePayload.deserialize(msg_3)
2020-02-16 18:23:16 +08:00
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"
)
2020-02-19 23:54:12 +08:00
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)
2020-02-09 00:33:26 +08:00
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,
)
2020-02-09 00:33:26 +08:00
async def handshake_outbound(
self, conn: IRawConnection, remote_peer: ID
) -> ISecureConn:
noise_state = self.create_noise_state()
read_writer = NoiseHandshakeReadWriter(conn, noise_state)
2020-02-09 00:33:26 +08:00
noise_state.set_as_initiator()
noise_state.start_handshake()
2020-02-16 18:23:16 +08:00
handshake_state = noise_state.noise_protocol.handshake_state
2020-02-16 18:23:16 +08:00
# Send msg#1, which is *not* encrypted.
msg_1 = b""
await read_writer.write_msg(msg_1)
2020-02-16 18:23:16 +08:00
# 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)
2020-02-16 18:23:16 +08:00
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"
)
2020-02-19 23:54:12 +08:00
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}"
)
2020-02-09 00:33:26 +08:00
2020-02-16 18:23:16 +08:00
# 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, prefix_encoded=True)
2020-02-09 00:33:26 +08:00
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,
2020-02-09 00:33:26 +08:00
)
2020-02-19 23:54:12 +08:00
@staticmethod
def _get_pubkey_from_noise_keypair(key_pair: NoiseKeyPair) -> PublicKey:
# Use `Ed25519PublicKey` since 25519 is used in our pattern.
# NOTE: Ignore the warning for now, since it is also not fixed in `noiseprotocol`.
# "CryptographyDeprecationWarning: public_bytes now requires
# encoding and format arguments. Support for calling without arguments will be
# removed in cryptography 2.7"
raw_bytes = key_pair.public.public_bytes()
return Ed25519PublicKey.from_bytes(raw_bytes)