#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Malware detection.
"""

__license__= """
GoLismero 2.0 - The web knife - Copyright (C) 2011-2014

Golismero project site: https://github.com/golismero
Golismero project mail: contact@golismero-project.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""

__all__ = ["Malware"]

from .. import Vulnerability
from ... import identity, Data
from ...information import Information
from ...resource import Resource
from ....crypto import validate_hash


#------------------------------------------------------------------------------
class Malware(Vulnerability):
    """
    Malware detected.
    """

    TARGET_CLASS = Data

    DEFAULTS = Vulnerability.DEFAULTS.copy()
    DEFAULTS["level"] = "critical"
    DEFAULTS["cwe"]   = "CWE-904"
    DEFAULTS["capec"] = "CAPEC-448"
    DEFAULTS["references"] = (
        "https://en.wikipedia.org/wiki/Malware",
        "https://www.owasp.org/index.php/OWASP_Anti-Malware_-_Knowledge_Base",
    )


    #--------------------------------------------------------------------------
    def __init__(self, target, classifications, hashes, **kwargs):
        """
        :param target: Resource or Information where the malware was detected.
        :type target: Resource | Information

        :param classifications: MAEC-compatible antivirus classifications, in
            the form of a list of tuples where each tuple contains the
            antivirus brand and the malware name.
            This argument is optional, but strongly encouraged.
        :type classifications: list( tuple(str, str), ... )

        :param hashes: List of hashes for the malware's binary, in the
            form of a list of tuples where each tuple contains the hash name
            and the hash value. Example:
            [ ("MD5", "076e5b2bae0b4b3a3d81c85610b95cd4"),
            ("SHA1", "4484e08903744ceeaedd8f5e1bfc06b2c4688e76") ]
            This argument is optional, but strongly encouraged.
        :type hashes: list( tuple(str, str), ... )

        """

        # Validate the resource or information.
        if (
            not isinstance(target, Resource) and
            not isinstance(target, Information)
        ):
            raise TypeError("Expected Resource or Information, got %r instead"
                            % type(target))

        # Valudate the classifications.
        if not classifications:
            classifications = ()
        else:
            try:
                classifications = tuple(sorted(set(
                    (brand, name)
                    for brand, name in classifications
                )))
            except Exception:
                raise TypeError("Invalid classifications list!")
            for brand, name in classifications:
                if type(brand) is not str:
                    raise TypeError(
                        "Expected str, got %r instead" % type(brand))
                if type(name) is not str:
                    raise TypeError(
                        "Expected str, got %r instead" % type(name))
                if not brand:
                    raise ValueError("Empty AV brand!")
                if not name:
                    raise ValueError("Empty AV name!")

        # Valudate the hashes.
        if not hashes:
            hashes = ()
        else:
            try:
                hashes = tuple(sorted(set(
                    (name, value)
                    for name, value in hashes
                )))
            except Exception:
                raise TypeError("Invalid hashes list!")
            for name, value in hashes:
                if type(name) is not str:
                    raise TypeError(
                        "Expected str, got %r instead" % type(name))
                if type(value) is not str:
                    raise TypeError(
                        "Expected str, got %r instead" % type(value))
                if not name:
                    raise ValueError("Empty hash name!")
                if not value:
                    raise ValueError("Empty hash value!")
                if validate_hash(name, value) is False:
                    raise ValueError("Invalid hash! %s: %s" % (name, value))

        # MAEC-compatible antivirus classifications.
        self.__classifications = classifications

        # Hashes for the malware's binary.
        self.__hashes = hashes

        # Parent constructor.
        super(Malware, self).__init__(target, **kwargs)

    __init__.__doc__ += Vulnerability.__init__.__doc__[
            Vulnerability.__init__.__doc__.find("\n        :keyword"):]


    #--------------------------------------------------------------------------
    @identity
    def classifications(self):
        """
        :returns: MAEC-compatible antivirus classifications, in
            the form of a list of tuples where each tuple contains the
            antivirus brand and the malware name.
        :rtype: tuple( tuple(str, str), ... )
        """
        return self.__classifications


    #--------------------------------------------------------------------------
    @identity
    def hashes(self):
        """
        :returns: List of hashes for the malware's binary, in the
            form of a list of tuples where each tuple contains the hash name
            and the hash value. Example:
            [ ("MD5", "076e5b2bae0b4b3a3d81c85610b95cd4"),
            ("SHA1", "4484e08903744ceeaedd8f5e1bfc06b2c4688e76") ]
        :rtype: tuple( tuple(str, str), ... )
        """
        return self.__hashes
