1"""
2Functions dealing with encryption
3"""
4import hashlib
5import logging
6import os
7
8import salt.loader
9import salt.utils.files
10from salt.exceptions import SaltInvocationError
11
12log = logging.getLogger(__name__)
13
14
15try:
16    import M2Crypto  # pylint: disable=unused-import
17
18    Random = None
19    HAS_M2CRYPTO = True
20except ImportError:
21    HAS_M2CRYPTO = False
22
23if not HAS_M2CRYPTO:
24    try:
25        from Cryptodome import Random
26
27        HAS_CRYPTODOME = True
28    except ImportError:
29        HAS_CRYPTODOME = False
30else:
31    HAS_CRYPTODOME = False
32
33if not HAS_M2CRYPTO and not HAS_CRYPTODOME:
34    try:
35        from Crypto import Random  # nosec
36
37        HAS_CRYPTO = True
38    except ImportError:
39        HAS_CRYPTO = False
40else:
41    HAS_CRYPTO = False
42
43
44def decrypt(
45    data, rend, translate_newlines=False, renderers=None, opts=None, valid_rend=None
46):
47    """
48    .. versionadded:: 2017.7.0
49
50    Decrypt a data structure using the specified renderer. Written originally
51    as a common codebase to handle decryption of encrypted elements within
52    Pillar data, but should be flexible enough for other uses as well.
53
54    Returns the decrypted result, but any decryption renderer should be
55    recursively decrypting mutable types in-place, so any data structure passed
56    should be automagically decrypted using this function. Immutable types
57    obviously won't, so it's a good idea to check if ``data`` is hashable in
58    the calling function, and replace the original value with the decrypted
59    result if that is not the case. For an example of this, see
60    salt.pillar.Pillar.decrypt_pillar().
61
62    data
63        The data to be decrypted. This can be a string of ciphertext or a data
64        structure. If it is a data structure, the items in the data structure
65        will be recursively decrypted.
66
67    rend
68        The renderer used to decrypt
69
70    translate_newlines : False
71        If True, then the renderer will convert a literal backslash followed by
72        an 'n' into a newline before performing the decryption.
73
74    renderers
75        Optionally pass a loader instance containing loaded renderer functions.
76        If not passed, then the ``opts`` will be required and will be used to
77        invoke the loader to get the available renderers. Where possible,
78        renderers should be passed to avoid the overhead of loading them here.
79
80    opts
81        The master/minion configuration opts. Used only if renderers are not
82        passed.
83
84    valid_rend
85        A list containing valid renderers, used to restrict the renderers which
86        this function will be allowed to use. If not passed, no restriction
87        will be made.
88    """
89    try:
90        if valid_rend and rend not in valid_rend:
91            raise SaltInvocationError(
92                "'{}' is not a valid decryption renderer. Valid choices are: {}".format(
93                    rend, ", ".join(valid_rend)
94                )
95            )
96    except TypeError as exc:
97        # SaltInvocationError inherits TypeError, so check for it first and
98        # raise if needed.
99        if isinstance(exc, SaltInvocationError):
100            raise
101        # 'valid' argument is not iterable
102        log.error("Non-iterable value %s passed for valid_rend", valid_rend)
103
104    if renderers is None:
105        if opts is None:
106            raise TypeError("opts are required")
107        renderers = salt.loader.render(opts, {})
108
109    rend_func = renderers.get(rend)
110    if rend_func is None:
111        raise SaltInvocationError(
112            "Decryption renderer '{}' is not available".format(rend)
113        )
114
115    return rend_func(data, translate_newlines=translate_newlines)
116
117
118def reinit_crypto():
119    """
120    When a fork arises, pycrypto needs to reinit
121    From its doc::
122
123        Caveat: For the random number generator to work correctly,
124        you must call Random.atfork() in both the parent and
125        child processes after using os.fork()
126
127    """
128    if HAS_CRYPTODOME or HAS_CRYPTO:
129        Random.atfork()
130
131
132def pem_finger(path=None, key=None, sum_type="sha256"):
133    """
134    Pass in either a raw pem string, or the path on disk to the location of a
135    pem file, and the type of cryptographic hash to use. The default is SHA256.
136    The fingerprint of the pem will be returned.
137
138    If neither a key nor a path are passed in, a blank string will be returned.
139    """
140    if not key:
141        if not os.path.isfile(path):
142            return ""
143
144        with salt.utils.files.fopen(path, "rb") as fp_:
145            key = b"".join([x for x in fp_.readlines() if x.strip()][1:-1])
146
147    pre = getattr(hashlib, sum_type)(key).hexdigest()
148    finger = ""
149    for ind, _ in enumerate(pre):
150        if ind % 2:
151            # Is odd
152            finger += "{}:".format(pre[ind])
153        else:
154            finger += pre[ind]
155    return finger.rstrip(":")
156