artemis_framework 0.1.1__tar.gz → 0.1.2__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.
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/PKG-INFO +1 -1
- artemis_framework-0.1.2/artemis_framework/asphodel/core/key_management.py +193 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/pyproject.toml +1 -1
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/LICENSE +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/README.md +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/__init__.py +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/__main__.py +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/asphodel/__init__.py +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/asphodel/core/__init__.py +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/asphodel/core/gpg_context.py +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/elysium/__ini +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/elysium/__init__.py +0 -0
- {artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/tartarus/__init__.py +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import textwrap
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from artemis_framework.asphodel.core.gpg_context import check_result
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
RSA_KEY_PARAMS = textwrap.dedent("""\
|
|
11
|
+
Key-Type: RSA
|
|
12
|
+
Key-Length: 4096
|
|
13
|
+
Subkey-Type: RSA
|
|
14
|
+
Subkey-Length: 4096
|
|
15
|
+
Name-Real: {name}
|
|
16
|
+
Name-Email: {email}
|
|
17
|
+
Name-Comment: {comment}
|
|
18
|
+
Expire-Date: {expire}
|
|
19
|
+
%no-protection
|
|
20
|
+
""")
|
|
21
|
+
|
|
22
|
+
ECC_KEY_PARAMS = textwrap.dedent("""\
|
|
23
|
+
Key-Type: EDDSA
|
|
24
|
+
Key-Curve: ed25519
|
|
25
|
+
Subkey-Type: ECDH
|
|
26
|
+
Subkey-Curve: cv25519
|
|
27
|
+
Name-Real: {name}
|
|
28
|
+
Name-Email: {email}
|
|
29
|
+
Name-Comment: {comment}
|
|
30
|
+
Expire-Date: {expire}
|
|
31
|
+
%no-protection
|
|
32
|
+
""")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def generate_key(
|
|
36
|
+
gpg: "gnupg.GPG", # type: ignore[name-defined]
|
|
37
|
+
name: str,
|
|
38
|
+
email: str,
|
|
39
|
+
*,
|
|
40
|
+
comment: str = "",
|
|
41
|
+
expire: str = "2y",
|
|
42
|
+
algorithm: str = "rsa",
|
|
43
|
+
passphrase: str | None = None
|
|
44
|
+
) -> str | None:
|
|
45
|
+
template = RSA_KEY_PARAMS if algorithm.lower() == "rsa" else ECC_KEY_PARAMS
|
|
46
|
+
|
|
47
|
+
params = template.format(
|
|
48
|
+
name=name,
|
|
49
|
+
email=email,
|
|
50
|
+
comment=comment,
|
|
51
|
+
expire=expire
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if passphrase:
|
|
55
|
+
params = params.replace(
|
|
56
|
+
"%no-protection",
|
|
57
|
+
f"Passphrase: {passphrase}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
print(f"Generating {algorithm.upper()} key for <{email}> … (this may take a moment)")
|
|
61
|
+
result = gpg.gen_key(params)
|
|
62
|
+
|
|
63
|
+
if check_result(result, label=f"gen_key({email})"):
|
|
64
|
+
return result.fingerprint
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def list_keys_verbose(gpg: "gnupg.GPG", *, secret: bool = False) -> list[dict]: # type: ignore[name-defined]
|
|
69
|
+
keys = gpg.list_keys(secret)
|
|
70
|
+
kind = "secret" if secret else "public"
|
|
71
|
+
|
|
72
|
+
print(f"\n{'='*60}")
|
|
73
|
+
print(f"{kind.upper()} KEYS ({len(keys)} total)")
|
|
74
|
+
print(f"{'='*60}")
|
|
75
|
+
|
|
76
|
+
for key in keys:
|
|
77
|
+
uid_str = "; ".join(key.get("uids", ["<no UID>"]))
|
|
78
|
+
print("\n")
|
|
79
|
+
|
|
80
|
+
print(f"Fingerprint : {key['fingerprint']}")
|
|
81
|
+
print(f"Key ID : {key['keyid']}")
|
|
82
|
+
print(f"UID(s) : {uid_str}")
|
|
83
|
+
print(f"Type/Length : {key.get('type', '?')} / {key.get('length', '?')}")
|
|
84
|
+
print(f"Trust : {key.get('trust', '?')}")
|
|
85
|
+
print(f"Expires : {key.get('expires', 'never') or 'never'}")
|
|
86
|
+
print(f"Subkeys : {len(key.get('subkeys', []))}")
|
|
87
|
+
|
|
88
|
+
return list(keys)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def find_key(gpg: "gnupg.GPG", identifier: str, *, secret: bool = False) -> dict | None: # type: ignore[name-defined]
|
|
92
|
+
keys = gpg.list_keys(secret)
|
|
93
|
+
ident_lower = identifier.lower()
|
|
94
|
+
|
|
95
|
+
for key in keys:
|
|
96
|
+
if ident_lower in key.get("fingerprint", "").lower():
|
|
97
|
+
return dict(key)
|
|
98
|
+
for uid in key.get("uids", []):
|
|
99
|
+
if ident_lower in uid.lower():
|
|
100
|
+
return dict(key)
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def export_public_key(
|
|
105
|
+
gpg: "gnupg.GPG", # type: ignore[name-defined]
|
|
106
|
+
fingerprint: str,
|
|
107
|
+
*,
|
|
108
|
+
armor: bool = True,
|
|
109
|
+
output_path: Path | None = None
|
|
110
|
+
) -> str | bytes:
|
|
111
|
+
data = gpg.export_keys(fingerprint, armor=armor)
|
|
112
|
+
|
|
113
|
+
if not data:
|
|
114
|
+
print(f"[ERR] export_public_key: no data returned for {fingerprint}")
|
|
115
|
+
return b"" if not armor else ""
|
|
116
|
+
|
|
117
|
+
if output_path:
|
|
118
|
+
Path(output_path).write_text(data) if armor else Path(output_path).write_bytes(data.encode())
|
|
119
|
+
print(f"[OK] Public key written to » {output_path}")
|
|
120
|
+
else:
|
|
121
|
+
print(f"[OK] export_public_key({fingerprint[:16]}…)")
|
|
122
|
+
|
|
123
|
+
return data
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def export_secret_key(
|
|
127
|
+
gpg: "gnupg.GPG", # type: ignore[name-defined]
|
|
128
|
+
fingerprint: str,
|
|
129
|
+
*,
|
|
130
|
+
armor: bool = True,
|
|
131
|
+
passphrase: str | None = None,
|
|
132
|
+
output_path: Path | None = None
|
|
133
|
+
) -> str | bytes:
|
|
134
|
+
data = gpg.export_keys(
|
|
135
|
+
fingerprint,
|
|
136
|
+
secret=True,
|
|
137
|
+
armor=armor,
|
|
138
|
+
passphrase=passphrase
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if not data:
|
|
142
|
+
print(f"[ERR] export_secret_key: no data returned for {fingerprint}")
|
|
143
|
+
return b"" if not armor else ""
|
|
144
|
+
|
|
145
|
+
if output_path:
|
|
146
|
+
Path(output_path).write_text(data) if armor else Path(output_path).write_bytes(data.encode())
|
|
147
|
+
print(f"[OK] Secret key written to » {output_path}")
|
|
148
|
+
else:
|
|
149
|
+
print(f"[OK] secret_key_export({fingerprint[:16]}…)")
|
|
150
|
+
|
|
151
|
+
return data
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def import_key_data(
|
|
155
|
+
gpg: "gnupg.GPG", # type: ignore[name-defined],
|
|
156
|
+
key_data: str | bytes,
|
|
157
|
+
*,
|
|
158
|
+
label: str = "import"
|
|
159
|
+
) -> list[str]:
|
|
160
|
+
result = gpg.import_keys(key_data)
|
|
161
|
+
check_result(result, label=label)
|
|
162
|
+
|
|
163
|
+
fps = result.fingerprints
|
|
164
|
+
print(f" imported : {len(fps)} key(s)")
|
|
165
|
+
for fp in fps:
|
|
166
|
+
print(f" {fp}")
|
|
167
|
+
|
|
168
|
+
return fps
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def import_key_file(
|
|
172
|
+
gpg: "gnupg.GPG", # type: ignore[name-defined],
|
|
173
|
+
path: Path | str,
|
|
174
|
+
*,
|
|
175
|
+
label: str | None = None
|
|
176
|
+
) -> list[str]:
|
|
177
|
+
p = Path(path)
|
|
178
|
+
label = label or f"import_key_file({p.name})"
|
|
179
|
+
key_data = p.read_text() if p.suffix in (".asc", ".txt") else p.read_bytes()
|
|
180
|
+
return import_key_data(gpg, key_data, label=label)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def delete_key(
|
|
184
|
+
gpg: "gnupg.GPG", # type: ignore
|
|
185
|
+
fingerprint: str,
|
|
186
|
+
*,
|
|
187
|
+
secret: bool = False,
|
|
188
|
+
passphrase: str | None = None
|
|
189
|
+
) -> bool:
|
|
190
|
+
kind = "secret" if secret else "public"
|
|
191
|
+
result = gpg.delete_keys(fingerprint, secret=secret, passphrase=passphrase)
|
|
192
|
+
return check_result(result, label=f"delete_key({kind}, {fingerprint[:16]}…)")
|
|
193
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/asphodel/core/__init__.py
RENAMED
|
File without changes
|
{artemis_framework-0.1.1 → artemis_framework-0.1.2}/artemis_framework/asphodel/core/gpg_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|