Use asymmetric key for signing JWTs
This commit is contained in:
@@ -769,6 +769,8 @@ CREDIT_HELP_LINK_URL = ENV_TOKENS.get('CREDIT_HELP_LINK_URL', CREDIT_HELP_LINK_U
|
||||
JWT_ISSUER = ENV_TOKENS.get('JWT_ISSUER', JWT_ISSUER)
|
||||
JWT_EXPIRATION = ENV_TOKENS.get('JWT_EXPIRATION', JWT_EXPIRATION)
|
||||
JWT_AUTH.update(ENV_TOKENS.get('JWT_AUTH', {}))
|
||||
PUBLIC_RSA_KEY = ENV_TOKENS.get('PUBLIC_RSA_KEY', PUBLIC_RSA_KEY)
|
||||
PRIVATE_RSA_KEY = ENV_TOKENS.get('PRIVATE_RSA_KEY', PRIVATE_RSA_KEY)
|
||||
|
||||
################# PROCTORING CONFIGURATION ##################
|
||||
|
||||
|
||||
@@ -2784,6 +2784,10 @@ LTI_AGGREGATE_SCORE_PASSBACK_DELAY = 15 * 60
|
||||
JWT_EXPIRATION = 30
|
||||
JWT_ISSUER = None
|
||||
|
||||
# For help generating a key pair import and run `openedx.core.lib.rsa_key_utils.generate_rsa_key_pair()`
|
||||
PUBLIC_RSA_KEY = None
|
||||
PRIVATE_RSA_KEY = None
|
||||
|
||||
# Credit notifications settings
|
||||
NOTIFICATION_EMAIL_CSS = "templates/credit_notifications/credit_notification.css"
|
||||
NOTIFICATION_EMAIL_EDX_LOGO = "templates/credit_notifications/edx-logo-header.png"
|
||||
|
||||
@@ -225,6 +225,47 @@ CORS_ORIGIN_WHITELIST = ()
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
|
||||
# JWT settings for devstack
|
||||
PUBLIC_RSA_KEY = """\
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCujf5oZBGK4MafMRGY9
|
||||
+zdRRI9YDm1r+81coDCysSrwkhTkFIwP2dmS6lYvJuQ5wifuQa3WFv1Kh9Nr2XRJ
|
||||
1m9OL3/JpmMyTi/YuwD7tIf65tab1SOSRYkoxOKRuuvZuXQG9nWbXrGDncnwuWxf
|
||||
eymwWaIrAhALUS5+nDa7dauj8VngsWauMrEA/MWShEzsR53wGKlciEZA1r/AfQ55
|
||||
XS42GvBobhhy9SeZ3B6LHiaAEywpwFmKPssuoHSNhbPa49LW3gXJ6CsFGRDcBFKd
|
||||
xJ/l8O847Q7kg1lvckpLsKyu5167NK9Qj1X/O3SwVBL3cxx1HpQ6+q3SGLZ4ngow
|
||||
hwIDAQAB
|
||||
-----END PUBLIC KEY-----"""
|
||||
|
||||
PRIVATE_RSA_KEY = """\
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCkK6N/mhkEYrgx
|
||||
p8xEZj37N1FEj1gObWv7zVygMLKxKvCSFOQUjA/Z2ZLqVi8m5DnCJ+5BrdYW/UqH
|
||||
02vZdEnWb04vf8mmYzJOL9i7APu0h/rm1pvVI5JFiSjE4pG669m5dAb2dZtesYOd
|
||||
yfC5bF97KbBZoisCEAtRLn6cNrt1q6PxWeCxZq4ysQD8xZKETOxHnfAYqVyIRkDW
|
||||
v8B9DnldLjYa8GhuGHL1J5ncHoseJoATLCnAWYo+yy6gdI2Fs9rj0tbeBcnoKwUZ
|
||||
ENwEUp3En+Xw7zjtDuSDWW9ySkuwrK7nXrs0r1CPVf87dLBUEvdzHHUelDr6rdIY
|
||||
tnieCjCHAgMBAAECggEBAJvTiAdQPzq4cVlAilTKLz7KTOsknFJlbj+9t5OdZZ9g
|
||||
wKQIDE2sfEcti5O+Zlcl/eTaff39gN6lYR73gMEQ7h0J3U6cnsy+DzvDkpY94qyC
|
||||
/ZYqUhPHBcnW3Mm0vNqNj0XGae15yBXjrKgSy9lUknSXJ3qMwQHeNL/DwA2KrfiL
|
||||
g0iVjk32dvSSHWcBh0M+Qy1WyZU0cf9VWzx+Q1YLj9eUCHteStVubB610XV3JUZt
|
||||
UTWiUCffpo2okHsTBuKPVXK/5BL+BpGplcxRSlnSbMaI611kN3iKlO8KGISXHBz7
|
||||
nOPdkfZC9poEXt5SshtINuGGCCc8hDxpg1otYqCLaYECgYEA1MSCPs3pBkEagchV
|
||||
g0rxYmDUC8QkeIOBuZFjhkdoUgZ6rFntyRZd1NbCUi3YBbV1YC12ZGohqWUWom1S
|
||||
AtNbQ2ZTbqEnDKWbNvLBRwkdp/9cKBce85lCCD6+U2o2Ha8C0+hKeLBn8un1y0zY
|
||||
1AQTqLAz9ItNr0aDPb89cs5voWcCgYEAxYdC8vR3t8iYMUnK6LWYDrKSt7YiorvF
|
||||
qXIMANcXQrnO0ptC0B56qrUCgKHNrtPi5bGpNBJ0oKMfbmGfwX+ca8sCUlLvq/O8
|
||||
S2WZwSJuaHH4lEBi8ErtY++8F4B4l3ENCT84Hyy5jiMpbpkHEnh/1GNcvvmyI8ud
|
||||
3jzovCNZ4+ECgYEA0r+Oz0zAOzyzV8gqw7Cw5iRJBRqUkXaZQUj8jt4eO9lFG4C8
|
||||
IolwCclrk2Drb8Qsbka51X62twZ1ZA/qwve9l0Y88ADaIBHNa6EKxyUFZglvrBoy
|
||||
w1GT8XzMou06iy52G5YkZeU+IYOSvnvw7hjXrChUXi65lRrAFqJd6GEIe5MCgYA/
|
||||
0LxDa9HFsWvh+JoyZoCytuSJr7Eu7AUnAi54kwTzzL3R8tE6Fa7BuesODbg6tD/I
|
||||
v4YPyaqePzUnXyjSxdyOQq8EU8EUx5Dctv1elTYgTjnmA4szYLGjKM+WtC3Bl4eD
|
||||
pkYGZFeqYRfAoHXVdNKvlk5fcKIpyF2/b+Qs7CrdYQKBgQCc/t+JxC9OpI+LhQtB
|
||||
tEtwvklxuaBtoEEKJ76P9vrK1semHQ34M1XyNmvPCXUyKEI38MWtgCCXcdmg5syO
|
||||
PBXdDINx+wKlW7LPgaiRL0Mi9G2aBpdFNI99CWVgCr88xqgSE24KsOxViMwmi0XB
|
||||
Ld/IRK0DgpGP5EJRwpKsDYe/UQ==
|
||||
-----END PRIVATE KEY-----"""
|
||||
|
||||
JWT_AUTH.update({
|
||||
'JWT_ALGORITHM': 'HS256',
|
||||
'JWT_SECRET_KEY': 'lms-secret',
|
||||
|
||||
21
openedx/core/lib/rsa_key_utils.py
Normal file
21
openedx/core/lib/rsa_key_utils.py
Normal file
@@ -0,0 +1,21 @@
|
||||
""" Utils for RSA keys"""
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives.serialization import(
|
||||
Encoding, PublicFormat, PrivateFormat, NoEncryption
|
||||
)
|
||||
|
||||
|
||||
def generate_rsa_key_pair(key_size=2048):
|
||||
""" Generates a public and private RSA PEM encoded key pair"""
|
||||
private_key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=key_size,
|
||||
backend=default_backend()
|
||||
)
|
||||
private_key_str = private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
|
||||
public_key_str = private_key.public_key().public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)
|
||||
|
||||
# Not intented for programmatic use, so we print the keys out
|
||||
print public_key_str
|
||||
print private_key_str
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Utilities for working with ID tokens."""
|
||||
import datetime
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
import jwt
|
||||
@@ -63,3 +65,50 @@ def get_id_token(user, client_name):
|
||||
}
|
||||
|
||||
return jwt.encode(payload, client.client_secret)
|
||||
|
||||
|
||||
def get_asymmetric_token(user):
|
||||
"""Construct a JWT signed with this app's private key.
|
||||
|
||||
The JWT includes the following claims:
|
||||
|
||||
preferred_username (str): The user's username. The claim name is borrowed from edx-oauth2-provider.
|
||||
name (str): The user's full name.
|
||||
email (str): The user's email address.
|
||||
administrator (Boolean): Whether the user has staff permissions.
|
||||
iss (str): Registered claim. Identifies the principal that issued the JWT.
|
||||
exp (int): Registered claim. Identifies the expiration time on or after which
|
||||
the JWT must NOT be accepted for processing.
|
||||
iat (int): Registered claim. Identifies the time at which the JWT was issued.
|
||||
sub (int): Registered claim. Identifies the user. This implementation uses the raw user id.
|
||||
|
||||
Arguments:
|
||||
user (User): User for which to generate the JWT.
|
||||
|
||||
Returns:
|
||||
str: the JWT
|
||||
|
||||
"""
|
||||
private_key = load_pem_private_key(settings.PRIVATE_RSA_KEY, None, default_backend())
|
||||
|
||||
try:
|
||||
# Service users may not have user profiles.
|
||||
full_name = UserProfile.objects.get(user=user).name
|
||||
except UserProfile.DoesNotExist:
|
||||
full_name = None
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
expires_in = getattr(settings, 'OAUTH_ID_TOKEN_EXPIRATION', 30)
|
||||
|
||||
payload = {
|
||||
'preferred_username': user.username,
|
||||
'name': full_name,
|
||||
'email': user.email,
|
||||
'administrator': user.is_staff,
|
||||
'iss': settings.OAUTH_OIDC_ISSUER,
|
||||
'exp': now + datetime.timedelta(seconds=expires_in),
|
||||
'iat': now,
|
||||
'sub': anonymous_id_for_user(user, None),
|
||||
}
|
||||
|
||||
return jwt.encode(payload, private_key, algorithm='RS512')
|
||||
|
||||
@@ -10,6 +10,7 @@ bleach==1.4
|
||||
html5lib==0.999
|
||||
boto==2.39.0
|
||||
celery==3.1.18
|
||||
cryptography==1.3.1
|
||||
cssselect==0.9.1
|
||||
dealer==2.0.4
|
||||
defusedxml==0.4.1
|
||||
|
||||
Reference in New Issue
Block a user