From 671b77f6a78655d44bc7441c0c68bd1182729748 Mon Sep 17 00:00:00 2001 From: Alexander Hughes Date: Tue, 19 Feb 2019 14:36:50 -0600 Subject: [PATCH] Add CLI generation of salt Salts and Passphrases are both strings used in cryptography. This patch: 1. Adds CLI generation of salt 2. Adds unit test for CLI generation of salt 3. Updates passphrase.py code to be more generic as it is used to generate both a passphrase and a salt 4. Update name of passphrase.py to be more generic 5. Update all references to, and tests of passphrase.py 6. Add documentation for CLI generation of salt Co-Authored-By: chittibabu Change-Id: I71858d63a2846290d22be96686ccfea3ba8aa6c0 --- doc/source/cli/cli.rst | 31 ++++++++++++++ pegleg/cli.py | 17 +++++++- .../engine/generators/passphrase_generator.py | 6 +-- pegleg/engine/secrets.py | 10 ++--- .../util/{passphrase.py => cryptostring.py} | 18 +++++---- ...rases.py => test_generate_cryptostring.py} | 40 +++++++++---------- tests/unit/test_cli.py | 5 +++ 7 files changed, 91 insertions(+), 36 deletions(-) rename pegleg/engine/util/{passphrase.py => cryptostring.py} (73%) rename tests/unit/engine/{test_generate_passphrases.py => test_generate_cryptostring.py} (89%) diff --git a/doc/source/cli/cli.rst b/doc/source/cli/cli.rst index 7279bb0e..f8b8383a 100644 --- a/doc/source/cli/cli.rst +++ b/doc/source/cli/cli.rst @@ -846,3 +846,34 @@ Example with length specified: :: ./pegleg.sh generate passphrase -l + +Salt +---- + +Generate a salt and print to ``stdout``. + +**-l / --length** (Optional). + +Length of salt to generate. By default length is 24. +Minimum length is 24. No maximum length. + +Usage: + +:: + + ./pegleg.sh generate salt -l + +Examples +^^^^^^^^ + +Example without length specified: + +:: + + ./pegleg.sh generate salt + +Example with length specified: + +:: + + ./pegleg.sh generate salt -l \ No newline at end of file diff --git a/pegleg/cli.py b/pegleg/cli.py index ca388644..a1982f19 100644 --- a/pegleg/cli.py +++ b/pegleg/cli.py @@ -549,4 +549,19 @@ def generate(): 'Length is >= 24, default length is 24, no maximum length') def generate_passphrase(length): click.echo("Generated Passhprase: {}".format( - engine.secrets.generate_passphrase(length))) + engine.secrets.generate_crypto_string(length))) + + +@generate.command( + 'salt', + help='Command to generate a salt and print out to stdout') +@click.option( + '-l', + '--length', + 'length', + default=24, + help='Generate a salt of the given length. ' + 'Length is >= 24, default length is 24, no maximum length') +def generate_salt(length): + click.echo("Generated Salt: {}".format( + engine.secrets.generate_crypto_string(length))) diff --git a/pegleg/engine/generators/passphrase_generator.py b/pegleg/engine/generators/passphrase_generator.py index c20f7f88..77b28f42 100644 --- a/pegleg/engine/generators/passphrase_generator.py +++ b/pegleg/engine/generators/passphrase_generator.py @@ -18,8 +18,8 @@ import logging from pegleg.engine.catalogs import passphrase_catalog from pegleg.engine.catalogs.passphrase_catalog import PassphraseCatalog from pegleg.engine.generators.base_generator import BaseGenerator +from pegleg.engine.util.cryptostring import CryptoString from pegleg.engine.util import files -from pegleg.engine.util.passphrase import Passphrase from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement __all__ = ['PassphraseGenerator'] @@ -49,7 +49,7 @@ class PassphraseGenerator(BaseGenerator): sitename, save_location, author) self._catalog = PassphraseCatalog( self._sitename, documents=self._documents) - self._pass_util = Passphrase() + self._pass_util = CryptoString() def generate(self, interactive=False): """ @@ -67,7 +67,7 @@ class PassphraseGenerator(BaseGenerator): prompt="Input passphrase for {}. Leave blank to " "auto-generate:\n".format(p_name)) if not passphrase: - passphrase = self._pass_util.get_pass( + passphrase = self._pass_util.get_crypto_string( self._catalog.get_length(p_name)) docs = list() storage_policy = self._catalog.get_storage_policy(p_name) diff --git a/pegleg/engine/secrets.py b/pegleg/engine/secrets.py index 04026905..5cc20927 100644 --- a/pegleg/engine/secrets.py +++ b/pegleg/engine/secrets.py @@ -16,9 +16,9 @@ import logging import os from pegleg.engine.generators.passphrase_generator import PassphraseGenerator +from pegleg.engine.util.cryptostring import CryptoString from pegleg.engine.util import definition from pegleg.engine.util import files -from pegleg.engine.util.passphrase import Passphrase from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement __all__ = ('encrypt', 'decrypt', 'generate_passphrases') @@ -132,12 +132,12 @@ def generate_passphrases(site_name, save_location, author, interactive=False): interactive=interactive) -def generate_passphrase(length): +def generate_crypto_string(length): """ - Create a passphrase. + Create a cryptographic string. - :param int length: Length of passphrase. + :param int length: Length of cryptographic string. :rtype: string """ - return Passphrase().get_pass(length) + return CryptoString().get_crypto_string(length) diff --git a/pegleg/engine/util/passphrase.py b/pegleg/engine/util/cryptostring.py similarity index 73% rename from pegleg/engine/util/passphrase.py rename to pegleg/engine/util/cryptostring.py index d81385e7..d50e6446 100644 --- a/pegleg/engine/util/passphrase.py +++ b/pegleg/engine/util/cryptostring.py @@ -17,17 +17,21 @@ from random import SystemRandom from rstr import Rstr import string -__all__ = ['Passphrase'] +__all__ = ['CryptoString'] -class Passphrase(object): +class CryptoString(object): def __init__(self): self._pool = string.ascii_letters + string.digits + string.punctuation self._rs = Rstr(SystemRandom()) - def get_pass(self, pass_len=24): - """Create and return a random password, of the ``pass_len`` length.""" - if pass_len < 24: - pass_len = 24 - return self._rs.rstr(self._pool, pass_len) + def get_crypto_string(self, len=24): + """ + Create and return a random cryptographic string, + of the ``len`` length. + """ + + if len < 24: + len = 24 + return self._rs.rstr(self._pool, len) diff --git a/tests/unit/engine/test_generate_passphrases.py b/tests/unit/engine/test_generate_cryptostring.py similarity index 89% rename from tests/unit/engine/test_generate_passphrases.py rename to tests/unit/engine/test_generate_cryptostring.py index 74f0af72..2797e8f3 100644 --- a/tests/unit/engine/test_generate_passphrases.py +++ b/tests/unit/engine/test_generate_cryptostring.py @@ -19,7 +19,7 @@ import mock import string import yaml -from pegleg.engine.util.passphrase import Passphrase +from pegleg.engine.util.cryptostring import CryptoString from pegleg.engine.generators.passphrase_generator import PassphraseGenerator from pegleg.engine.util import encryption from pegleg.engine import util @@ -99,30 +99,30 @@ TEST_SITE_DEFINITION = { TEST_SITE_DOCUMENTS = [TEST_SITE_DEFINITION, TEST_PASSPHRASES_CATALOG] -def test_passphrase_default_len(): - p_util = Passphrase() - passphrase = p_util.get_pass() - assert len(passphrase) == 24 +def test_cryptostring_default_len(): + s_util = CryptoString() + s = s_util.get_crypto_string() + assert len(s) == 24 alphabet = set(string.punctuation + string.ascii_letters + string.digits) - assert any(c in alphabet for c in passphrase) + assert any(c in alphabet for c in s) -def test_passphrase_short_len(): - p_util = Passphrase() - p = p_util.get_pass(0) - assert len(p) == 24 - p = p_util.get_pass(23) - assert len(p) == 24 - p = p_util.get_pass(-1) - assert len(p) == 24 +def test_cryptostring_short_len(): + s_util = CryptoString() + s = s_util.get_crypto_string(0) + assert len(s) == 24 + s = s_util.get_crypto_string(23) + assert len(s) == 24 + s = s_util.get_crypto_string(-1) + assert len(s) == 24 -def test_passphrase_long_len(): - p_util = Passphrase() - p = p_util.get_pass(25) - assert len(p) == 25 - p = p_util.get_pass(128) - assert len(p) == 128 +def test_cryptostring_long_len(): + s_util = CryptoString() + s = s_util.get_crypto_string(25) + assert len(s) == 25 + s = s_util.get_crypto_string(128) + assert len(s) == 128 @mock.patch.object( diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 170d81b3..6908308c 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -389,6 +389,11 @@ class TestGenerateActions(BaseCLIActionTest): assert result.exit_code == 0, result.output + def test_generate_salt(self): + result = self.runner.invoke(cli.generate, ['salt']) + + assert result.exit_code == 0, result.output + class TestRepoCliActions(BaseCLIActionTest): """Tests repo-level CLI actions."""