Skip to content

Commit 6ae4bcd

Browse files
authored
Merge pull request #184 from minvws/issue-149
Updated docs and some tests routes
2 parents b668c30 + 5b49092 commit 6ae4bcd

10 files changed

Lines changed: 143 additions & 60 deletions

File tree

README.md

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -102,44 +102,46 @@ docker run -ti --rm -p 6502:6502 \
102102

103103
This system uses OPRF for pseudonym generation. To test this, there are some available endpoints:
104104

105-
- '/test/oprf/gen_rsa_key' - Generate a new RSA keypair for OPRF (small 1024 bit for testing)
106105
- '/test/oprf/oprf/client' - Emulates a client that generates OPRF information for a given input
107106
- '/test/oprf/oprf/receiver' - Emulates the receiver of the pseudonym and returns diagnostic information
108107

109108
To use this system:
110109

111-
1. First, generate a new keypair that will be used for a test organization with `/test/oprf/gen_rsa_key`.
112-
```shell
113-
POST /test/oprf/gen_rsa_key
114-
115-
200 OK
116-
{
117-
"private_key_pem": "-----BEGIN PRIVATE KEY-----\nMIICdwIBA....neDKJ0DsvA5vfpt0=\n-----END PRIVATE KEY-----\n",
118-
"public_key_pem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqG....uDQIDAQAB\n-----END PUBLIC KEY-----\n",
119-
"public_key_kid": "ajGAx_LKNrJ8vqdahWlSJvOznBi6lnFfSOw72Z4R4uU"
120-
}
121-
```
110+
1. You will need a generated UZI Server certificate (https://www.uziregister.nl/servercertificaat) or create a
111+
self-signed certificate for testing purposes.
112+
113+
Since the system uses mTLS, you can either setup a mTLS setup (caddy, apache, etc), or enable the override in the
114+
app.conf file:
115+
116+
```
117+
[app]
118+
mtls_override_cert=./secrets/self-signed-uzi-server-cert.crt
122119
123-
2. Create a new organization into the key resolver with a POST to `/keys`. Add your organization name like
124-
`ura:12345678`, and you can use scope `test` for testing. Add the **PUBLIC key** to the `public_key` field.
120+
2. Insert a new organization via a POST to `/orgs`. The organization ura should be the URA of the uzi certificate you
121+
will be testing with.
125122
126-
```shell
127-
POST /keys
128-
{
129-
"organization": "ura:12345678",
130-
"scope": "test",
131-
"public_key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqG....uDQIDAQAB\n-----END PUBLIC KEY\n",
132-
}
123+
Note: there is no mTLS check here. You can add multiple organizations with different URA values for testing.
124+
125+
3. Next, you will need to register your public key to the key services. You can do this by calling `/register/certificate` with a JSON body like:
133126
134-
201 Created
135-
```
127+
```shell
128+
POST /register/certificate
129+
{
130+
"scope": [
131+
"nvi",
132+
]
133+
}
134+
```
135+
136+
This will take the public key from the uzi server certificate using in this mTLS connection, and register it for the given scope.
136137

137-
3. Emulate a client wanting to send a pseudonym over to a receiver by calling `/test/oprf/client` with a JSON body like:
138+
139+
4. Emulate a client wanting to send a pseudonym over to a receiver by calling `/test/oprf/client` with a JSON body like:
138140

139141
```shell
140142
POST /test/oprf/client
141143
{
142-
"personalId": "testinput-like-a-bsn-number-or-other-id"
144+
"personalId": "nl:bsn:950000012"
143145
}
144146

145147
200 OK
@@ -149,7 +151,7 @@ To use this system:
149151
}
150152
```
151153

152-
This returns the `blinded_input` that must be sent to the receiver, and the `blind_factor` that must be send to the
154+
This returns the `blinded_input` that must be sent to the receiver, and the `blind_factor` that must be sent to the
153155
receiver after the server has evaluated the blinded input.
154156

155157
4. Now we can call the "real" OPRF function `/oprf/eval` with the blinded input, the organization name and scope:
@@ -172,13 +174,13 @@ To use this system:
172174
is encrypted with the public key of the organization. At this point, the client is
173175
not able to decrypt this information. It can only forward this to the receiver.
174176

175-
5. Now emulate the receiver by calling `/test/oprf/receiver` with a JSON body like:
177+
5. Now emulate the receiving party by calling `/test/oprf/receiver` with a JSON body like:
176178
```shell
177179
POST /test/oprf/receiver
178180
{
179181
"blind_factor": "eNf80WNHbImaUNU-kokBr7ocELBjMtHcy0re_RKBPQ8=",
180182
"jwe": "eyJraWQiOiA...SzZbJUqbbSUIjqiw",
181-
"priv_key_pem": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIB...oCfe0=\n-----END RSA PRIVATE KEY-----\n"
183+
"priv_key_pem": "-----BEGIN RSA PRIVATE KEY----- MIICXAIB...oCfe0= -----END RSA PRIVATE KEY-----"
182184
}
183185
```
184186

@@ -190,7 +192,7 @@ To use this system:
190192
```json
191193
{
192194
"jwe_data": "eyJraWQiOiAi...zZbJUqbbSUIjqiw",
193-
"priv_key_pem": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgH6gmpXpdhtiE...UpWRvoCfe0=\n-----END RSA PRIVATE KEY-----\n",
195+
"priv_key_pem": "-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgH6gmpXpdhtiE...UpWRvoCfe0= -----END RSA PRIVATE KEY-----",
194196
"priv_key_kid": "rNv1O_mXgxF6QEMfaQGvjev7RbT1FG3sJXxxsu_KHbM",
195197
"blind_factor": "eNf80WNHbImaUNU-kokBr7ocELBjMtHcy0re_RKBPQ8=",
196198
"jwe": {
@@ -216,3 +218,21 @@ To use this system:
216218

217219
The `final_pseudonym` is the actual pseudonym that can be stored by the receiver. Note that this pseudonym is deterministic
218220
for the same input, organization and scope. However, it is not possible to reverse this into a BSN.
221+
222+
223+
224+
# max-key-usage
225+
226+
The max-key-usage is a property that defines what kind of pseudonyms an organization can create and reverse.
227+
228+
There are 3 levels of max_key_usage:
229+
230+
- BSN - can create reversible and irreversible pseudonyms, and can reverse reversible pseudonyms back to BSN
231+
- RP (Reversible Pseudonym) - can create reversible and irreversible pseudonyms, but cannot reverse any pseudonyms
232+
- IRP (Irreversible Pseudonym) - can only create irreversible pseudonyms, and cannot reverse any pseudonyms
233+
234+
The "RP" level is mainly intended for organizations that need to create reversible pseudonyms for other organizations,
235+
but is not allowed to reverse them back to BSN itself.
236+
237+
The "IRP" level is intended for organizations that only need to create irreversible pseudonyms, and do not have access to
238+
BSN information at all.

app.conf.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
[app]
22
loglevel=debug
33
mtls_allow_override_cert=
4+
# Enable the /test routes
5+
enable_test_routes=True
46

57
[database]
68
dsn=postgresql+psycopg://postgres:postgres@postgres:5432/postgres

app.test.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
[app]
33
loglevel=debug
44
mtls_allow_override_cert=
5+
enable_test_routes=True
56

67
[database]
78
dsn=postgresql+psycopg://postgres:postgres@localhost/testing

app/application.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ def setup_fastapi() -> FastAPI:
7272
FastAPI(
7373
docs_url=config.uvicorn.docs_url,
7474
redoc_url=config.uvicorn.redoc_url,
75-
title="PRS API",
75+
title="Pseudoniemendienst API",
76+
summary="API for the Pseudoniemendienst",
77+
description="Provides endpoints for OPRF, key management, organization management, and RID exchange.",
78+
7679
) if config.uvicorn.swagger_enabled else FastAPI(
7780
docs_url=None,
7881
redoc_url=None
@@ -83,11 +86,13 @@ def setup_fastapi() -> FastAPI:
8386
default_router,
8487
health_router,
8588
oprf_router,
86-
test_oprf_router,
8789
key_router,
8890
exchange_router,
8991
org_router,
9092
]
93+
if config.app.enable_test_routes:
94+
routers.append(test_oprf_router)
95+
9196
for router in routers:
9297
fastapi.include_router(router)
9398

app/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class LogLevel(str, Enum):
2222
class ConfigApp(BaseModel):
2323
loglevel: LogLevel = Field(default=LogLevel.info)
2424
mtls_override_cert: str | None = Field(default=None)
25+
enable_test_routes: bool = Field(default=False)
2526

2627
class ConfigDatabase(BaseModel):
2728
dsn: str

app/routers/exchange.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __init__(self, ura: str, scope: str) -> None:
3737
super().__init__(status_code=404, detail=f"No public key found for organization '{ura}' and scope '{scope}'")
3838

3939

40-
@router.post("/receive", summary="Receive and decrypt RID")
40+
@router.post("/receive", summary="Receive and decrypt RID", tags=["Exchange Services"])
4141
def receive(
4242
req: RidReceiveRequest,
4343
key_resolver: KeyResolver = Depends(container.get_key_resolver),
@@ -136,7 +136,7 @@ def receive(
136136
return JSONResponse(content={"pseudonym": value, "type": req.pseudonymType})
137137

138138

139-
@router.post("/exchange/rid", summary="Exchange RID")
139+
@router.post("/exchange/rid", summary="Exchange RID", tags=["Exchange Services"])
140140
def exchange_rid(
141141
req: RidExchangeRequest,
142142
key_resolver: KeyResolver = Depends(container.get_key_resolver),
@@ -180,7 +180,7 @@ def exchange_rid(
180180

181181
return Response(status_code=201, content=jwe, headers={"Content-Type": "application/jwe"})
182182

183-
@router.post("/exchange/pseudonym", summary="Exchange pseudonym")
183+
@router.post("/exchange/pseudonym", summary="Exchange pseudonym", tags=["Exchange Services"])
184184
def exchange_pseudonym(
185185
req: ExchangeRequest,
186186
request: Request,

app/routers/key.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
logger = logging.getLogger(__name__)
1515
router = APIRouter()
1616

17-
@router.post("/register/certificate", summary="Insert public key information for an organization", tags=["key-service"])
17+
@router.post("/register/certificate", summary="Insert public key information for an organization", tags=["Key Registration Services"])
1818
def post_key(
1919
req: RegisterRequest,
2020
request: Request,
@@ -38,7 +38,7 @@ def post_key(
3838
return JSONResponse(status_code=201, content={"message": "Key created successfully"})
3939

4040

41-
@router.get("/keys/{ura}", summary="List public key information for an organization", tags=["key-service"])
41+
@router.get("/keys/{ura}", summary="List public key information for an organization", tags=["Key Registration Services"])
4242
def list_keys_for_org(
4343
ura: str,
4444
key_resolver: KeyResolver = Depends(container.get_key_resolver),
@@ -55,7 +55,7 @@ def list_keys_for_org(
5555
return JSONResponse(status_code=200, content=[e.to_dict() for e in entries])
5656

5757

58-
@router.put("/keys/{key_id}", summary="Update specific key for key/scope", tags=["key-service"])
58+
@router.put("/keys/{key_id}", summary="Update specific key for key/scope", tags=["Key Registration Services"])
5959
def put_key(
6060
key_id: str,
6161
req: KeyRequest,
@@ -79,7 +79,7 @@ def put_key(
7979
return JSONResponse(status_code=200, content=updated_entry.to_dict())
8080

8181

82-
@router.delete("/keys/{key_id}", summary="Delete specific key for key/scope", tags=["key-service"])
82+
@router.delete("/keys/{key_id}", summary="Delete specific key for key/scope", tags=["Key Registration Services"])
8383
def delete_key(
8484
key_id: str,
8585
key_resolver: KeyResolver = Depends(container.get_key_resolver),

app/routers/oprf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
logger = logging.getLogger(__name__)
1212
router = APIRouter()
1313

14-
@router.post("/oprf/eval", summary="Evaluate OPRF blind and returns an encrypted JWE for the organization", tags=["oprf"])
14+
@router.post("/oprf/eval", summary="Evaluate OPRF blind and returns an encrypted JWE for the organization", tags=["OPRF Services"])
1515
def post_eval(
1616
req: BlindRequest,
1717
key_resolver: KeyResolver = Depends(container.get_key_resolver),

app/routers/org.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
logger = logging.getLogger(__name__)
1313
router = APIRouter()
1414

15-
@router.post("/orgs", summary="Create new organization", tags=["org-service"])
15+
@router.post("/orgs", summary="Create new organization", tags=["Organizational Services"])
1616
def post_org(
1717
req: OrgRequest,
1818
org_service: OrgService = Depends(container.get_org_service),
@@ -28,7 +28,7 @@ def post_org(
2828
return JSONResponse(status_code=201, content={"message": "Org created successfully"})
2929

3030

31-
@router.get("/org/{ura}", summary="List organization", tags=["org-service"])
31+
@router.get("/org/{ura}", summary="List organization", tags=["Organizational Services"])
3232
def list_keys_for_org(
3333
ura: str,
3434
org_service: OrgService = Depends(container.get_org_service),
@@ -40,7 +40,7 @@ def list_keys_for_org(
4040
return JSONResponse(status_code=200, content=org.to_dict())
4141

4242

43-
@router.put("/org/{ura}", summary="Update specific org", tags=["org-service"])
43+
@router.put("/org/{ura}", summary="Update specific org", tags=["Organizational Services"])
4444
def put_org(
4545
ura: str,
4646
req: OrgRequest,
@@ -62,7 +62,7 @@ def put_org(
6262
return JSONResponse(status_code=200, content=updated_entry.to_dict())
6363

6464

65-
@router.delete("/org/{ura}", summary="Delete specific org", tags=["org-service"])
65+
@router.delete("/org/{ura}", summary="Delete specific org", tags=["Organizational Services"])
6666
def delete_org(
6767
ura: str,
6868
org_service: OrgService = Depends(container.get_org_service),

0 commit comments

Comments
 (0)