mcp-security-framework 0.1.0__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 (76) hide show
  1. mcp_security_framework/__init__.py +96 -0
  2. mcp_security_framework/cli/__init__.py +18 -0
  3. mcp_security_framework/cli/cert_cli.py +511 -0
  4. mcp_security_framework/cli/security_cli.py +791 -0
  5. mcp_security_framework/constants.py +209 -0
  6. mcp_security_framework/core/__init__.py +61 -0
  7. mcp_security_framework/core/auth_manager.py +1011 -0
  8. mcp_security_framework/core/cert_manager.py +1663 -0
  9. mcp_security_framework/core/permission_manager.py +735 -0
  10. mcp_security_framework/core/rate_limiter.py +602 -0
  11. mcp_security_framework/core/security_manager.py +943 -0
  12. mcp_security_framework/core/ssl_manager.py +735 -0
  13. mcp_security_framework/examples/__init__.py +75 -0
  14. mcp_security_framework/examples/django_example.py +615 -0
  15. mcp_security_framework/examples/fastapi_example.py +472 -0
  16. mcp_security_framework/examples/flask_example.py +506 -0
  17. mcp_security_framework/examples/gateway_example.py +803 -0
  18. mcp_security_framework/examples/microservice_example.py +690 -0
  19. mcp_security_framework/examples/standalone_example.py +576 -0
  20. mcp_security_framework/middleware/__init__.py +250 -0
  21. mcp_security_framework/middleware/auth_middleware.py +292 -0
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
  23. mcp_security_framework/middleware/fastapi_middleware.py +757 -0
  24. mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
  25. mcp_security_framework/middleware/flask_middleware.py +591 -0
  26. mcp_security_framework/middleware/mtls_middleware.py +439 -0
  27. mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
  28. mcp_security_framework/middleware/security_middleware.py +507 -0
  29. mcp_security_framework/schemas/__init__.py +109 -0
  30. mcp_security_framework/schemas/config.py +694 -0
  31. mcp_security_framework/schemas/models.py +709 -0
  32. mcp_security_framework/schemas/responses.py +686 -0
  33. mcp_security_framework/tests/__init__.py +0 -0
  34. mcp_security_framework/utils/__init__.py +121 -0
  35. mcp_security_framework/utils/cert_utils.py +525 -0
  36. mcp_security_framework/utils/crypto_utils.py +475 -0
  37. mcp_security_framework/utils/validation_utils.py +571 -0
  38. mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
  39. mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
  40. mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
  41. mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
  42. mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
  43. tests/__init__.py +0 -0
  44. tests/test_cli/__init__.py +0 -0
  45. tests/test_cli/test_cert_cli.py +379 -0
  46. tests/test_cli/test_security_cli.py +657 -0
  47. tests/test_core/__init__.py +0 -0
  48. tests/test_core/test_auth_manager.py +582 -0
  49. tests/test_core/test_cert_manager.py +795 -0
  50. tests/test_core/test_permission_manager.py +395 -0
  51. tests/test_core/test_rate_limiter.py +626 -0
  52. tests/test_core/test_security_manager.py +841 -0
  53. tests/test_core/test_ssl_manager.py +532 -0
  54. tests/test_examples/__init__.py +8 -0
  55. tests/test_examples/test_fastapi_example.py +264 -0
  56. tests/test_examples/test_flask_example.py +238 -0
  57. tests/test_examples/test_standalone_example.py +292 -0
  58. tests/test_integration/__init__.py +0 -0
  59. tests/test_integration/test_auth_flow.py +502 -0
  60. tests/test_integration/test_certificate_flow.py +527 -0
  61. tests/test_integration/test_fastapi_integration.py +341 -0
  62. tests/test_integration/test_flask_integration.py +398 -0
  63. tests/test_integration/test_standalone_integration.py +493 -0
  64. tests/test_middleware/__init__.py +0 -0
  65. tests/test_middleware/test_fastapi_middleware.py +523 -0
  66. tests/test_middleware/test_flask_middleware.py +582 -0
  67. tests/test_middleware/test_security_middleware.py +493 -0
  68. tests/test_schemas/__init__.py +0 -0
  69. tests/test_schemas/test_config.py +811 -0
  70. tests/test_schemas/test_models.py +879 -0
  71. tests/test_schemas/test_responses.py +1054 -0
  72. tests/test_schemas/test_serialization.py +493 -0
  73. tests/test_utils/__init__.py +0 -0
  74. tests/test_utils/test_cert_utils.py +510 -0
  75. tests/test_utils/test_crypto_utils.py +603 -0
  76. tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,475 @@
