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 _decrypt_message(key: AES128Key, auth_tag: Nonce, encrypted_message: bytes, authenticated_data: bytes, message_type_registry: MessageTypeRegistry, ) -> BaseMessage: plain_text = aesgcm_decrypt( key=key, nonce=auth_tag, cipher_text=encrypted_message, authenticated_data=authenticated_data, ) try: message_type = plain_text[0] except IndexError: raise ValidationError("Decrypted message is empty") try: message_class = message_type_registry[message_type] except KeyError: raise ValidationError(f"Unknown message type {message_type}") try: message = rlp.decode(plain_text[1:], message_class) except DecodingError as error: raise ValidationError("Encrypted message does not contain valid RLP") from error return message
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 decrypt_auth_response( self, auth_response_key: AES128Key) -> Tuple[bytes, Optional[ENR]]: """Extract id nonce signature and optional ENR from auth header packet.""" plain_text = aesgcm_decrypt( key=auth_response_key, nonce=ZERO_NONCE, cipher_text=self.auth_header.encrypted_auth_response, authenticated_data=b"", ) try: decoded_rlp = rlp.decode(plain_text) except DecodingError: raise ValidationError( f"Auth response does not contain valid RLP: {encode_hex(plain_text)}" ) if not is_list_like(decoded_rlp): raise ValidationError( f"Auth response contains bytes instead of list: {encode_hex(decoded_rlp)}" ) if len(decoded_rlp) != 3: raise ValidationError( f"Auth response is a list of {len(decoded_rlp)} instead of three elements" ) version_bytes, id_nonce_signature, serialized_enr = decoded_rlp if not is_bytes(version_bytes): raise ValidationError( f"Version is a list instead of big endian encoded integer: {version_bytes}" ) version_int = big_endian_to_int(version_bytes) if version_int != AUTH_RESPONSE_VERSION: raise ValidationError( f"Expected auth response version {AUTH_RESPONSE_VERSION}, but got {version_int}" ) if not is_bytes(id_nonce_signature): raise ValidationError( f"Id nonce signature is a list instead of bytes: {id_nonce_signature}" ) if not is_list_like(serialized_enr): raise ValidationError( f"ENR is bytes instead of list: {encode_hex(serialized_enr)}") if len(serialized_enr) == 0: enr = None else: try: enr = ENR.deserialize(serialized_enr) except DeserializationError as error: raise ValidationError( "ENR in auth response is not properly encoded") from error return id_nonce_signature, enr
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 decrypt_auth_response( self, auth_response_key: AES128Key) -> Tuple[bytes, Optional[ENR]]: """Extract id nonce signature and optional ENR from auth header packet.""" plain_text = aesgcm_decrypt( key=auth_response_key, nonce=ZERO_NONCE, cipher_text=self.auth_header.encrypted_auth_response, authenticated_data=self.tag, ) try: decoded_rlp = rlp.decode(plain_text) except DecodingError as error: raise ValidationError( f"Auth response does not contain valid RLP: {encode_hex(plain_text)}" ) if not is_list_like(decoded_rlp): raise ValidationError( f"Auth response contains bytes instead of list: {encode_hex(decoded_rlp)}" ) if len(decoded_rlp) != 2: raise ValidationError( f"Auth response is a list of {len(decoded_rlp)} instead of two elements" ) id_nonce_signature, serialized_enr = decoded_rlp if not is_bytes(id_nonce_signature): raise ValidationError( f"Id nonce signature is a list instead of bytes: {id_nonce_signature}" ) if not is_list_like(serialized_enr): raise ValidationError( f"ENR is bytes instead of list: {encode_hex(serialized_enr)}") if len(serialized_enr) == 0: enr = None else: try: enr = ENR.deserialize(serialized_enr) except DeserializationError as error: raise ValidationError( "ENR in auth response is not properly encoded") from error return id_nonce_signature, enr
def test_auth_tag_packet_preparation(tag, auth_tag, key): message = PingMessage( request_id=5, enr_seq=3, ) packet = AuthTagPacket.prepare( tag=tag, auth_tag=auth_tag, message=message, key=key, ) assert packet.tag == tag assert packet.auth_tag == auth_tag decrypted_message = aesgcm_decrypt( key=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 test_roundtrip(key, nonce, plain_text, aad): cipher_text = aesgcm_encrypt(key, nonce, plain_text, aad) plain_text_recovered = aesgcm_decrypt(key, nonce, cipher_text, aad) assert plain_text_recovered == plain_text
def test_encryption_official(key, nonce, plain_text, aad, cipher_text): encrypted = aesgcm_encrypt(key, nonce, plain_text, aad) assert encrypted == cipher_text assert aesgcm_decrypt(key, nonce, cipher_text, aad) == plain_text