def message(cls, *, nonce: Optional[Nonce] = None, initiator_key: Optional[AES128Key] = None, message: Optional[BaseMessage] = None, source_node_id: Optional[NodeID] = None, dest_node_id: Optional[NodeID] = None, protocol_id: bytes = PROTOCOL_ID) -> Packet[MessagePacket]: if nonce is None: nonce = Nonce(secrets.token_bytes(12)) auth_data = MessagePacket(nonce) if message is None: message = RandomMessage() return cls._prepare( nonce=nonce, initiator_key=initiator_key, message=message, auth_data=auth_data, source_node_id=source_node_id, dest_node_id=dest_node_id, protocol_id=protocol_id, )
def _decode_auth(encoded_packet: bytes) -> Tuple[Union[AuthHeader, Nonce], int]: try: decoded_auth, _, message_start_index = rlp.codec.consume_item( encoded_packet, TAG_SIZE ) except DecodingError as error: raise ValidationError( "Packet authentication section is not proper RLP" ) from error if is_bytes(decoded_auth): validate_nonce(decoded_auth) return Nonce(decoded_auth), message_start_index elif is_list_like(decoded_auth): validate_length(decoded_auth, 5, "auth header") for index, element in enumerate(decoded_auth): if not is_bytes(element): raise ValidationError( f"Element {index} in auth header is not bytes: {element}" ) auth_header = AuthHeader( auth_tag=decoded_auth[0], id_nonce=decoded_auth[1], auth_scheme_name=decoded_auth[2], ephemeral_public_key=decoded_auth[3], encrypted_auth_response=decoded_auth[4], ) validate_auth_header(auth_header) return auth_header, message_start_index else: raise Exception("unreachable: RLP can only encode bytes and lists")
def _prepare(*, nonce: Optional[Nonce] = None, initiator_key: Optional[AES128Key] = None, message: BaseMessage, auth_data: TAuthData, source_node_id: Optional[NodeID] = None, dest_node_id: Optional[NodeID] = None, protocol_id: bytes = PROTOCOL_ID) -> Packet[TAuthData]: if nonce is None: nonce = Nonce(secrets.token_bytes(12)) if initiator_key is None: initiator_key = AES128Key(secrets.token_bytes(16)) if source_node_id is None: source_node_id = NodeID(secrets.token_bytes(32)) if dest_node_id is None: dest_node_id = NodeID(secrets.token_bytes(32)) return Packet.prepare( nonce=nonce, initiator_key=initiator_key, message=message, auth_data=auth_data, source_node_id=source_node_id, dest_node_id=dest_node_id, protocol_id=protocol_id, )
def from_wire_bytes(cls, data: bytes) -> "Header": if len(data) != HEADER_PACKET_SIZE: raise DecodingError( f"Invalid length for Header: actual={len(data)} " f"expected={HEADER_PACKET_SIZE} data={data.hex()}") stream = BytesIO(data) protocol_id = stream.read(6) if protocol_id != PROTOCOL_ID: raise DecodingError(f"Invalid protocol: {protocol_id!r}") version = stream.read(2) if version != b"\x00\x01": raise DecodingError(f"Unsupported version: {version!r}") flag = stream.read(1)[0] aes_gcm_nonce = Nonce(stream.read(12)) auth_data_size = int.from_bytes(stream.read(2), "big") return cls(protocol_id, version, flag, aes_gcm_nonce, auth_data_size)
def test_decryption_with_wrong_inputs(): key = AES128Key(b"\x00" * 16) nonce = Nonce(b"\x11" * 12) plain_text = b"\x33" * 5 aad = b"\x44" * 5 cipher_text = aesgcm_encrypt(key, nonce, plain_text, aad) assert aesgcm_decrypt(key, nonce, cipher_text, aad) == plain_text with pytest.raises(ValidationError): aesgcm_decrypt(b"", nonce, cipher_text, aad) with pytest.raises(ValidationError): aesgcm_decrypt(key, b"", cipher_text, aad) with pytest.raises(DecryptionError): aesgcm_decrypt(key, nonce, b"", aad) with pytest.raises(DecryptionError): aesgcm_decrypt(key, nonce, cipher_text, b"")
def _decode_who_are_you_payload(encoded_packet: bytes) -> Tuple[Nonce, IDNonce, int]: payload_rlp = encoded_packet[MAGIC_SIZE:] try: payload = rlp.decode(payload_rlp) except DecodingError as error: raise ValidationError( f"WHOAREYOU payload section is not proper RLP: {encode_hex(payload_rlp)}" ) from error if not is_list_like(payload): raise ValidationError( f"WHOAREYOU payload section is not an RLP encoded list: {payload}" ) if len(payload) != 3: raise ValidationError( f"WHOAREYOU payload consists of {len(payload)} instead of 3 elements: {payload}" ) token, id_nonce, enr_seq_bytes = payload enr_seq = big_endian_int.deserialize(enr_seq_bytes) validate_nonce(token) return Nonce(token), id_nonce, enr_seq
def get_random_auth_tag() -> Nonce: return Nonce(secrets.token_bytes(NONCE_SIZE))
def get_encryption_nonce(self) -> Nonce: return Nonce( next(self._nonce_counter).to_bytes(4, "big") + secrets.token_bytes(8))
"enr:-LK4QKWk9yZo258PQouLshTOEEGWVHH7GhKwpYmB5tmKE4eHeSfman0PZvM2Rpp54RWgoOagAsOfKoXgZSbiCYzERWABh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAAAAAAAAAAAAAAAAAAAAAAgmlkgnY0gmlwhDQlA5CJc2VjcDI1NmsxoQOYiWqrQtQksTEtS3qY6idxJE5wkm0t9wKqpzv2gCR21oN0Y3CCIyiDdWRwgiMo", # noqa: E501 "enr:-LK4QEnIS-PIxxLCadJdnp83VXuJqgKvC9ZTIWaJpWqdKlUFCiup2sHxWihF9EYGlMrQLs0mq_2IyarhNq38eoaOHUoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAAAAAAAAAAAAAAAAAAAAAAgmlkgnY0gmlwhA37LMaJc2VjcDI1NmsxoQJ7k0mKtTd_kdEq251flOjD1HKpqgMmIETDoD-Msy_O-4N0Y3CCIyiDdWRwgiMo", # noqa: E501 "enr:-KG4QIOJRu0BBlcXJcn3lI34Ub1aBLYipbnDaxBnr2uf2q6nE1TWnKY5OAajg3eG6mHheQSfRhXLuy-a8V5rqXKSoUEChGV0aDKQGK5MywAAAAH__________4JpZIJ2NIJpcIQKAAFhiXNlY3AyNTZrMaEDESplmV9c2k73v0DjxVXJ6__2bWyP-tK28_80lf7dUhqDdGNwgiMog3VkcIIjKA", # noqa: E501 # CatDog: bridge bootnodes "enr:-Ku4QKYN_qSG6WnGMs33F4STy8canm2X7vLaz0MB6bA84YJ-GtT5CeUvkuYvMUX-mwuU3Ju14-2wZj7rjwx7eAthAL4Dh2F0dG5ldHOIAAAAAAAAAACEZXRoMpDaNQiCAAAAA___________gmlkgnY0gmlwhBK4vdCJc2VjcDI1NmsxoQNYtv_PfWUWNRo99-21Y4dXl5Z-XGalHp-bJmDHod4x14N1ZHCCI1o", # noqa: E501 ) NONCE_SIZE = 12 # size of an AESGCM nonce TAG_SIZE = 32 # size of the tag packet prefix MAGIC_SIZE = 32 # size of the magic hash in the who are you packet ID_NONCE_SIZE = 32 # size of the id nonce in who are you and auth tag packets RANDOM_ENCRYPTED_DATA_SIZE = 12 # size of random data we send to initiate a handshake # safe upper bound on the size of the ENR list in a nodes message NODES_MESSAGE_PAYLOAD_SIZE = DISCOVERY_MAX_PACKET_SIZE - 200 ZERO_NONCE = Nonce(b"\x00" * NONCE_SIZE) # nonce used for the auth header packet AUTH_RESPONSE_VERSION = 5 # version number used in auth response AUTH_SCHEME_NAME = b"gcm" # the name of the only supported authentication scheme TOPIC_HASH_SIZE = 32 # size of a topic hash WHO_ARE_YOU_MAGIC_SUFFIX = b"WHOAREYOU" MAX_REQUEST_ID = 2**32 - 1 # highest request id used for outbound requests MAX_REQUEST_ID_ATTEMPTS = ( 100 # number of attempts we take to guess a available request id ) REQUEST_RESPONSE_TIMEOUT = ( 0.5 # timeout for waiting for response after request was sent )
def test_nonce_validation_valid(key): validate_nonce(Nonce(key))
def test_nonce_validation_invalid(): for length in (0, 11, 13, 16): with pytest.raises(ValidationError): validate_nonce(Nonce(b"\x00" * length))