Skip to content

Commit 652b4d0

Browse files
authored
Merge pull request #179 from minvws/cert-check
Checking on the test endpoint for mtls
2 parents d2a366d + ee24b24 commit 652b4d0

5 files changed

Lines changed: 55 additions & 18 deletions

File tree

app/container.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def container_config(binder: inject.Binder) -> None:
2727
org_service = OrgService(db)
2828
binder.bind(OrgService, org_service)
2929

30-
mtls_service = MtlsService(config.app.mtls_override_cert)
30+
mtls_service = MtlsService(config.app.mtls_override_cert, org_service)
3131
binder.bind(MtlsService, mtls_service)
3232

3333
try:

app/routers/exchange.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
from typing import Dict, Any
44

55
from fastapi import APIRouter, Depends, HTTPException
6+
from starlette.requests import Request
67
from starlette.responses import JSONResponse, Response
78

89
from app import container
910
from app.models.requests import ExchangeRequest, RidExchangeRequest, RidReceiveRequest
1011
from app.personal_id import PersonalId
11-
from app.rid import ALLOWED_BY_RID_USAGE, REQUIRED_MIN_USAGE, USAGE_RANK
12+
from app.rid import ALLOWED_BY_RID_USAGE, REQUIRED_MIN_USAGE, USAGE_RANK, RidUsage
1213
from app.services.key_resolver import KeyResolver
14+
from app.services.mtls_service import MtlsService
1315
from app.services.oprf.jwe_token import BlindJwe
1416
from app.services.org_service import OrgService
1517
from app.services.pseudonym_service import PseudonymService, PseudonymType
@@ -181,9 +183,11 @@ def exchange_rid(
181183
@router.post("/exchange/pseudonym", summary="Exchange pseudonym")
182184
def exchange_pseudonym(
183185
req: ExchangeRequest,
186+
request: Request,
184187
key_resolver: KeyResolver = Depends(container.get_key_resolver),
185188
pseudonym_service: PseudonymService = Depends(container.get_pseudonym_service),
186189
org_service: OrgService = Depends(container.get_org_service),
190+
mtls_service: MtlsService = Depends(container.get_mtls_service),
187191
) -> Response:
188192
if not req.recipientOrganization.startswith("ura:"):
189193
raise InvalidURA(req.recipientOrganization)
@@ -201,6 +205,13 @@ def exchange_pseudonym(
201205
)
202206
subject = "pseudonym:irreversible:" + res
203207
elif req.pseudonymType == PseudonymType.Reversible:
208+
source_org = mtls_service.get_org_from_request(request)
209+
if source_org.max_rid_usage == RidUsage.IrreversiblePseudonym:
210+
raise HTTPException(
211+
status_code=400,
212+
detail="Source organization is not allowed to exchange reversible pseudonyms.",
213+
)
214+
204215
res = pseudonym_service.exchange_reversible_pseudonym(
205216
personal_id=req.personalId,
206217
recipient_organization=recipient_organization,

app/routers/key.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,10 @@ def post_key(
2020
request: Request,
2121
mtls_service: MtlsService = Depends(container.get_mtls_service),
2222
key_resolver: KeyResolver = Depends(container.get_key_resolver),
23-
org_service: OrgService = Depends(container.get_org_service),
2423
) -> JSONResponse:
2524

26-
# Fetch public key from the client certificate
2725
mtls_pub_key = mtls_service.get_mtls_pub_key(request)
28-
29-
# Extract URA from the client certificate and validate S-type
30-
data = mtls_service.get_mtls_uzi_data(request)
31-
if data["CardType"] != "S":
32-
raise HTTPException(status_code=401, detail="Invalid client certificate. Need an UZI S-type certificate.")
33-
34-
ura = data["SubscriberNumber"]
35-
36-
# Make sure we have (pre)registered the organization for this URa
37-
org = org_service.get_by_ura(ura)
38-
if org is None:
39-
raise HTTPException(status_code=404, detail="organization for this URA is not registered")
26+
org = mtls_service.get_org_from_request(request)
4027

4128
# Create the key entry
4229
try:

app/routers/test_oprf.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
from fastapi import APIRouter, Depends
55
from fastapi.encoders import jsonable_encoder
66
from jwcrypto import jwe, jwk
7+
from starlette.requests import Request
78
from starlette.responses import JSONResponse
89

910
from app import container
1011
from app.models.requests import InputRequest, ReceiverRequest, JweReceiverRequest
1112
from app.personal_id import PersonalId, PersonalIdJSONEncoder
13+
from app.rid import RidUsage
14+
from app.services.mtls_service import MtlsService
1215
from app.services.oprf.oprf_service import OprfService
1316
from app.services.pseudonym_service import PseudonymService
1417

@@ -147,9 +150,18 @@ def post_test_jwe_decode(
147150

148151
@router.post("/test/pseudonym/reversible", summary="Reverse a pseudonym", tags=["test-oprf"])
149152
def post_test_reversible_pseudonym(
153+
request: Request,
150154
pseudonym: str,
151155
pseudonym_service: PseudonymService = Depends(container.get_pseudonym_service),
156+
mtls_service: MtlsService = Depends(container.get_mtls_service),
152157
) -> JSONResponse:
158+
# Check if we as an organization are allowed to reverse pseudonyms (max_key_usage == BSN)
159+
org = mtls_service.get_org_from_request(request)
160+
if org.max_rid_usage != RidUsage.Bsn:
161+
return JSONResponse({
162+
"error": "Organization is not authorized to reverse pseudonyms."
163+
}, status_code=403)
164+
153165
parts = pseudonym.split(":")
154166
if len(parts) == 3 and parts[0] == "pseudonym" and parts[1] == "reversible":
155167
pseudonym = parts[2]
@@ -165,10 +177,10 @@ def post_test_reversible_pseudonym(
165177
"error": f"Failed to reverse pseudonym: {str(e)}"
166178
}, status_code=400)
167179

168-
169180
return JSONResponse(content=jsonable_encoder({
170181
"pseudonym": pseudonym,
171182
"decoded": decoded,
172183
}, custom_encoder={PersonalId: lambda v: json.loads(json.dumps(v, cls=PersonalIdJSONEncoder))}))
173184

174185

186+

app/services/mtls_service.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,23 @@
77
from uzireader.uziserver import UziServer
88
import logging
99

10+
from app.db.entities.organization import Organization
11+
from app.services.org_service import OrgService
12+
1013
logger = logging.getLogger(__name__)
1114

1215
class MtlsService:
1316
_CERT_START = "-----BEGIN CERTIFICATE-----"
1417
_CERT_END = "-----END CERTIFICATE-----"
1518
_SSL_CLIENT_CERT_HEADER_NAME = "x-proxy-ssl_client_cert"
1619

17-
def __init__(self, override_cert: str|None) -> None:
20+
def __init__(
21+
self,
22+
override_cert: str|None,
23+
org_service: OrgService,
24+
) -> None:
1825
self.__cert: bytes | None = None
26+
self.org_service = org_service
1927

2028
if override_cert is not None and override_cert != "":
2129
with open(override_cert, "r") as f:
@@ -71,3 +79,22 @@ def get_mtls_uzi_data(self, request: Request) -> UziServer:
7179
cert_bytes = self.get_mtls_cert(request)
7280
formatted_cert = self._enforce_cert_newlines(cert_bytes)
7381
return UziServer(verify="SUCCESS", cert=formatted_cert)
82+
83+
84+
def get_org_from_request(self, request: Request) -> Organization:
85+
"""
86+
Extract the organization from the client certificate in the request
87+
"""
88+
89+
data = self.get_mtls_uzi_data(request)
90+
if data["CardType"] != "S":
91+
raise HTTPException(status_code=401,
92+
detail="Invalid client certificate. Need an UZI S-type certificate.")
93+
94+
ura = data["SubscriberNumber"]
95+
org = self.org_service.get_by_ura(ura)
96+
if org is None:
97+
raise HTTPException(status_code=404, detail=f"organization for URA {ura} is not registered")
98+
99+
return org
100+

0 commit comments

Comments
 (0)