Skip to content

Commit 2f3dd45

Browse files
authored
Cache loading of JWK object from OIDC private key (#1273)
* Cache loading of JWK object from OIDC private key * update AUTHORS * update changelog * test jwk_from_pem caches jwk object
1 parent 13a6143 commit 2f3dd45

File tree

6 files changed

+50
-3
lines changed

6 files changed

+50
-3
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Bart Merenda
2929
Bas van Oostveen
3030
Brian Helba
3131
Carl Schwan
32+
Daniel Golding
3233
Daniel 'Vector' Kerr
3334
Darrel O'Pry
3435
Dave Burkholder

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Security
1515
-->
1616

17+
## [unreleased]
18+
19+
### Added
20+
* #1273 Add caching of loading of OIDC private key.
21+
1722
## [2.3.0] 2023-05-31
1823

1924
### WARNING

oauth2_provider/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .generators import generate_client_id, generate_client_secret
2020
from .scopes import get_scopes_backend
2121
from .settings import oauth2_settings
22+
from .utils import jwk_from_pem
2223
from .validators import RedirectURIValidator, WildcardSet
2324

2425

@@ -234,7 +235,7 @@ def jwk_key(self):
234235
if self.algorithm == AbstractApplication.RS256_ALGORITHM:
235236
if not oauth2_settings.OIDC_RSA_PRIVATE_KEY:
236237
raise ImproperlyConfigured("You must set OIDC_RSA_PRIVATE_KEY to use RSA algorithm")
237-
return jwk.JWK.from_pem(oauth2_settings.OIDC_RSA_PRIVATE_KEY.encode("utf8"))
238+
return jwk_from_pem(oauth2_settings.OIDC_RSA_PRIVATE_KEY)
238239
elif self.algorithm == AbstractApplication.HS256_ALGORITHM:
239240
return jwk.JWK(kty="oct", k=base64url_encode(self.client_secret))
240241
raise ImproperlyConfigured("This application does not support signed tokens")

oauth2_provider/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import functools
2+
3+
from jwcrypto import jwk
4+
5+
6+
@functools.lru_cache()
7+
def jwk_from_pem(pem_string):
8+
"""
9+
A cached version of jwcrypto.JWK.from_pem.
10+
Converting from PEM is expensive for large keys such as those using RSA.
11+
"""
12+
return jwk.JWK.from_pem(pem_string.encode("utf-8"))

oauth2_provider/views/oidc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from django.utils.decorators import method_decorator
88
from django.views.decorators.csrf import csrf_exempt
99
from django.views.generic import FormView, View
10-
from jwcrypto import jwk, jwt
10+
from jwcrypto import jwt
1111
from jwcrypto.common import JWException
1212
from jwcrypto.jws import InvalidJWSObject
1313
from jwcrypto.jwt import JWTExpired
@@ -30,6 +30,7 @@
3030
get_refresh_token_model,
3131
)
3232
from ..settings import oauth2_settings
33+
from ..utils import jwk_from_pem
3334
from .mixins import OAuthLibMixin, OIDCLogoutOnlyMixin, OIDCOnlyMixin
3435

3536

@@ -114,7 +115,7 @@ def get(self, request, *args, **kwargs):
114115
oauth2_settings.OIDC_RSA_PRIVATE_KEY,
115116
*oauth2_settings.OIDC_RSA_PRIVATE_KEYS_INACTIVE,
116117
]:
117-
key = jwk.JWK.from_pem(pem.encode("utf8"))
118+
key = jwk_from_pem(pem)
118119
data = {"alg": "RS256", "use": "sig", "kid": key.thumbprint()}
119120
data.update(json.loads(key.export_public()))
120121
keys.append(data)

tests/test_utils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from oauth2_provider import utils
2+
3+
4+
def test_jwk_from_pem_caches_jwk():
5+
a_tiny_rsa_key = """-----BEGIN RSA PRIVATE KEY-----
6+
MGQCAQACEQCxqYaL6GtPooVMhVwcZrCfAgMBAAECECyNmdsuHvMqIEl9/Fex27kC
7+
CQDlc0deuSVrtQIJAMY4MTw2eCeDAgkA5VzfMykQ5yECCQCgkF4Zl0nHPwIJALPv
8+
+IAFUPv3
9+
-----END RSA PRIVATE KEY-----"""
10+
11+
# For the same private key we expect the same object to be returned
12+
13+
jwk1 = utils.jwk_from_pem(a_tiny_rsa_key)
14+
jwk2 = utils.jwk_from_pem(a_tiny_rsa_key)
15+
16+
assert jwk1 is jwk2
17+
18+
a_different_tiny_rsa_key = """-----BEGIN RSA PRIVATE KEY-----
19+
MGMCAQACEQCvyNNNw4J201yzFVogcfgnAgMBAAECEE3oXe5bNlle+xU4EVHTUIEC
20+
CQDpSvwIvDMSIQIJAMDk47DzG9FHAghtvg1TWpy3oQIJAL6NHlS+RBufAgkA6QLA
21+
2GK4aDc=
22+
-----END RSA PRIVATE KEY-----"""
23+
24+
# But for a different key, a different object
25+
jwk3 = utils.jwk_from_pem(a_different_tiny_rsa_key)
26+
27+
assert jwk3 is not jwk1

0 commit comments

Comments
 (0)