crypto.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. from abc import ABC, abstractmethod
  2. from datetime import datetime
  3. from typing import Optional, Tuple
  4. from .base import Provider, ProviderConfig
  5. class CryptoConfig(ProviderConfig):
  6. provider: Optional[str] = None
  7. @property
  8. def supported_providers(self) -> list[str]:
  9. return ["bcrypt", "nacl"]
  10. def validate_config(self) -> None:
  11. if self.provider not in self.supported_providers:
  12. raise ValueError(f"Unsupported crypto provider: {self.provider}")
  13. class CryptoProvider(Provider, ABC):
  14. def __init__(self, config: CryptoConfig):
  15. if not isinstance(config, CryptoConfig):
  16. raise ValueError(
  17. "CryptoProvider must be initialized with a CryptoConfig"
  18. )
  19. super().__init__(config)
  20. @abstractmethod
  21. def get_password_hash(self, password: str) -> str:
  22. """Hash a plaintext password using a secure password hashing algorithm (e.g., Argon2i)."""
  23. pass
  24. @abstractmethod
  25. def verify_password(
  26. self, plain_password: str, hashed_password: str
  27. ) -> bool:
  28. """Verify that a plaintext password matches the given hashed password."""
  29. pass
  30. @abstractmethod
  31. def generate_verification_code(self, length: int = 32) -> str:
  32. """Generate a random code for email verification or reset tokens."""
  33. pass
  34. @abstractmethod
  35. def generate_signing_keypair(self) -> Tuple[str, str, str]:
  36. """
  37. Generate a new Ed25519 signing keypair for request signing.
  38. Returns:
  39. A tuple of (key_id, private_key, public_key).
  40. - key_id: A unique identifier for this keypair.
  41. - private_key: Base64 encoded Ed25519 private key.
  42. - public_key: Base64 encoded Ed25519 public key.
  43. """
  44. pass
  45. @abstractmethod
  46. def sign_request(self, private_key: str, data: str) -> str:
  47. """Sign request data with an Ed25519 private key, returning the signature."""
  48. pass
  49. @abstractmethod
  50. def verify_request_signature(
  51. self, public_key: str, signature: str, data: str
  52. ) -> bool:
  53. """Verify a request signature using the corresponding Ed25519 public key."""
  54. pass
  55. @abstractmethod
  56. def generate_api_key(self) -> Tuple[str, str]:
  57. """
  58. Generate a new API key for a user.
  59. Returns:
  60. A tuple (key_id, raw_api_key):
  61. - key_id: A unique identifier for the API key.
  62. - raw_api_key: The plaintext API key to provide to the user.
  63. """
  64. pass
  65. @abstractmethod
  66. def hash_api_key(self, raw_api_key: str) -> str:
  67. """
  68. Hash a raw API key for secure storage in the database.
  69. Use strong parameters suitable for long-term secrets.
  70. """
  71. pass
  72. @abstractmethod
  73. def verify_api_key(self, raw_api_key: str, hashed_key: str) -> bool:
  74. """Verify that a provided API key matches the stored hashed version."""
  75. pass
  76. @abstractmethod
  77. def generate_secure_token(self, data: dict, expiry: datetime) -> str:
  78. """
  79. Generate a secure, signed token (e.g., JWT) embedding claims.
  80. Args:
  81. data: The claims to include in the token.
  82. expiry: A datetime at which the token expires.
  83. Returns:
  84. A JWT string signed with a secret key.
  85. """
  86. pass
  87. @abstractmethod
  88. def verify_secure_token(self, token: str) -> Optional[dict]:
  89. """
  90. Verify a secure token (e.g., JWT).
  91. Args:
  92. token: The token string to verify.
  93. Returns:
  94. The token payload if valid, otherwise None.
  95. """
  96. pass