Beispiel #1
0
def hkdf_expand_and_extract(
    secret: bytes,
    initiator_node_id: NodeID,
    recipient_node_id: NodeID,
    salt: bytes,
) -> Tuple[bytes, bytes, bytes]:
    info = b"".join((HKDF_INFO, initiator_node_id, recipient_node_id))

    hkdf = HKDF(
        algorithm=SHA256(),
        length=3 * AES128_KEY_SIZE,
        salt=salt,
        info=info,
        backend=cryptography_default_backend(),
    )
    expanded_key = hkdf.derive(secret)

    if len(expanded_key) != 3 * AES128_KEY_SIZE:
        raise Exception("Invariant: Secret is expanded to three AES128 keys")

    initiator_key = expanded_key[:AES128_KEY_SIZE]
    recipient_key = expanded_key[AES128_KEY_SIZE:2 *
                                 AES128_KEY_SIZE]  # noqa: E203
    auth_response_key = expanded_key[2 * AES128_KEY_SIZE:3 *
                                     AES128_KEY_SIZE  # noqa: E203
                                     ]

    return initiator_key, recipient_key, auth_response_key
Beispiel #2
0
class V4IdentityScheme(IdentityScheme):

    id = b"v4"
    public_key_enr_key = b"secp256k1"

    private_key_size = 32
    cryptography_backend = cryptography_default_backend()

    #
    # ENR
    #
    @classmethod
    def create_enr_signature(cls, enr: "BaseENR", private_key: bytes) -> bytes:
        message = enr.get_signing_message()
        private_key_object = PrivateKey(private_key)
        signature = private_key_object.sign_msg_non_recoverable(message)
        return bytes(signature)

    @classmethod
    def validate_enr_structure(cls, enr: "BaseENR") -> None:
        if cls.public_key_enr_key not in enr:
            raise ValidationError(
                f"ENR is missing required key {cls.public_key_enr_key}")

        public_key = cls.extract_public_key(enr)
        cls.validate_public_key(public_key)

    @classmethod
    def validate_enr_signature(cls, enr: "ENR") -> None:
        cls.validate_signature(
            message=enr.get_signing_message(),
            signature=enr.signature,
            public_key=enr.public_key,
        )

    @classmethod
    def extract_public_key(cls, enr: "BaseENR") -> bytes:
        try:
            return enr[cls.public_key_enr_key]
        except KeyError as error:
            raise KeyError("ENR does not contain public key") from error

    @classmethod
    def extract_node_id(cls, enr: "BaseENR") -> NodeID:
        public_key_object = PublicKey.from_compressed_bytes(enr.public_key)
        uncompressed_bytes = public_key_object.to_bytes()
        return NodeID(keccak(uncompressed_bytes))

    #
    # Handshake
    #
    @classmethod
    def create_handshake_key_pair(cls) -> Tuple[bytes, bytes]:
        private_key = secrets.token_bytes(cls.private_key_size)
        public_key = PrivateKey(private_key).public_key.to_compressed_bytes()
        return private_key, public_key

    @classmethod
    def validate_handshake_public_key(cls, public_key: bytes) -> None:
        cls.validate_public_key(public_key)

    @classmethod
    def compute_session_keys(cls, *, local_private_key: bytes,
                             remote_public_key: bytes, local_node_id: NodeID,
                             remote_node_id: NodeID, id_nonce: IDNonce,
                             is_locally_initiated: bool) -> SessionKeys:
        local_private_key_object = PrivateKey(local_private_key)
        remote_public_key_object = PublicKey.from_compressed_bytes(
            remote_public_key)
        secret = ecdh_agree(local_private_key_object, remote_public_key_object)

        if is_locally_initiated:
            initiator_node_id, recipient_node_id = local_node_id, remote_node_id
        else:
            initiator_node_id, recipient_node_id = remote_node_id, local_node_id

        info = b"".join((
            HKDF_INFO,
            initiator_node_id,
            recipient_node_id,
        ))

        hkdf = HKDF(
            algorithm=SHA256(),
            length=3 * AES128_KEY_SIZE,
            salt=id_nonce,
            info=info,
            backend=cls.cryptography_backend,
        )
        expanded_key = hkdf.derive(secret)

        if len(expanded_key) != 3 * AES128_KEY_SIZE:
            raise Exception(
                "Invariant: Secret is expanded to three AES128 keys")

        initiator_key = expanded_key[:AES128_KEY_SIZE]
        recipient_key = expanded_key[AES128_KEY_SIZE:2 * AES128_KEY_SIZE]
        auth_response_key = expanded_key[2 * AES128_KEY_SIZE:3 *
                                         AES128_KEY_SIZE]

        if is_locally_initiated:
            encryption_key, decryption_key = initiator_key, recipient_key
        else:
            encryption_key, decryption_key = recipient_key, initiator_key

        return SessionKeys(
            encryption_key=AES128Key(encryption_key),
            decryption_key=AES128Key(decryption_key),
            auth_response_key=AES128Key(auth_response_key),
        )

    @classmethod
    def create_id_nonce_signature(
        cls,
        *,
        id_nonce: IDNonce,
        private_key: bytes,
    ) -> bytes:
        private_key_object = PrivateKey(private_key)
        signature = private_key_object.sign_msg_non_recoverable(id_nonce)
        return bytes(signature)

    @classmethod
    def validate_id_nonce_signature(
        cls,
        *,
        id_nonce: IDNonce,
        signature: bytes,
        public_key: bytes,
    ) -> None:
        cls.validate_signature(
            message=id_nonce,
            signature=signature,
            public_key=public_key,
        )

    #
    # Helpers
    #
    @classmethod
    def validate_public_key(cls, public_key: bytes) -> None:
        try:
            PublicKey.from_compressed_bytes(public_key)
        except (EthKeysValidationError, ValueError) as error:
            raise ValidationError(
                f"Public key {encode_hex(public_key)} is invalid: {error}"
            ) from error

    @classmethod
    def validate_signature(cls, *, message: bytes, signature: bytes,
                           public_key: bytes) -> None:
        public_key_object = PublicKey.from_compressed_bytes(public_key)

        try:
            signature_object = NonRecoverableSignature(signature)
        except BadSignature:
            is_valid = False
        else:
            is_valid = signature_object.verify_msg(message, public_key_object)

        if not is_valid:
            raise ValidationError(
                f"Signature {encode_hex(signature)} is not valid for message {encode_hex(message)} "
                f"and public key {encode_hex(public_key)}")