provide-foundation 0.0.0.dev2__py3-none-any.whl → 0.0.0.dev3__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.
- provide/foundation/__init__.py +20 -20
- provide/foundation/archive/__init__.py +1 -1
- provide/foundation/archive/base.py +15 -14
- provide/foundation/archive/bzip2.py +40 -40
- provide/foundation/archive/gzip.py +42 -42
- provide/foundation/archive/operations.py +90 -91
- provide/foundation/archive/tar.py +33 -31
- provide/foundation/archive/zip.py +52 -50
- provide/foundation/asynctools/__init__.py +20 -0
- provide/foundation/asynctools/core.py +126 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +4 -4
- provide/foundation/cli/commands/logs/__init__.py +2 -2
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +3 -3
- provide/foundation/cli/commands/logs/send.py +2 -2
- provide/foundation/cli/commands/logs/tail.py +2 -2
- provide/foundation/cli/decorators.py +0 -1
- provide/foundation/cli/testing.py +0 -5
- provide/foundation/cli/utils.py +1 -2
- provide/foundation/config/__init__.py +19 -19
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +81 -83
- provide/foundation/config/defaults.py +1 -1
- provide/foundation/config/env.py +2 -1
- provide/foundation/config/loader.py +1 -1
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/output.py +7 -7
- provide/foundation/context/core.py +19 -17
- provide/foundation/crypto/certificates/__init__.py +9 -5
- provide/foundation/crypto/certificates/base.py +2 -2
- provide/foundation/crypto/certificates/certificate.py +48 -19
- provide/foundation/crypto/certificates/factory.py +26 -18
- provide/foundation/crypto/certificates/generator.py +24 -23
- provide/foundation/crypto/certificates/loader.py +24 -16
- provide/foundation/crypto/certificates/operations.py +17 -10
- provide/foundation/crypto/certificates/trust.py +21 -21
- provide/foundation/env/__init__.py +28 -0
- provide/foundation/env/core.py +218 -0
- provide/foundation/errors/__init__.py +3 -2
- provide/foundation/errors/decorators.py +0 -3
- provide/foundation/errors/types.py +0 -1
- provide/foundation/eventsets/display.py +13 -14
- provide/foundation/eventsets/registry.py +61 -31
- provide/foundation/eventsets/resolver.py +50 -46
- provide/foundation/eventsets/sets/das.py +8 -8
- provide/foundation/eventsets/sets/database.py +14 -14
- provide/foundation/eventsets/sets/http.py +21 -21
- provide/foundation/eventsets/sets/llm.py +16 -16
- provide/foundation/eventsets/sets/task_queue.py +13 -13
- provide/foundation/eventsets/types.py +7 -7
- provide/foundation/file/directory.py +1 -1
- provide/foundation/file/lock.py +2 -3
- provide/foundation/hub/components.py +19 -21
- provide/foundation/hub/config.py +25 -19
- provide/foundation/hub/discovery.py +5 -4
- provide/foundation/hub/handlers.py +13 -5
- provide/foundation/hub/lifecycle.py +10 -9
- provide/foundation/hub/manager.py +3 -0
- provide/foundation/hub/processors.py +8 -3
- provide/foundation/integrations/__init__.py +1 -1
- provide/foundation/integrations/openobserve/client.py +2 -2
- provide/foundation/integrations/openobserve/commands.py +9 -9
- provide/foundation/integrations/openobserve/config.py +2 -2
- provide/foundation/integrations/openobserve/otlp.py +2 -2
- provide/foundation/integrations/openobserve/search.py +1 -2
- provide/foundation/integrations/openobserve/streaming.py +1 -1
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +19 -19
- provide/foundation/logger/config/telemetry.py +11 -13
- provide/foundation/logger/factories.py +2 -2
- provide/foundation/logger/processors/main.py +12 -10
- provide/foundation/logger/ratelimit/limiters.py +4 -4
- provide/foundation/logger/ratelimit/processor.py +1 -1
- provide/foundation/logger/setup/coordinator.py +38 -24
- provide/foundation/logger/setup/processors.py +3 -3
- provide/foundation/logger/setup/testing.py +14 -0
- provide/foundation/logger/trace.py +5 -5
- provide/foundation/metrics/__init__.py +1 -1
- provide/foundation/metrics/otel.py +3 -1
- provide/foundation/observability/__init__.py +1 -1
- provide/foundation/process/__init__.py +1 -1
- provide/foundation/process/exit.py +6 -5
- provide/foundation/process/lifecycle.py +41 -18
- provide/foundation/resilience/__init__.py +6 -5
- provide/foundation/resilience/circuit.py +32 -30
- provide/foundation/resilience/decorators.py +58 -42
- provide/foundation/resilience/fallback.py +55 -40
- provide/foundation/resilience/retry.py +67 -65
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +8 -9
- provide/foundation/streams/console.py +3 -3
- provide/foundation/streams/core.py +2 -2
- provide/foundation/streams/file.py +1 -1
- provide/foundation/testing/__init__.py +22 -7
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +3 -6
- provide/foundation/testing/common/__init__.py +13 -13
- provide/foundation/testing/common/fixtures.py +27 -30
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +65 -92
- provide/foundation/testing/file/directory_fixtures.py +19 -19
- provide/foundation/testing/file/fixtures.py +14 -17
- provide/foundation/testing/file/special_fixtures.py +34 -42
- provide/foundation/testing/logger.py +28 -23
- provide/foundation/testing/mocking/__init__.py +21 -21
- provide/foundation/testing/mocking/fixtures.py +80 -67
- provide/foundation/testing/process/__init__.py +23 -23
- provide/foundation/testing/process/async_fixtures.py +89 -80
- provide/foundation/testing/process/fixtures.py +11 -13
- provide/foundation/testing/process/subprocess_fixtures.py +41 -40
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +21 -17
- provide/foundation/testing/threading/data_fixtures.py +18 -16
- provide/foundation/testing/threading/execution_fixtures.py +67 -52
- provide/foundation/testing/threading/fixtures.py +10 -14
- provide/foundation/testing/threading/sync_fixtures.py +21 -18
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +91 -79
- provide/foundation/testing/transport/__init__.py +9 -9
- provide/foundation/testing/transport/fixtures.py +54 -54
- provide/foundation/time/__init__.py +18 -0
- provide/foundation/time/core.py +63 -0
- provide/foundation/tools/__init__.py +2 -2
- provide/foundation/tools/base.py +68 -67
- provide/foundation/tools/cache.py +62 -69
- provide/foundation/tools/downloader.py +51 -56
- provide/foundation/tools/installer.py +51 -57
- provide/foundation/tools/registry.py +38 -45
- provide/foundation/tools/resolver.py +70 -68
- provide/foundation/tools/verifier.py +39 -50
- provide/foundation/tracer/spans.py +1 -13
- provide/foundation/transport/__init__.py +26 -33
- provide/foundation/transport/base.py +32 -30
- provide/foundation/transport/client.py +44 -49
- provide/foundation/transport/config.py +11 -13
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +86 -81
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +3 -2
- provide/foundation/utils/parsing.py +7 -7
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
- provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
- provide_foundation-0.0.0.dev2.dist-info/RECORD +0 -225
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -2,17 +2,15 @@
|
|
2
2
|
|
3
3
|
import copy
|
4
4
|
import json
|
5
|
-
import os
|
6
5
|
from pathlib import Path
|
7
6
|
from typing import Any
|
8
7
|
|
9
8
|
from attrs import define, field, fields, validators
|
10
9
|
|
11
|
-
from provide.foundation.config.
|
12
|
-
from provide.foundation.config.base import field as config_field, ConfigSource
|
10
|
+
from provide.foundation.config.base import ConfigSource, field as config_field
|
13
11
|
from provide.foundation.config.converters import parse_bool_strict
|
12
|
+
from provide.foundation.config.env import RuntimeConfig
|
14
13
|
from provide.foundation.logger import get_logger
|
15
|
-
from provide.foundation.logger.config import TelemetryConfig
|
16
14
|
|
17
15
|
try:
|
18
16
|
import tomli as tomllib
|
@@ -46,53 +44,53 @@ class CLIContext(RuntimeConfig):
|
|
46
44
|
env_var="PROVIDE_LOG_LEVEL",
|
47
45
|
converter=str.upper,
|
48
46
|
validator=validators.in_(VALID_LOG_LEVELS),
|
49
|
-
description="Logging level for CLI output"
|
47
|
+
description="Logging level for CLI output",
|
50
48
|
)
|
51
49
|
profile: str = config_field(
|
52
50
|
default="default",
|
53
51
|
env_var="PROVIDE_PROFILE",
|
54
|
-
description="Configuration profile to use"
|
52
|
+
description="Configuration profile to use",
|
55
53
|
)
|
56
54
|
debug: bool = config_field(
|
57
55
|
default=False,
|
58
56
|
env_var="PROVIDE_DEBUG",
|
59
57
|
converter=parse_bool_strict,
|
60
|
-
description="Enable debug mode"
|
58
|
+
description="Enable debug mode",
|
61
59
|
)
|
62
60
|
json_output: bool = config_field(
|
63
61
|
default=False,
|
64
62
|
env_var="PROVIDE_JSON_OUTPUT",
|
65
63
|
converter=parse_bool_strict,
|
66
|
-
description="Output in JSON format"
|
64
|
+
description="Output in JSON format",
|
67
65
|
)
|
68
66
|
config_file: Path | None = config_field(
|
69
67
|
default=None,
|
70
68
|
env_var="PROVIDE_CONFIG_FILE",
|
71
69
|
converter=lambda x: Path(x) if x else None,
|
72
|
-
description="Path to configuration file"
|
70
|
+
description="Path to configuration file",
|
73
71
|
)
|
74
72
|
log_file: Path | None = config_field(
|
75
73
|
default=None,
|
76
74
|
env_var="PROVIDE_LOG_FILE",
|
77
75
|
converter=lambda x: Path(x) if x else None,
|
78
|
-
description="Path to log file"
|
76
|
+
description="Path to log file",
|
79
77
|
)
|
80
78
|
log_format: str = config_field(
|
81
79
|
default="key_value",
|
82
80
|
env_var="PROVIDE_LOG_FORMAT",
|
83
|
-
description="Log output format (key_value or json)"
|
81
|
+
description="Log output format (key_value or json)",
|
84
82
|
)
|
85
83
|
no_color: bool = config_field(
|
86
84
|
default=False,
|
87
85
|
env_var="NO_COLOR",
|
88
86
|
converter=parse_bool_strict,
|
89
|
-
description="Disable colored output"
|
87
|
+
description="Disable colored output",
|
90
88
|
)
|
91
89
|
no_emoji: bool = config_field(
|
92
90
|
default=False,
|
93
91
|
env_var="PROVIDE_NO_EMOJI",
|
94
92
|
converter=parse_bool_strict,
|
95
|
-
description="Disable emoji in output"
|
93
|
+
description="Disable emoji in output",
|
96
94
|
)
|
97
95
|
|
98
96
|
# Private fields - using Factory for mutable defaults
|
@@ -116,13 +114,13 @@ class CLIContext(RuntimeConfig):
|
|
116
114
|
# Create default instance and environment instance
|
117
115
|
default_ctx = self.__class__() # All defaults
|
118
116
|
env_ctx = self.from_env(prefix=prefix) # Environment + defaults
|
119
|
-
|
117
|
+
|
120
118
|
# Only update fields where environment differs from default
|
121
119
|
for attr in fields(self.__class__):
|
122
120
|
if not attr.name.startswith("_"): # Skip private fields
|
123
121
|
default_value = getattr(default_ctx, attr.name)
|
124
122
|
env_value = getattr(env_ctx, attr.name)
|
125
|
-
|
123
|
+
|
126
124
|
# If environment value differs from default, it came from env
|
127
125
|
if env_value != default_value:
|
128
126
|
setattr(self, attr.name, env_value)
|
@@ -142,7 +140,9 @@ class CLIContext(RuntimeConfig):
|
|
142
140
|
}
|
143
141
|
|
144
142
|
@classmethod
|
145
|
-
def from_dict(
|
143
|
+
def from_dict(
|
144
|
+
cls, data: dict[str, Any], source: ConfigSource = ConfigSource.RUNTIME
|
145
|
+
) -> "CLIContext":
|
146
146
|
"""
|
147
147
|
Create context from dictionary.
|
148
148
|
|
@@ -265,7 +265,9 @@ class CLIContext(RuntimeConfig):
|
|
265
265
|
|
266
266
|
path.write_text(content)
|
267
267
|
|
268
|
-
def merge(
|
268
|
+
def merge(
|
269
|
+
self, other: "CLIContext", override_defaults: bool = False
|
270
|
+
) -> "CLIContext":
|
269
271
|
"""
|
270
272
|
Merge with another context, with other taking precedence.
|
271
273
|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# Import from submodules using absolute imports
|
4
4
|
from provide.foundation.crypto.certificates.base import (
|
5
|
+
_HAS_CRYPTO,
|
5
6
|
CertificateBase,
|
6
7
|
CertificateConfig,
|
7
8
|
CertificateError,
|
@@ -9,7 +10,6 @@ from provide.foundation.crypto.certificates.base import (
|
|
9
10
|
KeyPair,
|
10
11
|
KeyType,
|
11
12
|
PublicKey,
|
12
|
-
_HAS_CRYPTO,
|
13
13
|
_require_crypto,
|
14
14
|
)
|
15
15
|
from provide.foundation.crypto.certificates.certificate import Certificate
|
@@ -21,14 +21,18 @@ from provide.foundation.crypto.certificates.operations import (
|
|
21
21
|
|
22
22
|
# Re-export public types - maintaining exact same API
|
23
23
|
__all__ = [
|
24
|
+
"_HAS_CRYPTO", # For testing
|
24
25
|
"Certificate",
|
25
26
|
"CertificateBase",
|
26
27
|
"CertificateConfig",
|
27
28
|
"CertificateError",
|
28
29
|
"CurveType",
|
30
|
+
"KeyPair",
|
29
31
|
"KeyType",
|
30
|
-
"
|
31
|
-
"create_ca",
|
32
|
-
"_HAS_CRYPTO", # For testing
|
32
|
+
"PublicKey",
|
33
33
|
"_require_crypto", # For testing
|
34
|
-
|
34
|
+
"create_ca",
|
35
|
+
"create_self_signed",
|
36
|
+
"create_x509_certificate",
|
37
|
+
"validate_signature",
|
38
|
+
]
|
@@ -5,7 +5,7 @@ from enum import StrEnum, auto
|
|
5
5
|
import traceback
|
6
6
|
from typing import NotRequired, Self, TypeAlias, TypedDict
|
7
7
|
|
8
|
-
from attrs import define
|
8
|
+
from attrs import define
|
9
9
|
|
10
10
|
try:
|
11
11
|
from cryptography import x509
|
@@ -170,4 +170,4 @@ class CertificateBase:
|
|
170
170
|
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
171
171
|
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
172
172
|
]
|
173
|
-
)
|
173
|
+
)
|
@@ -11,7 +11,7 @@ try:
|
|
11
11
|
from cryptography.hazmat.primitives import serialization
|
12
12
|
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
13
13
|
from cryptography.x509 import Certificate as X509Certificate
|
14
|
-
|
14
|
+
|
15
15
|
_HAS_CRYPTO = True
|
16
16
|
except ImportError:
|
17
17
|
x509 = None
|
@@ -22,11 +22,17 @@ except ImportError:
|
|
22
22
|
_HAS_CRYPTO = False
|
23
23
|
|
24
24
|
from provide.foundation import logger
|
25
|
-
from provide.foundation.crypto.certificates.base import
|
25
|
+
from provide.foundation.crypto.certificates.base import (
|
26
|
+
CertificateBase,
|
27
|
+
CertificateError,
|
28
|
+
PublicKey,
|
29
|
+
)
|
26
30
|
from provide.foundation.crypto.certificates.generator import generate_certificate
|
27
31
|
from provide.foundation.crypto.certificates.loader import load_certificate_from_pem
|
28
32
|
from provide.foundation.crypto.certificates.operations import create_x509_certificate
|
29
|
-
from provide.foundation.crypto.certificates.trust import
|
33
|
+
from provide.foundation.crypto.certificates.trust import (
|
34
|
+
verify_trust as verify_trust_impl,
|
35
|
+
)
|
30
36
|
from provide.foundation.crypto.constants import (
|
31
37
|
DEFAULT_CERTIFICATE_CURVE,
|
32
38
|
DEFAULT_CERTIFICATE_KEY_TYPE,
|
@@ -83,13 +89,10 @@ class Certificate:
|
|
83
89
|
else:
|
84
90
|
# Load existing certificate
|
85
91
|
if not self.cert_pem_or_uri:
|
86
|
-
raise CertificateError(
|
87
|
-
|
88
|
-
)
|
89
|
-
|
92
|
+
raise CertificateError("cert_pem_or_uri required when not generating")
|
93
|
+
|
90
94
|
base, x509_cert, private_key, cert_pem, key_pem = load_certificate_from_pem(
|
91
|
-
self.cert_pem_or_uri,
|
92
|
-
self.key_pem_or_uri
|
95
|
+
self.cert_pem_or_uri, self.key_pem_or_uri
|
93
96
|
)
|
94
97
|
self._base = base
|
95
98
|
self._cert = x509_cert
|
@@ -174,9 +177,14 @@ class Certificate:
|
|
174
177
|
) -> "Certificate":
|
175
178
|
"""Creates a new self-signed CA certificate."""
|
176
179
|
from provide.foundation.crypto.certificates.factory import create_ca_certificate
|
180
|
+
|
177
181
|
return create_ca_certificate(
|
178
|
-
common_name,
|
179
|
-
|
182
|
+
common_name,
|
183
|
+
organization_name,
|
184
|
+
validity_days,
|
185
|
+
key_type,
|
186
|
+
key_size,
|
187
|
+
ecdsa_curve,
|
180
188
|
)
|
181
189
|
|
182
190
|
@classmethod
|
@@ -193,10 +201,20 @@ class Certificate:
|
|
193
201
|
is_client_cert: bool = False,
|
194
202
|
) -> "Certificate":
|
195
203
|
"""Creates a new certificate signed by the provided CA certificate."""
|
196
|
-
from provide.foundation.crypto.certificates.factory import
|
204
|
+
from provide.foundation.crypto.certificates.factory import (
|
205
|
+
create_signed_certificate,
|
206
|
+
)
|
207
|
+
|
197
208
|
return create_signed_certificate(
|
198
|
-
ca_certificate,
|
199
|
-
|
209
|
+
ca_certificate,
|
210
|
+
common_name,
|
211
|
+
organization_name,
|
212
|
+
validity_days,
|
213
|
+
alt_names,
|
214
|
+
key_type,
|
215
|
+
key_size,
|
216
|
+
ecdsa_curve,
|
217
|
+
is_client_cert,
|
200
218
|
)
|
201
219
|
|
202
220
|
@classmethod
|
@@ -211,10 +229,18 @@ class Certificate:
|
|
211
229
|
ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
|
212
230
|
) -> "Certificate":
|
213
231
|
"""Creates a new self-signed end-entity certificate suitable for a server."""
|
214
|
-
from provide.foundation.crypto.certificates.factory import
|
232
|
+
from provide.foundation.crypto.certificates.factory import (
|
233
|
+
create_self_signed_server_cert,
|
234
|
+
)
|
235
|
+
|
215
236
|
return create_self_signed_server_cert(
|
216
|
-
common_name,
|
217
|
-
|
237
|
+
common_name,
|
238
|
+
organization_name,
|
239
|
+
validity_days,
|
240
|
+
alt_names,
|
241
|
+
key_type,
|
242
|
+
key_size,
|
243
|
+
ecdsa_curve,
|
218
244
|
)
|
219
245
|
|
220
246
|
def verify_trust(self, other_cert: Self) -> bool:
|
@@ -243,7 +269,10 @@ class Certificate:
|
|
243
269
|
self, signed_cert: "Certificate", signing_cert: "Certificate"
|
244
270
|
) -> bool:
|
245
271
|
"""Internal helper: Validates signature and issuer/subject match."""
|
246
|
-
from provide.foundation.crypto.certificates.trust import
|
272
|
+
from provide.foundation.crypto.certificates.trust import (
|
273
|
+
validate_signature_wrapper,
|
274
|
+
)
|
275
|
+
|
247
276
|
return validate_signature_wrapper(signed_cert, signing_cert)
|
248
277
|
|
249
278
|
def __eq__(self, other: object) -> bool:
|
@@ -287,4 +316,4 @@ class Certificate:
|
|
287
316
|
|
288
317
|
|
289
318
|
# Type alias for backwards compatibility
|
290
|
-
KeyPair = rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey | None
|
319
|
+
KeyPair = rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey | None
|
@@ -1,9 +1,14 @@
|
|
1
1
|
"""Certificate factory methods."""
|
2
2
|
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
|
5
|
+
if TYPE_CHECKING:
|
6
|
+
from provide.foundation.crypto.certificates.certificate import Certificate
|
7
|
+
|
3
8
|
try:
|
4
9
|
from cryptography import x509
|
5
10
|
from cryptography.hazmat.primitives import serialization
|
6
|
-
|
11
|
+
|
7
12
|
_HAS_CRYPTO = True
|
8
13
|
except ImportError:
|
9
14
|
x509 = None
|
@@ -11,7 +16,10 @@ except ImportError:
|
|
11
16
|
_HAS_CRYPTO = False
|
12
17
|
|
13
18
|
from provide.foundation import logger
|
14
|
-
from provide.foundation.crypto.certificates.base import
|
19
|
+
from provide.foundation.crypto.certificates.base import (
|
20
|
+
CertificateError,
|
21
|
+
_require_crypto,
|
22
|
+
)
|
15
23
|
from provide.foundation.crypto.certificates.operations import create_x509_certificate
|
16
24
|
from provide.foundation.crypto.constants import (
|
17
25
|
DEFAULT_CERTIFICATE_CURVE,
|
@@ -32,7 +40,7 @@ def create_ca_certificate(
|
|
32
40
|
"""Creates a new self-signed CA certificate."""
|
33
41
|
# Import here to avoid circular dependency
|
34
42
|
from provide.foundation.crypto.certificates.certificate import Certificate
|
35
|
-
|
43
|
+
|
36
44
|
logger.info(
|
37
45
|
f"📜🔑🏭 Creating new CA certificate: CN={common_name}, Org={organization_name}"
|
38
46
|
)
|
@@ -76,7 +84,7 @@ def create_signed_certificate(
|
|
76
84
|
"""Creates a new certificate signed by the provided CA certificate."""
|
77
85
|
# Import here to avoid circular dependency
|
78
86
|
from provide.foundation.crypto.certificates.certificate import Certificate
|
79
|
-
|
87
|
+
|
80
88
|
logger.info(
|
81
89
|
f"📜🔑🏭 Creating new certificate signed by CA '{ca_certificate.subject}': "
|
82
90
|
f"CN={common_name}, Org={organization_name}, ClientCert={is_client_cert}"
|
@@ -91,7 +99,7 @@ def create_signed_certificate(
|
|
91
99
|
f"📜🔑⚠️ Signing certificate (Subject: {ca_certificate.subject}) "
|
92
100
|
"is not marked as a CA. This might lead to validation issues."
|
93
101
|
)
|
94
|
-
|
102
|
+
|
95
103
|
new_cert_obj = Certificate(
|
96
104
|
generate_keypair=True,
|
97
105
|
common_name=common_name,
|
@@ -102,7 +110,7 @@ def create_signed_certificate(
|
|
102
110
|
key_size=key_size,
|
103
111
|
ecdsa_curve=ecdsa_curve,
|
104
112
|
)
|
105
|
-
|
113
|
+
|
106
114
|
signed_x509_cert = create_x509_certificate(
|
107
115
|
base=new_cert_obj._base,
|
108
116
|
private_key=new_cert_obj._private_key,
|
@@ -112,12 +120,12 @@ def create_signed_certificate(
|
|
112
120
|
is_ca=False,
|
113
121
|
is_client_cert=is_client_cert,
|
114
122
|
)
|
115
|
-
|
123
|
+
|
116
124
|
new_cert_obj._cert = signed_x509_cert
|
117
125
|
new_cert_obj.cert = signed_x509_cert.public_bytes(
|
118
126
|
serialization.Encoding.PEM
|
119
127
|
).decode("utf-8")
|
120
|
-
|
128
|
+
|
121
129
|
logger.info(
|
122
130
|
f"📜🔑✅ Successfully created and signed certificate for "
|
123
131
|
f"CN={common_name} by CA='{ca_certificate.subject}'"
|
@@ -137,12 +145,12 @@ def create_self_signed_server_cert(
|
|
137
145
|
"""Creates a new self-signed end-entity certificate suitable for a server."""
|
138
146
|
# Import here to avoid circular dependency
|
139
147
|
from provide.foundation.crypto.certificates.certificate import Certificate
|
140
|
-
|
148
|
+
|
141
149
|
logger.info(
|
142
150
|
f"📜🔑🏭 Creating new self-signed SERVER certificate: "
|
143
151
|
f"CN={common_name}, Org={organization_name}"
|
144
152
|
)
|
145
|
-
|
153
|
+
|
146
154
|
cert_obj = Certificate(
|
147
155
|
generate_keypair=True,
|
148
156
|
common_name=common_name,
|
@@ -153,12 +161,12 @@ def create_self_signed_server_cert(
|
|
153
161
|
key_size=key_size,
|
154
162
|
ecdsa_curve=ecdsa_curve,
|
155
163
|
)
|
156
|
-
|
164
|
+
|
157
165
|
if not cert_obj._private_key:
|
158
166
|
raise CertificateError(
|
159
167
|
"Private key not generated for self-signed server certificate"
|
160
168
|
)
|
161
|
-
|
169
|
+
|
162
170
|
actual_x509_cert = create_x509_certificate(
|
163
171
|
base=cert_obj._base,
|
164
172
|
private_key=cert_obj._private_key,
|
@@ -166,12 +174,12 @@ def create_self_signed_server_cert(
|
|
166
174
|
is_ca=False,
|
167
175
|
is_client_cert=False,
|
168
176
|
)
|
169
|
-
|
177
|
+
|
170
178
|
cert_obj._cert = actual_x509_cert
|
171
|
-
cert_obj.cert = actual_x509_cert.public_bytes(
|
172
|
-
|
173
|
-
)
|
174
|
-
|
179
|
+
cert_obj.cert = actual_x509_cert.public_bytes(serialization.Encoding.PEM).decode(
|
180
|
+
"utf-8"
|
181
|
+
)
|
182
|
+
|
175
183
|
logger.info(
|
176
184
|
f"📜🔑✅ Successfully created self-signed SERVER certificate for CN={common_name}"
|
177
185
|
)
|
@@ -210,4 +218,4 @@ def create_ca(
|
|
210
218
|
organization_name=organization,
|
211
219
|
validity_days=validity_days,
|
212
220
|
key_type=key_type,
|
213
|
-
)
|
221
|
+
)
|
@@ -7,7 +7,7 @@ try:
|
|
7
7
|
from cryptography import x509
|
8
8
|
from cryptography.hazmat.primitives import serialization
|
9
9
|
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
10
|
-
|
10
|
+
|
11
11
|
_HAS_CRYPTO = True
|
12
12
|
except ImportError:
|
13
13
|
x509 = None
|
@@ -28,7 +28,6 @@ from provide.foundation.crypto.certificates.operations import create_x509_certif
|
|
28
28
|
from provide.foundation.crypto.constants import (
|
29
29
|
DEFAULT_CERTIFICATE_CURVE,
|
30
30
|
DEFAULT_CERTIFICATE_KEY_TYPE,
|
31
|
-
DEFAULT_CERTIFICATE_VALIDITY_DAYS,
|
32
31
|
DEFAULT_RSA_KEY_SIZE,
|
33
32
|
)
|
34
33
|
|
@@ -43,20 +42,26 @@ def generate_certificate(
|
|
43
42
|
alt_names: list[str] | None = None,
|
44
43
|
is_ca: bool = False,
|
45
44
|
is_client_cert: bool = False,
|
46
|
-
) -> tuple[
|
45
|
+
) -> tuple[
|
46
|
+
CertificateBase,
|
47
|
+
"x509.Certificate",
|
48
|
+
"rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey",
|
49
|
+
str,
|
50
|
+
str,
|
51
|
+
]:
|
47
52
|
"""
|
48
53
|
Generate a new certificate with a keypair.
|
49
|
-
|
54
|
+
|
50
55
|
Returns:
|
51
56
|
Tuple of (CertificateBase, X509Certificate, private_key, cert_pem, key_pem)
|
52
57
|
"""
|
53
58
|
try:
|
54
59
|
logger.debug("📜🔑🚀 Generating new keypair")
|
55
|
-
|
60
|
+
|
56
61
|
now = datetime.now(UTC)
|
57
62
|
not_valid_before = now - timedelta(days=1)
|
58
63
|
not_valid_after = now + timedelta(days=validity_days)
|
59
|
-
|
64
|
+
|
60
65
|
# Parse key type
|
61
66
|
normalized_key_type_str = key_type.lower()
|
62
67
|
match normalized_key_type_str:
|
@@ -69,21 +74,19 @@ def generate_certificate(
|
|
69
74
|
f"Unsupported key_type string: '{key_type}'. "
|
70
75
|
"Must be 'rsa' or 'ecdsa'."
|
71
76
|
)
|
72
|
-
|
77
|
+
|
73
78
|
# Configure key parameters
|
74
79
|
gen_curve: CurveType | None = None
|
75
80
|
gen_key_size = None
|
76
|
-
|
81
|
+
|
77
82
|
if gen_key_type == KeyType.ECDSA:
|
78
83
|
try:
|
79
84
|
gen_curve = CurveType[ecdsa_curve.upper()]
|
80
85
|
except KeyError as e_curve:
|
81
|
-
raise ValueError(
|
82
|
-
f"Unsupported ECDSA curve: {ecdsa_curve}"
|
83
|
-
) from e_curve
|
86
|
+
raise ValueError(f"Unsupported ECDSA curve: {ecdsa_curve}") from e_curve
|
84
87
|
else: # RSA
|
85
88
|
gen_key_size = key_size
|
86
|
-
|
89
|
+
|
87
90
|
# Build configuration
|
88
91
|
conf: CertificateConfig = {
|
89
92
|
"common_name": common_name,
|
@@ -98,10 +101,10 @@ def generate_certificate(
|
|
98
101
|
if gen_key_size is not None:
|
99
102
|
conf["key_size"] = gen_key_size
|
100
103
|
logger.debug(f"📜🔑🚀 Generation config: {conf}")
|
101
|
-
|
104
|
+
|
102
105
|
# Generate base certificate and private key
|
103
106
|
base, private_key = CertificateBase.create(conf)
|
104
|
-
|
107
|
+
|
105
108
|
# Create X.509 certificate
|
106
109
|
x509_cert = create_x509_certificate(
|
107
110
|
base=base,
|
@@ -110,12 +113,10 @@ def generate_certificate(
|
|
110
113
|
is_ca=is_ca,
|
111
114
|
is_client_cert=is_client_cert,
|
112
115
|
)
|
113
|
-
|
116
|
+
|
114
117
|
if x509_cert is None:
|
115
|
-
raise CertificateError(
|
116
|
-
|
117
|
-
)
|
118
|
-
|
118
|
+
raise CertificateError("Certificate object (_cert) is None after creation")
|
119
|
+
|
119
120
|
# Convert to PEM format
|
120
121
|
cert_pem = x509_cert.public_bytes(serialization.Encoding.PEM).decode("utf-8")
|
121
122
|
key_pem = private_key.private_bytes(
|
@@ -123,11 +124,11 @@ def generate_certificate(
|
|
123
124
|
format=serialization.PrivateFormat.PKCS8,
|
124
125
|
encryption_algorithm=serialization.NoEncryption(),
|
125
126
|
).decode("utf-8")
|
126
|
-
|
127
|
+
|
127
128
|
logger.debug("📜🔑✅ Generated cert and key")
|
128
|
-
|
129
|
+
|
129
130
|
return base, x509_cert, private_key, cert_pem, key_pem
|
130
|
-
|
131
|
+
|
131
132
|
except Exception as e:
|
132
133
|
logger.error(
|
133
134
|
f"📜❌ Failed to generate certificate. Error: {type(e).__name__}: {e}",
|
@@ -135,4 +136,4 @@ def generate_certificate(
|
|
135
136
|
)
|
136
137
|
raise CertificateError(
|
137
138
|
f"Failed to initialize certificate. Original error: {type(e).__name__}"
|
138
|
-
) from e
|
139
|
+
) from e
|
@@ -10,7 +10,7 @@ try:
|
|
10
10
|
from cryptography.hazmat.primitives import serialization
|
11
11
|
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
12
12
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
13
|
-
|
13
|
+
|
14
14
|
_HAS_CRYPTO = True
|
15
15
|
except ImportError:
|
16
16
|
x509 = None
|
@@ -21,7 +21,10 @@ except ImportError:
|
|
21
21
|
_HAS_CRYPTO = False
|
22
22
|
|
23
23
|
from provide.foundation import logger
|
24
|
-
from provide.foundation.crypto.certificates.base import
|
24
|
+
from provide.foundation.crypto.certificates.base import (
|
25
|
+
CertificateBase,
|
26
|
+
CertificateError,
|
27
|
+
)
|
25
28
|
|
26
29
|
|
27
30
|
def load_from_uri_or_pem(data: str) -> str:
|
@@ -53,30 +56,35 @@ def load_from_uri_or_pem(data: str) -> str:
|
|
53
56
|
|
54
57
|
|
55
58
|
def load_certificate_from_pem(
|
56
|
-
cert_pem_or_uri: str,
|
57
|
-
|
58
|
-
|
59
|
+
cert_pem_or_uri: str, key_pem_or_uri: str | None = None
|
60
|
+
) -> tuple[
|
61
|
+
CertificateBase,
|
62
|
+
"x509.Certificate",
|
63
|
+
"rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey | None",
|
64
|
+
str,
|
65
|
+
str | None,
|
66
|
+
]:
|
59
67
|
"""
|
60
68
|
Load a certificate and optionally its private key from PEM data or file URIs.
|
61
|
-
|
69
|
+
|
62
70
|
Returns:
|
63
71
|
Tuple of (CertificateBase, X509Certificate, private_key, cert_pem, key_pem)
|
64
72
|
"""
|
65
73
|
try:
|
66
74
|
logger.debug("📜🔑🚀 Loading certificate from provided data")
|
67
75
|
cert_data = load_from_uri_or_pem(cert_pem_or_uri)
|
68
|
-
|
76
|
+
|
69
77
|
logger.debug("📜🔑🔍 Loading X.509 certificate from PEM data")
|
70
78
|
x509_cert = x509.load_pem_x509_certificate(cert_data.encode("utf-8"))
|
71
79
|
logger.debug("📜🔑✅ X.509 certificate object loaded from PEM")
|
72
|
-
|
80
|
+
|
73
81
|
private_key = None
|
74
82
|
key_data = None
|
75
|
-
|
83
|
+
|
76
84
|
if key_pem_or_uri:
|
77
85
|
logger.debug("📜🔑🚀 Loading private key")
|
78
86
|
key_data = load_from_uri_or_pem(key_pem_or_uri)
|
79
|
-
|
87
|
+
|
80
88
|
loaded_priv_key = load_pem_private_key(
|
81
89
|
key_data.encode("utf-8"), password=None
|
82
90
|
)
|
@@ -90,7 +98,7 @@ def load_certificate_from_pem(
|
|
90
98
|
)
|
91
99
|
private_key = loaded_priv_key
|
92
100
|
logger.debug("📜🔑✅ Private key object loaded and type validated")
|
93
|
-
|
101
|
+
|
94
102
|
# Extract certificate details for CertificateBase
|
95
103
|
loaded_not_valid_before = x509_cert.not_valid_before_utc
|
96
104
|
loaded_not_valid_after = x509_cert.not_valid_after_utc
|
@@ -98,7 +106,7 @@ def load_certificate_from_pem(
|
|
98
106
|
loaded_not_valid_before = loaded_not_valid_before.replace(tzinfo=UTC)
|
99
107
|
if loaded_not_valid_after.tzinfo is None:
|
100
108
|
loaded_not_valid_after = loaded_not_valid_after.replace(tzinfo=UTC)
|
101
|
-
|
109
|
+
|
102
110
|
cert_public_key = x509_cert.public_key()
|
103
111
|
if not isinstance(
|
104
112
|
cert_public_key, rsa.RSAPublicKey | ec.EllipticCurvePublicKey
|
@@ -107,7 +115,7 @@ def load_certificate_from_pem(
|
|
107
115
|
f"Certificate's public key is of unsupported type: {type(cert_public_key)}. "
|
108
116
|
"Expected RSA or ECDSA public key."
|
109
117
|
)
|
110
|
-
|
118
|
+
|
111
119
|
base = CertificateBase(
|
112
120
|
subject=x509_cert.subject,
|
113
121
|
issuer=x509_cert.issuer,
|
@@ -117,9 +125,9 @@ def load_certificate_from_pem(
|
|
117
125
|
serial_number=x509_cert.serial_number,
|
118
126
|
)
|
119
127
|
logger.debug("📜🔑✅ Reconstructed CertificateBase from loaded cert")
|
120
|
-
|
128
|
+
|
121
129
|
return base, x509_cert, private_key, cert_data, key_data
|
122
|
-
|
130
|
+
|
123
131
|
except Exception as e:
|
124
132
|
logger.error(
|
125
133
|
f"📜❌ Failed to load certificate. Error: {type(e).__name__}: {e}",
|
@@ -127,4 +135,4 @@ def load_certificate_from_pem(
|
|
127
135
|
)
|
128
136
|
raise CertificateError(
|
129
137
|
f"Failed to initialize certificate. Original error: {type(e).__name__}"
|
130
|
-
) from e
|
138
|
+
) from e
|