provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev1__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.
Files changed (92) hide show
  1. provide/foundation/__init__.py +12 -20
  2. provide/foundation/archive/__init__.py +23 -0
  3. provide/foundation/archive/base.py +70 -0
  4. provide/foundation/archive/bzip2.py +157 -0
  5. provide/foundation/archive/gzip.py +159 -0
  6. provide/foundation/archive/operations.py +336 -0
  7. provide/foundation/archive/tar.py +164 -0
  8. provide/foundation/archive/zip.py +203 -0
  9. provide/foundation/config/base.py +2 -2
  10. provide/foundation/config/sync.py +19 -4
  11. provide/foundation/core.py +1 -2
  12. provide/foundation/crypto/__init__.py +2 -0
  13. provide/foundation/crypto/certificates/__init__.py +34 -0
  14. provide/foundation/crypto/certificates/base.py +173 -0
  15. provide/foundation/crypto/certificates/certificate.py +290 -0
  16. provide/foundation/crypto/certificates/factory.py +213 -0
  17. provide/foundation/crypto/certificates/generator.py +138 -0
  18. provide/foundation/crypto/certificates/loader.py +130 -0
  19. provide/foundation/crypto/certificates/operations.py +198 -0
  20. provide/foundation/crypto/certificates/trust.py +107 -0
  21. provide/foundation/eventsets/__init__.py +0 -0
  22. provide/foundation/eventsets/display.py +84 -0
  23. provide/foundation/eventsets/registry.py +160 -0
  24. provide/foundation/eventsets/resolver.py +192 -0
  25. provide/foundation/eventsets/sets/das.py +128 -0
  26. provide/foundation/eventsets/sets/database.py +125 -0
  27. provide/foundation/eventsets/sets/http.py +153 -0
  28. provide/foundation/eventsets/sets/llm.py +139 -0
  29. provide/foundation/eventsets/sets/task_queue.py +107 -0
  30. provide/foundation/eventsets/types.py +70 -0
  31. provide/foundation/hub/components.py +7 -133
  32. provide/foundation/logger/__init__.py +3 -10
  33. provide/foundation/logger/config/logging.py +6 -6
  34. provide/foundation/logger/core.py +0 -2
  35. provide/foundation/logger/custom_processors.py +1 -0
  36. provide/foundation/logger/factories.py +11 -2
  37. provide/foundation/logger/processors/main.py +20 -84
  38. provide/foundation/logger/setup/__init__.py +5 -1
  39. provide/foundation/logger/setup/coordinator.py +75 -23
  40. provide/foundation/logger/setup/processors.py +2 -9
  41. provide/foundation/logger/trace.py +27 -0
  42. provide/foundation/metrics/otel.py +10 -10
  43. provide/foundation/process/lifecycle.py +82 -26
  44. provide/foundation/testing/__init__.py +77 -0
  45. provide/foundation/testing/archive/__init__.py +24 -0
  46. provide/foundation/testing/archive/fixtures.py +217 -0
  47. provide/foundation/testing/common/__init__.py +34 -0
  48. provide/foundation/testing/common/fixtures.py +263 -0
  49. provide/foundation/testing/file/__init__.py +40 -0
  50. provide/foundation/testing/file/fixtures.py +523 -0
  51. provide/foundation/testing/logger.py +41 -11
  52. provide/foundation/testing/mocking/__init__.py +46 -0
  53. provide/foundation/testing/mocking/fixtures.py +331 -0
  54. provide/foundation/testing/process/__init__.py +48 -0
  55. provide/foundation/testing/process/fixtures.py +577 -0
  56. provide/foundation/testing/threading/__init__.py +38 -0
  57. provide/foundation/testing/threading/fixtures.py +520 -0
  58. provide/foundation/testing/time/__init__.py +32 -0
  59. provide/foundation/testing/time/fixtures.py +409 -0
  60. provide/foundation/testing/transport/__init__.py +30 -0
  61. provide/foundation/testing/transport/fixtures.py +280 -0
  62. provide/foundation/tools/__init__.py +58 -0
  63. provide/foundation/tools/base.py +348 -0
  64. provide/foundation/tools/cache.py +266 -0
  65. provide/foundation/tools/downloader.py +213 -0
  66. provide/foundation/tools/installer.py +254 -0
  67. provide/foundation/tools/registry.py +223 -0
  68. provide/foundation/tools/resolver.py +321 -0
  69. provide/foundation/tools/verifier.py +186 -0
  70. provide/foundation/tracer/otel.py +7 -11
  71. provide/foundation/transport/__init__.py +155 -0
  72. provide/foundation/transport/base.py +171 -0
  73. provide/foundation/transport/client.py +266 -0
  74. provide/foundation/transport/config.py +209 -0
  75. provide/foundation/transport/errors.py +79 -0
  76. provide/foundation/transport/http.py +232 -0
  77. provide/foundation/transport/middleware.py +366 -0
  78. provide/foundation/transport/registry.py +167 -0
  79. provide/foundation/transport/types.py +45 -0
  80. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/METADATA +5 -28
  81. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/RECORD +85 -34
  82. provide/foundation/cli/commands/logs/generate_old.py +0 -569
  83. provide/foundation/crypto/certificates.py +0 -896
  84. provide/foundation/logger/emoji/__init__.py +0 -44
  85. provide/foundation/logger/emoji/matrix.py +0 -209
  86. provide/foundation/logger/emoji/sets.py +0 -458
  87. provide/foundation/logger/emoji/types.py +0 -56
  88. provide/foundation/logger/setup/emoji_resolver.py +0 -64
  89. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/WHEEL +0 -0
  90. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/entry_points.txt +0 -0
  91. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/licenses/LICENSE +0 -0
  92. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/top_level.txt +0 -0
