provide-foundation 0.0.0.dev0__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/__init__.py +15 -0
- provide/foundation/__init__.py +155 -0
- provide/foundation/_version.py +58 -0
- provide/foundation/cli/__init__.py +67 -0
- provide/foundation/cli/commands/__init__.py +3 -0
- provide/foundation/cli/commands/deps.py +71 -0
- provide/foundation/cli/commands/logs/__init__.py +63 -0
- provide/foundation/cli/commands/logs/generate.py +357 -0
- provide/foundation/cli/commands/logs/generate_old.py +569 -0
- provide/foundation/cli/commands/logs/query.py +174 -0
- provide/foundation/cli/commands/logs/send.py +166 -0
- provide/foundation/cli/commands/logs/tail.py +112 -0
- provide/foundation/cli/decorators.py +262 -0
- provide/foundation/cli/main.py +65 -0
- provide/foundation/cli/testing.py +220 -0
- provide/foundation/cli/utils.py +210 -0
- provide/foundation/config/__init__.py +106 -0
- provide/foundation/config/base.py +295 -0
- provide/foundation/config/env.py +369 -0
- provide/foundation/config/loader.py +311 -0
- provide/foundation/config/manager.py +387 -0
- provide/foundation/config/schema.py +284 -0
- provide/foundation/config/sync.py +281 -0
- provide/foundation/config/types.py +78 -0
- provide/foundation/config/validators.py +80 -0
- provide/foundation/console/__init__.py +29 -0
- provide/foundation/console/input.py +364 -0
- provide/foundation/console/output.py +178 -0
- provide/foundation/context/__init__.py +12 -0
- provide/foundation/context/core.py +356 -0
- provide/foundation/core.py +20 -0
- provide/foundation/crypto/__init__.py +182 -0
- provide/foundation/crypto/algorithms.py +111 -0
- provide/foundation/crypto/certificates.py +896 -0
- provide/foundation/crypto/checksums.py +301 -0
- provide/foundation/crypto/constants.py +57 -0
- provide/foundation/crypto/hashing.py +265 -0
- provide/foundation/crypto/keys.py +188 -0
- provide/foundation/crypto/signatures.py +144 -0
- provide/foundation/crypto/utils.py +164 -0
- provide/foundation/errors/__init__.py +96 -0
- provide/foundation/errors/auth.py +73 -0
- provide/foundation/errors/base.py +81 -0
- provide/foundation/errors/config.py +103 -0
- provide/foundation/errors/context.py +299 -0
- provide/foundation/errors/decorators.py +484 -0
- provide/foundation/errors/handlers.py +360 -0
- provide/foundation/errors/integration.py +105 -0
- provide/foundation/errors/platform.py +37 -0
- provide/foundation/errors/process.py +140 -0
- provide/foundation/errors/resources.py +133 -0
- provide/foundation/errors/runtime.py +160 -0
- provide/foundation/errors/safe_decorators.py +133 -0
- provide/foundation/errors/types.py +276 -0
- provide/foundation/file/__init__.py +79 -0
- provide/foundation/file/atomic.py +157 -0
- provide/foundation/file/directory.py +134 -0
- provide/foundation/file/formats.py +236 -0
- provide/foundation/file/lock.py +175 -0
- provide/foundation/file/safe.py +179 -0
- provide/foundation/file/utils.py +170 -0
- provide/foundation/hub/__init__.py +88 -0
- provide/foundation/hub/click_builder.py +310 -0
- provide/foundation/hub/commands.py +42 -0
- provide/foundation/hub/components.py +640 -0
- provide/foundation/hub/decorators.py +244 -0
- provide/foundation/hub/info.py +32 -0
- provide/foundation/hub/manager.py +446 -0
- provide/foundation/hub/registry.py +279 -0
- provide/foundation/hub/type_mapping.py +54 -0
- provide/foundation/hub/types.py +28 -0
- provide/foundation/logger/__init__.py +41 -0
- provide/foundation/logger/base.py +22 -0
- provide/foundation/logger/config/__init__.py +16 -0
- provide/foundation/logger/config/base.py +40 -0
- provide/foundation/logger/config/logging.py +394 -0
- provide/foundation/logger/config/telemetry.py +188 -0
- provide/foundation/logger/core.py +239 -0
- provide/foundation/logger/custom_processors.py +172 -0
- provide/foundation/logger/emoji/__init__.py +44 -0
- provide/foundation/logger/emoji/matrix.py +209 -0
- provide/foundation/logger/emoji/sets.py +458 -0
- provide/foundation/logger/emoji/types.py +56 -0
- provide/foundation/logger/factories.py +56 -0
- provide/foundation/logger/processors/__init__.py +13 -0
- provide/foundation/logger/processors/main.py +254 -0
- provide/foundation/logger/processors/trace.py +113 -0
- provide/foundation/logger/ratelimit/__init__.py +31 -0
- provide/foundation/logger/ratelimit/limiters.py +294 -0
- provide/foundation/logger/ratelimit/processor.py +203 -0
- provide/foundation/logger/ratelimit/queue_limiter.py +305 -0
- provide/foundation/logger/setup/__init__.py +29 -0
- provide/foundation/logger/setup/coordinator.py +138 -0
- provide/foundation/logger/setup/emoji_resolver.py +64 -0
- provide/foundation/logger/setup/processors.py +85 -0
- provide/foundation/logger/setup/testing.py +39 -0
- provide/foundation/logger/trace.py +38 -0
- provide/foundation/metrics/__init__.py +119 -0
- provide/foundation/metrics/otel.py +122 -0
- provide/foundation/metrics/simple.py +165 -0
- provide/foundation/observability/__init__.py +53 -0
- provide/foundation/observability/openobserve/__init__.py +79 -0
- provide/foundation/observability/openobserve/auth.py +72 -0
- provide/foundation/observability/openobserve/client.py +307 -0
- provide/foundation/observability/openobserve/commands.py +357 -0
- provide/foundation/observability/openobserve/exceptions.py +41 -0
- provide/foundation/observability/openobserve/formatters.py +298 -0
- provide/foundation/observability/openobserve/models.py +134 -0
- provide/foundation/observability/openobserve/otlp.py +320 -0
- provide/foundation/observability/openobserve/search.py +222 -0
- provide/foundation/observability/openobserve/streaming.py +235 -0
- provide/foundation/platform/__init__.py +44 -0
- provide/foundation/platform/detection.py +193 -0
- provide/foundation/platform/info.py +157 -0
- provide/foundation/process/__init__.py +39 -0
- provide/foundation/process/async_runner.py +373 -0
- provide/foundation/process/lifecycle.py +406 -0
- provide/foundation/process/runner.py +390 -0
- provide/foundation/setup/__init__.py +101 -0
- provide/foundation/streams/__init__.py +44 -0
- provide/foundation/streams/console.py +57 -0
- provide/foundation/streams/core.py +65 -0
- provide/foundation/streams/file.py +104 -0
- provide/foundation/testing/__init__.py +166 -0
- provide/foundation/testing/cli.py +227 -0
- provide/foundation/testing/crypto.py +163 -0
- provide/foundation/testing/fixtures.py +49 -0
- provide/foundation/testing/hub.py +23 -0
- provide/foundation/testing/logger.py +106 -0
- provide/foundation/testing/streams.py +54 -0
- provide/foundation/tracer/__init__.py +49 -0
- provide/foundation/tracer/context.py +115 -0
- provide/foundation/tracer/otel.py +135 -0
- provide/foundation/tracer/spans.py +174 -0
- provide/foundation/types.py +32 -0
- provide/foundation/utils/__init__.py +97 -0
- provide/foundation/utils/deps.py +195 -0
- provide/foundation/utils/env.py +491 -0
- provide/foundation/utils/formatting.py +483 -0
- provide/foundation/utils/parsing.py +235 -0
- provide/foundation/utils/rate_limiting.py +112 -0
- provide/foundation/utils/streams.py +67 -0
- provide/foundation/utils/timing.py +93 -0
- provide_foundation-0.0.0.dev0.dist-info/METADATA +469 -0
- provide_foundation-0.0.0.dev0.dist-info/RECORD +149 -0
- provide_foundation-0.0.0.dev0.dist-info/WHEEL +5 -0
- provide_foundation-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- provide_foundation-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
- provide_foundation-0.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,188 @@
|
|
1
|
+
"""Unified key generation for all cryptographic algorithms."""
|
2
|
+
|
3
|
+
from typing import Any, Protocol, Union
|
4
|
+
|
5
|
+
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
6
|
+
|
7
|
+
from provide.foundation import logger
|
8
|
+
from provide.foundation.crypto.constants import (
|
9
|
+
DEFAULT_ECDSA_CURVE,
|
10
|
+
DEFAULT_RSA_KEY_SIZE,
|
11
|
+
SUPPORTED_EC_CURVES,
|
12
|
+
SUPPORTED_KEY_TYPES,
|
13
|
+
SUPPORTED_RSA_SIZES,
|
14
|
+
)
|
15
|
+
from provide.foundation.crypto.signatures import generate_ed25519_keypair
|
16
|
+
|
17
|
+
|
18
|
+
class KeyPair(Protocol):
|
19
|
+
"""Protocol for key pairs."""
|
20
|
+
|
21
|
+
def public_key(self) -> Any:
|
22
|
+
"""Get public key."""
|
23
|
+
...
|
24
|
+
|
25
|
+
|
26
|
+
# Type aliases for different key types
|
27
|
+
RSAKeyPair = tuple[rsa.RSAPublicKey, rsa.RSAPrivateKey]
|
28
|
+
ECKeyPair = tuple[ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey]
|
29
|
+
Ed25519KeyPair = tuple[bytes, bytes]
|
30
|
+
|
31
|
+
KeyPairType = Union[RSAKeyPair, ECKeyPair, Ed25519KeyPair]
|
32
|
+
|
33
|
+
|
34
|
+
def generate_rsa_keypair(key_size: int = DEFAULT_RSA_KEY_SIZE) -> RSAKeyPair:
|
35
|
+
"""Generate RSA key pair.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
key_size: RSA key size in bits (2048, 3072, or 4096)
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
tuple: (public_key, private_key)
|
42
|
+
|
43
|
+
Raises:
|
44
|
+
ValueError: If key size is not supported
|
45
|
+
"""
|
46
|
+
if key_size not in SUPPORTED_RSA_SIZES:
|
47
|
+
raise ValueError(
|
48
|
+
f"Unsupported RSA key size: {key_size}. "
|
49
|
+
f"Supported sizes: {sorted(SUPPORTED_RSA_SIZES)}"
|
50
|
+
)
|
51
|
+
|
52
|
+
logger.debug(f"🔑 Generating RSA key pair (size: {key_size})")
|
53
|
+
|
54
|
+
private_key = rsa.generate_private_key(
|
55
|
+
public_exponent=65537,
|
56
|
+
key_size=key_size,
|
57
|
+
)
|
58
|
+
public_key = private_key.public_key()
|
59
|
+
|
60
|
+
logger.debug(f"✅ Generated RSA key pair ({key_size} bits)")
|
61
|
+
return public_key, private_key
|
62
|
+
|
63
|
+
|
64
|
+
def generate_ec_keypair(curve: str = DEFAULT_ECDSA_CURVE) -> ECKeyPair:
|
65
|
+
"""Generate ECDSA key pair.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
curve: Elliptic curve name (secp256r1, secp384r1, or secp521r1)
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
tuple: (public_key, private_key)
|
72
|
+
|
73
|
+
Raises:
|
74
|
+
ValueError: If curve is not supported
|
75
|
+
AttributeError: If curve name is invalid
|
76
|
+
"""
|
77
|
+
if curve not in SUPPORTED_EC_CURVES:
|
78
|
+
raise ValueError(
|
79
|
+
f"Unsupported EC curve: {curve}. "
|
80
|
+
f"Supported curves: {sorted(SUPPORTED_EC_CURVES)}"
|
81
|
+
)
|
82
|
+
|
83
|
+
logger.debug(f"🔑 Generating ECDSA key pair (curve: {curve})")
|
84
|
+
|
85
|
+
# Map curve name to cryptography curve object
|
86
|
+
curve_obj = getattr(ec, curve.upper())()
|
87
|
+
|
88
|
+
private_key = ec.generate_private_key(curve_obj)
|
89
|
+
public_key = private_key.public_key()
|
90
|
+
|
91
|
+
logger.debug(f"✅ Generated ECDSA key pair ({curve})")
|
92
|
+
return public_key, private_key
|
93
|
+
|
94
|
+
|
95
|
+
def generate_keypair(
|
96
|
+
key_type: str,
|
97
|
+
key_size: int | None = None,
|
98
|
+
curve: str | None = None,
|
99
|
+
) -> KeyPairType:
|
100
|
+
"""Generate a key pair of the specified type.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
key_type: Type of key to generate ("rsa", "ecdsa", or "ed25519")
|
104
|
+
key_size: Key size in bits (required for RSA)
|
105
|
+
curve: Curve name (required for ECDSA)
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
KeyPair: Generated key pair
|
109
|
+
|
110
|
+
Raises:
|
111
|
+
ValueError: If key_type is not supported or required params missing
|
112
|
+
"""
|
113
|
+
key_type_lower = key_type.lower()
|
114
|
+
|
115
|
+
if key_type_lower not in SUPPORTED_KEY_TYPES:
|
116
|
+
raise ValueError(
|
117
|
+
f"Unsupported key type: {key_type}. "
|
118
|
+
f"Supported types: {sorted(SUPPORTED_KEY_TYPES)}"
|
119
|
+
)
|
120
|
+
|
121
|
+
match key_type_lower:
|
122
|
+
case "rsa":
|
123
|
+
if key_size is None:
|
124
|
+
key_size = DEFAULT_RSA_KEY_SIZE
|
125
|
+
logger.debug(f"🔑 Using default RSA key size: {key_size}")
|
126
|
+
return generate_rsa_keypair(key_size)
|
127
|
+
|
128
|
+
case "ecdsa":
|
129
|
+
if curve is None:
|
130
|
+
curve = DEFAULT_ECDSA_CURVE
|
131
|
+
logger.debug(f"🔑 Using default ECDSA curve: {curve}")
|
132
|
+
return generate_ec_keypair(curve)
|
133
|
+
|
134
|
+
case "ed25519":
|
135
|
+
# Ed25519 has fixed parameters
|
136
|
+
if key_size is not None or curve is not None:
|
137
|
+
logger.warning(
|
138
|
+
"🔑 Ed25519 has fixed parameters - ignoring key_size/curve"
|
139
|
+
)
|
140
|
+
return generate_ed25519_keypair()
|
141
|
+
|
142
|
+
case _:
|
143
|
+
# This shouldn't happen due to the check above
|
144
|
+
raise ValueError(f"Internal error: unhandled key type {key_type}")
|
145
|
+
|
146
|
+
|
147
|
+
# Convenience functions for specific use cases
|
148
|
+
def generate_signing_keypair() -> Ed25519KeyPair:
|
149
|
+
"""Generate Ed25519 keypair for digital signatures.
|
150
|
+
|
151
|
+
This is the recommended choice for new digital signature use cases.
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
tuple: (private_key_bytes, public_key_bytes)
|
155
|
+
"""
|
156
|
+
return generate_ed25519_keypair()
|
157
|
+
|
158
|
+
|
159
|
+
def generate_tls_keypair(
|
160
|
+
key_type: str = "ecdsa",
|
161
|
+
curve: str = DEFAULT_ECDSA_CURVE,
|
162
|
+
) -> ECKeyPair | RSAKeyPair:
|
163
|
+
"""Generate keypair suitable for TLS/certificates.
|
164
|
+
|
165
|
+
Args:
|
166
|
+
key_type: Either "ecdsa" (recommended) or "rsa"
|
167
|
+
curve: ECDSA curve (only used if key_type is "ecdsa")
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
KeyPair: Generated key pair
|
171
|
+
"""
|
172
|
+
if key_type == "ecdsa":
|
173
|
+
return generate_ec_keypair(curve)
|
174
|
+
elif key_type == "rsa":
|
175
|
+
return generate_rsa_keypair(DEFAULT_RSA_KEY_SIZE)
|
176
|
+
else:
|
177
|
+
raise ValueError(f"TLS key type must be 'ecdsa' or 'rsa', got {key_type}")
|
178
|
+
|
179
|
+
|
180
|
+
# Legacy compatibility functions (for smooth migration)
|
181
|
+
def generate_key_pair() -> Ed25519KeyPair:
|
182
|
+
"""Legacy compatibility function.
|
183
|
+
|
184
|
+
This maintains compatibility with existing flavorpack code.
|
185
|
+
New code should use generate_signing_keypair() or generate_keypair().
|
186
|
+
"""
|
187
|
+
logger.debug("🔑 Using legacy generate_key_pair() function")
|
188
|
+
return generate_ed25519_keypair()
|
@@ -0,0 +1,144 @@
|
|
1
|
+
"""Digital signature operations using Ed25519."""
|
2
|
+
|
3
|
+
try:
|
4
|
+
from cryptography.exceptions import InvalidSignature
|
5
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
6
|
+
|
7
|
+
_HAS_CRYPTO = True
|
8
|
+
except ImportError:
|
9
|
+
InvalidSignature = None
|
10
|
+
ed25519 = None
|
11
|
+
_HAS_CRYPTO = False
|
12
|
+
|
13
|
+
from provide.foundation import logger
|
14
|
+
from provide.foundation.crypto.constants import (
|
15
|
+
ED25519_PRIVATE_KEY_SIZE,
|
16
|
+
ED25519_PUBLIC_KEY_SIZE,
|
17
|
+
ED25519_SIGNATURE_SIZE,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
def _require_crypto() -> None:
|
22
|
+
"""Ensure cryptography is available."""
|
23
|
+
if not _HAS_CRYPTO:
|
24
|
+
raise ImportError(
|
25
|
+
"Cryptography features require optional dependencies. "
|
26
|
+
"Install with: pip install 'provide-foundation[crypto]'"
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
def generate_ed25519_keypair() -> tuple[bytes, bytes]:
|
31
|
+
"""Generate Ed25519 key pair for digital signatures.
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
tuple: (private_key_bytes, public_key_bytes)
|
35
|
+
- private_key_bytes: 32-byte Ed25519 private key seed
|
36
|
+
- public_key_bytes: 32-byte Ed25519 public key
|
37
|
+
"""
|
38
|
+
_require_crypto()
|
39
|
+
logger.debug("🔐 Generating Ed25519 key pair")
|
40
|
+
|
41
|
+
# Generate a new Ed25519 private key
|
42
|
+
private_key = ed25519.Ed25519PrivateKey.generate()
|
43
|
+
|
44
|
+
# Get the raw bytes for compatibility with other implementations
|
45
|
+
private_key_bytes = private_key.private_bytes_raw()
|
46
|
+
public_key_bytes = private_key.public_key().public_bytes_raw()
|
47
|
+
|
48
|
+
# Validate key sizes
|
49
|
+
assert len(private_key_bytes) == ED25519_PRIVATE_KEY_SIZE
|
50
|
+
assert len(public_key_bytes) == ED25519_PUBLIC_KEY_SIZE
|
51
|
+
|
52
|
+
logger.debug(
|
53
|
+
f"✅ Generated Ed25519 key pair (public: {len(public_key_bytes)} bytes)"
|
54
|
+
)
|
55
|
+
return private_key_bytes, public_key_bytes
|
56
|
+
|
57
|
+
|
58
|
+
def sign_data(data: bytes, private_key: bytes) -> bytes:
|
59
|
+
"""Sign data with Ed25519 private key.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
data: The data to sign
|
63
|
+
private_key: 32-byte Ed25519 private key seed
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
bytes: 64-byte Ed25519 signature
|
67
|
+
|
68
|
+
Raises:
|
69
|
+
ValueError: If private key is wrong size
|
70
|
+
"""
|
71
|
+
_require_crypto()
|
72
|
+
if len(private_key) != ED25519_PRIVATE_KEY_SIZE:
|
73
|
+
raise ValueError(
|
74
|
+
f"Private key must be {ED25519_PRIVATE_KEY_SIZE} bytes, "
|
75
|
+
f"got {len(private_key)}"
|
76
|
+
)
|
77
|
+
|
78
|
+
logger.debug(f"🔏 Signing {len(data)} bytes of data with Ed25519")
|
79
|
+
|
80
|
+
# Reconstruct the private key from the seed bytes
|
81
|
+
private_key_obj = ed25519.Ed25519PrivateKey.from_private_bytes(private_key)
|
82
|
+
|
83
|
+
# Sign the data
|
84
|
+
signature = private_key_obj.sign(data)
|
85
|
+
|
86
|
+
# Validate signature size
|
87
|
+
assert len(signature) == ED25519_SIGNATURE_SIZE
|
88
|
+
|
89
|
+
logger.debug(f"✅ Created Ed25519 signature ({len(signature)} bytes)")
|
90
|
+
return signature
|
91
|
+
|
92
|
+
|
93
|
+
def verify_signature(data: bytes, signature: bytes, public_key: bytes) -> bool:
|
94
|
+
"""Verify Ed25519 signature.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
data: The data that was signed
|
98
|
+
signature: 64-byte Ed25519 signature
|
99
|
+
public_key: 32-byte Ed25519 public key
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
bool: True if signature is valid, False otherwise
|
103
|
+
"""
|
104
|
+
_require_crypto()
|
105
|
+
if len(signature) != ED25519_SIGNATURE_SIZE:
|
106
|
+
logger.warning(
|
107
|
+
f"❌ Invalid signature size: expected {ED25519_SIGNATURE_SIZE}, "
|
108
|
+
f"got {len(signature)}"
|
109
|
+
)
|
110
|
+
return False
|
111
|
+
|
112
|
+
if len(public_key) != ED25519_PUBLIC_KEY_SIZE:
|
113
|
+
logger.warning(
|
114
|
+
f"❌ Invalid public key size: expected {ED25519_PUBLIC_KEY_SIZE}, "
|
115
|
+
f"got {len(public_key)}"
|
116
|
+
)
|
117
|
+
return False
|
118
|
+
|
119
|
+
logger.debug(f"🔍 Verifying Ed25519 signature for {len(data)} bytes of data")
|
120
|
+
|
121
|
+
try:
|
122
|
+
public_key_obj = ed25519.Ed25519PublicKey.from_public_bytes(public_key)
|
123
|
+
public_key_obj.verify(signature, data)
|
124
|
+
logger.debug("✅ Ed25519 signature verification successful")
|
125
|
+
return True
|
126
|
+
except InvalidSignature:
|
127
|
+
logger.debug("❌ Invalid Ed25519 signature")
|
128
|
+
return False
|
129
|
+
except Exception as e:
|
130
|
+
logger.error(f"❌ Ed25519 signature verification error: {e}")
|
131
|
+
return False
|
132
|
+
|
133
|
+
|
134
|
+
# Convenience function for generating signing keypairs
|
135
|
+
def generate_signing_keypair() -> tuple[bytes, bytes]:
|
136
|
+
"""Generate Ed25519 keypair for signing (convenience function).
|
137
|
+
|
138
|
+
This is an alias for generate_ed25519_keypair() that makes intent clear.
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
tuple: (private_key_bytes, public_key_bytes)
|
142
|
+
"""
|
143
|
+
_require_crypto()
|
144
|
+
return generate_ed25519_keypair()
|
@@ -0,0 +1,164 @@
|
|
1
|
+
"""Utility functions for hashing and cryptographic operations."""
|
2
|
+
|
3
|
+
import hashlib
|
4
|
+
|
5
|
+
|
6
|
+
def quick_hash(data: bytes) -> int:
|
7
|
+
"""Generate a quick non-cryptographic hash for lookups.
|
8
|
+
|
9
|
+
This uses Python's built-in hash function which is fast but not
|
10
|
+
cryptographically secure. Use only for hash tables and caching.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
data: Data to hash
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
32-bit hash value
|
17
|
+
"""
|
18
|
+
# Use Python's built-in hash for speed, mask to 32 bits
|
19
|
+
return hash(data) & 0xFFFFFFFF
|
20
|
+
|
21
|
+
|
22
|
+
def hash_name(name: str) -> int:
|
23
|
+
"""Generate a 64-bit hash of a string for fast lookup.
|
24
|
+
|
25
|
+
This is useful for creating numeric identifiers from strings.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
name: String to hash
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
64-bit integer hash
|
32
|
+
"""
|
33
|
+
# Use first 8 bytes of SHA256 for good distribution
|
34
|
+
hash_bytes = hashlib.sha256(name.encode("utf-8")).digest()[:8]
|
35
|
+
return int.from_bytes(hash_bytes, byteorder="little")
|
36
|
+
|
37
|
+
|
38
|
+
def compare_hash(hash1: str, hash2: str) -> bool:
|
39
|
+
"""Compare two hash values in a case-insensitive manner.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
hash1: First hash value
|
43
|
+
hash2: Second hash value
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
True if hashes match (case-insensitive)
|
47
|
+
"""
|
48
|
+
return hash1.lower() == hash2.lower()
|
49
|
+
|
50
|
+
|
51
|
+
def format_hash(
|
52
|
+
hash_value: str,
|
53
|
+
group_size: int = 8,
|
54
|
+
groups: int = 0,
|
55
|
+
separator: str = " ",
|
56
|
+
) -> str:
|
57
|
+
"""Format a hash value for display.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
hash_value: Hash value to format
|
61
|
+
group_size: Number of characters per group
|
62
|
+
groups: Number of groups to show (0 for all)
|
63
|
+
separator: Separator between groups
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
Formatted hash string
|
67
|
+
|
68
|
+
Examples:
|
69
|
+
>>> format_hash("abc123def456", group_size=4, separator="-")
|
70
|
+
"abc1-23de-f456"
|
71
|
+
>>> format_hash("abc123def456", group_size=4, groups=2)
|
72
|
+
"abc1 23de"
|
73
|
+
"""
|
74
|
+
if group_size <= 0:
|
75
|
+
return hash_value
|
76
|
+
|
77
|
+
formatted_parts = []
|
78
|
+
for i in range(0, len(hash_value), group_size):
|
79
|
+
formatted_parts.append(hash_value[i : i + group_size])
|
80
|
+
if groups > 0 and len(formatted_parts) >= groups:
|
81
|
+
break
|
82
|
+
|
83
|
+
return separator.join(formatted_parts)
|
84
|
+
|
85
|
+
|
86
|
+
def truncate_hash(hash_value: str, length: int = 16, suffix: str = "...") -> str:
|
87
|
+
"""Truncate a hash for display purposes.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
hash_value: Hash value to truncate
|
91
|
+
length: Number of characters to keep
|
92
|
+
suffix: Suffix to append
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
Truncated hash string
|
96
|
+
|
97
|
+
Examples:
|
98
|
+
>>> truncate_hash("abc123def456789", length=8)
|
99
|
+
"abc123de..."
|
100
|
+
"""
|
101
|
+
if len(hash_value) <= length:
|
102
|
+
return hash_value
|
103
|
+
return hash_value[:length] + suffix
|
104
|
+
|
105
|
+
|
106
|
+
def hash_to_int(hash_value: str) -> int:
|
107
|
+
"""Convert a hex hash string to an integer.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
hash_value: Hex hash string
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
Integer representation of the hash
|
114
|
+
"""
|
115
|
+
return int(hash_value, 16)
|
116
|
+
|
117
|
+
|
118
|
+
def int_to_hash(value: int, length: int | None = None) -> str:
|
119
|
+
"""Convert an integer to a hex hash string.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
value: Integer value
|
123
|
+
length: Desired length (will pad with zeros)
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
Hex string representation
|
127
|
+
"""
|
128
|
+
hex_str = format(value, "x")
|
129
|
+
if length and len(hex_str) < length:
|
130
|
+
hex_str = hex_str.zfill(length)
|
131
|
+
return hex_str
|
132
|
+
|
133
|
+
|
134
|
+
def is_valid_hash(hash_value: str, algorithm: str | None = None) -> bool:
|
135
|
+
"""Check if a string is a valid hash value.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
hash_value: String to check
|
139
|
+
algorithm: Optional algorithm to validate length against
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
True if string appears to be a valid hash
|
143
|
+
"""
|
144
|
+
# Check if it's a valid hex string
|
145
|
+
try:
|
146
|
+
int(hash_value, 16)
|
147
|
+
except ValueError:
|
148
|
+
return False
|
149
|
+
|
150
|
+
# If algorithm specified, check length
|
151
|
+
if algorithm:
|
152
|
+
from provide.foundation.crypto.algorithms import (
|
153
|
+
get_digest_size,
|
154
|
+
validate_algorithm,
|
155
|
+
)
|
156
|
+
|
157
|
+
try:
|
158
|
+
validate_algorithm(algorithm)
|
159
|
+
expected_length = get_digest_size(algorithm) * 2 # hex is 2 chars per byte
|
160
|
+
return len(hash_value) == expected_length
|
161
|
+
except Exception:
|
162
|
+
return False
|
163
|
+
|
164
|
+
return True
|
@@ -0,0 +1,96 @@
|
|
1
|
+
"""
|
2
|
+
Foundation error handling system.
|
3
|
+
|
4
|
+
Provides a comprehensive exception hierarchy, error context management,
|
5
|
+
and utilities for robust error handling throughout the application.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from provide.foundation.errors.auth import AuthenticationError, AuthorizationError
|
9
|
+
from provide.foundation.errors.base import FoundationError
|
10
|
+
from provide.foundation.errors.config import (
|
11
|
+
ConfigurationError,
|
12
|
+
ConfigValidationError,
|
13
|
+
ValidationError,
|
14
|
+
)
|
15
|
+
from provide.foundation.errors.context import (
|
16
|
+
ErrorCategory,
|
17
|
+
ErrorContext,
|
18
|
+
ErrorSeverity,
|
19
|
+
capture_error_context,
|
20
|
+
)
|
21
|
+
from provide.foundation.errors.decorators import (
|
22
|
+
fallback_on_error,
|
23
|
+
retry_on_error,
|
24
|
+
suppress_and_log,
|
25
|
+
with_error_handling,
|
26
|
+
)
|
27
|
+
from provide.foundation.errors.handlers import (
|
28
|
+
ErrorHandler,
|
29
|
+
error_boundary,
|
30
|
+
handle_error,
|
31
|
+
transactional,
|
32
|
+
)
|
33
|
+
from provide.foundation.errors.integration import (
|
34
|
+
IntegrationError,
|
35
|
+
NetworkError,
|
36
|
+
TimeoutError,
|
37
|
+
)
|
38
|
+
from provide.foundation.errors.process import (
|
39
|
+
CommandNotFoundError,
|
40
|
+
ProcessError,
|
41
|
+
ProcessTimeoutError,
|
42
|
+
)
|
43
|
+
from provide.foundation.errors.resources import (
|
44
|
+
AlreadyExistsError,
|
45
|
+
NotFoundError,
|
46
|
+
ResourceError,
|
47
|
+
)
|
48
|
+
from provide.foundation.errors.runtime import ConcurrencyError, RuntimeError, StateError
|
49
|
+
from provide.foundation.errors.safe_decorators import log_only_error_context
|
50
|
+
from provide.foundation.errors.types import (
|
51
|
+
ErrorCode,
|
52
|
+
ErrorMetadata,
|
53
|
+
RetryPolicy,
|
54
|
+
)
|
55
|
+
|
56
|
+
__all__ = [
|
57
|
+
"AlreadyExistsError",
|
58
|
+
"AuthenticationError",
|
59
|
+
"AuthorizationError",
|
60
|
+
"CommandNotFoundError",
|
61
|
+
"ConcurrencyError",
|
62
|
+
"ConfigurationError",
|
63
|
+
"ConfigValidationError",
|
64
|
+
"ErrorCategory",
|
65
|
+
# Types
|
66
|
+
"ErrorCode",
|
67
|
+
# Context
|
68
|
+
"ErrorContext",
|
69
|
+
"ErrorHandler",
|
70
|
+
"ErrorMetadata",
|
71
|
+
"ErrorSeverity",
|
72
|
+
# Base exceptions
|
73
|
+
"FoundationError",
|
74
|
+
"IntegrationError",
|
75
|
+
"NetworkError",
|
76
|
+
"NotFoundError",
|
77
|
+
"ProcessError",
|
78
|
+
"ProcessTimeoutError",
|
79
|
+
"ResourceError",
|
80
|
+
"RetryPolicy",
|
81
|
+
"RuntimeError",
|
82
|
+
"StateError",
|
83
|
+
"TimeoutError",
|
84
|
+
"ValidationError",
|
85
|
+
"capture_error_context",
|
86
|
+
# Handlers
|
87
|
+
"error_boundary",
|
88
|
+
"fallback_on_error",
|
89
|
+
"handle_error",
|
90
|
+
"log_only_error_context",
|
91
|
+
"retry_on_error",
|
92
|
+
"suppress_and_log",
|
93
|
+
"transactional",
|
94
|
+
# Decorators
|
95
|
+
"with_error_handling",
|
96
|
+
]
|
@@ -0,0 +1,73 @@
|
|
1
|
+
"""Authentication and authorization exceptions."""
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from provide.foundation.errors.base import FoundationError
|
6
|
+
|
7
|
+
|
8
|
+
class AuthenticationError(FoundationError):
|
9
|
+
"""Raised when authentication fails.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
message: Error message describing the authentication failure.
|
13
|
+
auth_method: Optional authentication method used.
|
14
|
+
realm: Optional authentication realm.
|
15
|
+
**kwargs: Additional context passed to FoundationError.
|
16
|
+
|
17
|
+
Examples:
|
18
|
+
>>> raise AuthenticationError("Invalid credentials")
|
19
|
+
>>> raise AuthenticationError("Token expired", auth_method="jwt")
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
message: str,
|
25
|
+
*,
|
26
|
+
auth_method: str | None = None,
|
27
|
+
realm: str | None = None,
|
28
|
+
**kwargs: Any,
|
29
|
+
) -> None:
|
30
|
+
if auth_method:
|
31
|
+
kwargs.setdefault("context", {})["auth.method"] = auth_method
|
32
|
+
if realm:
|
33
|
+
kwargs.setdefault("context", {})["auth.realm"] = realm
|
34
|
+
super().__init__(message, **kwargs)
|
35
|
+
|
36
|
+
def _default_code(self) -> str:
|
37
|
+
return "AUTH_ERROR"
|
38
|
+
|
39
|
+
|
40
|
+
class AuthorizationError(FoundationError):
|
41
|
+
"""Raised when authorization fails.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
message: Error message describing the authorization failure.
|
45
|
+
required_permission: Optional required permission.
|
46
|
+
resource: Optional resource being accessed.
|
47
|
+
actor: Optional actor (user/service) attempting access.
|
48
|
+
**kwargs: Additional context passed to FoundationError.
|
49
|
+
|
50
|
+
Examples:
|
51
|
+
>>> raise AuthorizationError("Access denied")
|
52
|
+
>>> raise AuthorizationError("Insufficient permissions", required_permission="admin")
|
53
|
+
"""
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self,
|
57
|
+
message: str,
|
58
|
+
*,
|
59
|
+
required_permission: str | None = None,
|
60
|
+
resource: str | None = None,
|
61
|
+
actor: str | None = None,
|
62
|
+
**kwargs: Any,
|
63
|
+
) -> None:
|
64
|
+
if required_permission:
|
65
|
+
kwargs.setdefault("context", {})["authz.permission"] = required_permission
|
66
|
+
if resource:
|
67
|
+
kwargs.setdefault("context", {})["authz.resource"] = resource
|
68
|
+
if actor:
|
69
|
+
kwargs.setdefault("context", {})["authz.actor"] = actor
|
70
|
+
super().__init__(message, **kwargs)
|
71
|
+
|
72
|
+
def _default_code(self) -> str:
|
73
|
+
return "AUTHZ_ERROR"
|