artemis_framework 0.1.2__tar.gz → 0.1.4__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.2 → artemis_framework-0.1.4}/PKG-INFO +1 -1
- artemis_framework-0.1.4/artemis_framework/asphodel/core/encrypt_decrypt.py +160 -0
- artemis_framework-0.1.4/artemis_framework/asphodel/core/sign_verify.py +257 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/pyproject.toml +1 -1
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/LICENSE +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/README.md +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/__init__.py +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/__main__.py +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/asphodel/__init__.py +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/asphodel/core/__init__.py +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/asphodel/core/gpg_context.py +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/asphodel/core/key_management.py +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/elysium/__ini +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/elysium/__init__.py +0 -0
- {artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/tartarus/__init__.py +0 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from artemis_framework.asphodel.core.gpg_context import build_gpg, check_result
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def encrypt_to_recipients(
|
|
10
|
+
gpg: "gnupg.GPG", # type: ignore
|
|
11
|
+
plaintext: str | bytes,
|
|
12
|
+
recipients: list[str],
|
|
13
|
+
*,
|
|
14
|
+
armor: bool = True,
|
|
15
|
+
sign: str | None = None,
|
|
16
|
+
passphrase: str | None = None,
|
|
17
|
+
always_trust: bool = True,
|
|
18
|
+
extra_args: list[str] | None = None
|
|
19
|
+
) -> str | bytes | None:
|
|
20
|
+
result = gpg.encrypt(
|
|
21
|
+
plaintext,
|
|
22
|
+
recipients,
|
|
23
|
+
armor=armor,
|
|
24
|
+
sign=sign,
|
|
25
|
+
passphrase=passphrase,
|
|
26
|
+
always_trust=always_trust,
|
|
27
|
+
extra_args=extra_args or []
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if not check_result(result, label=f"encrypt_to_recipients({recipients})"):
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
return result.data.decode("ascii") if armor else result.data
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def encrypt_symmetric(
|
|
37
|
+
gpg: "gnupg.GPG", # type: ignore
|
|
38
|
+
plaintext: str | bytes,
|
|
39
|
+
passphrase: str,
|
|
40
|
+
*,
|
|
41
|
+
armor: bool = True,
|
|
42
|
+
cipher_algo: str = "AES256"
|
|
43
|
+
) -> str | bytes | None:
|
|
44
|
+
result = gpg.encrypt(
|
|
45
|
+
plaintext,
|
|
46
|
+
recipients=None,
|
|
47
|
+
symmetric=True,
|
|
48
|
+
passphrase=passphrase,
|
|
49
|
+
armor=armor,
|
|
50
|
+
extra_args=["--cipher-algo", cipher_algo, "--no-symkey-cache"]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if not check_result(result, label="encrypt_symmetric"):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
return result.data.decode("ascii") if armor else result.data
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def decrypt_data(
|
|
60
|
+
gpg: "gnupg.GPG", # type: ignore
|
|
61
|
+
ciphertext: str | bytes,
|
|
62
|
+
*,
|
|
63
|
+
passphrase: str | None = None,
|
|
64
|
+
always_trust: bool = True
|
|
65
|
+
) -> bytes | None:
|
|
66
|
+
result = gpg.decrypt(
|
|
67
|
+
ciphertext,
|
|
68
|
+
passphrase=passphrase,
|
|
69
|
+
always_trust=always_trust
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if not check_result(result, label="decrypt_data"):
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
return result.data
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def encrypt_file(
|
|
79
|
+
gpg: "gnupg.GPG", # type: ignore
|
|
80
|
+
input_path: Path | str,
|
|
81
|
+
recipients: list[str],
|
|
82
|
+
*,
|
|
83
|
+
output_path: Path | str | None = None,
|
|
84
|
+
armor: bool = True,
|
|
85
|
+
sign : str | None = None,
|
|
86
|
+
passphrase: str | None = None,
|
|
87
|
+
always_trust: bool = True
|
|
88
|
+
) -> Path | None:
|
|
89
|
+
src = Path(input_path)
|
|
90
|
+
if not src.exists():
|
|
91
|
+
print(f"[ERR] encrypt_file: {src} not found.")
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
dst = Path(output_path) if output_path else src.with_suffix(src.suffix + (".asc" if armor else ".gpg"))
|
|
95
|
+
|
|
96
|
+
with open(src, "rb") as file:
|
|
97
|
+
result = gpg.encrypt_file(
|
|
98
|
+
file,
|
|
99
|
+
recipients=recipients,
|
|
100
|
+
armor=armor,
|
|
101
|
+
sign=sign,
|
|
102
|
+
passphrase=passphrase,
|
|
103
|
+
always_trust=always_trust,
|
|
104
|
+
output=str(dst)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if not check_result(result, label=f"encrypt_file({src.name})"):
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
print(f" output: {dst}")
|
|
111
|
+
return dst
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def decrypt_file(
|
|
115
|
+
gpg: "gnupg.GPG", # type: ignore
|
|
116
|
+
input_path: Path | str,
|
|
117
|
+
*,
|
|
118
|
+
output_path: Path | str | None = None,
|
|
119
|
+
passphrase: str | None = None,
|
|
120
|
+
always_trust: bool = True
|
|
121
|
+
) -> Path | None:
|
|
122
|
+
src = Path(input_path)
|
|
123
|
+
if not src.exists():
|
|
124
|
+
print(f"[ERR] decrypt_file: {src} not found.")
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
if output_path:
|
|
128
|
+
dst = Path(output_path)
|
|
129
|
+
else:
|
|
130
|
+
dst = src.with_suffix("")
|
|
131
|
+
|
|
132
|
+
with open(src, "rb") as file:
|
|
133
|
+
result = gpg.decrypt_file(
|
|
134
|
+
file,
|
|
135
|
+
passphrase=passphrase,
|
|
136
|
+
always_trust=always_trust,
|
|
137
|
+
output=str(dst)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if not check_result(result, label=f"decrypt_file({src.name})"):
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
print(f" output: {dst}")
|
|
144
|
+
return dst
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def print_decrypt_signature_info(result) -> None:
|
|
148
|
+
sig_id = getattr(result, "signature_id", None)
|
|
149
|
+
key_id = getattr(result, "key_id", None)
|
|
150
|
+
uid = getattr(result, "username", None)
|
|
151
|
+
trust = getattr(result, "trust_text", None)
|
|
152
|
+
|
|
153
|
+
if sig_id or key_id:
|
|
154
|
+
print("\n [Signature embedded in ciphertext]")
|
|
155
|
+
print(f" signer UID : {uid or '<unknown>'}")
|
|
156
|
+
print(f" key ID : {key_id or '<unknown>'}")
|
|
157
|
+
print(f" trust : {trust or '<unknown>'}")
|
|
158
|
+
else:
|
|
159
|
+
print("\n [No embedded signature detected]")
|
|
160
|
+
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from artemis_framework.asphodel.core.gpg_context import build_gpg, check_result
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class VerificationResult:
|
|
12
|
+
valid: bool
|
|
13
|
+
fingerprint: str
|
|
14
|
+
key_id: str
|
|
15
|
+
username: str
|
|
16
|
+
status: str
|
|
17
|
+
timestamp: int | None
|
|
18
|
+
expire_timestamp: int | None
|
|
19
|
+
trust_level: str
|
|
20
|
+
stderr: str
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
lines = (
|
|
24
|
+
f" valid : {self.valid}",
|
|
25
|
+
f" fingerprint : {self.fingerprint}",
|
|
26
|
+
f" key_id : {self.key_id}",
|
|
27
|
+
f" username : {self.username}",
|
|
28
|
+
f" status : {self.status}",
|
|
29
|
+
f" timestamp : {self.timestamp}",
|
|
30
|
+
f" trust_level : {self.trust_level}",
|
|
31
|
+
)
|
|
32
|
+
return "\n".join(lines)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_gnupg(cls, result) -> "VerificationResult":
|
|
36
|
+
sigs = getattr(result, "sig_info", {})
|
|
37
|
+
|
|
38
|
+
if sigs:
|
|
39
|
+
fp, info = next(iter(sigs.items()))
|
|
40
|
+
key_id = info.get("keyid", "")
|
|
41
|
+
username = info.get("username", "")
|
|
42
|
+
status = info.get("status", "")
|
|
43
|
+
ts = info.get("timestamp")
|
|
44
|
+
exp_ts = info.get("expire_timestamp")
|
|
45
|
+
trust = info.get("trust_level", "")
|
|
46
|
+
else:
|
|
47
|
+
fp = getattr(result, "fingerprint", "") or ""
|
|
48
|
+
key_id = getattr(result, "key_id", "") or ""
|
|
49
|
+
username = getattr(result, "username", "") or ""
|
|
50
|
+
status = getattr(result, "status", "") or ""
|
|
51
|
+
ts = getattr(result, "timestamp", None)
|
|
52
|
+
exp_ts = None
|
|
53
|
+
trust = getattr(result, "trust_level", "") or ""
|
|
54
|
+
valid = bool(getattr(result, "valid", False))
|
|
55
|
+
|
|
56
|
+
return cls(
|
|
57
|
+
valid=valid,
|
|
58
|
+
fingerprint=fp,
|
|
59
|
+
key_id=key_id,
|
|
60
|
+
username=username,
|
|
61
|
+
status=status,
|
|
62
|
+
timestamp=int(ts) if ts else None,
|
|
63
|
+
expire_timestamp=int(exp_ts) if exp_ts else None,
|
|
64
|
+
trust_level=trust,
|
|
65
|
+
stderr=getattr(result, "stderr", "") or ""
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def clearsign(
|
|
70
|
+
gpg,
|
|
71
|
+
message: str | bytes,
|
|
72
|
+
signing_fingerprint: str,
|
|
73
|
+
*,
|
|
74
|
+
passphrase: str | None = None,
|
|
75
|
+
detach: bool = False
|
|
76
|
+
) -> str | None:
|
|
77
|
+
result = gpg.sign(
|
|
78
|
+
message,
|
|
79
|
+
keyid=signing_fingerprint,
|
|
80
|
+
passphrase=passphrase,
|
|
81
|
+
clearsign=True
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if not result:
|
|
85
|
+
print(f"[ERR] clearsign failed for <{signing_fingerprint[:16]}…>")
|
|
86
|
+
print(f" stderr: {result.stderr}")
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
print(f"[OK] clearsign({signing_fingerprint[:16]}…)")
|
|
90
|
+
return str(result)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def sign_detached(
|
|
94
|
+
gpg,
|
|
95
|
+
message: str | bytes,
|
|
96
|
+
signing_fingerprint: str,
|
|
97
|
+
*,
|
|
98
|
+
passphrase: str | None = None,
|
|
99
|
+
armor: bool = True
|
|
100
|
+
) -> str | bytes | None:
|
|
101
|
+
result = gpg.sign(
|
|
102
|
+
message,
|
|
103
|
+
keyid=signing_fingerprint,
|
|
104
|
+
passphrase=passphrase,
|
|
105
|
+
detach=True,
|
|
106
|
+
clearsign=False
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if not result:
|
|
110
|
+
print(f"[ERR] sign_detached failed for <{signing_fingerprint[:16]}…>")
|
|
111
|
+
print(f" stderr: {result.stderr}")
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
sig_data = str(result)
|
|
115
|
+
print(f"[OK] sign_detached({signing_fingerprint[:16]}…, armor={armor})")
|
|
116
|
+
|
|
117
|
+
if armor:
|
|
118
|
+
return sig_data
|
|
119
|
+
|
|
120
|
+
return sig_data
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def sign_inline(
|
|
124
|
+
gpg,
|
|
125
|
+
message: str | bytes,
|
|
126
|
+
signing_fingerprint: str,
|
|
127
|
+
*,
|
|
128
|
+
passphrase: str | None = None
|
|
129
|
+
) -> bytes | None:
|
|
130
|
+
result = gpg.sign(
|
|
131
|
+
message,
|
|
132
|
+
keyid=signing_fingerprint,
|
|
133
|
+
passphrase=passphrase,
|
|
134
|
+
detach=False,
|
|
135
|
+
clearsign=False,
|
|
136
|
+
binary=True
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if not result:
|
|
140
|
+
print(f"[ERR] sign_inline failed for <{signing_fingerprint[:16]}…>")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
print(f"[OK] sign_inline({signing_fingerprint[:16]}…)")
|
|
144
|
+
return result.data
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def sign_file_detached(
|
|
148
|
+
gpg,
|
|
149
|
+
file_path: Path | str,
|
|
150
|
+
signing_fingerprint: str,
|
|
151
|
+
*,
|
|
152
|
+
passphrase: str | None = None,
|
|
153
|
+
output_path: Path | str | None = None,
|
|
154
|
+
armor: bool = True
|
|
155
|
+
) -> Path | None:
|
|
156
|
+
src = Path(file_path)
|
|
157
|
+
if not src.exists():
|
|
158
|
+
print(f"[ERR] sign_file_detached: {src} not found.")
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
ext = ".asc" if armor else ".sig"
|
|
162
|
+
dst = Path(output_path) if output_path else src.with_suffix(src.sufix + ext)
|
|
163
|
+
|
|
164
|
+
with open(src, "rb") as file:
|
|
165
|
+
result = gpg.sign_file(
|
|
166
|
+
file,
|
|
167
|
+
keyid=signing_fingerprint,
|
|
168
|
+
passphrase=passphrase,
|
|
169
|
+
detach=True,
|
|
170
|
+
clearsign=False,
|
|
171
|
+
output=str(dst)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if not result:
|
|
175
|
+
print(f"[ERR] sign_file_detached({src.name}): {result.stderr}")
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
print(f"[OK] sign_file_detached » {dst}")
|
|
179
|
+
return dst
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def verify_clearsign(
|
|
183
|
+
gpg,
|
|
184
|
+
signed_message: str,
|
|
185
|
+
*,
|
|
186
|
+
always_trust: bool = True
|
|
187
|
+
) -> VerificationResult:
|
|
188
|
+
result = gpg.verify(signed_message)
|
|
189
|
+
vr = VerificationResult.from_gnupg(result)
|
|
190
|
+
label = "verify_clearsign"
|
|
191
|
+
if vr.valid:
|
|
192
|
+
print(f"[OK] {label}")
|
|
193
|
+
else:
|
|
194
|
+
print(f"[ERR] {label}")
|
|
195
|
+
print(vr)
|
|
196
|
+
return vr
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def verify_detached(
|
|
200
|
+
gpg,
|
|
201
|
+
message: str | bytes,
|
|
202
|
+
signature: str | bytes
|
|
203
|
+
) -> VerificationResult:
|
|
204
|
+
import tempfile, os
|
|
205
|
+
|
|
206
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".asc") as tmp:
|
|
207
|
+
if isinstance(signature, str):
|
|
208
|
+
tmp.write(signature.encode())
|
|
209
|
+
else:
|
|
210
|
+
tmp.write(signature)
|
|
211
|
+
tmp_path = tmp.name
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
if isinstance(message, str):
|
|
215
|
+
message = message.encode()
|
|
216
|
+
|
|
217
|
+
result = gpg.verify_data(tmp_path, message)
|
|
218
|
+
finally:
|
|
219
|
+
os.unlink(tmp_path)
|
|
220
|
+
|
|
221
|
+
vr = VerificationResult.from_gnupg(result)
|
|
222
|
+
label = "verify_detached"
|
|
223
|
+
if vr.valid:
|
|
224
|
+
print(f"[OK] {label}")
|
|
225
|
+
else:
|
|
226
|
+
print(f"[ERR] {label}")
|
|
227
|
+
print(vr)
|
|
228
|
+
return vr
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def verify_detached_file(
|
|
232
|
+
gpg,
|
|
233
|
+
file_path: Path | str,
|
|
234
|
+
sig_path: Path | str
|
|
235
|
+
) -> VerificationResult:
|
|
236
|
+
src = Path(file_path)
|
|
237
|
+
sig = Path(sig_path)
|
|
238
|
+
|
|
239
|
+
message_bytes = src.read_bytes()
|
|
240
|
+
sig_bytes = sig.read_bytes()
|
|
241
|
+
|
|
242
|
+
return verify_detached(gpg, message_bytes, sig_bytes)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def verify_inline(gpg, signed_data: bytes) -> VerificationResult:
|
|
246
|
+
result = gpg.decrypt(signed_data, always_trust=True)
|
|
247
|
+
|
|
248
|
+
vr = VerificationResult.from_gnupg(result)
|
|
249
|
+
label = "verify_inline"
|
|
250
|
+
if vr.valid:
|
|
251
|
+
print(f"[OK] {label}")
|
|
252
|
+
else:
|
|
253
|
+
print(f"[WARN] {label} - no valid signature found (or unsigned data)")
|
|
254
|
+
print(vr)
|
|
255
|
+
return vr
|
|
256
|
+
|
|
257
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/asphodel/core/__init__.py
RENAMED
|
File without changes
|
{artemis_framework-0.1.2 → artemis_framework-0.1.4}/artemis_framework/asphodel/core/gpg_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|