@@ -174,8 +174,8 @@ class BaseConfig:
174
174
  Returns:
175
175
  Configuration instance
176
176
  """
177
- # Filter data to only include fields defined in the class
178
- field_names = {f.name for f in fields(cls)}
177
+ # Filter data to only include fields defined in the class, excluding private fields
178
+ field_names = {f.name for f in fields(cls) if not f.name.startswith('_')}
179
179
  filtered_data = {k: v for k, v in data.items() if k in field_names}
180
180
 
181
181
  # Create instance
@@ -20,6 +20,7 @@ from provide.foundation.config.loader import (
20
20
  )
21
21
  from provide.foundation.config.manager import ConfigManager
22
22
  from provide.foundation.config.types import ConfigDict, ConfigSource
23
+ from provide.foundation.config.loader import ConfigLoader
23
24
 
24
25
  T = TypeVar("T", bound=BaseConfig)
25
26
 
@@ -227,9 +228,14 @@ class SyncConfigManager:
227
228
  Provides a sync interface to the async ConfigManager.
228
229
  """
229
230
 
230
- def __init__(self) -> None:
231
- """Initialize sync config manager."""
231
+ def __init__(self, loader: ConfigLoader | None = None) -> None:
232
+ """Initialize sync config manager.
233
+
234
+ Args:
235
+ loader: Optional config loader for loading configurations.
236
+ """
232
237
  self._async_manager = ConfigManager()
238
+ self._loader = loader
233
239
 
234
240
  def register(self, name: str, config: BaseConfig | None = None, **kwargs) -> None:
235
241
  """Register a configuration (sync)."""
@@ -239,8 +245,17 @@ class SyncConfigManager:
239
245
  """Get a configuration by name (sync)."""
240
246
  return run_async(self._async_manager.get(name))
241
247
 
242
- def load(self, name: str, config_class: type[T], loader=None) -> T:
243
- """Load a configuration (sync)."""
248
+ def load(self, name: str, config_class: type[T], loader: ConfigLoader | None = None) -> T:
249
+ """Load a configuration (sync).
250
+
251
+ Args:
252
+ name: Configuration name
253
+ config_class: Configuration class
254
+ loader: Optional loader (uses registered if None)
255
+
256
+ Returns:
257
+ Configuration instance
258
+ """
244
259
  return run_async(self._async_manager.load(name, config_class, loader))
245
260
 
