-
-
Notifications
You must be signed in to change notification settings - Fork 240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
replace pyjwkest and cryptodome with pyjwt and cryptography #208
base: develop
Are you sure you want to change the base?
Conversation
I must dive into this |
I'll leave some inline comments explaining things. It's functionally the same as before (that's why there are no new or changed tests). I can answer any additional questions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just adding inline comments explaining my changes.
|
||
if django.VERSION >= (1, 11): | ||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pep8 compliance change.
return _jws.sign_compact(keys) | ||
|
||
|
||
def decode_id_token(token, client): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function was completely unused and so I removed it.
""" | ||
keys = get_client_alg_keys(client) | ||
return JWS().verify_compact(token, keys=keys) | ||
key = client.client_secret |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same: if the algorithm isn't RS256, then the key is the client's secret, otherwise it's the first private key in the database.
The key is passed to pyjwt so it can sign the jwt.
If the key is an rsa key, it must be passed to pyjwt in a format it understands.
This actually brings up another interesting issue: why have an RSAKey model when it could just be an rsakey that exists as a file on the server. I suspect it was just easier to start this way.
|
||
|
||
def client_id_from_id_token(id_token): | ||
""" | ||
Extracts the client id from a JSON Web Token (JWT). | ||
Returns a string or None. | ||
""" | ||
payload = JWT().unpack(id_token).payload() | ||
return payload.get('aud', None) | ||
return jwt.decode(id_token, verify=False).get('aud', None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically the same thing: from an id_token extract the aud. We set verify to False, because the provider is not the audience of this JWT.
@@ -138,22 +134,3 @@ def create_code(user, client, scope, nonce, is_authentication, | |||
code.is_authentication = is_authentication | |||
|
|||
return code | |||
|
|||
|
|||
def get_client_alg_keys(client): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is no longer necessary as the way to get the key has been simplified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By removing this, and switching to the new 'simplified' method, explicit checking for specific algorithms is lost, which opens the door to future issues. I would much prefer to see explicit checking added back - even if it adds some code complexity, the greater clarity in code paths and explicit exception for other algorithms is worth the cost.
@@ -9,8 +11,12 @@ class Command(BaseCommand): | |||
|
|||
def handle(self, *args, **options): | |||
try: | |||
key = RSA.generate(1024) | |||
rsakey = RSAKey(key=key.exportKey('PEM').decode('utf8')) | |||
key = generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The public exponent is the same as the default in pycryptodome. I've upped the default bitsize of the key to 2048 instead of 1024. The key is generated, then serialized into the database in the PEM format.
See https://en.wikipedia.org/wiki/Key_size#Asymmetric_algorithm_key_lengths.
It's a little weird that we always have to specify the default_backend(), but that's for old reasons:
https://cryptography.io/en/latest/hazmat/backends/
Cryptography just uses OpenSSL as its backend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like seeing the move to 2048 bit RSA - 1024 is too weak for use today. Anything less than 2048 should be avoided if at all possible (see for example the move away from 1024 by Web PKI).
@@ -284,18 +287,29 @@ def get(self, request, *args, **kwargs): | |||
|
|||
|
|||
class JwksView(View): | |||
def _num_to_b64_string(self, value): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think pyjwt actually provides an interface for generating a JWK from a key, so I'll change that in a future PR. For now, this is just the way JWK specifies how to give your public key.
@@ -38,10 +38,13 @@ | |||
test_suite='runtests.runtests', | |||
tests_require=[ | |||
'pyjwkest>=1.3.0', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept pyjwkest in the tests to show that this would all be compatible. Just want to show I'm not adding or removing functionality, just refactoring some libraries out.
Thanks for this contribution. Really great job but I don't understand RSA generation.. sorry.. From RTD:
|
key=key.private_bytes( | ||
encoding=serialization.Encoding.PEM, | ||
format=serialization.PrivateFormat.TraditionalOpenSSL, | ||
encryption_algorithm=serialization.NoEncryption()).decode('utf8')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, there would be support for encrypted private keys, to reduce the risk of exposing the private key - this would add a defense in depth layer, though would add somewhat to configuration complexity.
This would be a nice improvement over the current state.
@wojtek-fliposports RSA can be extremely tricky for those that aren't familiar with all the things that can go wrong. In this case, the
These don't have the risk level associated with the direct cryptographic operations that can easily introduce vulnerabilities if mis-used. For RSA Key Generation, it is using OpenSSL to generate a 2048-bit key (which is the minimum that should be used today), with For RSA Key Serialization, this simply encodes the key to the standard PEM format, as the code does today. This is standard and safe functionality. For this functionality, IMO, the hazmat warning can be ignored. This is safe functionality, and is equivalent to, or an improvement on, the current functionality. |
Unfortunatelly a lot of 3rd party libraries using OIDC auth flow requires |
Hi Wojciech - could you elaborate on this RS256 vs RS1024? (or maybe
you made a type with "rs1024" ;-) ) - thanks, Frank.
|
... blah.. delete my comment :/ |
What is the state of this? I would like to see pyca/cryptography beeing used. |
Hi @juanifioren, are you planning to merge this soon? |
See issue #207