gavinwahl / django-u2f Goto Github PK
View Code? Open in Web Editor NEWFIDO U2F security token support for Django
License: BSD 2-Clause "Simplified" License
FIDO U2F security token support for Django
License: BSD 2-Clause "Simplified" License
Backup codes by themselves are not a good option for multi-factor authentication, and yet at present it is too easy for users to generate backup codes and, in the process, enable backup-code-only MFA.
I can think of two changes that would help mitigate this:
Change the default templates such that backup code generation links are not displayed until either U2F or TOTP is enabled.
Remove backup codes from the requires_two_factor
function.
While (1) above may be sufficient to avoid the problem in most cases, I'm having a difficult time understanding why someone would want backup-code-only MFA, which is why I proposed (2) as well. That said, perhaps I'm missing something — if so, please enlighten me.
@gavinwahl: What do you think?
I set up the demo project, and after logging in I tried to add a key, but got the error "Attestation signature verification failed!" (from u2flib_server
).
To try to figure out what was going wrong, I tried doing the process manually: I started up u2f_server.py
from python-u2flib-server
(as described in their README) and used curl
to hit the enroll endpoint and get a challenge. I used the JS API (from the Chrome extension) to send the challenge to my key and get the response data, which I submitted to u2f_server.py
using curl
again...and it worked. (My key is also working in Gmail and Yubico's demo.)
I stepped through u2f.complete_register
for both the successful (manual) request as well as the unsuccessful one from the test project, and didn't spot a problem... Do you know what might be going wrong?
The server should verify the channel ID that the token signed:
https://developers.yubico.com/U2F/Protocol_details/Overview.html#_2_phishing_and_mitm_protection
https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-u2f-overview-ps-20141009.html#h2_man-in-the-middle-protections-during-authentication
I'm not sure how web servers pass Channel IDs to applications, so this will require some investigation.
I tried to install django-u2f followed all the procedure then when i ran the command
python manage.py syncdb
the error i am getting it
from django_u2f import views as u2f_views
File "/home/r3dt3am/Desktop/projects/U2F/venv/local/lib/python2.7/site-packages/django_u2f/views.py", line 24, in <module>
from u2flib_server import u2f_v2 as u2f
ImportError: cannot import name u2f_v2
Hi you.
I run server with the follow command:
......django-u2f/testproj$ sudo python manage.py runserver_plus --cert localhost
When I try to access "https://localhost:8000/u2f/login/" I meet an error as below:
127.0.0.1 - - [14/Jan/2017 17:00:42] "GET /u2f/login/ HTTP/1.1" 500 -
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/django/contrib/staticfiles/handlers.py", line 63, in call
return self.application(environ, start_response)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/wsgi.py", line 170, in call
response = self.get_response(request)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 124, in get_response
response = self._middleware_chain(request)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/exception.py", line 41, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/exception.py", line 86, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/exception.py", line 128, in handle_uncaught_exception
return debug.technical_500_response(request, *exc_info)
File "/usr/local/lib/python2.7/dist-packages/django_extensions/management/technical_response.py", line 6, in null_technical_500_response
six.reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/exception.py", line 39, in inner
response = get_response(request)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
response = self._get_response(request)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 217, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 215, in _get_response
response = response.render()
File "/usr/local/lib/python2.7/dist-packages/django/template/response.py", line 109, in render
self.content = self.rendered_content
File "/usr/local/lib/python2.7/dist-packages/django/template/response.py", line 84, in rendered_content
template = self.resolve_template(self.template_name)
File "/usr/local/lib/python2.7/dist-packages/django/template/response.py", line 68, in resolve_template
return get_template(template, using=self.using)
File "/usr/local/lib/python2.7/dist-packages/django/template/loader.py", line 25, in get_template
raise TemplateDoesNotExist(template_name, chain=chain)
TemplateDoesNotExist: u2f/login.html
I’ve seen error messages that weren’t encapsulated in the gettext_lazy()
function. We should make sure that every string echoed by Django-U2F is ready to be translated, and provide a second language support. I’m willing to work on french translations
I prefer to have more control over URL hierarchy. For example, I would prefer that the u2f:login
view be served via /login/
instead of /u2f/login/
, with the rest of the U2F-related URLs available underneath /settings/security/
. I also would like the ability to customize the URL slugs of some of the U2F views. In an effort to accomplish this, I currently use:
u2f_urls = [
url(r'^multi-factor/', u2f_views.two_factor_settings,
name='two-factor-settings'),
url(r'^add-key/', u2f_views.add_key, name='add-u2f-key'),
url(r'^verify-factor/', u2f_views.verify_second_factor,
name='verify-second-factor'),
url(r'^keys/', u2f_views.keys, name='u2f-keys'),
url(r'^backup-codes/', u2f_views.backup_codes, name='backup-codes'),
url(r'^add-totp-device/', u2f_views.add_totp, name='add-totp'),
url(r'^totp-devices/', u2f_views.totp_devices, name='totp-devices'),
]
urlpatterns = [
url(r'^login/', u2f_views.login, name='login'),
url(r'^logout/$', auth_views.LogoutView.as_view(
next_page=reverse_lazy('login')),
name='logout'),
url(r'^settings/security/', include(u2f_urls, namespace='u2f')),
[...]
Question 1: Is there a better way to handle this?
Question 2... In certain rare cases, there will be an unauthenticated request for the verify-second-factor
view, which is handled by this bit of code, which yields a NoReverseMatch
error because the u2f:login
route doesn't exist in my setup above.
I suppose I could add a duplicate login
entry in the top stanza so that u2f:login
exists, but then I've got redundant login routes at /login/
and /settings/security/login/
.
My understanding of Django URL route namespaces is admittedly quite limited, but I can't quite think of the best way to handle this. Might you have any suggestions?
I just ran the Demo and noticed a DeprecationWarning:
/home/user/django-u2f/venv/lib/python3.5/site-packages/u2flib_server/model.py:210: CryptographyDeprecationWarning: signer and verifier have been deprecated. Please use sign and verify instead.
verifier = pubkey.verifier(self.signature, ec.ECDSA(hashes.SHA256()))
I updated from an earlier version of django-u2f, and none of the key communication works. I can't sign in or add a new key.
The javascript console shows:
u2f-api.js:607 Unknown or missing requestId in response.
```
This is under Chrome 59.0.3071.86
We want templates that could be used in a real environment without too many shame, for a start
Installs via pip:
17:59:15 [aaron@ender:~/code] develop* 1 ± pip install git+https://github.com/gavinwahl/django-u2f.git
Downloading/unpacking git+https://github.com/gavinwahl/django-u2f.git
Cloning https://github.com/gavinwahl/django-u2f.git to /tmp/pip-yjKj6u-build
Running setup.py (path:/tmp/pip-yjKj6u-build/setup.py) egg_info for package from git+https://github.com/gavinwahl/django-u2f.git
Downloading/unpacking django-argonauts (from django-u2f==0.0.0)
Downloading django-argonauts-1.1.4.tar.gz
Running setup.py (path:/home/aaron/.virtualenvs/code/build/django-argonauts/setup.py) egg_info for package django-argonauts
Requirement already satisfied (use --upgrade to upgrade): django>=1.8 in /home/aaron/.virtualenvs/code/lib/python2.7/site-packages (from django-u2f==0.0.0)
Downloading/unpacking qrcode (from django-u2f==0.0.0)
Downloading qrcode-5.1.tar.gz
Running setup.py (path:/home/aaron/.virtualenvs/code/build/qrcode/setup.py) egg_info for package qrcode
Requirement already satisfied (use --upgrade to upgrade): six in /home/aaron/.virtualenvs/code/lib/python2.7/site-packages (from django-u2f==0.0.0)
Installing collected packages: django-argonauts, qrcode, django-u2f
Running setup.py install for django-argonauts
Running setup.py install for qrcode
Installing qr script to /home/aaron/.virtualenvs/bin
Running setup.py install for django-u2f
Successfully installed django-argonauts qrcode django-u2f
Cleaning up...
18:00:26 [aaron@ender:~/code] develop(+3/-0)* 1 ±
Try to run the devserver:
18:00:45 [aaron@ender:~/code] develop(+3/-0)* 1 ± python manage.py runserver 0.0.0.0:8000
Traceback (most recent call last):
File "manage.py", line 10, in <module>
execute_from_command_line(sys.argv)
File "/home/aaron/.virtualenvs/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
utility.execute()
File "/home/aaron/.virtualenvs/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 312, in execute
django.setup()
File "/home/aaron/.virtualenvs/local/lib/python2.7/site-packages/django/__init__.py", line 18, in setup
apps.populate(settings.INSTALLED_APPS)
File "/home/aaron/.virtualenvs/local/lib/python2.7/site-packages/django/apps/registry.py", line 85, in populate
app_config = AppConfig.create(entry)
File "/home/aaron/.virtualenvs/local/lib/python2.7/site-packages/django/apps/config.py", line 86, in create
module = import_module(entry)
File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
__import__(name)
ImportError: No module named django_u2f
18:02:25 [aaron@ender:~/code] develop(+3/-0)* 1 ±
Looking in site-packages directory:
18:04:07 [aaron@ender:~/.virtualenvs/lib/python2.7/site-packages] [] $ find | grep -i u2f
./django_u2f-0.0.0-py2.7.egg-info
./django_u2f-0.0.0-py2.7.egg-info/dependency_links.txt
./django_u2f-0.0.0-py2.7.egg-info/requires.txt
./django_u2f-0.0.0-py2.7.egg-info/SOURCES.txt
./django_u2f-0.0.0-py2.7.egg-info/PKG-INFO
./django_u2f-0.0.0-py2.7.egg-info/top_level.txt
./django_u2f-0.0.0-py2.7.egg-info/installed-files.txt
18:04:10 [aaron@ender:~/.virtualenvs/lib/python2.7/site-packages] [] $
There is no django_u2f module folder.
TemplateDoesNotExist at /u2f/keys/
base.html
Request Method: GET
Request URL: https://localhost:8000/u2f/keys/
Django Version: 1.9.6
Exception Type: TemplateDoesNotExist
Exception Value:
base.html
Exception Location: /home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python3.5/site-packages/django/template/engine.py in find_template, line 169
Python Executable: /home/niemand/Development/Django/u2f_django_nzpug/venv/bin/python
Python Version: 3.5.1
Python Path:
['/home/niemand/Development/Django/u2f_django_nzpug',
'/home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python35.zip',
'/home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python3.5',
'/home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python3.5/plat-linux',
'/home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python3.5/lib-dynload',
'/usr/lib64/python3.5',
'/usr/lib/python3.5',
'/usr/lib/python3.5/plat-linux',
'/home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python3.5/site-packages']
Server time: Thu, 12 May 2016 04:40:41 +0000
Template-loader postmortem
Django tried loading these templates, in this order:
Using engine django:
django.template.loaders.app_directories.Loader: /home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python3.5/site-packages/django/contrib/admin/templates/base.html (Source does not exist)
django.template.loaders.app_directories.Loader: /home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python3.5/site-packages/django/contrib/auth/templates/base.html (Source does not exist)
django.template.loaders.app_directories.Loader: /home/niemand/Development/Django/u2f_django_nzpug/venv/lib/python3.5/site-packages/django_u2f/templates/base.html (Source does not exist)
Yubico says that challenges should expire: https://developers.yubico.com/U2F/Libraries/Advanced_topics.html
Challenge Expiration
It is good practice to treat challenges older than X minutes as expired.
Gavin, I've made a hacky proof of concept for U2F with django-two-factor-auth moreati/django-two-factor-auth@a44ac23. It doesn't use django-u2f, but it's based heavily on it.
I think the proper route to integration is a django-otp plugin that implements U2F. For the next step the options I see are:
django_u2f.models
, if possible/desirable)Do you have a preference? Based on simplicity/maintenance mine is the 2nd, but I don't have an existing install base or users.
Are you aware of anybody using django-u2f in anger?
Any other thoughts? Queries?
The /add-key/
page displays the text:
To add a security key to your account, insert (and tap) it.
Error with U2F
With a textbox below it containing the text:
{"errorCode":2}
My key works for signing in to Google, Github, etc...so I don't think it's a key problem.
I don't get any errors in the Django console or the Chrome console.
Hi.
When trying this app, I used my main browser, Firefox, which normally works with u2f (on Github, for example). I created a self-signed certificate, and served the testproject on HTTPS, as described on the readme.
On a logged-in account, the https://localhost:8000/u2f/add-key/
page immediately shows an error in the input field: {"errorCode":2}
(which means that the appId is incorrect, but the code shows "appId": "https://localhost:8000"
).
On Chromium (where my keys work as expected), the register request works fine, the page waits for me to plug my key, and when I press the button, the token is written on the field, and the form is submitted.
I tried to simulate a non-local environment, with a fake host in /etc/hosts
and a listen port of 443. Still no success (chromium works, but not firefox).
I can’t figure yet what is wrong for Firefox. Do you have any idea?
Thanks.
The admin uses it's own login view, which delegates to django.contrib.auth.views.login, which doesn't require a key to log in. In the current implementation, this allows you to bypass two-factor auth by just using the admin login view instead of django-u2f's.
The only solution I can't think of is to replace (monkey-patch) auth.views.login with django_u2f.views.login.
There's also a similar bug when someone accidentally registers a non-u2f login view urlpattern accidentally. If the monkey patching happens before urls are loaded, we can prevent this mistake too.
Many thanks, Gavin, for all your work on django-u2f
. I was very pleasantly surprised to see the inclusion of the demo project, which by the way functioned perfectly right out of the box. Bravo!
With full realization that the ReadMe mentions Chrome/Chromium as the only supported browser, I could not help but try Firefox with the U2F add-on (source). When I tapped the U2F device to finalize adding the key, I got an error: ValueError: Missing required fields: version
(full traceback follows below).
Interestingly, if I use Chromium to add the U2F key, I can subsequently use Firefox and the U2F add-on to log in successfully.
That said, it would be nice to be able to use Firefox to add U2F keys as well. Do you have any idea what might be behind the error, and perhaps what might be done to fix it?
Any exception (for example, a JSONDecodeError
) while decoding the responses from the client leads to an error page instead of showing the form again with a readable error
ie, /u2f/login/?next=/foo/
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.