246
261
  def update(
@@ -5,7 +5,7 @@
5
5
  Foundation Telemetry Core Setup Functions.
6
6
  """
7
7
 
8
- from provide.foundation.logger.setup.emoji_resolver import ResolvedEmojiConfig
8
+ # Emoji resolver removed - using event sets now
9
9
  from provide.foundation.setup import (
10
10
  reset_foundation_setup_for_testing,
11
11
  setup_telemetry,
@@ -13,7 +13,6 @@ from provide.foundation.setup import (
13
13
  )
14
14
 
15
15
  __all__ = [
16
- "ResolvedEmojiConfig",
17
16
  "reset_foundation_setup_for_testing",
18
17
  "setup_telemetry",
19
18
  "shutdown_foundation_telemetry",
@@ -179,4 +179,6 @@ __all__ = [
179
179
  "CurveType",
180
180
  # Legacy compatibility
181
181
  "generate_key_pair",
182
+ # Internal flags (for tests)
183
+ "_HAS_CRYPTO",
182
184
  ]
@@ -0,0 +1,34 @@
1
+ """X.509 certificate generation and management."""
2
+
3
+ # Import from submodules using absolute imports
4
+ from provide.foundation.crypto.certificates.base import (
5
+ CertificateBase,
6
+ CertificateConfig,
7
+ CertificateError,
8
+ CurveType,
9
+ KeyPair,
10
+ KeyType,
11
+ PublicKey,
12
+ _HAS_CRYPTO,
13
+ _require_crypto,
14
+ )
15
+ from provide.foundation.crypto.certificates.certificate import Certificate
16
+ from provide.foundation.crypto.certificates.factory import create_ca, create_self_signed
17
+ from provide.foundation.crypto.certificates.operations import (
18
+ create_x509_certificate,
19
+ validate_signature,
20
+ )
21
+
22
+ # Re-export public types - maintaining exact same API
23
+ __all__ = [
24
+ "Certificate",
25
+ "CertificateBase",
26
+ "CertificateConfig",
27
+ "CertificateError",
28
+ "CurveType",
29
+ "KeyType",
30
+ "create_self_signed",
31
+ "create_ca",
32
+ "_HAS_CRYPTO", # For testing
33
+ "_require_crypto", # For testing
34
+ ]
@@ -0,0 +1,173 @@
1
+ """Certificate base classes, types, and utilities."""
2
+
3
+ from datetime import UTC, datetime
4
+ from enum import StrEnum, auto
5
+ import traceback
6
+ from typing import NotRequired, Self, TypeAlias, TypedDict
7
+
8
+ from attrs import define, field
9
+
10
+ try:
11
+ from cryptography import x509
12
+ from cryptography.hazmat.backends import default_backend
13
+ from cryptography.hazmat.primitives import hashes, serialization
14
+ from cryptography.hazmat.primitives.asymmetric import ec, rsa
15
+ from cryptography.hazmat.primitives.serialization import load_pem_private_key
16
+ from cryptography.x509 import Certificate as X509Certificate
17
+ from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID
18
+
19
+ _HAS_CRYPTO = True
20
+ except ImportError:
21
+ # Stub out cryptography types for type hints
22
+ x509 = None
23
+ default_backend = None
24
+ hashes = None
25
+ serialization = None
26
+ ec = None
27
+ rsa = None
28
+ load_pem_private_key = None
29
+ X509Certificate = None
30
+ ExtendedKeyUsageOID = None
31
+ NameOID = None
32
+ _HAS_CRYPTO = False
33
+
34
+ from provide.foundation import logger
35
+ from provide.foundation.crypto.constants import (
36
+ DEFAULT_RSA_KEY_SIZE,
37
+ )
38
+ from provide.foundation.errors.config import ValidationError
39
+
40
+
41
+ def _require_crypto():
42
+ """Ensure cryptography is available for crypto operations."""
43
+ if not _HAS_CRYPTO:
44
+ raise ImportError(
45
+ "Cryptography features require optional dependencies. Install with: "
46
+ "pip install 'provide-foundation[crypto]'"
47
+ )
48
+
49
+
50
+ class CertificateError(ValidationError):
51
+ """Certificate-related errors."""
52
+
53
+ def __init__(self, message: str, hint: str | None = None) -> None:
54
+ super().__init__(
55
+ message=message,
56
+ field="certificate",
57
+ value=None,
58
+ rule=hint or "Certificate operation failed",
59
+ )
60
+
61
+
62
+ class KeyType(StrEnum):
63
+ RSA = auto()
64
+ ECDSA = auto()
65
+
66
+
67
+ class CurveType(StrEnum):
68
+ SECP256R1 = auto()
69
+ SECP384R1 = auto()
70
+ SECP521R1 = auto()
71
+
72
+
73
+ class CertificateConfig(TypedDict):
74
+ common_name: str
75
+ organization: str
76
+ alt_names: list[str]
77
+ key_type: KeyType
78
+ not_valid_before: datetime
79
+ not_valid_after: datetime
80
+ # Optional key generation parameters
81
+ key_size: NotRequired[int]
82
+ curve: NotRequired[CurveType]
83
+
84
+
85
+ if _HAS_CRYPTO:
86
+ KeyPair: TypeAlias = rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey
87
+ PublicKey: TypeAlias = rsa.RSAPublicKey | ec.EllipticCurvePublicKey
88
+ else:
89
+ KeyPair: TypeAlias = None
90
+ PublicKey: TypeAlias = None
91
+
92
+
93
+ @define(slots=True, frozen=True)
94
+ class CertificateBase:
95
+ """Immutable base certificate data."""
96
+
97
+ subject: "x509.Name"
98
+ issuer: "x509.Name"
99
+ public_key: "PublicKey"
100
+ not_valid_before: datetime
101
+ not_valid_after: datetime
102
+ serial_number: int
103
+
104
+ @classmethod
105
+ def create(cls, config: CertificateConfig) -> tuple[Self, "KeyPair"]:
106
+ """Create a new certificate base and private key."""
107
+ _require_crypto()
108
+ try:
109
+ logger.debug("📜📝🚀 CertificateBase.create: Starting base creation")
110
+ not_valid_before = config["not_valid_before"]
111
+ not_valid_after = config["not_valid_after"]
112
+
113
+ if not_valid_before.tzinfo is None:
114
+ not_valid_before = not_valid_before.replace(tzinfo=UTC)
115
+ if not_valid_after.tzinfo is None:
116
+ not_valid_after = not_valid_after.replace(tzinfo=UTC)
117
+
118
+ logger.debug(
119
+ f"📜⏳✅ CertificateBase.create: Using validity: "
120
+ f"{not_valid_before} to {not_valid_after}"
121
+ )
122
+
123
+ private_key: KeyPair
124
+ match config["key_type"]:
125
+ case KeyType.RSA:
126
+ key_size = config.get("key_size", DEFAULT_RSA_KEY_SIZE)
127
+ logger.debug(f"📜🔑🚀 Generating RSA key (size: {key_size})")
128
+ private_key = rsa.generate_private_key(
129
+ public_exponent=65537, key_size=key_size
130
+ )
131
+ case KeyType.ECDSA:
132
+ curve_choice = config.get("curve", CurveType.SECP384R1)
133
+ logger.debug(f"📜🔑🚀 Generating ECDSA key (curve: {curve_choice})")
134
+ curve = getattr(ec, curve_choice.name)()
135
+ private_key = ec.generate_private_key(curve)
136
+ case _:
137
+ raise ValueError(
138
+ f"Internal Error: Unsupported key type: {config['key_type']}"
139
+ )
140
+
141
+ subject = cls._create_name(config["common_name"], config["organization"])
142
+ issuer = cls._create_name(config["common_name"], config["organization"])
143
+
144
+ serial_number = x509.random_serial_number()
145
+ logger.debug(f"📜🔑✅ Generated serial number: {serial_number}")
146
+
147
+ base = cls(
148
+ subject=subject,
149
+ issuer=issuer,
150
+ public_key=private_key.public_key(),
151
+ not_valid_before=not_valid_before,
152
+ not_valid_after=not_valid_after,
153
+ serial_number=serial_number,
154
+ )
155
+ logger.debug("📜📝✅ CertificateBase.create: Base creation complete")
156
+ return base, private_key
157
+
158
+ except Exception as e:
159
+ logger.error(
160
+ f"📜❌ CertificateBase.create: Failed: {e}",
161
+ extra={"error": str(e), "trace": traceback.format_exc()},
162
+ )
163
+ raise CertificateError(f"Failed to generate certificate base: {e}") from e
164
+
165
+ @staticmethod
166
+ def _create_name(common_name: str, org: str) -> "x509.Name":
167
+ """Helper method to construct an X.509 name."""
168
+ return x509.Name(
169
+ [
170
+ x509.NameAttribute(NameOID.COMMON_NAME, common_name),
171
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
172
+ ]
173
+ )
@@ -0,0 +1,290 @@
1
+ """Main Certificate class."""
2
+
3
+ from datetime import UTC, datetime
4
+ from functools import cached_property
5
+ from typing import Self
6
+
7
+ from attrs import Factory, define, field
8
+
9
+ try:
10
+ from cryptography import x509
11
+ from cryptography.hazmat.primitives import serialization
12
+ from cryptography.hazmat.primitives.asymmetric import ec, rsa
13
+ from cryptography.x509 import Certificate as X509Certificate
14
+
15
+ _HAS_CRYPTO = True
16
+ except ImportError:
17
+ x509 = None
18
+ serialization = None
19
+ ec = None
20
+ rsa = None
21
+ X509Certificate = None
22
+ _HAS_CRYPTO = False
23
+
24
+ from provide.foundation import logger
25
+ from provide.foundation.crypto.certificates.base import CertificateBase, CertificateError, PublicKey
26
+ from provide.foundation.crypto.certificates.generator import generate_certificate
27
+ from provide.foundation.crypto.certificates.loader import load_certificate_from_pem
28
+ from provide.foundation.crypto.certificates.operations import create_x509_certificate
29
+ from provide.foundation.crypto.certificates.trust import verify_trust as verify_trust_impl
30
+ from provide.foundation.crypto.constants import (
31
+ DEFAULT_CERTIFICATE_CURVE,
32
+ DEFAULT_CERTIFICATE_KEY_TYPE,
33
+ DEFAULT_CERTIFICATE_VALIDITY_DAYS,
34
+ DEFAULT_RSA_KEY_SIZE,
35
+ )
36
+
37
+
38
+ @define(slots=True, eq=False, hash=False, repr=False)
39
+ class Certificate:
40
+ """X.509 certificate management using attrs."""
41
+
42
+ cert_pem_or_uri: str | None = field(default=None, kw_only=True)
43
+ key_pem_or_uri: str | None = field(default=None, kw_only=True)
44
+ generate_keypair: bool = field(default=False, kw_only=True)
45
+ key_type: str = field(default=DEFAULT_CERTIFICATE_KEY_TYPE, kw_only=True)
46
+ key_size: int = field(default=DEFAULT_RSA_KEY_SIZE, kw_only=True)
47
+ ecdsa_curve: str = field(default=DEFAULT_CERTIFICATE_CURVE, kw_only=True)
48
+ common_name: str = field(default="localhost", kw_only=True)
49
+ alt_names: list[str] | None = field(
50
+ default=Factory(lambda: ["localhost"]), kw_only=True
51
+ )
52
+ organization_name: str = field(default="Default Organization", kw_only=True)
53
+ validity_days: int = field(default=DEFAULT_CERTIFICATE_VALIDITY_DAYS, kw_only=True)
54
+
55
+ _base: CertificateBase = field(init=False, repr=False)
56
+ _private_key: "KeyPair | None" = field(init=False, default=None, repr=False)
57
+ _cert: "X509Certificate" = field(init=False, repr=False)
58
+ _trust_chain: list["Certificate"] = field(init=False, factory=list, repr=False)
59
+
60
+ cert: str = field(init=False, default="", repr=True)
61
+ key: str | None = field(init=False, default=None, repr=False)
62
+
63
+ def __attrs_post_init__(self) -> None:
64
+ """Handle loading or generation logic after attrs initialization."""
65
+ if self.generate_keypair:
66
+ # Generate new certificate
67
+ base, x509_cert, private_key, cert_pem, key_pem = generate_certificate(
68
+ common_name=self.common_name,
69
+ organization_name=self.organization_name,
70
+ validity_days=self.validity_days,
71
+ key_type=self.key_type,
72
+ key_size=self.key_size,
73
+ ecdsa_curve=self.ecdsa_curve,
74
+ alt_names=self.alt_names,
75
+ is_ca=False,
76
+ is_client_cert=True,
77
+ )
78
+ self._base = base
79
+ self._cert = x509_cert
80
+ self._private_key = private_key
81
+ self.cert = cert_pem
82
+ self.key = key_pem
83
+ else:
84
+ # Load existing certificate
85
+ if not self.cert_pem_or_uri:
86
+ raise CertificateError(
87
+ "cert_pem_or_uri required when not generating"
88
+ )
89
+
90
+ 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
93
+ )
94
+ self._base = base
95
+ self._cert = x509_cert
96
+ self._private_key = private_key
97
+ self.cert = cert_pem
98
+ self.key = key_pem
99
+
100
+ # Properties
101
+ @property
102
+ def trust_chain(self) -> list["Certificate"]:
103
+ """Returns the list of trusted certificates associated with this one."""
104
+ return self._trust_chain
105
+
106
+ @trust_chain.setter
107
+ def trust_chain(self, value: list["Certificate"]) -> None:
108
+ """Sets the list of trusted certificates."""
109
+ self._trust_chain = value
110
+
111
+ @cached_property
112
+ def is_valid(self) -> bool:
113
+ """Checks if the certificate is currently valid based on its dates."""
114
+ if not hasattr(self, "_base"):
115
+ return False
116
+ now = datetime.now(UTC)
117
+ valid = self._base.not_valid_before <= now <= self._base.not_valid_after
118
+ return valid
119
+
120
+ @property
121
+ def is_ca(self) -> bool:
122
+ """Checks if the certificate has the Basic Constraints CA flag set to True."""
123
+ if not hasattr(self, "_cert"):
124
+ return False
125
+ try:
126
+ ext = self._cert.extensions.get_extension_for_oid(
127
+ x509.oid.ExtensionOID.BASIC_CONSTRAINTS
128
+ )
129
+ if isinstance(ext.value, x509.BasicConstraints):
130
+ return ext.value.ca
131
+ return False
132
+ except x509.ExtensionNotFound:
133
+ logger.debug("📜🔍⚠️ is_ca: Basic Constraints extension not found")
134
+ return False
135
+
136
+ @property
137
+ def subject(self) -> str:
138
+ """Returns the certificate subject as an RFC4514 string."""
139
+ if not hasattr(self, "_base"):
140
+ return "SubjectNotInitialized"
141
+ return self._base.subject.rfc4514_string()
142
+
143
+ @property
144
+ def issuer(self) -> str:
145
+ """Returns the certificate issuer as an RFC4514 string."""
146
+ if not hasattr(self, "_base"):
147
+ return "IssuerNotInitialized"
148
+ return self._base.issuer.rfc4514_string()
149
+
150
+ @property
151
+ def public_key(self) -> "PublicKey | None":
152
+ """Returns the public key object from the certificate."""
153
+ if not hasattr(self, "_base"):
154
+ return None
155
+ return self._base.public_key
156
+
157
+ @property
158
+ def serial_number(self) -> int | None:
159
+ """Returns the certificate serial number."""
160
+ if not hasattr(self, "_base"):
161
+ return None
162
+ return self._base.serial_number
163
+
164
+ # Factory methods - moved to factory.py but kept as classmethods for compatibility
165
+ @classmethod
166
+ def create_ca(
167
+ cls,
168
+ common_name: str,
169
+ organization_name: str,
170
+ validity_days: int,
171
+ key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
172
+ key_size: int = DEFAULT_RSA_KEY_SIZE,
173
+ ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
174
+ ) -> "Certificate":
175
+ """Creates a new self-signed CA certificate."""
176
+ from provide.foundation.crypto.certificates.factory import create_ca_certificate
177
+ return create_ca_certificate(
178
+ common_name, organization_name, validity_days,
179
+ key_type, key_size, ecdsa_curve
180
+ )
181
+
182
+ @classmethod
183
+ def create_signed_certificate(
184
+ cls,
185
+ ca_certificate: "Certificate",
186
+ common_name: str,
187
+ organization_name: str,
188
+ validity_days: int,
189
+ alt_names: list[str] | None = None,
190
+ key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
191
+ key_size: int = DEFAULT_RSA_KEY_SIZE,
192
+ ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
193
+ is_client_cert: bool = False,
194
+ ) -> "Certificate":
195
+ """Creates a new certificate signed by the provided CA certificate."""
196
+ from provide.foundation.crypto.certificates.factory import create_signed_certificate
197
+ return create_signed_certificate(
198
+ ca_certificate, common_name, organization_name, validity_days,
199
+ alt_names, key_type, key_size, ecdsa_curve, is_client_cert
200
+ )
201
+
202
+ @classmethod
203
+ def create_self_signed_server_cert(
204
+ cls,
205
+ common_name: str,
206
+ organization_name: str,
207
+ validity_days: int,
208
+ alt_names: list[str] | None = None,
209
+ key_type: str = DEFAULT_CERTIFICATE_KEY_TYPE,
210
+ key_size: int = DEFAULT_RSA_KEY_SIZE,
211
+ ecdsa_curve: str = DEFAULT_CERTIFICATE_CURVE,
212
+ ) -> "Certificate":
213
+ """Creates a new self-signed end-entity certificate suitable for a server."""
214
+ from provide.foundation.crypto.certificates.factory import create_self_signed_server_cert
215
+ return create_self_signed_server_cert(
216
+ common_name, organization_name, validity_days,
217
+ alt_names, key_type, key_size, ecdsa_curve
218
+ )
219
+
220
+ def verify_trust(self, other_cert: Self) -> bool:
221
+ """Verifies if the `other_cert` is trusted based on this certificate's trust chain."""
222
+ return verify_trust_impl(self, other_cert, self._trust_chain)
223
+
224
+ def _create_x509_certificate(
225
+ self,
226
+ issuer_name_override: "x509.Name | None" = None,
227
+ signing_key_override: "KeyPair | None" = None,
228
+ is_ca: bool = False,
229
+ is_client_cert: bool = False,
230
+ ) -> "X509Certificate":
231
+ """Internal helper to build and sign the X.509 certificate object."""
232
+ return create_x509_certificate(
233
+ base=self._base,
234
+ private_key=self._private_key,
235
+ alt_names=self.alt_names,
236
+ issuer_name_override=issuer_name_override,
237
+ signing_key_override=signing_key_override,
238
+ is_ca=is_ca,
239
+ is_client_cert=is_client_cert,
240
+ )
241
+
242
+ def _validate_signature(
243
+ self, signed_cert: "Certificate", signing_cert: "Certificate"
244
+ ) -> bool:
245
+ """Internal helper: Validates signature and issuer/subject match."""
246
+ from provide.foundation.crypto.certificates.trust import validate_signature_wrapper
247
+ return validate_signature_wrapper(signed_cert, signing_cert)
248
+
249
+ def __eq__(self, other: object) -> bool:
250
+ """Custom equality based on subject and serial number."""
251
+ if not isinstance(other, Certificate):
252
+ return NotImplemented
253
+ if not hasattr(self, "_base") or not hasattr(other, "_base"):
254
+ return False
255
+ eq = (
256
+ self._base.subject == other._base.subject
257
+ and self._base.serial_number == other._base.serial_number
258
+ )
259
+ return eq
260
+
261
+ def __hash__(self) -> int:
262
+ """Custom hash based on subject and serial number."""
263
+ if not hasattr(self, "_base"):
264
+ logger.warning("📜🔍⚠️ __hash__ called before _base initialized")
265
+ return hash((None, None))
266
+
267
+ h = hash((self._base.subject, self._base.serial_number))
268
+ return h
269
+
270
+ def __repr__(self) -> str:
271
+ try:
272
+ subject_str = self.subject
273
+ issuer_str = self.issuer
274
+ valid_str = str(self.is_valid)
275
+ ca_str = str(self.is_ca)
276
+ except AttributeError:
277
+ subject_str = "PartiallyInitialized"
278
+ issuer_str = "PartiallyInitialized"
279
+ valid_str = "Unknown"
280
+ ca_str = "Unknown"
281
+
282
+ return (
283
+ f"Certificate(subject='{subject_str}', issuer='{issuer_str}', "
284
+ f"common_name='{self.common_name}', valid={valid_str}, ca={ca_str}, "
285
+ f"key_type='{self.key_type}')"
286
+ )
287
+
288
+
289
+ # Type alias for backwards compatibility
290
+ KeyPair = rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey | None