pyencode-protector 0.3.0__py3-none-any.whl

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.
pyencode/opaque.py ADDED
@@ -0,0 +1,574 @@
1
+ """Opaque PYE2 artifacts and encrypted protected-module indexes.
2
+
3
+ This module contains the format-independent core used by a future builder and
4
+ runtime. PYE2 deliberately stores no semantic header on disk: module names,
5
+ package flags, Python compatibility metadata, and artifact tokens are supplied
6
+ as authenticated context after the encrypted module index has been opened.
7
+
8
+ The wire format for one artifact is exactly::
9
+
10
+ b"PYE2" || nonce[12] || aes_gcm_ciphertext_and_tag
11
+
12
+ All key derivations are domain-separated HKDF-SHA256 operations. Callers are
13
+ expected to pass the support-bound root key produced by
14
+ ``derive_bound_master_key`` when encrypting an index, then derive a module root
15
+ from that encrypted index before packing artifacts.
16
+
17
+ The protocol derivations are::
18
+
19
+ K_index = HKDF(K_support, salt=build_id,
20
+ info=b"pyencode-module-index-v1\\0")
21
+ C_index = SHA256(b"pyencode-module-index-commitment-v1\\0"
22
+ + index_aad + nonce + ciphertext)
23
+ K_modules = HKDF(K_support, salt=build_id,
24
+ info=b"pyencode-module-root-v2\\0" + C_index)
25
+ K_artifact = HKDF(K_modules, salt=build_id,
26
+ info=b"pyencode-artifact-v2\\0" + token_bytes
27
+ + b"\\0" + module_utf8)
28
+
29
+ Here ``HKDF`` means HKDF-SHA256 with a 32-byte output. Index plaintext and all
30
+ associated data use the canonical JSON helpers in this module.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import hashlib
36
+ import json
37
+ import re
38
+ import zlib
39
+ from dataclasses import dataclass
40
+ from typing import Any, Mapping, TypedDict, cast
41
+
42
+ from cryptography.hazmat.primitives import hashes
43
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
44
+
45
+ from .crypto import (
46
+ KEY_SIZE,
47
+ NONCE_SIZE,
48
+ TAG_SIZE,
49
+ BytesLike,
50
+ decrypt_payload,
51
+ encrypt_payload,
52
+ )
53
+ from .errors import (
54
+ ContainerError,
55
+ CryptoError,
56
+ InvalidKeyError,
57
+ ManifestError,
58
+ )
59
+
60
+
61
+ MAGIC_PYE2 = b"PYE2"
62
+ OPAQUE_ARTIFACT_FORMAT = 2
63
+ MODULE_INDEX_FORMAT = 1
64
+ TOKEN_SIZE = 16
65
+ INDEX_COMMITMENT_SIZE = 32
66
+
67
+ INDEX_KEY_INFO = b"pyencode-module-index-v1\0"
68
+ INDEX_COMMITMENT_DOMAIN = b"pyencode-module-index-commitment-v1\0"
69
+ MODULE_ROOT_KEY_INFO = b"pyencode-module-root-v2\0"
70
+ ARTIFACT_KEY_INFO = b"pyencode-artifact-v2\0"
71
+
72
+ _BUILD_ID_RE = re.compile(r"[0-9a-f]{32}\Z")
73
+ _TOKEN_RE = re.compile(r"[0-9a-f]{32}\Z")
74
+ _PYTHON_TAG_RE = re.compile(r"cp[0-9]{2,}\Z")
75
+ _MAX_MODULE_INDEX_SIZE = 16 * 1024 * 1024
76
+ _MAX_ARTIFACT_PLAINTEXT_SIZE = 256 * 1024 * 1024
77
+
78
+
79
+ class ModuleIndexEntry(TypedDict):
80
+ """One normalized entry in a decrypted module index."""
81
+
82
+ artifact: str
83
+ package: bool
84
+
85
+
86
+ class ModuleIndex(TypedDict):
87
+ """Canonical plaintext schema encrypted into a module index."""
88
+
89
+ format: int
90
+ entry_module: str
91
+ modules: dict[str, ModuleIndexEntry]
92
+
93
+
94
+ @dataclass(frozen=True, slots=True)
95
+ class OpaqueModuleContext:
96
+ """Authenticated context required to encrypt or open one PYE2 artifact."""
97
+
98
+ build_id: str
99
+ python_tag: str
100
+ module: str
101
+ package: bool
102
+ token: str
103
+ marshal_version: int
104
+
105
+ def __post_init__(self) -> None:
106
+ _validate_build_id(self.build_id, ContainerError)
107
+ _validate_python_tag(self.python_tag, ContainerError)
108
+ _validate_module_name(self.module, ContainerError)
109
+ _validate_token(self.token, ContainerError)
110
+ if type(self.package) is not bool:
111
+ raise ContainerError("opaque module package flag must be a boolean")
112
+ if type(self.marshal_version) is not int or self.marshal_version < 0:
113
+ raise ContainerError(
114
+ "opaque module marshal_version must be a non-negative integer"
115
+ )
116
+
117
+
118
+ @dataclass(frozen=True, slots=True)
119
+ class ParsedOpaqueArtifact:
120
+ """The structural fields of a still-encrypted PYE2 artifact."""
121
+
122
+ nonce: bytes
123
+ ciphertext: bytes
124
+
125
+
126
+ @dataclass(frozen=True, slots=True)
127
+ class EncryptedModuleIndex:
128
+ """AES-GCM fields stored by a signed outer manifest."""
129
+
130
+ nonce: bytes
131
+ ciphertext: bytes
132
+
133
+
134
+ def _as_bytes(value: BytesLike, *, name: str, error: type[Exception]) -> bytes:
135
+ if not isinstance(value, (bytes, bytearray, memoryview)):
136
+ raise error(f"{name} must be bytes-like")
137
+ return bytes(value)
138
+
139
+
140
+ def _validate_key(value: BytesLike, name: str = "root_key") -> bytes:
141
+ key = _as_bytes(value, name=name, error=InvalidKeyError)
142
+ if len(key) != KEY_SIZE:
143
+ raise InvalidKeyError(f"{name} must be exactly {KEY_SIZE} bytes")
144
+ return key
145
+
146
+
147
+ def _validate_build_id(value: object, error: type[Exception]) -> str:
148
+ if not isinstance(value, str) or _BUILD_ID_RE.fullmatch(value) is None:
149
+ raise error("build_id must be exactly 32 lowercase hexadecimal characters")
150
+ return value
151
+
152
+
153
+ def _build_id_bytes(value: object, error: type[Exception]) -> bytes:
154
+ return bytes.fromhex(_validate_build_id(value, error))
155
+
156
+
157
+ def _validate_python_tag(value: object, error: type[Exception]) -> str:
158
+ if not isinstance(value, str) or _PYTHON_TAG_RE.fullmatch(value) is None:
159
+ raise error("python_tag must be a canonical CPython tag such as 'cp314'")
160
+ return value
161
+
162
+
163
+ def _validate_module_name(value: object, error: type[Exception]) -> str:
164
+ if not isinstance(value, str) or not value:
165
+ raise error("module name must be a non-empty string")
166
+ if any(not part or not part.isidentifier() for part in value.split(".")):
167
+ raise error(f"invalid protected module name {value!r}")
168
+ return value
169
+
170
+
171
+ def _validate_token(value: object, error: type[Exception]) -> str:
172
+ if not isinstance(value, str) or _TOKEN_RE.fullmatch(value) is None:
173
+ raise error("artifact token must be exactly 32 lowercase hexadecimal characters")
174
+ return value
175
+
176
+
177
+ def _canonical_json(value: Any, error: type[Exception]) -> bytes:
178
+ try:
179
+ return json.dumps(
180
+ value,
181
+ sort_keys=True,
182
+ separators=(",", ":"),
183
+ ensure_ascii=False,
184
+ allow_nan=False,
185
+ ).encode("utf-8")
186
+ except (TypeError, ValueError, UnicodeEncodeError) as exc:
187
+ raise error("metadata cannot be encoded as canonical JSON") from exc
188
+
189
+
190
+ def _hkdf(root_key: BytesLike, build_id: str, info: bytes, *, name: str) -> bytes:
191
+ key = _validate_key(root_key, name)
192
+ salt = _build_id_bytes(build_id, CryptoError)
193
+ return HKDF(
194
+ algorithm=hashes.SHA256(),
195
+ length=KEY_SIZE,
196
+ salt=salt,
197
+ info=info,
198
+ ).derive(key)
199
+
200
+
201
+ def module_index_aad(build_id: str, python_tag: str) -> bytes:
202
+ """Return the canonical public context authenticated with an index."""
203
+
204
+ validated_build_id = _validate_build_id(build_id, ManifestError)
205
+ validated_python_tag = _validate_python_tag(python_tag, ManifestError)
206
+ return _canonical_json(
207
+ {
208
+ "build_id": validated_build_id,
209
+ "cipher": "aes-256-gcm",
210
+ "compression": "zlib",
211
+ "format": MODULE_INDEX_FORMAT,
212
+ "python_tag": validated_python_tag,
213
+ },
214
+ ManifestError,
215
+ )
216
+
217
+
218
+ def derive_module_index_key(support_root_key: BytesLike, build_id: str) -> bytes:
219
+ """Derive the AES-256 key used only for the encrypted module index."""
220
+
221
+ return _hkdf(
222
+ support_root_key,
223
+ build_id,
224
+ INDEX_KEY_INFO,
225
+ name="support_root_key",
226
+ )
227
+
228
+
229
+ def module_index_commitment(
230
+ encrypted_index: EncryptedModuleIndex,
231
+ *,
232
+ build_id: str,
233
+ python_tag: str,
234
+ ) -> bytes:
235
+ """Commit to the exact encrypted index and its authenticated context."""
236
+
237
+ nonce, ciphertext = _encrypted_index_parts(encrypted_index)
238
+ aad = module_index_aad(build_id, python_tag)
239
+ return hashlib.sha256(
240
+ INDEX_COMMITMENT_DOMAIN + aad + nonce + ciphertext
241
+ ).digest()
242
+
243
+
244
+ def derive_module_root_key(
245
+ support_root_key: BytesLike,
246
+ build_id: str,
247
+ index_commitment: BytesLike,
248
+ ) -> bytes:
249
+ """Derive the root used by all PYE2 artifacts in one encrypted index."""
250
+
251
+ commitment = _as_bytes(
252
+ index_commitment,
253
+ name="index_commitment",
254
+ error=CryptoError,
255
+ )
256
+ if len(commitment) != INDEX_COMMITMENT_SIZE:
257
+ raise CryptoError(
258
+ f"index_commitment must be exactly {INDEX_COMMITMENT_SIZE} bytes"
259
+ )
260
+ return _hkdf(
261
+ support_root_key,
262
+ build_id,
263
+ MODULE_ROOT_KEY_INFO + commitment,
264
+ name="support_root_key",
265
+ )
266
+
267
+
268
+ def opaque_artifact_aad(context: OpaqueModuleContext) -> bytes:
269
+ """Return semantic artifact metadata authenticated but never stored."""
270
+
271
+ if not isinstance(context, OpaqueModuleContext):
272
+ raise ContainerError("context must be an OpaqueModuleContext")
273
+ return _canonical_json(
274
+ {
275
+ "artifact": context.token,
276
+ "build_id": context.build_id,
277
+ "compression": "zlib",
278
+ "format": OPAQUE_ARTIFACT_FORMAT,
279
+ "marshal_version": context.marshal_version,
280
+ "module": context.module,
281
+ "package": context.package,
282
+ "python_tag": context.python_tag,
283
+ },
284
+ ContainerError,
285
+ )
286
+
287
+
288
+ def derive_opaque_artifact_key(
289
+ module_root_key: BytesLike,
290
+ context: OpaqueModuleContext,
291
+ ) -> bytes:
292
+ """Derive one artifact key from its opaque token and protected fullname."""
293
+
294
+ if not isinstance(context, OpaqueModuleContext):
295
+ raise ContainerError("context must be an OpaqueModuleContext")
296
+ info = (
297
+ ARTIFACT_KEY_INFO
298
+ + bytes.fromhex(context.token)
299
+ + b"\0"
300
+ + context.module.encode("utf-8")
301
+ )
302
+ return _hkdf(
303
+ module_root_key,
304
+ context.build_id,
305
+ info,
306
+ name="module_root_key",
307
+ )
308
+
309
+
310
+ def pack_opaque_artifact(
311
+ payload: BytesLike,
312
+ module_root_key: BytesLike,
313
+ context: OpaqueModuleContext,
314
+ *,
315
+ nonce: BytesLike | None = None,
316
+ compression_level: int = 9,
317
+ ) -> bytes:
318
+ """Compress and encrypt bytes into the headerless PYE2 wire format."""
319
+
320
+ plaintext = _as_bytes(payload, name="payload", error=ContainerError)
321
+ if len(plaintext) > _MAX_ARTIFACT_PLAINTEXT_SIZE:
322
+ raise ContainerError("PYE2 artifact payload exceeds the safety limit")
323
+ if type(compression_level) is not int or not -1 <= compression_level <= 9:
324
+ raise ContainerError("zlib compression_level must be between -1 and 9")
325
+ aad = opaque_artifact_aad(context)
326
+ key = derive_opaque_artifact_key(module_root_key, context)
327
+ encrypted = encrypt_payload(
328
+ zlib.compress(plaintext, level=compression_level),
329
+ key,
330
+ aad=aad,
331
+ nonce=nonce,
332
+ )
333
+ return MAGIC_PYE2 + encrypted.nonce + encrypted.ciphertext
334
+
335
+
336
+ def parse_opaque_artifact(data: BytesLike) -> ParsedOpaqueArtifact:
337
+ """Validate PYE2 framing without decrypting its ciphertext."""
338
+
339
+ artifact = _as_bytes(data, name="data", error=ContainerError)
340
+ minimum = len(MAGIC_PYE2) + NONCE_SIZE + TAG_SIZE
341
+ if len(artifact) < minimum:
342
+ raise ContainerError("PYE2 artifact is truncated")
343
+ if artifact[: len(MAGIC_PYE2)] != MAGIC_PYE2:
344
+ raise ContainerError("invalid PYE2 artifact magic")
345
+ nonce_start = len(MAGIC_PYE2)
346
+ ciphertext_start = nonce_start + NONCE_SIZE
347
+ return ParsedOpaqueArtifact(
348
+ nonce=artifact[nonce_start:ciphertext_start],
349
+ ciphertext=artifact[ciphertext_start:],
350
+ )
351
+
352
+
353
+ def unpack_opaque_artifact(
354
+ data: BytesLike,
355
+ module_root_key: BytesLike,
356
+ context: OpaqueModuleContext,
357
+ ) -> bytes:
358
+ """Authenticate, decrypt, and decompress one PYE2 artifact."""
359
+
360
+ parsed = parse_opaque_artifact(data)
361
+ aad = opaque_artifact_aad(context)
362
+ key = derive_opaque_artifact_key(module_root_key, context)
363
+ compressed = decrypt_payload(
364
+ parsed.ciphertext,
365
+ key,
366
+ parsed.nonce,
367
+ aad=aad,
368
+ )
369
+ return _decompress_strict(
370
+ compressed,
371
+ maximum=_MAX_ARTIFACT_PLAINTEXT_SIZE,
372
+ description="PYE2 artifact payload",
373
+ error=ContainerError,
374
+ )
375
+
376
+
377
+ def normalize_module_index(value: Mapping[str, Any]) -> ModuleIndex:
378
+ """Validate and return the exact canonical plaintext index schema."""
379
+
380
+ if not isinstance(value, Mapping):
381
+ raise ManifestError("module index must be a JSON object")
382
+ required = {"format", "entry_module", "modules"}
383
+ raw_fields = set(value)
384
+ if raw_fields != required:
385
+ missing = sorted(required.difference(raw_fields))
386
+ extra = sorted(raw_fields.difference(required), key=repr)
387
+ details: list[str] = []
388
+ if missing:
389
+ details.append("missing " + ", ".join(missing))
390
+ if extra:
391
+ details.append("unknown " + ", ".join(repr(item) for item in extra))
392
+ raise ManifestError(
393
+ "module index has invalid fields (" + "; ".join(details) + ")"
394
+ )
395
+ if type(value["format"]) is not int or value["format"] != MODULE_INDEX_FORMAT:
396
+ raise ManifestError("unsupported module index format")
397
+ entry_module = _validate_module_name(value["entry_module"], ManifestError)
398
+ raw_modules = value["modules"]
399
+ if not isinstance(raw_modules, Mapping) or not raw_modules:
400
+ raise ManifestError("module index modules must be a non-empty object")
401
+
402
+ modules: dict[str, ModuleIndexEntry] = {}
403
+ tokens: set[str] = set()
404
+ for raw_name, raw_metadata in raw_modules.items():
405
+ name = _validate_module_name(raw_name, ManifestError)
406
+ if name in modules:
407
+ raise ManifestError(f"duplicate protected module {name!r}")
408
+ if not isinstance(raw_metadata, Mapping):
409
+ raise ManifestError(f"metadata for module {name!r} must be an object")
410
+ if set(raw_metadata) != {"artifact", "package"}:
411
+ raise ManifestError(
412
+ f"metadata for module {name!r} must contain only "
413
+ "'artifact' and 'package'"
414
+ )
415
+ token = _validate_token(raw_metadata["artifact"], ManifestError)
416
+ if token in tokens:
417
+ raise ManifestError(f"duplicate artifact token {token!r}")
418
+ package = raw_metadata["package"]
419
+ if type(package) is not bool:
420
+ raise ManifestError(f"package flag for module {name!r} must be boolean")
421
+ tokens.add(token)
422
+ modules[name] = {"artifact": token, "package": package}
423
+
424
+ if entry_module not in modules:
425
+ raise ManifestError("module index does not contain its configured entry module")
426
+ return cast(
427
+ ModuleIndex,
428
+ {
429
+ "format": MODULE_INDEX_FORMAT,
430
+ "entry_module": entry_module,
431
+ "modules": dict(sorted(modules.items())),
432
+ },
433
+ )
434
+
435
+
436
+ def canonical_module_index_bytes(value: Mapping[str, Any]) -> bytes:
437
+ """Return canonical UTF-8 JSON for a strictly normalized module index."""
438
+
439
+ return _canonical_json(normalize_module_index(value), ManifestError)
440
+
441
+
442
+ def decode_module_index(data: BytesLike) -> ModuleIndex:
443
+ """Decode only canonical JSON matching the strict module-index schema."""
444
+
445
+ raw = _as_bytes(data, name="module index", error=ManifestError)
446
+ try:
447
+ decoded = json.loads(raw.decode("utf-8"))
448
+ except (UnicodeDecodeError, json.JSONDecodeError) as exc:
449
+ raise ManifestError("module index is not valid UTF-8 JSON") from exc
450
+ normalized = normalize_module_index(decoded)
451
+ if _canonical_json(normalized, ManifestError) != raw:
452
+ raise ManifestError("module index JSON is not canonical")
453
+ return normalized
454
+
455
+
456
+ def encrypt_module_index(
457
+ value: Mapping[str, Any],
458
+ support_root_key: BytesLike,
459
+ *,
460
+ build_id: str,
461
+ python_tag: str,
462
+ nonce: BytesLike | None = None,
463
+ compression_level: int = 9,
464
+ ) -> EncryptedModuleIndex:
465
+ """Canonicalize, compress, and encrypt a protected-module index."""
466
+
467
+ if type(compression_level) is not int or not -1 <= compression_level <= 9:
468
+ raise ManifestError("zlib compression_level must be between -1 and 9")
469
+ plaintext = canonical_module_index_bytes(value)
470
+ if len(plaintext) > _MAX_MODULE_INDEX_SIZE:
471
+ raise ManifestError("module index exceeds the safety limit")
472
+ key = derive_module_index_key(support_root_key, build_id)
473
+ aad = module_index_aad(build_id, python_tag)
474
+ encrypted = encrypt_payload(
475
+ zlib.compress(plaintext, level=compression_level),
476
+ key,
477
+ aad=aad,
478
+ nonce=nonce,
479
+ )
480
+ return EncryptedModuleIndex(encrypted.nonce, encrypted.ciphertext)
481
+
482
+
483
+ def decrypt_module_index(
484
+ encrypted_index: EncryptedModuleIndex,
485
+ support_root_key: BytesLike,
486
+ *,
487
+ build_id: str,
488
+ python_tag: str,
489
+ ) -> ModuleIndex:
490
+ """Authenticate, decrypt, and strictly decode a protected-module index."""
491
+
492
+ nonce, ciphertext = _encrypted_index_parts(encrypted_index)
493
+ key = derive_module_index_key(support_root_key, build_id)
494
+ aad = module_index_aad(build_id, python_tag)
495
+ compressed = decrypt_payload(ciphertext, key, nonce, aad=aad)
496
+ plaintext = _decompress_strict(
497
+ compressed,
498
+ maximum=_MAX_MODULE_INDEX_SIZE,
499
+ description="module index",
500
+ error=ManifestError,
501
+ )
502
+ return decode_module_index(plaintext)
503
+
504
+
505
+ def _encrypted_index_parts(
506
+ value: EncryptedModuleIndex,
507
+ ) -> tuple[bytes, bytes]:
508
+ if not isinstance(value, EncryptedModuleIndex):
509
+ raise ManifestError("encrypted index must be an EncryptedModuleIndex")
510
+ nonce = _as_bytes(value.nonce, name="module index nonce", error=ManifestError)
511
+ ciphertext = _as_bytes(
512
+ value.ciphertext,
513
+ name="module index ciphertext",
514
+ error=ManifestError,
515
+ )
516
+ if len(nonce) != NONCE_SIZE:
517
+ raise ManifestError(f"module index nonce must be exactly {NONCE_SIZE} bytes")
518
+ if len(ciphertext) < TAG_SIZE:
519
+ raise ManifestError("module index ciphertext is shorter than its GCM tag")
520
+ return nonce, ciphertext
521
+
522
+
523
+ def _decompress_strict(
524
+ compressed: bytes,
525
+ *,
526
+ maximum: int,
527
+ description: str,
528
+ error: type[Exception],
529
+ ) -> bytes:
530
+ decompressor = zlib.decompressobj()
531
+ try:
532
+ value = decompressor.decompress(compressed, maximum + 1)
533
+ if len(value) > maximum or decompressor.unconsumed_tail:
534
+ raise error(f"{description} exceeds the safety limit")
535
+ value += decompressor.flush()
536
+ except zlib.error as exc:
537
+ raise error(f"{description} is not valid zlib data") from exc
538
+ if len(value) > maximum:
539
+ raise error(f"{description} exceeds the safety limit")
540
+ if not decompressor.eof or decompressor.unused_data:
541
+ raise error(f"{description} contains incomplete or trailing compressed data")
542
+ return value
543
+
544
+
545
+ __all__ = [
546
+ "ARTIFACT_KEY_INFO",
547
+ "EncryptedModuleIndex",
548
+ "INDEX_COMMITMENT_DOMAIN",
549
+ "INDEX_COMMITMENT_SIZE",
550
+ "INDEX_KEY_INFO",
551
+ "MAGIC_PYE2",
552
+ "MODULE_INDEX_FORMAT",
553
+ "MODULE_ROOT_KEY_INFO",
554
+ "ModuleIndex",
555
+ "ModuleIndexEntry",
556
+ "OPAQUE_ARTIFACT_FORMAT",
557
+ "OpaqueModuleContext",
558
+ "ParsedOpaqueArtifact",
559
+ "TOKEN_SIZE",
560
+ "canonical_module_index_bytes",
561
+ "decode_module_index",
562
+ "decrypt_module_index",
563
+ "derive_module_index_key",
564
+ "derive_module_root_key",
565
+ "derive_opaque_artifact_key",
566
+ "encrypt_module_index",
567
+ "module_index_aad",
568
+ "module_index_commitment",
569
+ "normalize_module_index",
570
+ "opaque_artifact_aad",
571
+ "pack_opaque_artifact",
572
+ "parse_opaque_artifact",
573
+ "unpack_opaque_artifact",
574
+ ]
@@ -0,0 +1,99 @@
1
+ """Public runtime interface copied into every protected distribution."""
2
+
3
+ import os as _os
4
+ import sys as _sys
5
+
6
+
7
+ _sys.dont_write_bytecode = True
8
+ _runtime_directory = _os.path.dirname(_os.path.realpath(__file__))
9
+ _distribution_root = _os.path.dirname(_runtime_directory)
10
+ _expected_runtime_files = {"__init__.py", "_mp_main.py", "_runtime.py", "_build.py"}
11
+ try:
12
+ with _os.scandir(_runtime_directory) as _runtime_iterator:
13
+ _runtime_entries = tuple(_runtime_iterator)
14
+ except OSError as exc:
15
+ raise ImportError("pyencode runtime directory is missing or unreadable") from exc
16
+ _runtime_names = {entry.name for entry in _runtime_entries}
17
+ _runtime_problems = []
18
+ if _expected_runtime_files - _runtime_names:
19
+ _runtime_problems.append("missing " + ", ".join(sorted(_expected_runtime_files - _runtime_names)))
20
+ if _runtime_names - _expected_runtime_files:
21
+ _runtime_problems.append("unexpected " + ", ".join(sorted(_runtime_names - _expected_runtime_files)))
22
+ _non_files = sorted(
23
+ entry.name
24
+ for entry in _runtime_entries
25
+ if entry.is_symlink() or not entry.is_file(follow_symlinks=False)
26
+ )
27
+ if _non_files:
28
+ _runtime_problems.append("not regular files " + ", ".join(_non_files))
29
+ if _runtime_problems:
30
+ raise ImportError(
31
+ "pyencode runtime directory contains unsigned files or directories ("
32
+ + "; ".join(_runtime_problems)
33
+ + ")"
34
+ )
35
+
36
+ from . import _build as _bootstrap_build
37
+
38
+ _raw_bootstrap_paths = getattr(_bootstrap_build, "BOOTSTRAP_PATHS", ())
39
+ if not isinstance(_raw_bootstrap_paths, (tuple, list)):
40
+ raise ImportError("pyencode runtime bootstrap path configuration is invalid")
41
+ _bootstrap_roots = set()
42
+ for _relative in _raw_bootstrap_paths:
43
+ if _relative != "_vendor":
44
+ raise ImportError("pyencode runtime bootstrap path configuration is invalid")
45
+ _bootstrap_roots.add(
46
+ _os.path.normcase(_os.path.realpath(_os.path.join(_distribution_root, _relative)))
47
+ )
48
+
49
+
50
+ def _is_distribution_path(_entry):
51
+ if not isinstance(_entry, str):
52
+ return False
53
+ _candidate = _entry or _os.getcwd()
54
+ try:
55
+ _root = _os.path.normcase(_os.path.realpath(_distribution_root))
56
+ _resolved = _os.path.normcase(_os.path.realpath(_candidate))
57
+ if _resolved in _bootstrap_roots:
58
+ return False
59
+ return _os.path.commonpath((_root, _resolved)) == _root
60
+ except (OSError, TypeError, ValueError):
61
+ return False
62
+
63
+
64
+ # Do not let an injected top-level module beside the bundle shadow stdlib or
65
+ # cryptography while the verifier itself is importing. Relative imports from
66
+ # this already-located package continue to resolve through ``__path__``.
67
+ _original_sys_path = list(_sys.path)
68
+ _sys.path[:] = [_entry for _entry in _sys.path if not _is_distribution_path(_entry)]
69
+ try:
70
+ from cryptography.exceptions import InvalidSignature as _InvalidSignature
71
+ from cryptography.exceptions import InvalidTag as _InvalidTag
72
+ from cryptography.hazmat.primitives import hashes as _crypto_hashes
73
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
74
+ Ed25519PublicKey as _Ed25519PublicKey,
75
+ )
76
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM as _AESGCM
77
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF as _HKDF
78
+
79
+ from ._runtime import (
80
+ CompatibilityError,
81
+ IntegrityError,
82
+ PolicyError,
83
+ PyEncodeRuntimeError,
84
+ install,
85
+ load_entry,
86
+ run,
87
+ )
88
+ finally:
89
+ _sys.path[:] = _original_sys_path
90
+
91
+ __all__ = [
92
+ "CompatibilityError",
93
+ "IntegrityError",
94
+ "PolicyError",
95
+ "PyEncodeRuntimeError",
96
+ "install",
97
+ "load_entry",
98
+ "run",
99
+ ]
@@ -0,0 +1,15 @@
1
+ """Build-specific values replaced by :mod:`pyencode` in protected output.
2
+
3
+ This file deliberately contains no usable defaults. Keeping the names here
4
+ makes the runtime template importable while it is part of the pyencode source
5
+ tree; a build must replace every value except ``MANIFEST_NAME``.
6
+ """
7
+
8
+ BUILD_ID = ""
9
+ PYTHON_TAG = ""
10
+ PUBLIC_KEY_B64 = ""
11
+ KEY_PARTS = ()
12
+ KEY_ORDER = ()
13
+ MANIFEST_NAME = "pyencode-manifest.json"
14
+ LAUNCHER_NAME = "run.py"
15
+ BOOTSTRAP_PATHS = []
@@ -0,0 +1,7 @@
1
+ """Multiprocessing-spawn trampoline for an encrypted application entry."""
2
+
3
+ from ._runtime import _multiprocessing_bootstrap
4
+
5
+
6
+ _multiprocessing_bootstrap(globals())
7
+ del _multiprocessing_bootstrap