detssh 0.7.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {detssh-0.7.0 → detssh-0.8.0}/CHANGELOG.md +11 -0
- detssh-0.8.0/PKG-INFO +40 -0
- detssh-0.8.0/README.md +18 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/__init__.py +1 -1
- detssh-0.8.0/detssh/backends/base.py +122 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/_argon2.py +8 -12
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/base.py +4 -2
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/bcrypt_pbkdf.py +6 -8
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/pbkdf2.py +7 -8
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/scrypt.py +9 -12
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/base.py +4 -2
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/blake2b.py +13 -12
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/blake2s.py +13 -12
- detssh-0.8.0/detssh/cli.py +285 -0
- detssh-0.8.0/docs/README.md +10 -0
- detssh-0.8.0/docs/authorize.md +19 -0
- detssh-0.8.0/docs/backends.md +37 -0
- detssh-0.8.0/docs/determinism.md +6 -0
- detssh-0.8.0/docs/development.md +36 -0
- detssh-0.8.0/docs/labels.md +33 -0
- detssh-0.8.0/docs/reference.md +34 -0
- detssh-0.8.0/docs/ssh.md +36 -0
- detssh-0.8.0/docs/usage.md +35 -0
- {detssh-0.7.0 → detssh-0.8.0}/pyproject.toml +2 -1
- {detssh-0.7.0 → detssh-0.8.0}/tests/golden/README.md +7 -0
- detssh-0.8.0/tests/golden/defaults.json +47 -0
- detssh-0.8.0/tests/mutmut_known_equivalents.txt +74 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_cli.py +141 -14
- detssh-0.8.0/tests/test_golden_defaults.py +47 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_properties.py +29 -467
- {detssh-0.7.0 → detssh-0.8.0}/uv.lock +19 -2
- detssh-0.7.0/PKG-INFO +0 -174
- detssh-0.7.0/README.md +0 -153
- detssh-0.7.0/detssh/backends/base.py +0 -252
- detssh-0.7.0/detssh/cli.py +0 -359
- detssh-0.7.0/tests/mutmut_known_equivalents.txt +0 -130
- {detssh-0.7.0 → detssh-0.8.0}/.github/workflows/publish.yml +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/.github/workflows/tests.yml +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/.gitignore +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/LICENSE +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/__init__.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/__init__.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/argon2d.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/argon2i.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/kdf/argon2id.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/__init__.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/sha256.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/sha384.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/sha3_256.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/sha3_384.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/sha3_512.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/backends/salt/sha512.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/keygen.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/registry.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/ssh_cli.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/detssh/ssh_config.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/golden/help_default.txt +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/golden/help_pbkdf2_sha256.txt +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/golden/variations.csv +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/golden/vectors.csv +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_authorize.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_cli_combinations.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_determinism.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_golden_vectors.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_help_text.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_registry.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_ssh_cli.py +0 -0
- {detssh-0.7.0 → detssh-0.8.0}/tests/test_ssh_config.py +0 -0
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Options, questions and their checks now come from one declaration per
|
|
15
|
+
setting, built on `isik[cli]` (a new dependency). `--help`, every flag,
|
|
16
|
+
every question and every derived key are unchanged.
|
|
17
|
+
- In interactive mode, `--overwrite-files` with "Only add the public key" is
|
|
18
|
+
now refused, as it already was with `--authorize`. Before, it was quietly
|
|
19
|
+
ignored.
|
|
20
|
+
|
|
10
21
|
## [0.7.0] - 2026-10-05
|
|
11
22
|
|
|
12
23
|
### Added
|
detssh-0.8.0/PKG-INFO
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: detssh
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Deterministic Ed25519 SSH key generation from a seed passphrase, via configurable KDF and salt-hash backends.
|
|
5
|
+
Project-URL: Homepage, https://github.com/isik-kaplan/detssh
|
|
6
|
+
Project-URL: Repository, https://github.com/isik-kaplan/detssh
|
|
7
|
+
Author-email: Işık Kaplan <isik.kaplan@outlook.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: argon2,bcrypt,cryptography,ed25519,kdf,pbkdf2,scrypt,ssh
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Security :: Cryptography
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: argon2-cffi>=21.3
|
|
16
|
+
Requires-Dist: bcrypt>=5.0.0
|
|
17
|
+
Requires-Dist: click>=8.1
|
|
18
|
+
Requires-Dist: cryptography>=41.0
|
|
19
|
+
Requires-Dist: isik[cli]>=0.34.0
|
|
20
|
+
Requires-Dist: questionary>=2.1.1
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# detssh
|
|
24
|
+
|
|
25
|
+
Deterministic Ed25519 SSH keys from a passphrase: same passphrase, same key,
|
|
26
|
+
on any machine. Nothing to back up.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
uv tool install detssh
|
|
30
|
+
detssh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`detssh` asks for everything it needs.
|
|
34
|
+
|
|
35
|
+
I name labels `<owner>:<site>:<machine>`, e.g. `isik:home:laptop` or
|
|
36
|
+
`client:cloud:web1`
|
|
37
|
+
([more](https://github.com/isik-kaplan/detssh/blob/master/docs/labels.md)).
|
|
38
|
+
|
|
39
|
+
See the [docs](https://github.com/isik-kaplan/detssh/tree/master/docs) for
|
|
40
|
+
flags and everything else.
|
detssh-0.8.0/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# detssh
|
|
2
|
+
|
|
3
|
+
Deterministic Ed25519 SSH keys from a passphrase: same passphrase, same key,
|
|
4
|
+
on any machine. Nothing to back up.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
uv tool install detssh
|
|
8
|
+
detssh
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`detssh` asks for everything it needs.
|
|
12
|
+
|
|
13
|
+
I name labels `<owner>:<site>:<machine>`, e.g. `isik:home:laptop` or
|
|
14
|
+
`client:cloud:web1`
|
|
15
|
+
([more](https://github.com/isik-kaplan/detssh/blob/master/docs/labels.md)).
|
|
16
|
+
|
|
17
|
+
See the [docs](https://github.com/isik-kaplan/detssh/tree/master/docs) for
|
|
18
|
+
flags and everything else.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from isik.common.settings import Settings
|
|
5
|
+
|
|
6
|
+
from detssh.keygen import (
|
|
7
|
+
authorize_public_key,
|
|
8
|
+
keypair_from_seed,
|
|
9
|
+
public_key_line,
|
|
10
|
+
public_key_path,
|
|
11
|
+
ssh_dir,
|
|
12
|
+
write_keypair,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _display_path(path):
|
|
17
|
+
try:
|
|
18
|
+
return f"~/{Path(path).relative_to(Path.home())}"
|
|
19
|
+
except (TypeError, ValueError):
|
|
20
|
+
return str(path)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NoSettings(Settings):
|
|
24
|
+
"""The settings of a backend that has none to give."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def note_if_exists(default):
|
|
28
|
+
"""Before asking for an output path: say so when the offered default would overwrite a key."""
|
|
29
|
+
if default is not None and (Path(default).exists() or public_key_path(default).exists()):
|
|
30
|
+
click.echo(f"Note: {_display_path(default)} already exists - enter a different path to avoid this.")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def confirm_overwrite(output, overwrite_files):
|
|
34
|
+
if overwrite_files:
|
|
35
|
+
return
|
|
36
|
+
output = Path(output)
|
|
37
|
+
if (output.exists() or public_key_path(output).exists()) and not click.confirm(
|
|
38
|
+
f"{output} already exists. Overwrite?"
|
|
39
|
+
):
|
|
40
|
+
raise click.Abort()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def confirm_create_parent_dirs(output, create_parent_dirs, interactive):
|
|
44
|
+
"""Decide whether to create the output path's missing parent directories.
|
|
45
|
+
|
|
46
|
+
Returns the (possibly updated) flag for write_keypair_and_recap to act on. Flag
|
|
47
|
+
mode requires --create-parent-dirs up front and fails fast otherwise, so a typo
|
|
48
|
+
surfaces before key derivation rather than as a raw OSError after it. Interactive
|
|
49
|
+
mode prompts for the same decision instead, defaulting to yes under ~/.ssh and to
|
|
50
|
+
no (with a warning) elsewhere - matching what --create-parent-dirs would have done.
|
|
51
|
+
"""
|
|
52
|
+
parent = Path(output).parent
|
|
53
|
+
if parent.exists() or parent == ssh_dir():
|
|
54
|
+
return create_parent_dirs
|
|
55
|
+
|
|
56
|
+
outside = not parent.is_relative_to(ssh_dir())
|
|
57
|
+
|
|
58
|
+
if not interactive:
|
|
59
|
+
if not create_parent_dirs:
|
|
60
|
+
raise click.UsageError(f"{_display_path(parent)} doesn't exist. Pass --create-parent-dirs to create it.")
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
if not create_parent_dirs and not outside:
|
|
64
|
+
question, default = f"{_display_path(parent)} doesn't exist. Create it?", True
|
|
65
|
+
elif outside:
|
|
66
|
+
click.echo(f"Warning: {_display_path(parent)} is outside ~/.ssh.")
|
|
67
|
+
question, default = "Create it anyway?", False
|
|
68
|
+
else:
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
if not click.confirm(question, default=default):
|
|
72
|
+
raise click.Abort()
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def write_keypair_and_recap(seed_bytes, output, comment, recap, key_passphrase="", create_parent_dirs=False):
|
|
77
|
+
private_key, public_key = keypair_from_seed(seed_bytes)
|
|
78
|
+
try:
|
|
79
|
+
priv_path, pub_path = write_keypair(
|
|
80
|
+
private_key,
|
|
81
|
+
public_key,
|
|
82
|
+
output,
|
|
83
|
+
comment=comment,
|
|
84
|
+
key_passphrase=key_passphrase,
|
|
85
|
+
create_parent_dirs=create_parent_dirs,
|
|
86
|
+
)
|
|
87
|
+
except OSError as e:
|
|
88
|
+
raise click.ClickException(f"couldn't write {output}: {e.strerror or e}") from e
|
|
89
|
+
except ValueError as e:
|
|
90
|
+
raise click.UsageError(str(e)) from e
|
|
91
|
+
|
|
92
|
+
click.echo(f"Wrote private key: {priv_path}{' (encrypted)' if key_passphrase else ''}")
|
|
93
|
+
click.echo(f"Wrote public key: {pub_path}")
|
|
94
|
+
|
|
95
|
+
echo_recap(recap)
|
|
96
|
+
|
|
97
|
+
return priv_path, pub_path
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def authorize_and_recap(seed_bytes, authorized_keys, comment, recap, create_parent_dirs=False):
|
|
101
|
+
_, public_key = keypair_from_seed(seed_bytes)
|
|
102
|
+
try:
|
|
103
|
+
added = authorize_public_key(
|
|
104
|
+
public_key, authorized_keys, comment=comment, create_parent_dirs=create_parent_dirs
|
|
105
|
+
)
|
|
106
|
+
except OSError as e:
|
|
107
|
+
raise click.ClickException(f"couldn't write {authorized_keys}: {e.strerror or e}") from e
|
|
108
|
+
|
|
109
|
+
if added:
|
|
110
|
+
click.echo(f"Added public key to {authorized_keys}:")
|
|
111
|
+
else:
|
|
112
|
+
click.echo(f"Public key already in {authorized_keys}, left it unchanged:")
|
|
113
|
+
click.echo(f" {public_key_line(public_key, comment).decode('utf-8').rstrip()}")
|
|
114
|
+
|
|
115
|
+
echo_recap(recap)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def echo_recap(recap):
|
|
119
|
+
click.echo()
|
|
120
|
+
click.echo("To recreate this exact key, remember your passphrase (keep it secret) plus:")
|
|
121
|
+
for name, value in (*recap, ("algorithm", "ed25519")):
|
|
122
|
+
click.echo(f" {name:<18} {value}")
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
from argon2.exceptions import Argon2Error
|
|
2
2
|
from argon2.low_level import hash_secret_raw
|
|
3
|
+
from isik.common.settings import Setting, Settings
|
|
3
4
|
|
|
4
|
-
from detssh.backends.base import Field
|
|
5
5
|
from detssh.backends.kdf.base import KDFBackend, KDFError, SaltConstraints
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class Argon2Settings(Settings):
|
|
9
|
+
iterations = Setting(int, "Argon2 iterations", default=10, soft_max=100)
|
|
10
|
+
memory = Setting(int, "Argon2 memory, in MiB", default=1024, soft_max=2048)
|
|
11
|
+
parallelism = Setting(int, "Argon2 parallelism", default=4, soft_max=16)
|
|
12
|
+
|
|
13
|
+
|
|
8
14
|
class Argon2Base(KDFBackend):
|
|
9
15
|
salt_constraints = SaltConstraints(min_size=8)
|
|
10
16
|
type = None
|
|
11
|
-
|
|
12
|
-
fields = {
|
|
13
|
-
"iterations": Field("Argon2 iterations", kind="int", soft_max=100),
|
|
14
|
-
"memory": Field("Argon2 memory, in MiB", kind="int", soft_max=2048),
|
|
15
|
-
"parallelism": Field("Argon2 parallelism", kind="int", soft_max=16),
|
|
16
|
-
}
|
|
17
|
-
defaults = {
|
|
18
|
-
"iterations": 10,
|
|
19
|
-
"memory": 1024,
|
|
20
|
-
"parallelism": 4,
|
|
21
|
-
}
|
|
17
|
+
settings = Argon2Settings
|
|
22
18
|
|
|
23
19
|
@classmethod
|
|
24
20
|
def run(cls, resolved, salt):
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from detssh.backends.base import NoSettings
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class SaltConstraints:
|
|
2
5
|
def __init__(self, min_size=None, max_size=None, exact_size=None):
|
|
3
6
|
self.min_size = min_size
|
|
@@ -24,8 +27,7 @@ class KDFError(Exception):
|
|
|
24
27
|
class KDFBackend:
|
|
25
28
|
name = None
|
|
26
29
|
salt_constraints = None
|
|
27
|
-
|
|
28
|
-
defaults = {}
|
|
30
|
+
settings = NoSettings
|
|
29
31
|
|
|
30
32
|
@classmethod
|
|
31
33
|
def run(cls, resolved, salt):
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import bcrypt
|
|
2
|
+
from isik.common.settings import Setting, Settings
|
|
2
3
|
|
|
3
|
-
from detssh.backends.base import Field
|
|
4
4
|
from detssh.backends.kdf.base import KDFBackend, KDFError, SaltConstraints
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
class BcryptPbkdfSettings(Settings):
|
|
8
|
+
rounds = Setting(int, "bcrypt_pbkdf rounds", default=16, soft_max=2000)
|
|
9
|
+
|
|
10
|
+
|
|
7
11
|
class BcryptPbkdf(KDFBackend):
|
|
8
12
|
name = "bcrypt_pbkdf"
|
|
9
13
|
salt_constraints = SaltConstraints(min_size=1)
|
|
10
|
-
|
|
11
|
-
fields = {
|
|
12
|
-
"rounds": Field("bcrypt_pbkdf rounds", kind="int", soft_max=2000),
|
|
13
|
-
}
|
|
14
|
-
defaults = {
|
|
15
|
-
"rounds": 16,
|
|
16
|
-
}
|
|
14
|
+
settings = BcryptPbkdfSettings
|
|
17
15
|
|
|
18
16
|
@classmethod
|
|
19
17
|
def run(cls, resolved, salt):
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from isik.common.settings import Setting, Settings
|
|
4
|
+
|
|
4
5
|
from detssh.backends.kdf.base import NO_SALT_CONSTRAINTS, KDFBackend, KDFError
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
class Pbkdf2Settings(Settings):
|
|
9
|
+
iterations = Setting(int, "PBKDF2 iterations", default=600_000, soft_max=50_000_000)
|
|
10
|
+
|
|
11
|
+
|
|
7
12
|
class Pbkdf2(KDFBackend):
|
|
8
13
|
name = "pbkdf2"
|
|
9
14
|
salt_constraints = NO_SALT_CONSTRAINTS
|
|
10
|
-
|
|
11
|
-
fields = {
|
|
12
|
-
"iterations": Field("PBKDF2 iterations", kind="int", soft_max=50_000_000),
|
|
13
|
-
}
|
|
14
|
-
defaults = {
|
|
15
|
-
"iterations": 600_000,
|
|
16
|
-
}
|
|
15
|
+
settings = Pbkdf2Settings
|
|
17
16
|
|
|
18
17
|
@classmethod
|
|
19
18
|
def run(cls, resolved, salt):
|
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from isik.common.settings import Setting, Settings
|
|
4
|
+
|
|
4
5
|
from detssh.backends.kdf.base import NO_SALT_CONSTRAINTS, KDFBackend, KDFError
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
class ScryptSettings(Settings):
|
|
9
|
+
cost = Setting(int, "scrypt cost (N, power of two)", default=2**14, soft_max=2**18)
|
|
10
|
+
block_size = Setting(int, "scrypt block size (r)", default=8, soft_max=16)
|
|
11
|
+
parallelism = Setting(int, "scrypt parallelism (p)", default=1, soft_max=8)
|
|
12
|
+
|
|
13
|
+
|
|
7
14
|
class Scrypt(KDFBackend):
|
|
8
15
|
name = "scrypt"
|
|
9
16
|
salt_constraints = NO_SALT_CONSTRAINTS
|
|
10
|
-
|
|
11
|
-
fields = {
|
|
12
|
-
"cost": Field("scrypt cost (N, power of two)", kind="int", soft_max=2**18),
|
|
13
|
-
"block_size": Field("scrypt block size (r)", kind="int", soft_max=16),
|
|
14
|
-
"parallelism": Field("scrypt parallelism (p)", kind="int", soft_max=8),
|
|
15
|
-
}
|
|
16
|
-
defaults = {
|
|
17
|
-
"cost": 2**14,
|
|
18
|
-
"block_size": 8,
|
|
19
|
-
"parallelism": 1,
|
|
20
|
-
}
|
|
17
|
+
settings = ScryptSettings
|
|
21
18
|
|
|
22
19
|
@classmethod
|
|
23
20
|
def run(cls, resolved, salt):
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from detssh.backends.base import NoSettings
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class SaltError(Exception):
|
|
2
5
|
pass
|
|
3
6
|
|
|
@@ -7,8 +10,7 @@ class SaltAlgo:
|
|
|
7
10
|
digest_size = None
|
|
8
11
|
min_digest_size = None
|
|
9
12
|
max_digest_size = None
|
|
10
|
-
|
|
11
|
-
defaults = {}
|
|
13
|
+
settings = NoSettings
|
|
12
14
|
|
|
13
15
|
@classmethod
|
|
14
16
|
def is_variable(cls):
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from isik.common.settings import Setting, Settings
|
|
4
|
+
|
|
4
5
|
from detssh.backends.salt.base import SaltAlgo, SaltError
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
MIN_DIGEST_SIZE = 1
|
|
9
|
+
MAX_DIGEST_SIZE = 64
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Blake2bSettings(Settings):
|
|
13
|
+
salt_digest_size = Setting(int, "Salt digest size, in bytes", default=16, min=MIN_DIGEST_SIZE, max=MAX_DIGEST_SIZE)
|
|
14
|
+
|
|
15
|
+
|
|
7
16
|
class Blake2b(SaltAlgo):
|
|
8
17
|
name = "blake2b"
|
|
9
|
-
min_digest_size =
|
|
10
|
-
max_digest_size =
|
|
11
|
-
|
|
12
|
-
fields = {
|
|
13
|
-
"salt_digest_size": Field(
|
|
14
|
-
"Salt digest size, in bytes", kind="int", min_value=min_digest_size, max_value=max_digest_size
|
|
15
|
-
),
|
|
16
|
-
}
|
|
17
|
-
defaults = {
|
|
18
|
-
"salt_digest_size": 16,
|
|
19
|
-
}
|
|
18
|
+
min_digest_size = MIN_DIGEST_SIZE
|
|
19
|
+
max_digest_size = MAX_DIGEST_SIZE
|
|
20
|
+
settings = Blake2bSettings
|
|
20
21
|
|
|
21
22
|
@classmethod
|
|
22
23
|
def digest(cls, label, resolved):
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from isik.common.settings import Setting, Settings
|
|
4
|
+
|
|
4
5
|
from detssh.backends.salt.base import SaltAlgo, SaltError
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
MIN_DIGEST_SIZE = 1
|
|
9
|
+
MAX_DIGEST_SIZE = 32
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Blake2sSettings(Settings):
|
|
13
|
+
salt_digest_size = Setting(int, "Salt digest size, in bytes", default=16, min=MIN_DIGEST_SIZE, max=MAX_DIGEST_SIZE)
|
|
14
|
+
|
|
15
|
+
|
|
7
16
|
class Blake2s(SaltAlgo):
|
|
8
17
|
name = "blake2s"
|
|
9
|
-
min_digest_size =
|
|
10
|
-
max_digest_size =
|
|
11
|
-
|
|
12
|
-
fields = {
|
|
13
|
-
"salt_digest_size": Field(
|
|
14
|
-
"Salt digest size, in bytes", kind="int", min_value=min_digest_size, max_value=max_digest_size
|
|
15
|
-
),
|
|
16
|
-
}
|
|
17
|
-
defaults = {
|
|
18
|
-
"salt_digest_size": 16,
|
|
19
|
-
}
|
|
18
|
+
min_digest_size = MIN_DIGEST_SIZE
|
|
19
|
+
max_digest_size = MAX_DIGEST_SIZE
|
|
20
|
+
settings = Blake2sSettings
|
|
20
21
|
|
|
21
22
|
@classmethod
|
|
22
23
|
def digest(cls, label, resolved):
|