1"""
2Common code shared between the nacl module and runner.
3"""
4
5
6import base64
7import logging
8import os
9
10import salt.syspaths
11import salt.utils.files
12import salt.utils.platform
13import salt.utils.stringutils
14import salt.utils.versions
15import salt.utils.win_dacl
16import salt.utils.win_functions
17
18log = logging.getLogger(__name__)
19
20REQ_ERROR = None
21try:
22    import libnacl.secret
23    import libnacl.sealed
24except (ImportError, OSError) as e:
25    REQ_ERROR = (
26        "libnacl import error, perhaps missing python libnacl package or should update."
27    )
28
29__virtualname__ = "nacl"
30
31
32def __virtual__():
33    if __opts__["fips_mode"] is True:
34        return False, "nacl utils not available in FIPS mode"
35    return check_requirements()
36
37
38def check_requirements():
39    """
40    Check required libraries are available
41    """
42    return (REQ_ERROR is None, REQ_ERROR)
43
44
45def _get_config(**kwargs):
46    """
47    Return configuration
48    """
49    sk_file = kwargs.get("sk_file")
50    if not sk_file:
51        sk_file = os.path.join(kwargs["opts"].get("pki_dir"), "master/nacl")
52
53    pk_file = kwargs.get("pk_file")
54    if not pk_file:
55        pk_file = os.path.join(kwargs["opts"].get("pki_dir"), "master/nacl.pub")
56
57    config = {
58        "box_type": kwargs.get("box_type", "sealedbox"),
59        "sk": None,
60        "sk_file": sk_file,
61        "pk": None,
62        "pk_file": pk_file,
63    }
64
65    config_key = "{}.config".format(__virtualname__)
66    try:
67        config.update(__salt__["config.get"](config_key, {}))
68    except (NameError, KeyError) as e:
69        # likely using salt-run so fallback to __opts__
70        config.update(kwargs["opts"].get(config_key, {}))
71    # pylint: disable=C0201
72    for k in set(config.keys()) & set(kwargs.keys()):
73        config[k] = kwargs[k]
74    return config
75
76
77def _get_sk(**kwargs):
78    """
79    Return sk
80    """
81    config = _get_config(**kwargs)
82    key = None
83    if config["sk"]:
84        key = salt.utils.stringutils.to_str(config["sk"])
85    sk_file = config["sk_file"]
86    if not key and sk_file:
87        try:
88            with salt.utils.files.fopen(sk_file, "rb") as keyf:
89                key = salt.utils.stringutils.to_unicode(keyf.read()).rstrip("\n")
90        except OSError:
91            raise Exception("no key or sk_file found")
92    return base64.b64decode(key)
93
94
95def _get_pk(**kwargs):
96    """
97    Return pk
98    """
99    config = _get_config(**kwargs)
100    pubkey = None
101    if config["pk"]:
102        pubkey = salt.utils.stringutils.to_str(config["pk"])
103    pk_file = config["pk_file"]
104    if not pubkey and pk_file:
105        try:
106            with salt.utils.files.fopen(pk_file, "rb") as keyf:
107                pubkey = salt.utils.stringutils.to_unicode(keyf.read()).rstrip("\n")
108        except OSError:
109            raise Exception("no pubkey or pk_file found")
110    pubkey = str(pubkey)
111    return base64.b64decode(pubkey)
112
113
114def keygen(sk_file=None, pk_file=None, **kwargs):
115    """
116    Use libnacl to generate a keypair.
117
118    If no `sk_file` is defined return a keypair.
119
120    If only the `sk_file` is defined `pk_file` will use the same name with a postfix `.pub`.
121
122    When the `sk_file` is already existing, but `pk_file` is not. The `pk_file` will be generated
123    using the `sk_file`.
124
125    CLI Examples:
126
127    .. code-block:: bash
128
129        salt-call nacl.keygen
130        salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl
131        salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl pk_file=/etc/salt/pki/master/nacl.pub
132        salt-call --local nacl.keygen
133
134    sk_file
135      Path to where there secret key exists.
136      The argrument ``keyfile`` was deprecated
137      in favor of ``sk_file``. ``keyfile`` will
138      continue to work to ensure backwards
139      compatbility, but please use the preferred
140      ``sk_file``.
141    """
142    if "keyfile" in kwargs:
143        sk_file = kwargs["keyfile"]
144
145    if sk_file is None:
146        kp = libnacl.public.SecretKey()
147        return {"sk": base64.b64encode(kp.sk), "pk": base64.b64encode(kp.pk)}
148
149    if pk_file is None:
150        pk_file = "{}.pub".format(sk_file)
151
152    if sk_file and pk_file is None:
153        if not os.path.isfile(sk_file):
154            kp = libnacl.public.SecretKey()
155            with salt.utils.files.fopen(sk_file, "wb") as keyf:
156                keyf.write(base64.b64encode(kp.sk))
157            if salt.utils.platform.is_windows():
158                cur_user = salt.utils.win_functions.get_current_user()
159                salt.utils.win_dacl.set_owner(sk_file, cur_user)
160                salt.utils.win_dacl.set_permissions(
161                    sk_file,
162                    cur_user,
163                    "full_control",
164                    "grant",
165                    reset_perms=True,
166                    protected=True,
167                )
168            else:
169                # chmod 0600 file
170                os.chmod(sk_file, 1536)
171            return "saved sk_file: {}".format(sk_file)
172        else:
173            raise Exception("sk_file:{} already exist.".format(sk_file))
174
175    if sk_file is None and pk_file:
176        raise Exception("sk_file: Must be set inorder to generate a public key.")
177
178    if os.path.isfile(sk_file) and os.path.isfile(pk_file):
179        raise Exception(
180            "sk_file:{} and pk_file:{} already exist.".format(sk_file, pk_file)
181        )
182
183    if os.path.isfile(sk_file) and not os.path.isfile(pk_file):
184        # generate pk using the sk
185        with salt.utils.files.fopen(sk_file, "rb") as keyf:
186            sk = salt.utils.stringutils.to_unicode(keyf.read()).rstrip("\n")
187            sk = base64.b64decode(sk)
188        kp = libnacl.public.SecretKey(sk)
189        with salt.utils.files.fopen(pk_file, "wb") as keyf:
190            keyf.write(base64.b64encode(kp.pk))
191        return "saved pk_file: {}".format(pk_file)
192
193    kp = libnacl.public.SecretKey()
194    with salt.utils.files.fopen(sk_file, "wb") as keyf:
195        keyf.write(base64.b64encode(kp.sk))
196    if salt.utils.platform.is_windows():
197        cur_user = salt.utils.win_functions.get_current_user()
198        salt.utils.win_dacl.set_owner(sk_file, cur_user)
199        salt.utils.win_dacl.set_permissions(
200            sk_file, cur_user, "full_control", "grant", reset_perms=True, protected=True
201        )
202    else:
203        # chmod 0600 file
204        os.chmod(sk_file, 1536)
205    with salt.utils.files.fopen(pk_file, "wb") as keyf:
206        keyf.write(base64.b64encode(kp.pk))
207    return "saved sk_file:{}  pk_file: {}".format(sk_file, pk_file)
208
209
210def enc(data, **kwargs):
211    """
212    Alias to `{box_type}_encrypt`
213
214    box_type: secretbox, sealedbox(default)
215
216    sk_file
217      Path to where there secret key exists.
218      The argrument ``keyfile`` was deprecated
219      in favor of ``sk_file``. ``keyfile`` will
220      continue to work to ensure backwards
221      compatbility, but please use the preferred
222      ``sk_file``.
223    sk
224      Secret key contents. The argument ``key``
225      was deprecated in favor of ``sk``. ``key``
226      will continue to work to ensure backwards
227      compatibility, but please use the preferred
228      ``sk``.
229    """
230    if "keyfile" in kwargs:
231        kwargs["sk_file"] = kwargs["keyfile"]
232
233        # set boxtype to `secretbox` to maintain backward compatibility
234        kwargs["box_type"] = "secretbox"
235
236    if "key" in kwargs:
237        kwargs["sk"] = kwargs["key"]
238
239        # set boxtype to `secretbox` to maintain backward compatibility
240        kwargs["box_type"] = "secretbox"
241
242    box_type = _get_config(**kwargs)["box_type"]
243    if box_type == "secretbox":
244        return secretbox_encrypt(data, **kwargs)
245    return sealedbox_encrypt(data, **kwargs)
246
247
248def enc_file(name, out=None, **kwargs):
249    """
250    This is a helper function to encrypt a file and return its contents.
251
252    You can provide an optional output file using `out`
253
254    `name` can be a local file or when not using `salt-run` can be a url like `salt://`, `https://` etc.
255
256    CLI Examples:
257
258    .. code-block:: bash
259
260        salt-run nacl.enc_file name=/tmp/id_rsa
261        salt-call nacl.enc_file name=salt://crt/mycert out=/tmp/cert
262        salt-run nacl.enc_file name=/tmp/id_rsa box_type=secretbox \
263            sk_file=/etc/salt/pki/master/nacl.pub
264    """
265    try:
266        data = __salt__["cp.get_file_str"](name)
267    except Exception as e:  # pylint: disable=broad-except
268        # likly using salt-run so fallback to local filesystem
269        with salt.utils.files.fopen(name, "rb") as f:
270            data = salt.utils.stringutils.to_unicode(f.read())
271    d = enc(data, **kwargs)
272    if out:
273        if os.path.isfile(out):
274            raise Exception("file:{} already exist.".format(out))
275        with salt.utils.files.fopen(out, "wb") as f:
276            f.write(salt.utils.stringutils.to_bytes(d))
277        return "Wrote: {}".format(out)
278    return d
279
280
281def dec(data, **kwargs):
282    """
283    Alias to `{box_type}_decrypt`
284
285    box_type: secretbox, sealedbox(default)
286
287    sk_file
288      Path to where there secret key exists.
289      The argrument ``keyfile`` was deprecated
290      in favor of ``sk_file``. ``keyfile`` will
291      continue to work to ensure backwards
292      compatbility, but please use the preferred
293      ``sk_file``.
294    sk
295      Secret key contents. The argument ``key``
296      was deprecated in favor of ``sk``. ``key``
297      will continue to work to ensure backwards
298      compatibility, but please use the preferred
299      ``sk``.
300    """
301    if "keyfile" in kwargs:
302        kwargs["sk_file"] = kwargs["keyfile"]
303
304        # set boxtype to `secretbox` to maintain backward compatibility
305        kwargs["box_type"] = "secretbox"
306
307    if "key" in kwargs:
308        kwargs["sk"] = kwargs["key"]
309
310        # set boxtype to `secretbox` to maintain backward compatibility
311        kwargs["box_type"] = "secretbox"
312
313    box_type = _get_config(**kwargs)["box_type"]
314    if box_type == "secretbox":
315        return secretbox_decrypt(data, **kwargs)
316    return sealedbox_decrypt(data, **kwargs)
317
318
319def dec_file(name, out=None, **kwargs):
320    """
321    This is a helper function to decrypt a file and return its contents.
322
323    You can provide an optional output file using `out`
324
325    `name` can be a local file or when not using `salt-run` can be a url like `salt://`, `https://` etc.
326
327    CLI Examples:
328
329    .. code-block:: bash
330
331        salt-run nacl.dec_file name=/tmp/id_rsa.nacl
332        salt-call nacl.dec_file name=salt://crt/mycert.nacl out=/tmp/id_rsa
333        salt-run nacl.dec_file name=/tmp/id_rsa.nacl box_type=secretbox \
334            sk_file=/etc/salt/pki/master/nacl.pub
335    """
336    try:
337        data = __salt__["cp.get_file_str"](name)
338    except Exception as e:  # pylint: disable=broad-except
339        # likly using salt-run so fallback to local filesystem
340        with salt.utils.files.fopen(name, "rb") as f:
341            data = salt.utils.stringutils.to_unicode(f.read())
342    d = dec(data, **kwargs)
343    if out:
344        if os.path.isfile(out):
345            raise Exception("file:{} already exist.".format(out))
346        with salt.utils.files.fopen(out, "wb") as f:
347            f.write(salt.utils.stringutils.to_bytes(d))
348        return "Wrote: {}".format(out)
349    return d
350
351
352def sealedbox_encrypt(data, **kwargs):
353    """
354    Encrypt data using a public key generated from `nacl.keygen`.
355    The encryptd data can be decrypted using `nacl.sealedbox_decrypt` only with the secret key.
356
357    CLI Examples:
358
359    .. code-block:: bash
360
361        salt-run nacl.sealedbox_encrypt datatoenc
362        salt-call --local nacl.sealedbox_encrypt datatoenc pk_file=/etc/salt/pki/master/nacl.pub
363        salt-call --local nacl.sealedbox_encrypt datatoenc pk='vrwQF7cNiNAVQVAiS3bvcbJUnF0cN6fU9YTZD9mBfzQ='
364    """
365    # ensure data is in bytes
366    data = salt.utils.stringutils.to_bytes(data)
367
368    pk = _get_pk(**kwargs)
369    b = libnacl.sealed.SealedBox(pk)
370    return base64.b64encode(b.encrypt(data))
371
372
373def sealedbox_decrypt(data, **kwargs):
374    """
375    Decrypt data using a secret key that was encrypted using a public key with `nacl.sealedbox_encrypt`.
376
377    CLI Examples:
378
379    .. code-block:: bash
380
381        salt-call nacl.sealedbox_decrypt pEXHQM6cuaF7A=
382        salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl
383        salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo='
384    """
385    if data is None:
386        return None
387
388    # ensure data is in bytes
389    data = salt.utils.stringutils.to_bytes(data)
390
391    sk = _get_sk(**kwargs)
392    keypair = libnacl.public.SecretKey(sk)
393    b = libnacl.sealed.SealedBox(keypair)
394    return b.decrypt(base64.b64decode(data))
395
396
397def secretbox_encrypt(data, **kwargs):
398    """
399    Encrypt data using a secret key generated from `nacl.keygen`.
400    The same secret key can be used to decrypt the data using `nacl.secretbox_decrypt`.
401
402    CLI Examples:
403
404    .. code-block:: bash
405
406        salt-run nacl.secretbox_encrypt datatoenc
407        salt-call --local nacl.secretbox_encrypt datatoenc sk_file=/etc/salt/pki/master/nacl
408        salt-call --local nacl.secretbox_encrypt datatoenc sk='YmFkcGFzcwo='
409    """
410    # ensure data is in bytes
411    data = salt.utils.stringutils.to_bytes(data)
412
413    sk = _get_sk(**kwargs)
414    b = libnacl.secret.SecretBox(sk)
415    return base64.b64encode(b.encrypt(data))
416
417
418def secretbox_decrypt(data, **kwargs):
419    """
420    Decrypt data that was encrypted using `nacl.secretbox_encrypt` using the secret key
421    that was generated from `nacl.keygen`.
422
423    CLI Examples:
424
425    .. code-block:: bash
426
427        salt-call nacl.secretbox_decrypt pEXHQM6cuaF7A=
428        salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl
429        salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo='
430    """
431    if data is None:
432        return None
433
434    # ensure data is in bytes
435    data = salt.utils.stringutils.to_bytes(data)
436
437    key = _get_sk(**kwargs)
438    b = libnacl.secret.SecretBox(key=key)
439
440    return b.decrypt(base64.b64decode(data))
441