# Copyright (c) 2015 VMware, Inc.
#
#  Licensed under the Apache License, Version 2.0 (the "License"); you may
#  not use this file except in compliance with the License. You may obtain
#  a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#  License for the specific language governing permissions and limitations
#  under the License.

r"""
=========================================
B505: Test for weak cryptographic key use
=========================================

As computational power increases, so does the ability to break ciphers with
smaller key lengths. The recommended key length size for RSA and DSA algorithms
is 2048 and higher. 1024 bits and below are now considered breakable. EC key
length sizes are recommended to be 224 and higher with 160 and below considered
breakable. This plugin test checks for use of any key less than those limits
and returns a high severity error if lower than the lower threshold and a
medium severity error for those lower than the higher threshold.

:Example:

.. code-block:: none

    >> Issue: DSA key sizes below 1024 bits are considered breakable.
       Severity: High   Confidence: High
       Location: examples/weak_cryptographic_key_sizes.py:36
    35  # Also incorrect: without keyword args
    36  dsa.generate_private_key(512,
    37                           backends.default_backend())
    38  rsa.generate_private_key(3,

.. seealso::

 - http://csrc.nist.gov/publications/nistpubs/800-131A/sp800-131A.pdf
 - https://security.openstack.org/guidelines/dg_strong-crypto.html

.. versionadded:: 0.14.0

"""

import bandit
from bandit.core import test_properties as test


def gen_config(name):
    if name == 'weak_cryptographic_key':
        return {
            'weak_key_size_dsa_high': 1024,
            'weak_key_size_dsa_medium': 2048,
            'weak_key_size_rsa_high': 1024,
            'weak_key_size_rsa_medium': 2048,
            'weak_key_size_ec_high': 160,
            'weak_key_size_ec_medium': 224,
        }


def _classify_key_size(config, key_type, key_size):
    if isinstance(key_size, str):
        # size provided via a variable - can't process it at the moment
        return

    key_sizes = {
        'DSA': [(config['weak_key_size_dsa_high'], bandit.HIGH),
                (config['weak_key_size_dsa_medium'], bandit.MEDIUM)],
        'RSA': [(config['weak_key_size_rsa_high'], bandit.HIGH),
                (config['weak_key_size_rsa_medium'], bandit.MEDIUM)],
        'EC': [(config['weak_key_size_ec_high'], bandit.HIGH),
               (config['weak_key_size_ec_medium'], bandit.MEDIUM)],
    }

    for size, level in key_sizes[key_type]:
        if key_size < size:
            return bandit.Issue(
                severity=level,
                confidence=bandit.HIGH,
                text='%s key sizes below %d bits are considered breakable. ' %
                (key_type, size))


def _weak_crypto_key_size_cryptography_io(context, config):
    func_key_type = {
        'cryptography.hazmat.primitives.asymmetric.dsa.'
        'generate_private_key': 'DSA',
        'cryptography.hazmat.primitives.asymmetric.rsa.'
        'generate_private_key': 'RSA',
        'cryptography.hazmat.primitives.asymmetric.ec.'
        'generate_private_key': 'EC',
    }
    arg_position = {
        'DSA': 0,
        'RSA': 1,
        'EC': 0,
    }
    key_type = func_key_type.get(context.call_function_name_qual)
    if key_type in ['DSA', 'RSA']:
        key_size = (context.get_call_arg_value('key_size') or
                    context.get_call_arg_at_position(arg_position[key_type]) or
                    2048)
        return _classify_key_size(config, key_type, key_size)
    elif key_type == 'EC':
        curve_key_sizes = {
            'SECP192R1': 192,
            'SECT163K1': 163,
            'SECT163R2': 163,
        }
        curve = (context.get_call_arg_value('curve') or
                 context.call_args[arg_position[key_type]])
        key_size = curve_key_sizes[curve] if curve in curve_key_sizes else 224
        return _classify_key_size(config, key_type, key_size)


def _weak_crypto_key_size_pycrypto(context, config):
    func_key_type = {
        'Crypto.PublicKey.DSA.generate': 'DSA',
        'Crypto.PublicKey.RSA.generate': 'RSA',
        'Cryptodome.PublicKey.DSA.generate': 'DSA',
        'Cryptodome.PublicKey.RSA.generate': 'RSA',
    }
    key_type = func_key_type.get(context.call_function_name_qual)
    if key_type:
        key_size = (context.get_call_arg_value('bits') or
                    context.get_call_arg_at_position(0) or
                    2048)
        return _classify_key_size(config, key_type, key_size)


@test.takes_config
@test.checks('Call')
@test.test_id('B505')
def weak_cryptographic_key(context, config):
    return (_weak_crypto_key_size_cryptography_io(context, config) or
            _weak_crypto_key_size_pycrypto(context, config))
