Skip to content

Commit 3b29541

Browse files
authored
Merge pull request #337 from minvws/prs-key-id
Added key-id we can send over via the JWE
2 parents 8438c50 + a4c3904 commit 3b29541

9 files changed

Lines changed: 26 additions & 16 deletions

File tree

app/routers/exchange.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def exchange_rid(
217217
if org is None:
218218
raise OrganizationNotFound(oin)
219219

220-
pub_key_jwk = key_resolver.resolve(org.id, req.recipientScope)
220+
(pub_key_jwk, pub_key_id) = key_resolver.resolve(org.id, req.recipientScope)
221221
if pub_key_jwk is None:
222222
logger.warning(
223223
"no public key found for organization '%s' and scope '%s'",
@@ -232,6 +232,7 @@ def exchange_rid(
232232
scope=req.recipientScope,
233233
subject=f"rid:{rid}",
234234
pub_key=pub_key_jwk,
235+
pub_key_id=pub_key_id,
235236
extra_claims={
236237
"ridUsage": rid_data["usage"],
237238
},
@@ -299,7 +300,7 @@ def exchange_pseudonym(
299300
)
300301
raise HTTPException(status_code=500, detail="Pseudonym exchange failed")
301302

302-
pub_key_jwk = key_resolver.resolve(org.id, req.recipientScope)
303+
(pub_key_jwk, pub_key_id) = key_resolver.resolve(org.id, req.recipientScope)
303304
if pub_key_jwk is None:
304305
logger.warning(
305306
"no public key found for organization '%s' and scope '%s'",
@@ -313,6 +314,7 @@ def exchange_pseudonym(
313314
scope=req.recipientScope,
314315
subject=subject,
315316
pub_key=pub_key_jwk,
317+
pub_key_id=pub_key_id,
316318
)
317319

318320
return Response(

app/routers/oprf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def post_eval(
7070
pub_key_jwk = jwk.JWK.from_pem(key_entry.key_data.encode("ascii"))
7171

7272
try:
73-
result = oprf_service.eval_blind(req, pub_key_jwk)
73+
result = oprf_service.eval_blind(req, pub_key_jwk, key_entry.key_id)
7474
except ValueError as e:
7575
log_event(
7676
logger,

app/services/key_resolver.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,15 @@ def resolve_entry(self, org_id: uuid.UUID, scope: str) -> Optional[OrganizationK
8484
with self.db.get_db_session() as session:
8585
return session.get_repository(OrganizationKeyRepository).get(org_id, scope)
8686

87-
def resolve(self, org_id: uuid.UUID, scope: str) -> Optional[jwk.JWK]:
87+
def resolve(
88+
self, org_id: uuid.UUID, scope: str
89+
) -> tuple[Optional[jwk.JWK], Optional[str]]:
8890
entry = self.resolve_entry(org_id, scope)
8991

9092
if entry is None:
91-
return None
93+
return None, None
9294

93-
return jwk.JWK.from_pem(entry.key_data.encode("ascii"))
95+
return jwk.JWK.from_pem(entry.key_data.encode("ascii")), entry.key_id
9496

9597
def create(
9698
self, org_id: uuid.UUID, scope: list[str], key_id: Optional[str], key_data: str

app/services/oprf/jwe_token.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def build(
1111
scope: str,
1212
subject: str,
1313
pub_key: jwk.JWK,
14+
pub_key_id: str | None,
1415
extra_claims: dict[str, Any] = {},
1516
) -> str:
1617
"""
@@ -28,7 +29,7 @@ def build(
2829
}
2930

3031
protected_headers = {
31-
"kid": pub_key.thumbprint(),
32+
"kid": pub_key_id if pub_key_id else pub_key.thumbprint(),
3233
"alg": "RSA-OAEP-256",
3334
"enc": "A256GCM",
3435
"cty": "application/json",

app/services/oprf/oprf_service.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ def generate_server_key() -> str:
6868
"""
6969
return base64.urlsafe_b64encode(pyoprf.keygen()).decode("ascii")
7070

71-
def eval_blind(self, req: BlindRequest, pub_key: jwk.JWK) -> OprfEvalResult:
71+
def eval_blind(
72+
self, req: BlindRequest, pub_key: jwk.JWK, pub_key_id: str | None
73+
) -> OprfEvalResult:
7274
"""
7375
Evaluate a blind and returns a JWE encrypted on the pubkey, plus the
7476
key versions the blind was evaluated against
@@ -123,6 +125,7 @@ def eval_blind(self, req: BlindRequest, pub_key: jwk.JWK) -> OprfEvalResult:
123125
scope=req.recipientScope,
124126
subject=subject,
125127
pub_key=pub_key,
128+
pub_key_id=pub_key_id,
126129
extra_claims={"extra_versions": extra_versions},
127130
)
128131

tests/test_hsm_key_version_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def fake_post(url: str, json: dict, **kwargs: object) -> MagicMock: # type: ign
234234
)
235235

236236
with patch("app.services.oprf.oprf_service.requests.post", side_effect=fake_post):
237-
result = service.eval_blind(req, pub)
237+
result = service.eval_blind(req, pub, None)
238238

239239
assert result.key_versions == (2, 7)
240240

@@ -328,7 +328,7 @@ def fake_post(url: str, json: dict, **kwargs: object) -> MagicMock: # type: ign
328328
)
329329

330330
with patch("app.services.oprf.oprf_service.requests.post", side_effect=fake_post):
331-
result = service.eval_blind(req, pub)
331+
result = service.eval_blind(req, pub, None)
332332

333333
assert result.key_versions == (3, 5)
334334

tests/test_key_resolver.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ def test_resolver_create_and_resolve_roundtrip(
3636
assert entry.organization_id == org.id
3737
assert sorted(entry.scope) == ["lmr", "nvi"]
3838

39-
key = key_resolver.resolve(org.id, "nvi")
39+
(key, key_id) = key_resolver.resolve(org.id, "nvi")
4040
assert isinstance(key, jwk.JWK)
41+
assert key_id is not None
42+
assert key_id == "my-key-id"
4143
assert not key.has_private
4244

4345

tests/test_logging_events.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def test_eval_blind_invalid_input_raises_invalid_blinded_input(
9393
service = OprfService(server_key=OprfService.generate_server_key())
9494

9595
with pytest.raises(OprfEvaluationError) as exc:
96-
service.eval_blind(_blind_request(), pub_key)
96+
service.eval_blind(_blind_request(), pub_key, None)
9797

9898
assert exc.value.error_type == "invalid_blinded_input"
9999

@@ -110,7 +110,7 @@ def test_eval_blind_without_active_versions_raises_secret_version_destroyed(
110110
)
111111

112112
with pytest.raises(OprfEvaluationError) as exc:
113-
service.eval_blind(_blind_request(), pub_key)
113+
service.eval_blind(_blind_request(), pub_key, None)
114114

115115
assert exc.value.error_type == "secret_version_destroyed"
116116

@@ -135,6 +135,6 @@ def test_eval_blind_hsm_failure_raises_crypto_evaluation_failure(
135135
),
136136
pytest.raises(OprfEvaluationError) as exc,
137137
):
138-
service.eval_blind(_blind_request(), pub_key)
138+
service.eval_blind(_blind_request(), pub_key, None)
139139

140140
assert exc.value.error_type == "crypto_evaluation_failure"

tests/test_oprf_integration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def test_oprf_eval_failure_emits_failed_event_with_error_type(
337337
oprf_event_records: List[logging.LogRecord],
338338
) -> None:
339339
class FailingOprfService:
340-
def eval_blind(self, req: object, pub_key_jwk: object) -> str:
340+
def eval_blind(self, req: object, pub_key_jwk: object, pub_key_id: str|None) -> str:
341341
raise OprfEvaluationError(
342342
"invalid blinded input", error_type="invalid_blinded_input"
343343
)
@@ -372,7 +372,7 @@ def test_oprf_eval_when_service_rejects_blind_returns_bad_request(
372372
oprf_context: OprfIntegrationContext,
373373
) -> None:
374374
class FailingOprfService:
375-
def eval_blind(self, req: object, pub_key_jwk: object) -> str:
375+
def eval_blind(self, req: object, pub_key_jwk: object, pub_key_id: str|None) -> str:
376376
raise ValueError("invalid blinded input")
377377

378378
app.dependency_overrides[container.get_oprf_service] = lambda: FailingOprfService()

0 commit comments

Comments
 (0)