aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpal Symes <leesdolphin@gmail.com>2018-08-06 13:18:15 +1200
committerOpal Symes <leesdolphin@gmail.com>2018-08-06 13:18:15 +1200
commit9125e21073d033c7cd7665af681b74dbc1086c35 (patch)
treed55e2a269694be26e7f082a9f4cd17e88d630dae
parent28f20ceea004bfe2a6e3dcdedd5576a39cb45b2d (diff)
downloadpy-fido-9125e21073d033c7cd7665af681b74dbc1086c35.tar.gz
py-fido-9125e21073d033c7cd7665af681b74dbc1086c35.tar.bz2
py-fido-9125e21073d033c7cd7665af681b74dbc1086c35.zip
Working with a test server.
Need to iron out the dependencies and do a release
-rw-r--r--Pipfile1
-rw-r--r--Pipfile.lock98
-rw-r--r--fido_u2f/registration.py9
-rw-r--r--fido_u2f/sample/device.py41
-rw-r--r--fido_u2f/sample/flask.py167
-rw-r--r--fido_u2f/sample/login.js31
-rw-r--r--fido_u2f/sample/register.js31
-rw-r--r--fido_u2f/sample/tables.py57
-rw-r--r--fido_u2f/tests/test_response_parse_validate.py14
-rw-r--r--fido_u2f/utils.py1
-rw-r--r--fido_u2f/verification.py17
-rw-r--r--setup.py13
12 files changed, 410 insertions, 70 deletions
diff --git a/Pipfile b/Pipfile
index a291e68..a72cc86 100644
--- a/Pipfile
+++ b/Pipfile
@@ -10,6 +10,7 @@ docs = "sphinx-build -M html docs/source docs/build"
[packages]
"e1839a8" = {path = ".", editable = true}
+pyopenssl = "*"
[dev-packages]
"flake8" = "~=3.5"
diff --git a/Pipfile.lock b/Pipfile.lock
index 3ff090e..5d08bb2 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "8b74a7a13e96e5e6476adf6bcff979e1277baeb32c6c6a0cf1ab04a828ac225b"
+ "sha256": "254eb06b1a34d53c52a00fcabd6a0d07960e92d4831aed81afe521d9e3d8fb86"
},
"pipfile-spec": 6,
"requires": {
@@ -60,6 +60,13 @@
],
"version": "==1.11.5"
},
+ "click": {
+ "hashes": [
+ "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
+ "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
+ ],
+ "version": "==6.7"
+ },
"cryptography": {
"hashes": [
"sha256:21af753934f2f6d1a10fe8f4c0a64315af209ef6adeaee63ca349797d747d687",
@@ -88,6 +95,20 @@
"editable": true,
"path": "."
},
+ "flask": {
+ "hashes": [
+ "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
+ "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
+ ],
+ "version": "==1.0.2"
+ },
+ "flask-sqlalchemy": {
+ "hashes": [
+ "sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b",
+ "sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53"
+ ],
+ "version": "==2.3.2"
+ },
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
@@ -95,18 +116,58 @@
],
"version": "==2.7"
},
+ "itsdangerous": {
+ "hashes": [
+ "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
+ ],
+ "version": "==0.24"
+ },
+ "jinja2": {
+ "hashes": [
+ "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
+ "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
+ ],
+ "version": "==2.10"
+ },
+ "markupsafe": {
+ "hashes": [
+ "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
+ ],
+ "version": "==1.0"
+ },
"pycparser": {
"hashes": [
"sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226"
],
"version": "==2.18"
},
+ "pyopenssl": {
+ "hashes": [
+ "sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854",
+ "sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580"
+ ],
+ "index": "pypi",
+ "version": "==18.0.0"
+ },
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
+ },
+ "sqlalchemy": {
+ "hashes": [
+ "sha256:72325e67fb85f6e9ad304c603d83626d1df684fdf0c7ab1f0352e71feeab69d8"
+ ],
+ "version": "==1.2.10"
+ },
+ "werkzeug": {
+ "hashes": [
+ "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
+ "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
+ ],
+ "version": "==0.14.1"
}
},
"develop": {
@@ -249,11 +310,11 @@
},
"more-itertools": {
"hashes": [
- "sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
- "sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3",
- "sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"
+ "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
+ "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
+ "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
],
- "version": "==4.2.0"
+ "version": "==4.3.0"
},
"mypy": {
"hashes": [
@@ -270,6 +331,14 @@
],
"version": "==17.1"
},
+ "pathlib2": {
+ "hashes": [
+ "sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83",
+ "sha256:d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a"
+ ],
+ "markers": "python_version < '3.6'",
+ "version": "==2.3.2"
+ },
"pep8-naming": {
"hashes": [
"sha256:360308d2c5d2fff8031c1b284820fbdb27a63274c0c1a8ce884d631836da4bdd",
@@ -280,11 +349,10 @@
},
"pluggy": {
"hashes": [
- "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
- "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
- "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
+ "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
+ "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
],
- "version": "==0.6.0"
+ "version": "==0.7.1"
},
"py": {
"hashes": [
@@ -328,11 +396,11 @@
},
"pytest": {
"hashes": [
- "sha256:0453c8676c2bee6feb0434748b068d5510273a916295fd61d306c4f22fbfd752",
- "sha256:4b208614ae6d98195430ad6bde03641c78553acee7c83cec2e85d613c0cd383d"
+ "sha256:86a8dbf407e437351cef4dba46736e9c5a6e3c3ac71b2e942209748e76ff2086",
+ "sha256:e74466e97ac14582a8188ff4c53e6cc3810315f342f6096899332ae864c1d432"
],
"index": "pypi",
- "version": "==3.6.3"
+ "version": "==3.7.1"
},
"pytz": {
"hashes": [
@@ -380,11 +448,11 @@
},
"sphinx-rtd-theme": {
"hashes": [
- "sha256:aa3e190392e963551432de7df24b8a5fbe5b71a2f4fcd9d5b75808b52ad999e5",
- "sha256:de88d637a60371d4f923e06b79c4ba260490c57d2ab5a8316942ab5d9a6ce1bf"
+ "sha256:3b49758a64f8a1ebd8a33cb6cc9093c3935a908b716edfaa5772fd86aac27ef6",
+ "sha256:80e01ec0eb711abacb1fa507f3eae8b805ae8fa3e8b057abfdf497e3f644c82c"
],
"index": "pypi",
- "version": "==0.4.0"
+ "version": "==0.4.1"
},
"sphinxcontrib-websupport": {
"hashes": [
diff --git a/fido_u2f/registration.py b/fido_u2f/registration.py
index 9e45c18..97f4947 100644
--- a/fido_u2f/registration.py
+++ b/fido_u2f/registration.py
@@ -26,11 +26,12 @@ class U2FRegistrationManager(abc.ABC):
@abc.abstractmethod
def create_device_registration_model(
self,
+ *,
version: str,
app_id: str,
key_handle: bytes,
public_key: bytes,
- transports: U2FTransports,
+ transports: U2FTransports
) -> DeviceRegistration:
...
@@ -42,7 +43,7 @@ class U2FRegistrationManager(abc.ABC):
"""
Generate a challenge and return information for the registration step.
- This will generate a secure random challenge, placing it in the session
+ This will generate a secure random challenge, placing it in the session
object, and then return a JSON-safe object for use by the client to
complete the challenge
"""
@@ -89,7 +90,7 @@ class U2FRegistrationManager(abc.ABC):
) -> 'RegistrationData':
try:
registration_data = RegistrationData.from_base64(
- response_dict.get('responseData', ''))
+ response_dict.get('registrationData', ''))
except (ValueError, IndexError) as e:
raise U2FInvalidDataException('Invalid registration data.') from e
# Client data comes in as base64(usually?), so we standardise it
@@ -101,7 +102,7 @@ class U2FRegistrationManager(abc.ABC):
self.app_id,
challenge,
)
- challenge_param = sha_256(client_data)
+ challenge_param = sha_256(client_data.encode('utf-8'))
app_param = sha_256(self.app_id.encode('idna'))
registration_data.verify(app_param, challenge_param)
return registration_data
diff --git a/fido_u2f/sample/device.py b/fido_u2f/sample/device.py
deleted file mode 100644
index d37a783..0000000
--- a/fido_u2f/sample/device.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# import typing as typ
-#
-# from .enums import U2FTransport
-# from .utils import websafe_encode, websafe_decode
-#
-#
-# class DeviceRegistration():
-#
-# """The U2F protocol version to use."""
-# version: str
-# """The appId used during registration."""
-# app_id: str
-# """The base64 encoded `keyHandle`."""
-# encoded_key_handle: str
-# """The base64 encoded `pubKey`."""
-# encoded_public_key: str
-# """An integer-encoded `U2FTransport`. Use `u2f_transports` instead."""
-# transports: int
-# """A 32-bit unsigned integer representing the last seen counter."""
-# counter: int
-#
-# @property
-# def u2f_transports(self) -> typ.Optional[typ.Collection[U2FTransport]]:
-# if self._transports < 0:
-# return None
-# return U2FTransport._from_internal_int(self._transports)
-#
-# @u2f_transports.setter
-# def u2f_transports(
-# self,
-# transports: typ.Optional[typ.Collection[U2FTransport]],
-# ):
-# self._transports = U2FTransport._to_internal_int(transports)
-#
-# @property
-# def key_handle(self) -> bytes:
-# return websafe_decode(self.encoded_key_handle)
-#
-# @property
-# def public_key(self) -> bytes:
-# return websafe_decode(self.encoded_public_key)
diff --git a/fido_u2f/sample/flask.py b/fido_u2f/sample/flask.py
new file mode 100644
index 0000000..20cc1d7
--- /dev/null
+++ b/fido_u2f/sample/flask.py
@@ -0,0 +1,167 @@
+import json
+import pathlib
+
+from flask import Flask, request, session, redirect
+
+from .tables import db, User, Device
+from .. import registration, verification
+
+app = Flask(__name__)
+app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/data.db'
+app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
+app.config['SECRET_KEY'] = 'WebAuthN would be nice one day.'
+app.config['DEBUG'] = True
+
+db.init_app(app)
+
+@app.before_first_request
+def _():
+ db.create_all()
+
+
+class U2FManager(registration.U2FRegistrationManager, verification.U2FSigningManager):
+
+ def __init__(self):
+ self.app_id = 'https://localhost:5000'
+
+ def create_device_registration_model(self, *, transports, **kwargs):
+ device = Device()
+ device.u2f_transports = transports
+ for k, v in kwargs.items():
+ setattr(device, k, v)
+ db.session.add(device)
+ db.session.commit()
+ return device
+
+ def update_device_registration_counter(self, device, counter):
+ device.counter = counter
+ return device
+
+
+u2f_manager = U2FManager()
+
+
+@app.route("/")
+def hello():
+ dat = '<table>'
+ dat += '<tr><th>Username</th><th>Registered keys</th></tr>'
+ for user in User.query.all():
+ dat += '<tr><td>' + user.name + '</td><td><table>\n'
+ dat += '<tr><th>Index</th><th>Key Handle</th><th>Counter</th></tr>\n'
+ for idx, key in enumerate(user.devices):
+ dat += '<tr><td>{0}</td><td>{1.key_handle}</td><td>{1.counter}</td></tr>\n'.format(idx, key)
+ dat += '</table></td></tr>'
+ dat += '</table>'
+
+ pre = ''
+ if request.args.get('login', None) == 'success':
+ pre = '<h1>You logged in successfully!!!!!!!!!!!!!!!</h1>'
+
+ return pre + """
+ <form method='POST' action='/register' >
+ <input type='text' name='name' value='admin' />
+ <input type='submit' value='Register new Device' />
+ </form>
+ <form method='POST' action='/login' >
+ <input type='text' name='name' value='admin' />
+ <input type='submit' value='Verify registered device' />
+ </form>
+ """ + dat
+
+
+@app.route("/register.js", methods=['GET'])
+def get_register_js():
+ return (pathlib.Path(__file__).parent / 'register.js').open('r').read()
+
+
+@app.route("/register", methods=['POST'])
+def do_register_start():
+ user_name = request.form['name']
+ session['user_name'] = user_name
+ user = User.query.filter_by(name=user_name).first()
+ if not user:
+ user = User(name=user_name)
+ db.session.add(user)
+ db.session.commit()
+ data = u2f_manager.create_registration_challenge(session, user.devices)
+
+ js = """
+ const appId = {appId};
+ const registerRequests = {registerRequests};
+ const registeredKeys = {registeredKeys};
+ """.format(
+ appId=json.dumps(data['appId']),
+ registerRequests=json.dumps(data['registerRequests']),
+ registeredKeys=json.dumps(data['registeredKeys'])
+ )
+ return (
+ '<pre>' + js + '</pre><script>' + js + '</script>' +
+ '<span id="u2f_status">Loading</span><br /><a href="/">Back</a>' +
+ '<form id="u2f_data" method="POST" action="/register2">'+
+ '<input name="data" id="data" />'+
+ '</form>'
+ '<script src="register.js"></script>'
+ )
+
+@app.route("/register2", methods=['POST'])
+def do_register_verify():
+ user_name = session['user_name']
+ user = User.query.filter_by(name=user_name).first()
+ if not user:
+ return 'No user by that name; <a href="/">Back</a>'
+ data = request.json or json.loads(request.form['data'])
+ device = u2f_manager.process_registration_response(
+ session, data
+ )
+ device.user = user
+ db.session.commit()
+ return redirect('/')
+
+
+@app.route("/login.js", methods=['GET'])
+def get_login_js():
+ return (pathlib.Path(__file__).parent / 'login.js').open('r').read()
+
+@app.route("/login", methods=['POST'])
+def do_login_start():
+ user_name = request.form['name']
+ session.user_name = user_name
+ user = User.query.filter_by(name=user_name).first()
+ if not user:
+ return 'No user by that name; register a device first? <a href="/">Back</a>'
+ if not user.devices:
+ return 'No devices for that user; register a device first? <a href="/">Back</a>'
+
+ devices = user.devices
+ data = u2f_manager.create_signing_challenge(session, devices)
+ js = """
+ const appId = {appId};
+ const challenge = {challenge};
+ const registeredKeys = {registeredKeys};
+ """.format(
+ appId=json.dumps(data['appId']),
+ challenge=json.dumps(data['challenge']),
+ registeredKeys=json.dumps(data['registeredKeys'])
+ )
+ return (
+ '<pre>' + js + '</pre><script>' + js + '</script>' +
+ '<span id="u2f_status">Loading</span><br /><a href="/">Back</a>' +
+ '<form id="u2f_data" method="POST" action="/login2">'+
+ '<input name="data" id="data" />'+
+ '</form>'
+ '<script src="login.js"></script>'
+ )
+
+
+@app.route("/login2", methods=['POST'])
+def do_login_verify():
+ user_name = session['user_name']
+ user = User.query.filter_by(name=user_name).first()
+ if not user:
+ return 'No user by that name; <a href="/">Back</a>'
+ data = request.json or json.loads(request.form['data'])
+ device = u2f_manager.process_signing_response(
+ session, data, user.devices
+ )
+ db.session.commit()
+ return redirect('/?login=success')
diff --git a/fido_u2f/sample/login.js b/fido_u2f/sample/login.js
new file mode 100644
index 0000000..c91de5e
--- /dev/null
+++ b/fido_u2f/sample/login.js
@@ -0,0 +1,31 @@
+if (window.u2f) { window.u2f_status.innerText = 'Register your device now!' }
+else { window.u2f_status.innerText = 'No u2f library.' }
+window.u2f.sign(appId, challenge, registeredKeys, function (response) {
+ console.log(response);
+ if (response.errorCode) {
+ window.u2f_status.innerText = "Failed with error.";
+ } else {
+ window.u2f_status.innerText = "Registration recieved; just verifying now."
+ window.data.value = JSON.stringify(response)
+ window.setTimeout(function () { window.u2f_data.submit() }, 1);
+
+ // window.fetch(
+ // '/register2',
+ // {
+ // 'method': 'POST',
+ // 'body': JSON.stringify(response),
+ // 'headers': {'content-type': 'application/json'},
+ // 'redirect': 'follow',
+ // }
+ // ).then(function (resp) {
+ // console.log(resp);
+ // if (resp.ok) {
+ // window.u2f_status.innerText = "Registration Completed"
+ // } else {
+ // window.u2f_status.innerText = "Registration errored!"
+ // }
+ // }).catch(function (err) {
+ // window.u2f_status.innerText = "Registration errored!"
+ // })
+ }
+});
diff --git a/fido_u2f/sample/register.js b/fido_u2f/sample/register.js
new file mode 100644
index 0000000..577614a
--- /dev/null
+++ b/fido_u2f/sample/register.js
@@ -0,0 +1,31 @@
+if (window.u2f) { window.u2f_status.innerText = 'Register your device now!' }
+else { window.u2f_status.innerText = 'No u2f library.' }
+window.u2f.register(appId, registerRequests, registeredKeys, function (response) {
+ console.log(response);
+ if (response.errorCode) {
+ window.u2f_status.innerText = "Failed with error.";
+ } else {
+ window.u2f_status.innerText = "Registration recieved; just verifying now."
+ window.data.value = JSON.stringify(response)
+ window.setTimeout(function () { window.u2f_data.submit() }, 1);
+
+ // window.fetch(
+ // '/register2',
+ // {
+ // 'method': 'POST',
+ // 'body': JSON.stringify(response),
+ // 'headers': {'content-type': 'application/json'},
+ // 'redirect': 'follow',
+ // }
+ // ).then(function (resp) {
+ // console.log(resp);
+ // if (resp.ok) {
+ // window.u2f_status.innerText = "Registration Completed"
+ // } else {
+ // window.u2f_status.innerText = "Registration errored!"
+ // }
+ // }).catch(function (err) {
+ // window.u2f_status.innerText = "Registration errored!"
+ // })
+ }
+});
diff --git a/fido_u2f/sample/tables.py b/fido_u2f/sample/tables.py
new file mode 100644
index 0000000..c9d6dd4
--- /dev/null
+++ b/fido_u2f/sample/tables.py
@@ -0,0 +1,57 @@
+from flask_sqlalchemy import SQLAlchemy
+from sqlalchemy import Column, ForeignKey, Integer, LargeBinary, String
+from sqlalchemy.orm import relationship
+
+from ..enums import U2FTransport
+from ..device import DeviceRegistration
+
+
+db = SQLAlchemy()
+
+
+class User(db.Model):
+ __tablename__ = 'User'
+
+ id = Column(Integer, primary_key=True)
+ name = Column(String)
+ devices = relationship('Device', back_populates='user')
+
+ def __init__(self, name=None):
+ self.name = name
+
+ def __repr__(self):
+ return "<User(%s, name='%s')>" % (self.id, self.name)
+
+
+class Device(db.Model, DeviceRegistration):
+ __tablename__ = 'Device'
+
+ id = Column(Integer, primary_key=True)
+ user_id = Column(Integer, ForeignKey('User.id'))
+ user = relationship("User", back_populates="devices")
+
+ """The U2F protocol version to use."""
+ version = Column(String)
+ """The appId used during registration."""
+ app_id = Column(String)
+ """The raw `keyHandle`."""
+ key_handle = Column(LargeBinary)
+ """The raw `pubKey`."""
+ public_key = Column(LargeBinary)
+ """An integer-encoded `U2FTransport`. Use `u2f_transports` instead."""
+ transports = Column(Integer)
+ """A 32-bit unsigned integer representing the last seen counter."""
+ counter = Column(Integer)
+
+ @property
+ def u2f_transports(self):
+ if self.transports < 0:
+ return None
+ return U2FTransport._from_internal_int(self.transports)
+
+ @u2f_transports.setter
+ def u2f_transports(
+ self,
+ transports,
+ ):
+ self.transports = U2FTransport._to_internal_int(transports)
diff --git a/fido_u2f/tests/test_response_parse_validate.py b/fido_u2f/tests/test_response_parse_validate.py
new file mode 100644
index 0000000..ab8bb78
--- /dev/null
+++ b/fido_u2f/tests/test_response_parse_validate.py
@@ -0,0 +1,14 @@
+
+
+ATTESTION_DATA = """
+{
+ "rawId": "imCIoe8U_N9M1rTGeCqJ96TAu5uqSPa7YUzdh7qq-AdJlnBl8NwCpu2-sNj9UIVH5rAjX_RXlSGTGWKexKIZXA",
+ "id": "imCIoe8U_N9M1rTGeCqJ96TAu5uqSPa7YUzdh7qq-AdJlnBl8NwCpu2-sNj9UIVH5rAjX_RXlSGTGWKexKIZXA",
+ "response": {
+ "attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEYwRAIgRvfOaUcMVmHqrKzXSH2Inb4PIshESObwuPrtTS_W3RMCICF_qfvwZhDRF8bqiNGYty2iXcOxY8Tgi7TgQJHZqi4wY3g1Y4FZAlMwggJPMIIBN6ADAgECAgQ8aClNMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAxMS8wLQYDVQQDDCZZdWJpY28gVTJGIEVFIFNlcmlhbCAyMzkyNTczNDgxMTExNzkwMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL3fZ5Pbd5TDUDFx7SxNRUrZc2Z1Gki6pdn5tWo6IIF5a07fK817knoUkxD7xGhHb_xXkql9ti-gKGvGoyACDmOjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS41MBMGCysGAQQBguUcAgEBBAQDAgUgMA0GCSqGSIb3DQEBCwUAA4IBAQCqwA1RCX7sFaSGs3m8xINA-GfTly7Oamf7pHDjYMZEWfCtOELT_wgeceqJU5cbI_klwK0AwkcxGFIG8LOpGSn7kbdmtT_hM1Iqg1i40SC0q_t_6O8ke2T_xqYhSsHZvnM2_eDzqBg_k0tSGHX14_eJgK-XClseBCo4dtdLqL7v6S3S43PMZEHIlK182aT0fa09pP6vR5GYR1PjWgic5Mvj08g26tCip86lYVrX5EgQhsN3s2ZE0vuZa7zimyGtuJX3k4LuxUk-TsEzwhZ_B3H1mTFzEg_yjVPogaiXQMEyzzw0aCy7z05dvcHggCIfh1KZgUHdFJbXDzqwPyxbwH-taGF1dGhEYXRhWMRJlg3liA6MaHQ0Fw9kdmBbj-SuuaKGMseZXPO6gx2XY0EAAAAAAAAAAAAAAAAAAAAAAAAAAABAimCIoe8U_N9M1rTGeCqJ96TAu5uqSPa7YUzdh7qq-AdJlnBl8NwCpu2-sNj9UIVH5rAjX_RXlSGTGWKexKIZXKUBAgMmIAEhWCB7XpGVxTYo6jtkxB7sBR4Af_YM0GvInN5V7IvUIilN2yJYINphegJ6kNET_VIp0QOxssW8xxUFEgg5ic3HXmoGg4fS",
+ "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJJSFdtWjFPa1MydDZLaHZYLWtvTnh1dGtZdU1WRXVuQ2pZTlNYWGdBeHZVIiwibmV3X2tleXNfbWF5X2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0"
+ },
+ "getClientExtensionResults": {},
+ "type": "public-key"
+}
+"""
diff --git a/fido_u2f/utils.py b/fido_u2f/utils.py
index ecd3a68..d82cf28 100644
--- a/fido_u2f/utils.py
+++ b/fido_u2f/utils.py
@@ -50,6 +50,7 @@ def validate_client_data(
) -> str:
standardised_client_data = standardise_client_data(raw_client_data)
client_data = load_client_data(standardised_client_data)
+ print(client_data)
if client_data.get('typ', None) != request_type.value:
raise U2FInvalidDataException('Invalid or missing request type')
if client_data.get('origin', None) != app_id:
diff --git a/fido_u2f/verification.py b/fido_u2f/verification.py
index 6f8620b..c1309e0 100644
--- a/fido_u2f/verification.py
+++ b/fido_u2f/verification.py
@@ -42,8 +42,9 @@ class U2FSigningManager(abc.ABC):
@abc.abstractmethod
def update_device_registration_counter(
self,
+ *,
device: DeviceRegistration,
- counter: int,
+ counter: int
) -> DeviceRegistration:
...
@@ -82,15 +83,19 @@ class U2FSigningManager(abc.ABC):
registered_devices: typ.Collection[DeviceRegistration] = (),
) -> DeviceRegistration:
registered_devices = self.filter_devices_by_app_id(registered_devices)
- key_handle = response_dict.get('keyHandle', '')
+ key_handle = websafe_decode(response_dict.get('keyHandle', ''))
challenge = session.get(self.SIGNING_SESSION_KEY, '')
if not challenge:
raise U2FStateException('Session missing required key.')
device = self.get_key_by_handle(registered_devices, key_handle)
signature_data = self.verify_signature_data(response_dict, challenge,
device)
+ # Only update the counter once we've verified the device.
counter = signature_data.counter
- return self.update_device_registration_counter(device, counter)
+ return self.update_device_registration_counter(
+ device=device,
+ counter=counter
+ )
def get_key_by_handle(
self,
@@ -118,16 +123,16 @@ class U2FSigningManager(abc.ABC):
# for the verification step.
client_data = validate_client_data(
response_dict.get('clientData', ''),
- RequestType.REGISTER,
+ RequestType.SIGN,
self.app_id,
challenge,
)
- challenge_param = sha_256(client_data)
+ challenge_param = sha_256(client_data.encode('utf-8'))
app_param = sha_256(self.app_id.encode('idna'))
signature_data.verify(
app_param,
challenge_param,
- websafe_decode(device.public_key),
+ device.public_key,
)
return signature_data
diff --git a/setup.py b/setup.py
index fcdb8c9..41c8da3 100644
--- a/setup.py
+++ b/setup.py
@@ -7,6 +7,11 @@ requirements = [
'cryptography>=2.3,<3',
]
+flask_sample_requires = [
+ 'flask',
+ 'Flask-SQLAlchemy',
+]
+
packages = find_packages(
where='./',
@@ -22,17 +27,17 @@ with open('./README.rst', 'r') as readme_file:
setup(
name='py-fido',
- version='0.1.1',
+ version='0.2.0',
description=(
'A framework-agnostic implementation of the FIDO U2F server workflow'
),
long_description=readme,
- author='The Operations Team(Catalyst IT Ltd.)',
- author_email='sysadmins@catalyst.net.nz',
+ author='Opal Symes',
+ author_email='code@opal.codes',
url='https://github.com/leesdolphin/py-fido/',
packages=packages,
include_package_data=True,
- install_requires=requirements,
+ install_requires=requirements + flask_sample_requires,
zip_safe=False,
package_data={
'fido_u2f': ['py.typed'],