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