flechtctl 0.16.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.
- flechtctl-0.16.0/.gitignore +18 -0
- flechtctl-0.16.0/PKG-INFO +105 -0
- flechtctl-0.16.0/README.md +81 -0
- flechtctl-0.16.0/pyproject.toml +48 -0
- flechtctl-0.16.0/src/flechtctl/__init__.py +90 -0
- flechtctl-0.16.0/src/flechtctl/__main__.py +7 -0
- flechtctl-0.16.0/src/flechtctl/cli.py +437 -0
- flechtctl-0.16.0/src/flechtctl/errors.py +119 -0
- flechtctl-0.16.0/src/flechtctl/keyring.py +474 -0
- flechtctl-0.16.0/src/flechtctl/py.typed +0 -0
- flechtctl-0.16.0/src/flechtctl/tokens.py +514 -0
- flechtctl-0.16.0/tests/ctl_cli_test.py +509 -0
- flechtctl-0.16.0/tests/ctl_hygiene_test.py +61 -0
- flechtctl-0.16.0/tests/ctl_keyring_file_test.py +86 -0
- flechtctl-0.16.0/tests/ctl_keyring_test.py +207 -0
- flechtctl-0.16.0/tests/ctl_tokens_test.py +259 -0
- flechtctl-0.16.0/tests/fixtures/face_vector_token.txt +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
*.egg-info
|
|
2
|
+
.cache/
|
|
3
|
+
.coverage
|
|
4
|
+
.coverage.*
|
|
5
|
+
.hypothesis/
|
|
6
|
+
.idea/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.venv
|
|
9
|
+
.vscode/
|
|
10
|
+
/.claude/settings.local.json
|
|
11
|
+
/CLAUDE.local.md
|
|
12
|
+
/graphify-out/
|
|
13
|
+
/site/
|
|
14
|
+
__pycache__/
|
|
15
|
+
build/
|
|
16
|
+
coverage.xml
|
|
17
|
+
dist/
|
|
18
|
+
htmlcov/
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: flechtctl
|
|
3
|
+
Version: 0.16.0
|
|
4
|
+
Summary: Keyrings and flenc:/flsig: secret tokens for Flechtwerk, without the framework: a library and a command line tool.
|
|
5
|
+
Project-URL: Documentation, https://bsure-analytics.github.io/flechtwerk/guides/flechtctl/
|
|
6
|
+
Project-URL: Homepage, https://github.com/bsure-analytics/flechtwerk
|
|
7
|
+
Project-URL: Issues, https://github.com/bsure-analytics/flechtwerk/issues
|
|
8
|
+
Project-URL: Repository, https://github.com/bsure-analytics/flechtwerk
|
|
9
|
+
Author: Christian Schlichtherle, Viktor Penev
|
|
10
|
+
Maintainer: b.sure GmbH
|
|
11
|
+
License: MIT
|
|
12
|
+
Keywords: flechtwerk,jwe,jws,keyring,secrets
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Security :: Cryptography
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Requires-Dist: joserfc~=1.7
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# flechtctl
|
|
26
|
+
|
|
27
|
+
Keyrings and `flenc:` / `flsig:` secret tokens for
|
|
28
|
+
[Flechtwerk](https://github.com/bsure-analytics/flechtwerk) — as a small library and a
|
|
29
|
+
command line tool, **without the framework**. `flechtctl` depends on
|
|
30
|
+
[joserfc](https://jose.authlib.org/) only; it never imports `flechtwerk`, so it installs and
|
|
31
|
+
runs wherever Python does (including interpreters the full stream-processing framework does
|
|
32
|
+
not support yet).
|
|
33
|
+
|
|
34
|
+
It does three things:
|
|
35
|
+
|
|
36
|
+
- **Keyrings.** Create and edit keyring files — an RFC 7517 JWK Set holding oct (symmetric),
|
|
37
|
+
X25519 (sealing) and Ed25519 (signing) keys, each optionally bound to scopes. Optionally
|
|
38
|
+
password-protected.
|
|
39
|
+
- **Value crypto.** Seal, open, sign and verify values: `flenc:jwe:…` compact JWE tokens and
|
|
40
|
+
`flsig:jws:…` Ed25519 compact JWS tokens.
|
|
41
|
+
- **Token tooling.** Inspect a token's header, re-seal a token under another key.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uvx flechtctl --help # run it without installing
|
|
47
|
+
uv tool install flechtctl # or install the command
|
|
48
|
+
pip install flechtctl # or use it as a library
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Python 3.12 or newer.
|
|
52
|
+
|
|
53
|
+
## Command line quick start
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
flechtctl keyring new keyring.json --kid prod-2026-10 # a keyring with one fresh 32-byte key
|
|
57
|
+
TOKEN=$(flechtctl encrypt -k keyring.json --scope api_key --string 's3cret')
|
|
58
|
+
flechtctl inspect "$TOKEN" # the unauthenticated header: kid, scope, alg
|
|
59
|
+
flechtctl decrypt -k keyring.json --scope api_key --string "$TOKEN"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`-k/--keyring` is required for every command that needs key material: the command reads no
|
|
63
|
+
environment variables. `flechtctl keyring --help` lists the keyring commands — `new`, `add`,
|
|
64
|
+
`remove`, `primary`, `signing`, `show`, `export`, `import`, `protect` and `unprotect`. A keyring
|
|
65
|
+
file works like a keystore: a set of named entries, handed on one public half at a time with
|
|
66
|
+
`export` and `import`.
|
|
67
|
+
|
|
68
|
+
## Library quick start
|
|
69
|
+
|
|
70
|
+
The functions take the keyring explicitly — there is no global state:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from pathlib import Path
|
|
74
|
+
|
|
75
|
+
from flechtctl import Keyring, decrypt, encrypt, sign, verify
|
|
76
|
+
|
|
77
|
+
keyring = Keyring.from_json(Path("keyring.json").read_text())
|
|
78
|
+
|
|
79
|
+
token = encrypt(keyring, "s3cret", scope="api_key") # "flenc:jwe:…"
|
|
80
|
+
assert decrypt(keyring, token, scope="api_key") == "s3cret"
|
|
81
|
+
|
|
82
|
+
signed = sign(keyring, {"order": 17}, scope="orders") # "flsig:jws:…" — needs a signing key for the scope
|
|
83
|
+
assert verify(keyring, signed, scope="orders") == {"order": 17}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`encrypt`, `decrypt`, `sign` and `verify` work on any JSON value; the `*_bytes` variants take
|
|
87
|
+
raw payload bytes. `reencrypt` moves a token to the scope's current sealing key, `read_header`
|
|
88
|
+
shows a token's unauthenticated header, and `generate_oct_key`, `generate_x25519_key` and
|
|
89
|
+
`generate_ed25519_key` mint key material.
|
|
90
|
+
|
|
91
|
+
## Equivalence with Flechtwerk's codecs
|
|
92
|
+
|
|
93
|
+
`flechtctl.encrypt(keyring, value, scope=s)` writes exactly the token that Flechtwerk's
|
|
94
|
+
`ENCRYPTED(inner, scope=s)` codec writes for `value == inner.encode(x)`, and every token
|
|
95
|
+
Flechtwerk writes opens here. For a `BYTES` field, pass the base64 string. Flechtwerk itself
|
|
96
|
+
is built on this package: `pip install "flechtwerk[secrets]"` installs the matching `flechtctl`.
|
|
97
|
+
|
|
98
|
+
## More
|
|
99
|
+
|
|
100
|
+
The guide [Keyrings and Secret Tokens with flechtctl](https://bsure-analytics.github.io/flechtwerk/guides/flechtctl/)
|
|
101
|
+
walks through a complete reader/writer setup, password-protected keyrings and the library API.
|
|
102
|
+
The format itself is specified on the
|
|
103
|
+
[Encrypted Secrets](https://bsure-analytics.github.io/flechtwerk/concepts/secrets/) page.
|
|
104
|
+
|
|
105
|
+
MIT licensed.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# flechtctl
|
|
2
|
+
|
|
3
|
+
Keyrings and `flenc:` / `flsig:` secret tokens for
|
|
4
|
+
[Flechtwerk](https://github.com/bsure-analytics/flechtwerk) — as a small library and a
|
|
5
|
+
command line tool, **without the framework**. `flechtctl` depends on
|
|
6
|
+
[joserfc](https://jose.authlib.org/) only; it never imports `flechtwerk`, so it installs and
|
|
7
|
+
runs wherever Python does (including interpreters the full stream-processing framework does
|
|
8
|
+
not support yet).
|
|
9
|
+
|
|
10
|
+
It does three things:
|
|
11
|
+
|
|
12
|
+
- **Keyrings.** Create and edit keyring files — an RFC 7517 JWK Set holding oct (symmetric),
|
|
13
|
+
X25519 (sealing) and Ed25519 (signing) keys, each optionally bound to scopes. Optionally
|
|
14
|
+
password-protected.
|
|
15
|
+
- **Value crypto.** Seal, open, sign and verify values: `flenc:jwe:…` compact JWE tokens and
|
|
16
|
+
`flsig:jws:…` Ed25519 compact JWS tokens.
|
|
17
|
+
- **Token tooling.** Inspect a token's header, re-seal a token under another key.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uvx flechtctl --help # run it without installing
|
|
23
|
+
uv tool install flechtctl # or install the command
|
|
24
|
+
pip install flechtctl # or use it as a library
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Python 3.12 or newer.
|
|
28
|
+
|
|
29
|
+
## Command line quick start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
flechtctl keyring new keyring.json --kid prod-2026-10 # a keyring with one fresh 32-byte key
|
|
33
|
+
TOKEN=$(flechtctl encrypt -k keyring.json --scope api_key --string 's3cret')
|
|
34
|
+
flechtctl inspect "$TOKEN" # the unauthenticated header: kid, scope, alg
|
|
35
|
+
flechtctl decrypt -k keyring.json --scope api_key --string "$TOKEN"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`-k/--keyring` is required for every command that needs key material: the command reads no
|
|
39
|
+
environment variables. `flechtctl keyring --help` lists the keyring commands — `new`, `add`,
|
|
40
|
+
`remove`, `primary`, `signing`, `show`, `export`, `import`, `protect` and `unprotect`. A keyring
|
|
41
|
+
file works like a keystore: a set of named entries, handed on one public half at a time with
|
|
42
|
+
`export` and `import`.
|
|
43
|
+
|
|
44
|
+
## Library quick start
|
|
45
|
+
|
|
46
|
+
The functions take the keyring explicitly — there is no global state:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
|
|
51
|
+
from flechtctl import Keyring, decrypt, encrypt, sign, verify
|
|
52
|
+
|
|
53
|
+
keyring = Keyring.from_json(Path("keyring.json").read_text())
|
|
54
|
+
|
|
55
|
+
token = encrypt(keyring, "s3cret", scope="api_key") # "flenc:jwe:…"
|
|
56
|
+
assert decrypt(keyring, token, scope="api_key") == "s3cret"
|
|
57
|
+
|
|
58
|
+
signed = sign(keyring, {"order": 17}, scope="orders") # "flsig:jws:…" — needs a signing key for the scope
|
|
59
|
+
assert verify(keyring, signed, scope="orders") == {"order": 17}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`encrypt`, `decrypt`, `sign` and `verify` work on any JSON value; the `*_bytes` variants take
|
|
63
|
+
raw payload bytes. `reencrypt` moves a token to the scope's current sealing key, `read_header`
|
|
64
|
+
shows a token's unauthenticated header, and `generate_oct_key`, `generate_x25519_key` and
|
|
65
|
+
`generate_ed25519_key` mint key material.
|
|
66
|
+
|
|
67
|
+
## Equivalence with Flechtwerk's codecs
|
|
68
|
+
|
|
69
|
+
`flechtctl.encrypt(keyring, value, scope=s)` writes exactly the token that Flechtwerk's
|
|
70
|
+
`ENCRYPTED(inner, scope=s)` codec writes for `value == inner.encode(x)`, and every token
|
|
71
|
+
Flechtwerk writes opens here. For a `BYTES` field, pass the base64 string. Flechtwerk itself
|
|
72
|
+
is built on this package: `pip install "flechtwerk[secrets]"` installs the matching `flechtctl`.
|
|
73
|
+
|
|
74
|
+
## More
|
|
75
|
+
|
|
76
|
+
The guide [Keyrings and Secret Tokens with flechtctl](https://bsure-analytics.github.io/flechtwerk/guides/flechtctl/)
|
|
77
|
+
walks through a complete reader/writer setup, password-protected keyrings and the library API.
|
|
78
|
+
The format itself is specified on the
|
|
79
|
+
[Encrypted Secrets](https://bsure-analytics.github.io/flechtwerk/concepts/secrets/) page.
|
|
80
|
+
|
|
81
|
+
MIT licensed.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "hatchling.build"
|
|
3
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Christian Schlichtherle" },
|
|
8
|
+
{ name = "Viktor Penev" },
|
|
9
|
+
]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Topic :: Security :: Cryptography",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"joserfc~=1.7",
|
|
22
|
+
]
|
|
23
|
+
description = "Keyrings and flenc:/flsig: secret tokens for Flechtwerk, without the framework: a library and a command line tool."
|
|
24
|
+
dynamic = ["version"]
|
|
25
|
+
keywords = ["flechtwerk", "jwe", "jws", "keyring", "secrets"]
|
|
26
|
+
license = { text = "MIT" }
|
|
27
|
+
maintainers = [
|
|
28
|
+
{ name = "b.sure GmbH" },
|
|
29
|
+
]
|
|
30
|
+
name = "flechtctl"
|
|
31
|
+
readme = "README.md"
|
|
32
|
+
requires-python = ">=3.12"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
flechtctl = "flechtctl.cli:main"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Documentation = "https://bsure-analytics.github.io/flechtwerk/guides/flechtctl/"
|
|
39
|
+
Homepage = "https://github.com/bsure-analytics/flechtwerk"
|
|
40
|
+
Issues = "https://github.com/bsure-analytics/flechtwerk/issues"
|
|
41
|
+
Repository = "https://github.com/bsure-analytics/flechtwerk"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/flechtctl"]
|
|
45
|
+
|
|
46
|
+
[tool.hatch.version]
|
|
47
|
+
raw-options = { root = ".." }
|
|
48
|
+
source = "vcs"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Keyrings and `flenc:`/`flsig:` secret tokens for Flechtwerk — without the framework.
|
|
2
|
+
|
|
3
|
+
This package owns the keyring model (`Keyring`, `X25519Key`, `Ed25519Key`) and
|
|
4
|
+
the token format (seal, open, sign, verify, the scope ratchet, the key-binding
|
|
5
|
+
checks), and depends on joserfc only — never on flechtwerk. The API takes the
|
|
6
|
+
keyring explicitly, with no process-global state:
|
|
7
|
+
|
|
8
|
+
from flechtctl import Keyring, decrypt, encrypt
|
|
9
|
+
|
|
10
|
+
keyring = Keyring.from_json(open("keyring.json").read())
|
|
11
|
+
token = encrypt(keyring, "s3cret", scope="api_key") # flenc:jwe:...
|
|
12
|
+
decrypt(keyring, token, scope="api_key") # "s3cret"
|
|
13
|
+
|
|
14
|
+
`encrypt(k, v, scope=s)` writes the same token flechtwerk's
|
|
15
|
+
`ENCRYPTED(inner, scope=s)` codec writes for `v == inner.encode(x)` — for a
|
|
16
|
+
`BYTES` field, pass the base64 string. The `flechtctl` command wraps the same
|
|
17
|
+
functions for the shell (`flechtctl --help`).
|
|
18
|
+
|
|
19
|
+
`import flechtctl` loads no joserfc: the keyring and error names are imported
|
|
20
|
+
eagerly, every `flechtctl.tokens` name lazily on first access (PEP 562).
|
|
21
|
+
"""
|
|
22
|
+
import importlib
|
|
23
|
+
|
|
24
|
+
from .errors import (
|
|
25
|
+
PlaintextSecretError,
|
|
26
|
+
SecretDecryptError,
|
|
27
|
+
SecretError,
|
|
28
|
+
SecretFormatError,
|
|
29
|
+
SecretSignatureError,
|
|
30
|
+
UnsignedSecretError,
|
|
31
|
+
)
|
|
32
|
+
from .keyring import (
|
|
33
|
+
Ed25519Key,
|
|
34
|
+
KEY_BYTES,
|
|
35
|
+
Keyring,
|
|
36
|
+
KeyringError,
|
|
37
|
+
UnknownKeyError,
|
|
38
|
+
X25519Key,
|
|
39
|
+
is_encrypted_keyring,
|
|
40
|
+
keyring_from_jwk,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"Ed25519Key",
|
|
45
|
+
"KEY_BYTES",
|
|
46
|
+
"Keyring",
|
|
47
|
+
"KeyringError",
|
|
48
|
+
"PREFIX",
|
|
49
|
+
"PlaintextSecretError",
|
|
50
|
+
"SCHEME",
|
|
51
|
+
"SIGNATURE_PREFIX",
|
|
52
|
+
"SIGNATURE_SCHEME",
|
|
53
|
+
"SecretDecryptError",
|
|
54
|
+
"SecretError",
|
|
55
|
+
"SecretFormatError",
|
|
56
|
+
"SecretSignatureError",
|
|
57
|
+
"UnknownKeyError",
|
|
58
|
+
"UnsignedSecretError",
|
|
59
|
+
"X25519Key",
|
|
60
|
+
"canonical_json",
|
|
61
|
+
"decrypt",
|
|
62
|
+
"decrypt_bytes",
|
|
63
|
+
"decrypt_keyring",
|
|
64
|
+
"encrypt",
|
|
65
|
+
"encrypt_bytes",
|
|
66
|
+
"encrypt_keyring",
|
|
67
|
+
"generate_ed25519_key",
|
|
68
|
+
"generate_oct_key",
|
|
69
|
+
"generate_x25519_key",
|
|
70
|
+
"is_encrypted",
|
|
71
|
+
"is_encrypted_keyring",
|
|
72
|
+
"is_signed",
|
|
73
|
+
"keyring_from_jwk",
|
|
74
|
+
"kid_of",
|
|
75
|
+
"read_header",
|
|
76
|
+
"reencrypt",
|
|
77
|
+
"sign",
|
|
78
|
+
"sign_bytes",
|
|
79
|
+
"signer_of",
|
|
80
|
+
"verify",
|
|
81
|
+
"verify_bytes",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
_TOKEN_NAMES = frozenset(name for name in __all__ if name not in globals()) # everything the eager imports did not bind
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def __getattr__(name: str) -> object:
|
|
88
|
+
if name in _TOKEN_NAMES:
|
|
89
|
+
return getattr(importlib.import_module(".tokens", __name__), name)
|
|
90
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|