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.
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)}