bizteamai-smcp 1.13.1__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.
- bizteamai_smcp-1.13.1.dist-info/METADATA +117 -0
- bizteamai_smcp-1.13.1.dist-info/RECORD +21 -0
- bizteamai_smcp-1.13.1.dist-info/WHEEL +5 -0
- bizteamai_smcp-1.13.1.dist-info/entry_points.txt +3 -0
- bizteamai_smcp-1.13.1.dist-info/top_level.txt +1 -0
- smcp/__init__.py +29 -0
- smcp/allowlist.py +169 -0
- smcp/app_wrapper.py +216 -0
- smcp/cli/__init__.py +3 -0
- smcp/cli/approve.py +261 -0
- smcp/cli/gen_key.py +73 -0
- smcp/cli/mkcert.py +327 -0
- smcp/cli/revoke.py +73 -0
- smcp/confirm.py +262 -0
- smcp/cpu.py +67 -0
- smcp/decorators.py +97 -0
- smcp/enforce.py +100 -0
- smcp/filters.py +176 -0
- smcp/license.py +113 -0
- smcp/logchain.py +270 -0
- smcp/tls.py +160 -0
smcp/tls.py
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
"""
|
2
|
+
TLS context factory and certificate helpers for mutual TLS authentication.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import ssl
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Dict, Optional
|
8
|
+
|
9
|
+
|
10
|
+
class TLSContextFactory:
|
11
|
+
"""Factory for creating SSL contexts for mutual TLS authentication."""
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
def server_context(cls, cfg: Dict[str, str]) -> ssl.SSLContext:
|
15
|
+
"""
|
16
|
+
Create an SSL context for a server with mutual TLS.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
cfg: Configuration dictionary containing paths to certificates
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
Configured SSL context for server-side mutual TLS
|
23
|
+
|
24
|
+
Raises:
|
25
|
+
FileNotFoundError: If certificate files don't exist
|
26
|
+
ssl.SSLError: If certificate loading fails
|
27
|
+
"""
|
28
|
+
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
29
|
+
|
30
|
+
# Load server certificate and key
|
31
|
+
cert_path = cfg["cert_path"]
|
32
|
+
key_path = cfg["key_path"]
|
33
|
+
context.load_cert_chain(cert_path, key_path)
|
34
|
+
|
35
|
+
# Load CA certificate for client verification
|
36
|
+
ca_path = cfg["ca_path"]
|
37
|
+
context.load_verify_locations(ca_path)
|
38
|
+
|
39
|
+
# Require client certificates
|
40
|
+
context.verify_mode = ssl.CERT_REQUIRED
|
41
|
+
context.check_hostname = False # We'll validate through CA
|
42
|
+
|
43
|
+
return context
|
44
|
+
|
45
|
+
@classmethod
|
46
|
+
def client_context(cls, cfg: Dict[str, str]) -> ssl.SSLContext:
|
47
|
+
"""
|
48
|
+
Create an SSL context for a client with mutual TLS.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
cfg: Configuration dictionary containing paths to certificates
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
Configured SSL context for client-side mutual TLS
|
55
|
+
"""
|
56
|
+
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
57
|
+
|
58
|
+
# Load client certificate and key
|
59
|
+
cert_path = cfg["cert_path"]
|
60
|
+
key_path = cfg["key_path"]
|
61
|
+
context.load_cert_chain(cert_path, key_path)
|
62
|
+
|
63
|
+
# Load CA certificate for server verification
|
64
|
+
ca_path = cfg["ca_path"]
|
65
|
+
context.load_verify_locations(ca_path)
|
66
|
+
|
67
|
+
# Verify server certificates
|
68
|
+
context.verify_mode = ssl.CERT_REQUIRED
|
69
|
+
context.check_hostname = True
|
70
|
+
|
71
|
+
return context
|
72
|
+
|
73
|
+
|
74
|
+
def tls_configured(cfg: Dict[str, str]) -> bool:
|
75
|
+
"""
|
76
|
+
Check if TLS is properly configured.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
cfg: Configuration dictionary
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
True if all required TLS configuration is present and files exist
|
83
|
+
"""
|
84
|
+
required_keys = ["ca_path", "cert_path", "key_path"]
|
85
|
+
|
86
|
+
# Check if all required keys are present
|
87
|
+
if not all(key in cfg for key in required_keys):
|
88
|
+
return False
|
89
|
+
|
90
|
+
# Check if all certificate files exist
|
91
|
+
for key in required_keys:
|
92
|
+
path = Path(cfg[key])
|
93
|
+
if not path.exists() or not path.is_file():
|
94
|
+
return False
|
95
|
+
|
96
|
+
return True
|
97
|
+
|
98
|
+
|
99
|
+
def validate_cert_paths(cfg: Dict[str, str]) -> None:
|
100
|
+
"""
|
101
|
+
Validate that certificate paths exist and are readable.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
cfg: Configuration dictionary containing certificate paths
|
105
|
+
|
106
|
+
Raises:
|
107
|
+
FileNotFoundError: If any certificate file is missing
|
108
|
+
PermissionError: If any certificate file is not readable
|
109
|
+
"""
|
110
|
+
required_keys = ["ca_path", "cert_path", "key_path"]
|
111
|
+
|
112
|
+
for key in required_keys:
|
113
|
+
if key not in cfg:
|
114
|
+
raise FileNotFoundError(f"Missing required TLS configuration: {key}")
|
115
|
+
|
116
|
+
path = Path(cfg[key])
|
117
|
+
if not path.exists():
|
118
|
+
raise FileNotFoundError(f"Certificate file not found: {path}")
|
119
|
+
|
120
|
+
if not path.is_file():
|
121
|
+
raise FileNotFoundError(f"Certificate path is not a file: {path}")
|
122
|
+
|
123
|
+
# Test readability
|
124
|
+
try:
|
125
|
+
with open(path, 'rb') as f:
|
126
|
+
f.read(1)
|
127
|
+
except PermissionError:
|
128
|
+
raise PermissionError(f"Cannot read certificate file: {path}")
|
129
|
+
|
130
|
+
|
131
|
+
def get_cert_info(cert_path: str) -> Dict[str, str]:
|
132
|
+
"""
|
133
|
+
Extract basic information from a certificate file.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
cert_path: Path to the certificate file
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
Dictionary containing certificate information
|
140
|
+
"""
|
141
|
+
try:
|
142
|
+
import cryptography.x509
|
143
|
+
from cryptography.hazmat.backends import default_backend
|
144
|
+
|
145
|
+
with open(cert_path, 'rb') as f:
|
146
|
+
cert_data = f.read()
|
147
|
+
|
148
|
+
cert = cryptography.x509.load_pem_x509_certificate(cert_data, default_backend())
|
149
|
+
|
150
|
+
return {
|
151
|
+
"subject": str(cert.subject),
|
152
|
+
"issuer": str(cert.issuer),
|
153
|
+
"serial_number": str(cert.serial_number),
|
154
|
+
"not_valid_before": cert.not_valid_before.isoformat(),
|
155
|
+
"not_valid_after": cert.not_valid_after.isoformat(),
|
156
|
+
}
|
157
|
+
except ImportError:
|
158
|
+
return {"error": "cryptography package not available"}
|
159
|
+
except Exception as e:
|
160
|
+
return {"error": str(e)}
|