(backend) add a '/jwks' endpoint

Introduce a new endpoint, /jwks, which returns a JSON Web Key Set (JWKS).
This set of public crypto keys will be used by external parties to encrypt
data intended for our backend. In the context of the resource server, this key
will be used by the authorization server to encrypt the introspection response.

The current implementation exposes a single public key, with the private key
configurable in the app settings. The private key is represented as a string.
For enhanced security, we might prefer to store this data in a .pem file
excluded from version control.

A few parameters for this key, such as its type and encoding, are configurable
in the settings.

A critique of the current design is its lack of extensibility.
If we decide to offer more than one encryption method, this view will require
refactoring.

Additionally, the current implementation is tightly coupled with joserfc.

This lays the foundation for further improvements.

Please note, this endpoint only public components of the key, there is no
chance for any secret leaking.
This commit is contained in:
lebaudantoine
2024-07-28 01:52:24 +02:00
committed by aleb_the_flash
parent b40aefc505
commit 21371dbd1b
10 changed files with 306 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
"""Resource Server URL Configuration"""
from django.urls import path
from .views import JWKSView
urlpatterns = [
path("jwks", JWKSView.as_view(), name="resource_server_jwks"),
]

View File

@@ -0,0 +1,48 @@
"""Resource Server utils functions"""
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from joserfc.jwk import JWKRegistry
def import_private_key_from_settings():
"""Import the private key used by the resource server when interacting with the OIDC provider.
This private key is crucial; its public components are exposed in the JWK endpoints,
while its private component is used for decrypting the introspection token retrieved
from the OIDC provider.
By default, we recommend using RSAKey for asymmetric encryption,
known for its strong security features.
Note:
- The function requires the 'OIDC_RS_PRIVATE_KEY_STR' setting to be configured.
- The 'OIDC_RS_ENCRYPTION_KEY_TYPE' and 'OIDC_RS_ENCRYPTION_ALGO' settings can be customized
based on the chosen key type.
Raises:
ImproperlyConfigured: If the private key setting is missing, empty, or incorrect.
Returns:
joserfc.jwk.JWK: The imported private key as a JWK object.
"""
private_key_str = getattr(settings, "OIDC_RS_PRIVATE_KEY_STR", None)
if not private_key_str:
raise ImproperlyConfigured(
"OIDC_RS_PRIVATE_KEY_STR setting is missing or empty."
)
private_key_pem = private_key_str.encode()
try:
private_key = JWKRegistry.import_key(
private_key_pem,
key_type=settings.OIDC_RS_ENCRYPTION_KEY_TYPE,
parameters={"alg": settings.OIDC_RS_ENCRYPTION_ALGO, "use": "enc"},
)
except ValueError as err:
raise ImproperlyConfigured("OIDC_RS_PRIVATE_KEY_STR setting is wrong.") from err
return private_key

View File

@@ -0,0 +1,40 @@
"""Resource Server views"""
from django.core.exceptions import ImproperlyConfigured
from joserfc.jwk import KeySet
from rest_framework.response import Response
from rest_framework.views import APIView
from . import utils
class JWKSView(APIView):
"""
API endpoint for retrieving a JSON Web Keys Set (JWKS).
Returns:
Response: JSON response containing the JWKS data.
"""
authentication_classes = [] # disable authentication
permission_classes = [] # disable permission
def get(self, request):
"""Handle GET requests to retrieve JSON Web Keys Set (JWKS).
Returns:
Response: JSON response containing the JWKS data.
"""
try:
private_key = utils.import_private_key_from_settings()
except (ImproperlyConfigured, ValueError) as err:
return Response({"error": str(err)}, status=500)
try:
jwk = KeySet([private_key]).as_dict(private=False)
except (TypeError, ValueError, AttributeError):
return Response({"error": "Could not load key"}, status=500)
return Response(jwk)