|
1 | 1 | import base64 |
| 2 | +import logging |
2 | 3 |
|
3 | 4 | import inject |
4 | 5 |
|
|
14 | 15 | from app.services.org_service import OrgService |
15 | 16 | from app.services.pseudonym_service import PseudonymService |
16 | 17 | from app.services.rid_service import RidService |
17 | | -import logging |
18 | 18 |
|
19 | 19 | logger = logging.getLogger(__name__) |
20 | 20 |
|
| 21 | +# Minimum master key size in bytes. The master key must have at least as much |
| 22 | +# entropy as the 256-bit keys HKDF derives from it. |
| 23 | +_MIN_MASTER_KEY_BYTES = 32 |
| 24 | + |
| 25 | + |
| 26 | +def _load_master_key(raw: str) -> bytes: |
| 27 | + """ |
| 28 | + Decode and validate the configured pseudonym master key. |
| 29 | +
|
| 30 | + All pseudonym, reversible-pseudonym and RID keys are derived from this key, |
| 31 | + so an empty or weak master key would make every pseudonym forgeable and every |
| 32 | + reversible pseudonym/RID decryptable. Refuse to start rather than silently |
| 33 | + run with a b"" key. |
| 34 | + """ |
| 35 | + if not raw: |
| 36 | + raise ValueError( |
| 37 | + "pseudonym.master_key is not configured. Set a base64-encoded key of " |
| 38 | + "at least 32 bytes, e.g. `openssl rand -base64 32`." |
| 39 | + ) |
| 40 | + |
| 41 | + key = base64.urlsafe_b64decode(raw) |
| 42 | + if len(key) < _MIN_MASTER_KEY_BYTES: |
| 43 | + raise ValueError( |
| 44 | + f"pseudonym.master_key is too short ({len(key)} bytes decoded); " |
| 45 | + f"at least {_MIN_MASTER_KEY_BYTES} bytes are required." |
| 46 | + ) |
| 47 | + |
| 48 | + return key |
| 49 | + |
21 | 50 |
|
22 | 51 | def container_config(binder: inject.Binder) -> None: |
23 | 52 | config = get_config() |
24 | 53 |
|
25 | | - db = Database(dsn=config.database.dsn) |
| 54 | + db = Database( |
| 55 | + dsn=config.database.dsn, |
| 56 | + pool_size=config.database.pool_size, |
| 57 | + max_overflow=config.database.max_overflow, |
| 58 | + pool_pre_ping=config.database.pool_pre_ping, |
| 59 | + pool_recycle=config.database.pool_recycle, |
| 60 | + ) |
26 | 61 | binder.bind(Database, db) |
27 | 62 |
|
28 | 63 | key_resolver = KeyResolver(db) |
@@ -66,17 +101,13 @@ def container_config(binder: inject.Binder) -> None: |
66 | 101 | oprf_service = OprfService(server_key=key) |
67 | 102 | binder.bind(OprfService, oprf_service) |
68 | 103 |
|
69 | | - pseudonym_service = PseudonymService( |
70 | | - # This should be done through an HSM |
71 | | - base64.urlsafe_b64decode(config.pseudonym.master_key or ""), |
72 | | - ) |
| 104 | + # This should be done through an HSM |
| 105 | + master_key = _load_master_key(config.pseudonym.master_key) |
| 106 | + |
| 107 | + pseudonym_service = PseudonymService(master_key) |
73 | 108 | binder.bind(PseudonymService, pseudonym_service) |
74 | 109 |
|
75 | | - rid_service = RidService( |
76 | | - # This should be done through an HSM |
77 | | - base64.urlsafe_b64decode(config.pseudonym.master_key or ""), |
78 | | - b"RID:v1", |
79 | | - ) |
| 110 | + rid_service = RidService(master_key, b"RID:v1") |
80 | 111 | binder.bind(RidService, rid_service) |
81 | 112 |
|
82 | 113 |
|
|
0 commit comments