- Notifications
You must be signed in to change notification settings - Fork1
Multiple Authentication eXchange (MAX) - OIDC to SAML
License
EUPL-1.2, Unknown licenses found
Licenses found
minvws/nl-rdo-max
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Multiple Authentication eXchange (MAX, formerly inge6) is build as a bridge between an OIDC client and a TVS (Toegang VerleningsService). In this case the TVS could be DigiD or any other authentication method provider that is exposed through thenl-uzi-login-controller. To clarify, this means that any authenticationmethods other than DigiD, will pass from MAX through the login controller (DigiD Mock included).Functionally this means that MAX allows an end-user to login into DigiD and provides the app with a token, which can beused to retrieve the BSN of that same end-user. This BSN can be used in different ways depending on your use case. Forinstance, it was used for the CoronaCheckAppsigning service to retrieve therelated vaccination and test data from the existing provider. MAX is also capable of exchanging an encrypted BSN forproperties from an external register. This functionality is used in theUZI project, where it exchanges the BSN for data from the UZIregister.
Flow:
In the diagram below you can see the flow of the first use case (token based). An overview of the used endpoints canalso be found in the/docs/endpoints.md
.
Throughout the first part of the flow (after /authorize), the call isdirectly linked to some randstate (generated directly after the first call). The latter part of the flow that sameuser is linked using the generated code coupled to that randstate. Using these random state parameters we track theuser throughout the complete flow, and separate that user from other users interacting with the system*IdPx is an identity provider*RD-BC is the (hidden) IdPx Backend providing the artifacts
OIDC:
If you are not familiar with OIDC (OpenID Connect), you can find more about it in broad terms on theOIDC website. More specifically we are using the PKCE flow (RFC 7636), which is visualized in the diagram below.The diagram is taken fromthis medium blog postwhere you can read a more thorough explanation.
+-------------------+ | Authz Server (MAX)| +--------+ | +---------------+ || |--(A)- Authorization Request ---->| | || | + t(code_verifier), t_m | | Authorization | || | | | Endpoint | || |<-(B)---- Authorization Code -----| | || OIDC | | +---------------+ | | Client | | || | | +---------------+ || |--(C)-- Access Token Request ---->| | || | + code_verifier | | Token | || | | | Endpoint | || |<-(D)------ Access Token ---------| | |+--------+ | +---------------+ | +-------------------+ | ^ | | ∀ | +-------------------+ | TVS | | e.g. DigiD or | | login-controller | +-------------------+
If you are looking to set up MAX locally as part of the UZI project, please refer to the instructions in thenl-rdo-uzi-coordination repository. For more in depth set updocumentation specifically for MAX you can check the/docs/setup.md
. Otherwise, you can read the documentation belowfor the basics:
As MAX is a OIDC <-> SAML bridge, one has to have files for both. Each file is described below. Further, one needsto create anmax.conf
to define all settings. An example is found in max.conf.example with the correspondingexplanations. To make use of all default settings, a single run ofmake setup
should be sufficient. Allowing youto run the service on all default settings.
To use DigiD or TVS you first need to download the metadata. During setup this is done in the make setup or makemetadata step. This can manually be done using curl or another downloading tool. The URLs for the pre-productionenvironment are included below as a reference.
curl "https://was-preprod1.digid.nl/saml/idp/metadata" --output saml/digid/metadata/idp_metadata.xmlcurl "https://pp2.toegang.overheid.nl/kvs/rd/metadata" --output saml/tvs/metadata/idp_metadata.xml
MAX needs two keys to encrypt and sign the JWT containing the BSN details. This is an Ed25519 keypair on MAX's part,and a X25519 keypair for the requesting party. The requesting party would beinge4in the case of the CoronaCheck app and thenl-uzi-login-controllerfor the UZI project. To generate an Ed25519 keypair one can perform the following code:
importbase64fromcryptography.hazmat.primitives.asymmetric.ed25519importEd25519PrivateKey,Ed25519PublicKeyfromcryptography.hazmat.primitivesimportserialization# To generate a new keyprivkey=Ed25519PrivateKey.generate()privkey_bytes=privkey.private_bytes(encoding=serialization.Encoding.Raw,format=serialization.PrivateFormat.Raw,encryption_algorithm=serialization.NoEncryption())# print base64 private keybase64_privkey=base64.b64encode(privkey_bytes)# To load a key from a base64 encoded keyprivkey_bytes=base64.b64decode(base64_privkey)privkey=Ed25519PrivateKey.from_private_bytes(privkey_bytes)# To get the pubkeypubkey_bytes=privkey.public_key().public_bytes(encoding=serialization.Encoding.Raw,format=serialization.PublicFormat.Raw)base64_pubkey=base64.b64encode(pubkey_bytes)
The code is identical for creating a X25519 key, but then just needs a different import (and use the similar classesfor loading and generation of the keys):
fromcryptography.hazmat.primitives.asymmetric.x25519importX25519PrivateKey,X25519PublicKey
13/07/2023:Most likely still needed but can not confirm since I am running MacOS. Please update accordingly.
Some Ubuntu dependencies that should be installed:libxmlsec1-dev pkg-config
To activate an overflow IDP, secondary IDP when primary is too busy, the following settings should be configured inthe max.conf settings.
Further redis expects the keys configured in the config to have a valid value. The keys expected to be set are definedin the config under the following names:
primary_idp_key
user_limit_key
(if there is a user limit to be handled by the ratelimiter)
Optionally, to enable ratelimit overflow, extra keys are expected to be set. The names of these keys are defined in theconfig under the following config names:
overflow_idp_key
user_limit_key_overflow_idp
(if there is a user limit on the overflow idp to be handled by the ratelimiter)
For development purposes we have created a 'mock' to retrieve a JWT Token for arbitrary BSNs. This is only availablewhendigid_mock
has been added to thelogin_methods
in themax.conf
and the setenvironment
does notstart with 'prod'. Note that in the case ofenvironment = production
the option will still show up, but under the hoodit will disable the mock (taken from thehandle_assertion_consumer_service
method in theSAMLProvider
class):
if (notself._environment.startswith("prod")andauthentication_context.authentication_method=="digid_mock"):artifact_response:ArtifactResponse=ArtifactResponseMock(request.SAMLart)else:artifact_response=identity_provider.resolve_artifact(request.SAMLart)
Configuring this adds a DigiD Mock option to the "login method chooser page". When clicking this option asimple page will show with an input element in which you can enter your desired BSN value. You can then simply press "login".It will then try to retrieve mock data based on this BSN from themock_register.json
in thenl-uzipoc-register-api repository. When this is successful your loginwill be complete and allow for the same functionality as any of the other methods.
It is the responsibility of the client to generate a unique code_verifier and code_challenge pair. To make sure thatthe code_verifier is cryptographically secure one should use the following definition (as defined in:https://datatracker.ietf.org/doc/html/rfc7636#section-4.1):
code_verifier = high-entropy cryptographic random STRING using theunreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"from Section 2.3 of [RFC3986], with a minimum length of 43 charactersand a maximum length of 128 characters.
To find the code in this library used for verifying the code_verifier and code_challenge pair, have a look at the codesnippet highlighted in the following github permalink:
nl-rdo-max/inge6/oidc/authorize.py
Lines 17 to 49 ine858cb2
def_compute_code_challenge(code_verifier:str): | |
""" | |
Given a code verifier compute the code_challenge. This code_challenge is computed as defined (https://datatracker.ietf.org/doc/html/rfc7636#section-4.2): | |
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))). | |
This shows that the SHA256 of the ascii encoded code_verifier is URLSafe base64 encoded. We have adjusted the encoding to the ISO_8859_1 encoding, | |
conform to the AppAuth SDK for Android and IOS. Moreover, we remove the base64 padding (=). | |
:param code_verifier: the code verifier to transform to the Code Challenge | |
""" | |
verifier_hash=nacl.hash.sha256( | |
code_verifier.encode("ISO_8859_1"),encoder=URLSafeBase64Encoder | |
) | |
returnverifier_hash.decode().replace("=","") | |
defverify_code_verifier(cc_cm:Dict[str,str],code_verifier:str)->bool: | |
""" | |
Verify that the given code_verifier complies with the initially supplied code_challenge. | |
Only supports the SHA256 code challenge method, plaintext is regarded as unsafe. | |
:param cc_cm: the initially supplied Code Challenge Code challenge Method dictionary | |
:param code_verifier: the code_verfier to check against the code challenge. | |
:returns: whether the code_verifier is what was expected given the cc_cm | |
""" | |
code_challenge_method=cc_cm["code_challenge_method"] | |
ifnotcode_challenge_method=="S256": | |
returnFalse | |
code_challenge=_compute_code_challenge(code_verifier) | |
returncode_challenge==cc_cm["code_challenge"] |
This snippet verifies the pair as defined inhttps://datatracker.ietf.org/doc/html/rfc7636#section-4.2
The development team works on the repository in a private fork (for reasons of compliance with existing processes) andshares its work as often as possible.
If you plan to make non-trivial changes, we recommend to open an issue beforehand where we can discuss your plannedchanges. This increases the chance that we might be able to use your contribution (or it avoids doing work if thereare reasons why we wouldn't be able to use it).
Note that all commits should be signed using a gpg key.
Security issues can be reported through a github issue, atsecurity@rdobeheer.nl, or through thehttps://www.ncsc.nl/contact/kwetsbaarheid-melden.
The Logging/monitoring of data processing are, on the one hand, important measures to detect,among other things, unauthorized access to personal data. On the other hand, Logging/monitoringconstitutes new processing of personal data, with associated privacy risks. Therefore,the question of how logging/monitoring should be set up requires consideration.
With regard to this application, the choice has been made not to log data processing because:
- Processing of personal data within this application takes place encrypted.
- Users do not have access to personal data processed within this application,and they cannot undo the encryption.
- Logging of data processing within this application is not necessary in light ofthe obligation of healthcare providers to be able to comply with their obligationto record actions related to the electronic patient record.
Docker containers and their configurations are meant to be used for development purposes only. And not meant to be used in a production setup.
About
Multiple Authentication eXchange (MAX) - OIDC to SAML