# -*- coding: utf-8 -*-

# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing checks for potentially hardcoded AWS passwords.
"""

#
# This is a modified version of the one found at
# https://pypi.org/project/bandit-aws/.
#
# Original Copyright 2020 CMCRC (devcdt@cmcrc.com)
#
# Original License: GPLv3
#

from collections import Counter
import math
import re
import string


def getChecks():
    """
    Public method to get a dictionary with checks handled by this module.
    
    @return dictionary containing checker lists containing checker function and
        list of codes
    @rtype dict
    """
    return {
        "Str": [
            (checkHardcodedAwsKey, ("S801", "S802")),
        ],
    }

AWS_ACCESS_KEY_ID_SYMBOLS = string.ascii_uppercase + string.digits
AWS_ACCESS_KEY_ID_REGEX = re.compile(
    '[' + AWS_ACCESS_KEY_ID_SYMBOLS + ']{20}'
)
AWS_ACCESS_KEY_ID_MAX_ENTROPY = 3

AWS_SECRET_ACCESS_KEY_SYMBOLS = string.ascii_letters + string.digits + '/+='
AWS_SECRET_ACCESS_KEY_REGEX = re.compile(
    '[' + AWS_SECRET_ACCESS_KEY_SYMBOLS + ']{40}'
)
AWS_SECRET_ACCESS_KEY_MAX_ENTROPY = 4.5


def shannonEntropy(data, symbols):
    """
    Function to caclculate the Shannon entropy of some given data.
    
    Source:
    http://blog.dkbza.org/2007/05/scanning-data-for-entropy-anomalies.html
    
    @param data data to calculate the entropy for
    @type str
    @param symbols allowed symbols
    @type str
    @return Shannon entropy of the given data
    @rtype float
    """
    if not data:
        return 0
    entropy = 0
    counts = Counter(data)
    for x in symbols:
        p_x = float(counts[x]) / len(data)
        if p_x > 0:
            entropy += - p_x * math.log(p_x, 2)
    return entropy


def checkHardcodedAwsKey(reportError, context, config):
    """
    Function to check for potentially hardcoded AWS passwords.
    
    @param reportError function to be used to report errors
    @type func
    @param context security context object
    @type SecurityContext
    @param config dictionary with configuration data
    @type dict
    """
    node = context.node
    if AWS_ACCESS_KEY_ID_REGEX.fullmatch(node.s):
        entropy = shannonEntropy(node.s, AWS_ACCESS_KEY_ID_SYMBOLS)
        if entropy > AWS_ACCESS_KEY_ID_MAX_ENTROPY:
            reportError(
                context.node.lineno - 1,
                context.node.col_offset,
                "S801",
                "L",
                "M",
                node.s
            )
    
    elif AWS_SECRET_ACCESS_KEY_REGEX.fullmatch(node.s):
        entropy = shannonEntropy(node.s, AWS_SECRET_ACCESS_KEY_SYMBOLS)
        if entropy > AWS_SECRET_ACCESS_KEY_MAX_ENTROPY:
            reportError(
                context.node.lineno - 1,
                context.node.col_offset,
                "S802",
                "M",
                "M",
                node.s
            )
