pallets-eco / flask-security

Quick and simple security for Flask applications
MIT License
647 stars 155 forks source link

TLS client-cert authentication built-in? #158

Closed TaaviE closed 1 year ago

TaaviE commented 5 years ago

Given that client certificates are commonly regarded as the best method of verifying if the user is authorized or not it would be really nice if Flask-Security could allow such logins with less setup required.

It's also worth mentioning that client certificates are something that quite a lot of countries provide to their citizens (for ex. Estonia, Latvia, Belgium, Finland) and is very commonly used in at least Estonia so there'd definitely be a relatively large user-base for such integration.

I myself implemented it by reusing Flask-Dance's OAuth link schema and providing a few endpoints to accept client-certificates. It would be much easier if common web-server configuration examples and etc. were provided and the endpoints with necessary checks existed.

jwag956 commented 5 years ago

Intriguing - can you point to some code or documentation on exactly how this would work?

TaaviE commented 5 years ago

TLS client certificate as such is specified by the TLS spec. Individual web servers, such as nginx and Apache have their own configuration options for requesting the certificates from the client based on some chain of trust.

After a web server such as nginx has requested, received and verified the certificate it got one basically has the entire client certificate .pem if needed. After that it's up to the implementation how user lookup and auth is done.

I implemented it by just taking $ssl_client_verify, made sure the certificate is issued by the right issuer. After that I took $ssl_client_s_dn's hash, looked that up from the database and then logged the person in if it matched someone. Downside to that approach is that a new cert issued to the same person could mean they get a different DN. Doing it the "proper way", would be parsing out personal identification numbers from all the different countries' DN fields (Estonia, Latvia, etc.) and looking up the user based on that, I didn't do that because I didn't have the resources.

Due to web server limitations I also had to implement a redirect from main domain to the TLS client cert domain, do the authentication (set the cookie) and redirect back, Flask-Security could simplify this quite a bit I think.

https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_client_certificate https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_verify

jwag956 commented 5 years ago

Thanks for the details. So you get the DN from whatever is doing ssl termination. Then you need to either look that up or map it first (using some kind of custom code) then look it up. Seems like that is quite doable today - by creating an @cert_required decorator - then picking the proper request (uwsgi) parameter, and doing any required mapping then issuing a find_user(dn=xxx).

I am not quite seeing what FS could bring since where to find the DN in the parameters would need to be configured, and any sort of mapping from DN to ID would have to be a plugin.

I suppose one nice thing would be that you could annotate @auth_required("token", "cert") which would be convenient. Seems pretty simple really. But I wouldn't try to include mapping algorithms in FS - just allow a callback.

TaaviE commented 5 years ago

I am not quite seeing what FS could bring

I'm thinking that FS could provide:

jwag956 commented 5 years ago

Ok - so it would be more like a new view: /login-cert that would grab uwsgi/request environment variables, invoke a DN to 'id' and look that up, and if everything ok - would login the user just as username/password does today. From their they can use session or token authentication for future requests...

Not sure your concern with cookie - I believe FS / Flask manages session cookie domains correctly (i.e. it has various config parameters to manage it).

DO you have any pointers to these eID/ country certs? This sure feels like a separate (small) package.

TaaviE commented 5 years ago

Not sure your concern with cookie - I believe FS / Flask manages session cookie domains correctly (i.e. it has various config parameters to manage it).

Yes, but it doesn't by-default set a cookie for an another domain name other than the one it's hosted from, with cert auth it needs to set the cookie for one level higher (app.domain.com instead of certauth.app.domain.com for example), that could be automatic.

DO you have any pointers to these eID/ country certs?

https://eid.eesti.ee/index.php/Authenticating_in_web_applications http://wiki.yobi.be/wiki/Belgian_eID

jwag956 commented 5 years ago

Thanks for the links - interesting. Can you explain the cookie issue? Why would you need a subdomain called certauth.app.domain.com?

TaaviE commented 5 years ago

Why would you need a subdomain called certauth.app.domain.com?

Because for example nginx can't ask a client-cert in a path block, only on a server block. Just as a clarifying example /certauth (that requests the cert from the client) can't be done, certauth.app.domain.com however can be done.

jwag956 commented 5 years ago

Thanks for the clarification. Can you point me to some examples of sites that actually accept this? Do they start at app.domain.com - then redirect if you want to log in using cert? how does that actually work.

As for mapping DN to ID - I do think that should be a separate package - mostly so that it can release/evolve separately from FS. We can make it seamless - if that package is installed - FS automatically picks it up.

TaaviE commented 5 years ago

Can you point me to some examples of sites that actually accept this?

https://tara.ria.ee/login https://my.zone.eu/et/zid/server/auth https://id.seb.ee/cgi-bin/ipank/ipank.p https://maasikas.emta.ee

Using cert auth is called ID-kaart in Estonian, Log in is usually Logi sisse or Sisene

As for mapping DN to ID - I do think that should be a separate package - mostly so that it can release/evolve separately from FS. We can make it seamless - if that package is installed - FS automatically picks it up.

It's not that much code though? I suspect it could maybe be done with a few regexes but I lack other ID-cards.

jwag956 commented 1 year ago

With passkeys/webauthn gaining traction - I can't see adding this.