1
+ """
2
+ Cryptographic Utilities Module
3
+
4
+ This module provides comprehensive cryptographic utilities for the
5
+ MCP Security Framework. It includes functions for hashing, key generation,
6
+ signing, verification, and JWT operations.
7
+
8
+ Key Features:
9
+ - Secure hash functions (SHA-256, SHA-512)
10
+ - Key generation utilities
11
+ - Digital signature creation and verification
12
+ - JWT token creation and validation
13
+ - Password hashing and verification
14
+ - Random data generation
15
+
16
+ Functions:
17
+ hash_password: Hash password with salt
18
+ verify_password: Verify password against hash
19
+ generate_random_bytes: Generate random bytes
20
+ generate_api_key: Generate secure API key
21
+ create_jwt_token: Create JWT token
22
+ verify_jwt_token: Verify JWT token
23
+ hash_data: Hash data with specified algorithm
24
+ sign_data: Sign data with private key
25
+ verify_signature: Verify signature with public key
26
+
27
+ Author: MCP Security Team
28
+ Version: 1.0.0
29
+ License: MIT
30
+ """
31
+
32
+ import base64
33
+ import hashlib
34
+ import hmac
35
+ import secrets
36
+ from datetime import datetime, timedelta, timezone
37
+ from typing import Dict, Optional, Union
38
+
39
+ import jwt
40
+ from cryptography.hazmat.primitives import hashes, serialization
41
+ from cryptography.hazmat.primitives.asymmetric import padding, rsa
42
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
43
+
44
+
45
+ class CryptoError(Exception):
46
+ """Raised when cryptographic operations fail."""
47
+
48
+ def __init__(self, message: str, error_code: int = -32001):
49
+ self.message = message
50
+ self.error_code = error_code
51
+ super().__init__(self.message)
52
+
53
+
54
+ def hash_password(password: str, salt: Optional[str] = None) -> Dict[str, str]:
55
+ """
56
+ Hash password with salt using PBKDF2.
57
+
58
+ Args:
59
+ password: Plain text password to hash
60
+ salt: Optional salt. If None, generates random salt
61
+
62
+ Returns:
63
+ Dictionary containing hash and salt
64
+
65
+ Raises:
66
+ CryptoError: If password is empty or hashing fails
67
+ """
68
+ if not password or not password.strip():
69
+ raise CryptoError("Password cannot be empty")
70
+
71
+ try:
72
+ # Generate salt if not provided
73
+ if salt is None:
74
+ salt = secrets.token_hex(16)
75
+
76
+ # Convert password and salt to bytes
77
+ password_bytes = password.encode("utf-8")
78
+ salt_bytes = salt.encode("utf-8")
79
+
80
+ # Use PBKDF2 for key derivation
81
+ kdf = PBKDF2HMAC(
82
+ algorithm=hashes.SHA256(),
83
+ length=32,
84
+ salt=salt_bytes,
85
+ iterations=100000,
86
+ )
87
+
88
+ # Generate hash
89
+ key = kdf.derive(password_bytes)
90
+ hash_hex = key.hex()
91
+
92
+ return {"hash": hash_hex, "salt": salt}
93
+ except Exception as e:
94
+ raise CryptoError(f"Password hashing failed: {str(e)}")
95
+
96
+
97
+ def verify_password(password: str, hash_value: str, salt: str) -> bool:
98
+ """
99
+ Verify password against stored hash and salt.
100
+
101
+ Args:
102
+ password: Plain text password to verify
103
+ hash_value: Stored hash value
104
+ salt: Stored salt value
105
+
106
+ Returns:
107
+ True if password matches, False otherwise
108
+
109
+ Raises:
110
+ CryptoError: If verification fails
111
+ """
112
+ try:
113
+ # Hash the provided password with the stored salt
114
+ result = hash_password(password, salt)
115
+ return result["hash"] == hash_value
116
+ except Exception as e:
117
+ raise CryptoError(f"Password verification failed: {str(e)}")
118
+
119
+
120
+ def generate_random_bytes(length: int = 32) -> bytes:
121
+ """
122
+ Generate cryptographically secure random bytes.
123
+
124
+ Args:
125
+ length: Number of bytes to generate
126
+
127
+ Returns:
128
+ Random bytes
129
+
130
+ Raises:
131
+ CryptoError: If length is invalid or generation fails
132
+ """
133
+ if length <= 0:
134
+ raise CryptoError("Length must be positive")
135
+
136
+ try:
137
+ return secrets.token_bytes(length)
138
+ except Exception as e:
139
+ raise CryptoError(f"Random bytes generation failed: {str(e)}")
140
+
141
+
142
+ def generate_api_key(length: int = 32) -> str:
143
+ """
144
+ Generate secure API key.
145
+
146
+ Args:
147
+ length: Length of API key in bytes
148
+
149
+ Returns:
150
+ Base64 encoded API key
151
+
152
+ Raises:
153
+ CryptoError: If generation fails
154
+ """
155
+ try:
156
+ random_bytes = generate_random_bytes(length)
157
+ return base64.urlsafe_b64encode(random_bytes).decode("utf-8").rstrip("=")
158
+ except Exception as e:
159
+ raise CryptoError(f"API key generation failed: {str(e)}")
160
+
161
+
162
+ def hash_data(data: Union[str, bytes], algorithm: str = "sha256") -> str:
163
+ """
164
+ Hash data with specified algorithm.
165
+
166
+ Args:
167
+ data: Data to hash
168
+ algorithm: Hash algorithm (sha256, sha512, md5)
169
+
170
+ Returns:
171
+ Hexadecimal hash string
172
+
173
+ Raises:
174
+ CryptoError: If algorithm is unsupported or hashing fails
175
+ """
176
+ if isinstance(data, str):
177
+ data = data.encode("utf-8")
178
+
179
+ try:
180
+ if algorithm.lower() == "sha256":
181
+ hash_obj = hashlib.sha256(data)
182
+ elif algorithm.lower() == "sha512":
183
+ hash_obj = hashlib.sha512(data)
184
+ elif algorithm.lower() == "md5":
185
+ hash_obj = hashlib.md5(data)
186
+ else:
187
+ raise CryptoError(f"Unsupported hash algorithm: {algorithm}")
188
+
189
+ return hash_obj.hexdigest()
190
+ except Exception as e:
191
+ raise CryptoError(f"Data hashing failed: {str(e)}")
192
+
193
+
194
+ def create_jwt_token(
195
+ payload: Dict,
196
+ secret: str,
197
+ algorithm: str = "HS256",
198
+ expires_in: Optional[int] = None,
199
+ ) -> str:
200
+ """
201
+ Create JWT token.
202
+
203
+ Args:
204
+ payload: Token payload data
205
+ secret: Secret key for signing
206
+ algorithm: JWT algorithm (HS256, HS512, RS256)
207
+ expires_in: Token expiration time in seconds
208
+
209
+ Returns:
210
+ JWT token string
211
+
212
+ Raises:
213
+ CryptoError: If token creation fails
214
+ """
215
+ try:
216
+ # Add expiration if specified
217
+ if expires_in:
218
+ payload["exp"] = datetime.now(timezone.utc) + timedelta(seconds=expires_in)
219
+
220
+ # Add issued at time
221
+ payload["iat"] = datetime.now(timezone.utc)
222
+
223
+ return jwt.encode(payload, secret, algorithm=algorithm)
224
+ except Exception as e:
225
+ raise CryptoError(f"JWT token creation failed: {str(e)}")
226
+
227
+
228
+ def verify_jwt_token(
229
+ token: str, secret: str, algorithms: Optional[list] = None
230
+ ) -> Dict:
231
+ """
232
+ Verify JWT token.
233
+
234
+ Args:
235
+ token: JWT token to verify
236
+ secret: Secret key for verification
237
+ algorithms: List of allowed algorithms
238
+
239
+ Returns:
240
+ Decoded token payload
241
+
242
+ Raises:
243
+ CryptoError: If token verification fails
244
+ """
245
+ if algorithms is None:
246
+ algorithms = ["HS256", "HS512"]
247
+
248
+ try:
249
+ payload = jwt.decode(token, secret, algorithms=algorithms)
250
+ return payload
251
+ except jwt.ExpiredSignatureError:
252
+ raise CryptoError("JWT token has expired")
253
+ except jwt.InvalidTokenError as e:
254
+ raise CryptoError(f"Invalid JWT token: {str(e)}")
255
+ except Exception as e:
256
+ raise CryptoError(f"JWT token verification failed: {str(e)}")
257
+
258
+
259
+ def generate_rsa_key_pair(key_size: int = 2048) -> Dict[str, str]:
260
+ """
261
+ Generate RSA key pair.
262
+
263
+ Args:
264
+ key_size: Key size in bits (2048, 4096)
265
+
266
+ Returns:
267
+ Dictionary containing private and public keys in PEM format
268
+
269
+ Raises:
270
+ CryptoError: If key generation fails
271
+ """
272
+ if key_size not in [2048, 4096]:
273
+ raise CryptoError("Key size must be 2048 or 4096 bits")
274
+
275
+ try:
276
+ # Generate private key
277
+ private_key = rsa.generate_private_key(public_exponent=65537, key_size=key_size)
278
+
279
+ # Get public key
280
+ public_key = private_key.public_key()
281
+
282
+ # Serialize keys to PEM format
283
+ private_pem = private_key.private_bytes(
284
+ encoding=serialization.Encoding.PEM,
285
+ format=serialization.PrivateFormat.PKCS8,
286
+ encryption_algorithm=serialization.NoEncryption(),
287
+ )
288
+
289
+ public_pem = public_key.public_bytes(
290
+ encoding=serialization.Encoding.PEM,
291
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
292
+ )
293
+
294
+ return {
295
+ "private_key": private_pem.decode("utf-8"),
296
+ "public_key": public_pem.decode("utf-8"),
297
+ }
298
+ except Exception as e:
299
+ raise CryptoError(f"RSA key pair generation failed: {str(e)}")
300
+
301
+
302
+ def sign_data(data: Union[str, bytes], private_key_pem: str) -> str:
303
+ """
304
+ Sign data with private key.
305
+
306
+ Args:
307
+ data: Data to sign
308
+ private_key_pem: Private key in PEM format
309
+
310
+ Returns:
311
+ Base64 encoded signature
312
+
313
+ Raises:
314
+ CryptoError: If signing fails
315
+ """
316
+ if isinstance(data, str):
317
+ data = data.encode("utf-8")
318
+
319
+ try:
320
+ # Load private key
321
+ private_key = serialization.load_pem_private_key(
322
+ private_key_pem.encode("utf-8"), password=None
323
+ )
324
+
325
+ # Sign data
326
+ signature = private_key.sign(
327
+ data,
328
+ padding.PSS(
329
+ mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
330
+ ),
331
+ hashes.SHA256(),
332
+ )
333
+
334
+ return base64.b64encode(signature).decode("utf-8")
335
+ except Exception as e:
336
+ raise CryptoError(f"Data signing failed: {str(e)}")
337
+
338
+
339
+ def verify_signature(
340
+ data: Union[str, bytes], signature: str, public_key_pem: str
341
+ ) -> bool:
342
+ """
343
+ Verify signature with public key.
344
+
345
+ Args:
346
+ data: Original data
347
+ signature: Base64 encoded signature
348
+ public_key_pem: Public key in PEM format
349
+
350
+ Returns:
351
+ True if signature is valid, False otherwise
352
+
353
+ Raises:
354
+ CryptoError: If verification fails
355
+ """
356
+ if isinstance(data, str):
357
+ data = data.encode("utf-8")
358
+
359
+ try:
360
+ # Load public key
361
+ public_key = serialization.load_pem_public_key(public_key_pem.encode("utf-8"))
362
+
363
+ # Decode signature
364
+ signature_bytes = base64.b64decode(signature)
365
+
366
+ # Verify signature
367
+ public_key.verify(
368
+ signature_bytes,
369
+ data,
370
+ padding.PSS(
371
+ mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
372
+ ),
373
+ hashes.SHA256(),
374
+ )
375
+
376
+ return True
377
+ except Exception:
378
+ return False
379
+
380
+
381
+ def generate_hmac(data: Union[str, bytes], key: Union[str, bytes]) -> str:
382
+ """
383
+ Generate HMAC for data.
384
+
385
+ Args:
386
+ data: Data to hash
387
+ key: HMAC key
388
+
389
+ Returns:
390
+ Hexadecimal HMAC string
391
+
392
+ Raises:
393
+ CryptoError: If HMAC generation fails
394
+ """
395
+ if isinstance(data, str):
396
+ data = data.encode("utf-8")
397
+ if isinstance(key, str):
398
+ key = key.encode("utf-8")
399
+
400
+ try:
401
+ hmac_obj = hmac.new(key, data, hashlib.sha256)
402
+ return hmac_obj.hexdigest()
403
+ except Exception as e:
404
+ raise CryptoError(f"HMAC generation failed: {str(e)}")
405
+
406
+
407
+ def verify_hmac(
408
+ data: Union[str, bytes], key: Union[str, bytes], expected_hmac: str
409
+ ) -> bool:
410
+ """
411
+ Verify HMAC for data.
412
+
413
+ Args:
414
+ data: Original data
415
+ key: HMAC key
416
+ expected_hmac: Expected HMAC value
417
+
418
+ Returns:
419
+ True if HMAC matches, False otherwise
420
+ """
421
+ try:
422
+ actual_hmac = generate_hmac(data, key)
423
+ return hmac.compare_digest(actual_hmac, expected_hmac)
424
+ except Exception:
425
+ return False
426
+
427
+
428
+ def generate_secure_token(length: int = 32) -> str:
429
+ """
430
+ Generate secure random token.
431
+
432
+ Args:
433
+ length: Length of token in bytes
434
+
435
+ Returns:
436
+ Hexadecimal token string
437
+
438
+ Raises:
439
+ CryptoError: If generation fails
440
+ """
441
+ try:
442
+ random_bytes = generate_random_bytes(length)
443
+ return random_bytes.hex()
444
+ except Exception as e:
445
+ raise CryptoError(f"Secure token generation failed: {str(e)}")
446
+
447
+
448
+ def validate_api_key_format(api_key: str) -> bool:
449
+ """
450
+ Validate API key format.
451
+
452
+ Args:
453
+ api_key: API key to validate
454
+
455
+ Returns:
456
+ True if format is valid, False otherwise
457
+ """
458
+ if not api_key or not isinstance(api_key, str):
459
+ return False
460
+
461
+ # Check minimum length (at least 8 characters for flexibility)
462
+ if len(api_key) < 8:
463
+ return False
464
+
465
+ # Check maximum length (reasonable limit)
466
+ if len(api_key) > 256:
467
+ return False
468
+
469
+ # Check for valid characters (alphanumeric, hyphens, underscores)
470
+ import re
471
+
472
+ if not re.match(r"^[a-zA-Z0-9_-]+$", api_key):
473
+ return False
474
+
475
+ return True