# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.


import binascii
import os

import pytest

from cryptography.exceptions import AlreadyFinalized, InvalidKey, _Reasons
from cryptography.hazmat.backends.interfaces import HMACBackend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand

from ...utils import (
    load_nist_vectors,
    load_vectors_from_file,
    raises_unsupported_algorithm,
)


@pytest.mark.requires_backend_interface(interface=HMACBackend)
class TestHKDF(object):
    def test_length_limit(self, backend):
        big_length = 255 * hashes.SHA256().digest_size + 1

        with pytest.raises(ValueError):
            HKDF(
                hashes.SHA256(),
                big_length,
                salt=None,
                info=None,
                backend=backend,
            )

    def test_already_finalized(self, backend):
        hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend)

        hkdf.derive(b"\x01" * 16)

        with pytest.raises(AlreadyFinalized):
            hkdf.derive(b"\x02" * 16)

        hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend)

        hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u")

        with pytest.raises(AlreadyFinalized):
            hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u")

        hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend)

    def test_verify(self, backend):
        hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend)

        hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u")

    def test_verify_invalid(self, backend):
        hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend)

        with pytest.raises(InvalidKey):
            hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u")

    def test_unicode_typeerror(self, backend):
        with pytest.raises(TypeError):
            HKDF(
                hashes.SHA256(),
                16,
                salt="foo",  # type: ignore[arg-type]
                info=None,
                backend=backend,
            )

        with pytest.raises(TypeError):
            HKDF(
                hashes.SHA256(),
                16,
                salt=None,
                info="foo",  # type: ignore[arg-type]
                backend=backend,
            )

        with pytest.raises(TypeError):
            hkdf = HKDF(
                hashes.SHA256(), 16, salt=None, info=None, backend=backend
            )

            hkdf.derive("foo")  # type: ignore[arg-type]

        with pytest.raises(TypeError):
            hkdf = HKDF(
                hashes.SHA256(), 16, salt=None, info=None, backend=backend
            )

            hkdf.verify("foo", b"bar")  # type: ignore[arg-type]

        with pytest.raises(TypeError):
            hkdf = HKDF(
                hashes.SHA256(), 16, salt=None, info=None, backend=backend
            )

            hkdf.verify(b"foo", "bar")  # type: ignore[arg-type]

    def test_derive_short_output(self, backend):
        hkdf = HKDF(hashes.SHA256(), 4, salt=None, info=None, backend=backend)

        assert hkdf.derive(b"\x01" * 16) == b"gJ\xfb{"

    def test_derive_long_output(self, backend):
        vector = load_vectors_from_file(
            os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors
        )[0]
        hkdf = HKDF(
            hashes.SHA256(),
            int(vector["l"]),
            salt=vector["salt"],
            info=vector["info"],
            backend=backend,
        )
        ikm = binascii.unhexlify(vector["ikm"])

        assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"])

    def test_buffer_protocol(self, backend):
        vector = load_vectors_from_file(
            os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors
        )[0]
        hkdf = HKDF(
            hashes.SHA256(),
            int(vector["l"]),
            salt=vector["salt"],
            info=vector["info"],
            backend=backend,
        )
        ikm = bytearray(binascii.unhexlify(vector["ikm"]))

        assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"])


@pytest.mark.requires_backend_interface(interface=HMACBackend)
class TestHKDFExpand(object):
    def test_derive(self, backend):
        prk = binascii.unhexlify(
            b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"
        )

        okm = (
            b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c"
            b"5bf34007208d5b887185865"
        )

        info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9")
        hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend)

        assert binascii.hexlify(hkdf.derive(prk)) == okm

    def test_buffer_protocol(self, backend):
        prk = bytearray(
            binascii.unhexlify(
                b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2"
                b"b3e5"
            )
        )

        okm = (
            b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c"
            b"5bf34007208d5b887185865"
        )

        info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9")
        hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend)

        assert binascii.hexlify(hkdf.derive(prk)) == okm

    def test_verify(self, backend):
        prk = binascii.unhexlify(
            b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"
        )

        okm = (
            b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c"
            b"5bf34007208d5b887185865"
        )

        info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9")
        hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend)

        hkdf.verify(prk, binascii.unhexlify(okm))

    def test_invalid_verify(self, backend):
        prk = binascii.unhexlify(
            b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"
        )

        info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9")
        hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend)

        with pytest.raises(InvalidKey):
            hkdf.verify(prk, b"wrong key")

    def test_already_finalized(self, backend):
        info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9")
        hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend)

        hkdf.derive(b"first")

        with pytest.raises(AlreadyFinalized):
            hkdf.derive(b"second")

    def test_unicode_error(self, backend):
        info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9")
        hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend)

        with pytest.raises(TypeError):
            hkdf.derive("first")  # type: ignore[arg-type]


def test_invalid_backend():
    pretend_backend = object()

    with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE):
        HKDF(hashes.SHA256(), 16, None, None, pretend_backend)

    with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE):
        HKDFExpand(hashes.SHA256(), 16, None, pretend_backend)
