Skip to content
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

Add client certificate to request scope #745

Closed
2 tasks done
mdgilene opened this issue Aug 4, 2020 · 6 comments
Closed
2 tasks done

Add client certificate to request scope #745

mdgilene opened this issue Aug 4, 2020 · 6 comments

Comments

@mdgilene
Copy link

mdgilene commented Aug 4, 2020

Checklist

  • There are no similar issues or pull requests for this yet.
  • I discussed this idea on the community chat and feedback is positive.

Is your feature related to a problem? Please describe.

Currently there is no way to access the client certificate sent with a request from application without setting up some kind of proxy in front of the server that adds the information to the request headers.

Describe the solution you would like.

The client certificate information should be added to the request scope.

I have already tested a solution to this and would be ready to submit a PR for this feature. You can view the potential implementation here mdgilene@9407f0b

Describe alternatives you considered

Setup a proxy server in front of Uvicorn to add the required information to the request headers.

Additional context

This ability is available in server implementations in other languages. Why should the python world be different?

@tomchristie
Copy link
Member

tomchristie commented Aug 7, 2020

Copying context from our chat space... https://gitter.im/encode/community?at=5f2d60db79da21426f1d11a0

mdgilene:

Can I get some input on my feature request #745
To me this seems like a pretty significant missing feature.
Currently, client certificates are only validated.
But this only handles the authentication portion. It does not do anything for authorization.
Having access to the client certificate within an application allows for developers to utilize the certificate for further authorization purposes or to connect external authorization providers such as LDAP.

tomchristie:

Honestly, we're probably not likely to support that, since it's not functionality that's available in either WSGI or ASGI.
You might want to have a look into if and how folks have done that with WSGI servers in the past, since that might give some good indications on how to go about it, or on how to make a case for it in ASGI
Also you'd possibly need to be making a case for it first w/ the maintainer of the ASGI spec, before we'd think about if it's something we wanted to add to uvicorn.
You might find that you need to fork one of the ASGI servers to add that functionality.
I realise that's not the answer you're looking for, but hopefully it's clear why we're currently not likely to add it to uvicorn, unless something in the ASGI spec changes.

You might be able to do something like have nginx pass the client cert into on into a custom header or something?
https://serverfault.com/questions/622855/nginx-proxy-to-back-end-with-ssl-client-certificate-authentication ?

@mdgilene
Copy link
Author

mdgilene commented Aug 7, 2020

I have submitted a proposal to the ASGI spec django/asgiref#187 and will see if it gets any traction. If it does, then I will re-raise the issue here.

@jonfoster
Copy link

I've created a pull request for an "ASGI TLS Extension" that would include this, as well as various other bits of TLS information. django/asgiref#192. Comments & feedback welcome on the pull request.

@wagnerflo
Copy link

wagnerflo commented Dec 21, 2020

Hey @jonfoster,

I've just started implementing this in wagnerflo/uvicorn@de23ac8. For my personal use I just need scope["extensions"]["tls"]["client_cert_chain"][0] to be able to do authorization.

Since I need this for testing and development only (in production I'll be running my applications behind a reverse proxy that'll send the client certificate as a header) I won't be putting any more work in.

Also with my very, very limited experience working with the standard library ssl package it seems to me that your proposed spec is rather hard to implement without a significant amount of code and/or dependencies:

  • SSLObject.getpeercert(binary_form=True) returns DER as bytes. Turning that into PEM requires a call to ssl.DER_cert_to_PEM_cert. No additional dependency here, but thats more work the server has to do and I don't see a gain passing PEM instead of DER.
  • tls_version: SSLObject.version() returns a string that needs to be converted to integer.
  • Turning SSLObject.cipher() into cipher_suite seems like even more work.
  • SSLObject.getpeercert() can return a decoded client certificate but turning the subject key (RDN as a list of tuples) into the RFC4514 encoding for client_cert_name seems like quite a bit of work judging from a quick look at how other libraries do that.
  • Can you even access the complete certificate chain from SSLObject?

I appreciate the work you've put into this and would very much like to have this included in ASGI servers, but IMHO you're setting the bar to entry (work and amount of code/dependencies) to fully support the extension too high to get it adopted widely.

Just my two cents.

@jonfoster
Copy link

Hi @wagnerflo,

I agree with you about the certificate form, I hadn't realised DER was easier. I can change that.

The problem I have with tls_version/cipher is that I want the values to be well-specified, so it is interoperable. And not just specified as "go read the OpenSSL source code and figure out what it does". My experience with OpenSSL is that it's API is extremely poorly documented. I'm not aware of a document mapping the OpenSSL-defined cipher names to the standard names specified in the RFCs and/or the standard cipher numbers.

A lot of open-source software does things the OpenSSL way just because they use OpenSSL and that's easiest for the implementer. But it's hard for someone using the API, or even for someone trying to implement the API using a different TLS library, because the API is effectively undocumented. I'd include Python's standard "ssl" module in that category.

My background includes writing international standards, I want things to be properly specified & documented.

So the API design I came up with puts the cost of understanding OpenSSL, and translating OpenSSL's non-standard cipher names etc into something standard, on the person using OpenSSL. An application using the API user has a well-documented API.

Probably the best long-term solution would be to extend both the OpenSSL API and the Python SSL module, so they can provide the version and ciphersuite using the wire-protocol numbers. In the short term, perhaps I (or another volunteer) could write a library to do the conversions and publish it on PyPi, for use on existing versions of Python that don't have those APIs.

Re the subject key, I agree it's not good but I struggled to find a better solution. I couldn't find any other specification for how to change a subject into a string. And I wanted a single string as that can be used directly as a "username". Perhaps I can specify a simpler encoding.

Thanks for the feedback!

@wagnerflo
Copy link

The problem I have with tls_version/cipher is that I want the values to be well-specified, so it is interoperable. [...]

Probably the best long-term solution would be to extend both the OpenSSL API and the Python SSL module, so they can provide the version and ciphersuite using the wire-protocol numbers. In the short term, perhaps I (or another volunteer) could write a library to do the conversions and publish it on PyPi, for use on existing versions of Python that don't have those APIs.

I'm very much with you there. It's just a hurdle. Though just simply putting off implementing this part of your specification might be fine, IMHO. I can't think of any reason I'd need these protocol parameters in the web application proper.

Re the subject key, I agree it's not good but I struggled to find a better solution. I couldn't find any other specification for how to change a subject into a string. [...]

There also seems to be a format specified in RFC2253. No idea if that'd be any easier to convert to from the SSLObject.getpeercert() tuple, but it could more easily be supported by extending the ssl module since OpenSSL provides an API (also see man openssl-x509).

And I wanted a single string as that can be used directly as a "username".

Yes. That's genuinely useful and basically what I'm also doing. Just that I'm only using the CN part...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants