Files

248 lines
7.2 KiB
Python
Raw Permalink Normal View History

2019-08-09 11:33:47 +08:00
import math
2019-08-13 22:40:20 +02:00
import random
import secrets
import sys
2020-02-27 15:25:46 -05:00
from threading import Event, Timer
from numbers import Number
2019-08-06 13:35:09 +08:00
2019-08-13 22:40:20 +02:00
from fido2.ctap2 import ES256, AttestedCredentialData, PinProtocolV1
from fido2.utils import hmac_sha256, sha256
2019-08-06 13:35:09 +08:00
if 'trezor' in sys.argv:
from .vendor.trezor.utils import DeviceSelectCredential
else:
from .vendor.solo.utils import DeviceSelectCredential
2019-08-08 03:05:15 +02:00
name_list = open("data/first-names.txt").readlines()
2019-08-06 13:35:09 +08:00
2019-08-08 03:05:15 +02:00
2019-08-09 11:33:47 +08:00
def shannon_entropy(data):
s = 0.0
total = len(data)
for x in range(0, 256):
freq = data.count(x)
p = freq / total
if p > 0:
s -= p * math.log2(p)
return s
2019-08-08 03:05:15 +02:00
def verify(reg, auth, cdh=None):
2019-08-05 20:02:20 +08:00
credential_data = AttestedCredentialData(reg.auth_data.credential_data)
2019-08-08 03:05:15 +02:00
if cdh is None:
cdh = auth.request.cdh
2019-08-05 20:02:20 +08:00
auth.verify(cdh, credential_data.public_key)
2020-03-24 13:27:30 -04:00
assert auth.auth_data.rp_id_hash == reg.auth_data.rp_id_hash
if auth.credential is not None:
assert auth.credential["id"] == reg.auth_data.credential_data.credential_id
2019-08-05 13:40:54 +08:00
def generate_rp():
return {"id": "example.org", "name": "ExampleRP"}
2019-08-08 03:05:15 +02:00
2019-08-05 13:40:54 +08:00
def generate_user():
2019-08-06 13:35:09 +08:00
# https://www.w3.org/TR/webauthn/#user-handle
2019-08-08 03:05:15 +02:00
user_id_length = random.randint(1, 64)
2019-08-06 13:35:09 +08:00
user_id = secrets.token_bytes(user_id_length)
# https://www.w3.org/TR/webauthn/#dictionary-pkcredentialentity
2019-08-08 03:05:15 +02:00
name = " ".join(random.choice(name_list).strip() for i in range(0, 3))
icon = "https://www.w3.org/TR/webauthn/"
2019-08-06 13:35:09 +08:00
display_name = "Displayed " + name
2019-08-08 03:05:15 +02:00
return {"id": user_id, "name": name, "icon": icon, "displayName": display_name}
2019-08-05 13:40:54 +08:00
counter = 1
def generate_user_maximum():
"""
Generate RK with the maximum lengths of the fields, according to the minimal requirements of the FIDO2 spec
"""
global counter
# https://www.w3.org/TR/webauthn/#user-handle
user_id_length = 64
user_id = secrets.token_bytes(user_id_length)
# https://www.w3.org/TR/webauthn/#dictionary-pkcredentialentity
name = " ".join(random.choice(name_list).strip() for i in range(0, 30))
name = f'{counter}: {name}'
icon = "https://www.w3.org/TR/webauthn/" + 'A'*128
display_name = "Displayed " + name
name = name[:64]
display_name = display_name[:64]
icon = icon[:128]
counter += 1
return {"id": user_id, "name": name, "icon": icon, "displayName": display_name}
2019-08-05 13:40:54 +08:00
def generate_challenge():
2019-08-06 13:35:09 +08:00
return secrets.token_bytes(32)
2019-08-05 13:40:54 +08:00
2019-08-08 03:05:15 +02:00
2019-08-05 13:40:54 +08:00
def get_key_params():
return [{"type": "public-key", "alg": ES256.ALGORITHM}]
2019-08-08 03:05:15 +02:00
2019-08-05 13:40:54 +08:00
def generate_cdh():
return b"123456789abcdef0123456789abcdef0"
2019-08-08 03:05:15 +02:00
2019-08-05 13:40:54 +08:00
def generate(param):
2019-08-08 03:05:15 +02:00
if param == "rp":
2019-08-05 13:40:54 +08:00
return generate_rp()
2019-08-08 03:05:15 +02:00
if param == "user":
2019-08-05 13:40:54 +08:00
return generate_user()
2019-08-08 03:05:15 +02:00
if param == "challenge":
2019-08-05 13:40:54 +08:00
return generate_challenge()
2019-08-08 03:05:15 +02:00
if param == "cdh":
2019-08-05 13:40:54 +08:00
return generate_cdh()
2019-08-08 03:05:15 +02:00
if param == "key_params":
2019-08-05 13:40:54 +08:00
return get_key_params()
2019-08-08 03:05:15 +02:00
if param == "allow_list":
2019-08-05 13:40:54 +08:00
return []
if param == "on_keepalive":
return DeviceSelectCredential(1)
2019-08-05 20:02:20 +08:00
return None
2019-08-05 13:40:54 +08:00
2019-08-08 03:05:15 +02:00
2019-08-05 13:40:54 +08:00
class Empty:
pass
2019-08-08 03:05:15 +02:00
class FidoRequest:
def __init__(self, request=None, **kwargs):
2019-08-05 13:40:54 +08:00
2019-08-05 20:02:20 +08:00
if not isinstance(request, FidoRequest) and request is not None:
request = request.request
2019-08-05 13:40:54 +08:00
2019-08-05 20:02:20 +08:00
self.request = request
2019-08-08 03:05:15 +02:00
for i in (
"cdh",
"key_params",
"allow_list",
"challenge",
"rp",
"user",
"pin_protocol",
"options",
"appid",
"exclude_list",
"extensions",
"pin_auth",
2019-08-15 19:06:52 +08:00
"timeout",
"on_keepalive",
2019-08-08 03:05:15 +02:00
):
2019-08-05 13:40:54 +08:00
self.save_attr(i, kwargs.get(i, Empty), request)
2019-08-08 03:05:15 +02:00
if isinstance(self.rp, dict) and "id" in self.rp:
if hasattr(self.rp["id"], "encode"):
2019-08-05 20:02:20 +08:00
self.appid = sha256(self.rp["id"].encode("utf8"))
2019-08-08 03:05:15 +02:00
# self.chal = sha256(self.challenge.encode("utf8"))
2019-08-05 20:02:20 +08:00
2019-08-08 03:05:15 +02:00
def save_attr(self, attr, value, request):
2019-08-05 13:40:54 +08:00
"""
2019-08-09 11:33:47 +08:00
Will assign attribute from source, in following priority:
2019-08-05 13:40:54 +08:00
Argument, request object, generated
"""
if value != Empty:
setattr(self, attr, value)
elif request is not None:
2019-08-08 03:05:15 +02:00
setattr(self, attr, getattr(request, attr))
2019-08-05 13:40:54 +08:00
else:
setattr(self, attr, generate(attr))
def toGA(self,):
2019-08-08 03:05:15 +02:00
return [
None if not self.rp else self.rp["id"],
self.cdh,
self.allow_list,
self.extensions,
self.options,
self.pin_auth,
self.pin_protocol,
2019-08-15 19:06:52 +08:00
self.timeout,
self.on_keepalive,
2019-08-08 03:05:15 +02:00
]
2019-08-05 13:40:54 +08:00
def toMC(self,):
2019-08-08 03:05:15 +02:00
return [
self.cdh,
self.rp,
self.user,
self.key_params,
self.exclude_list,
self.extensions,
self.options,
self.pin_auth,
self.pin_protocol,
2019-08-15 19:06:52 +08:00
self.timeout,
self.on_keepalive,
2019-08-08 03:05:15 +02:00
]
2019-08-05 20:02:20 +08:00
return args + self.get_optional_args()
2020-02-27 15:25:46 -05:00
# Timeout from:
# https://github.com/Yubico/python-fido2/blob/f1dc028d6158e1d6d51558f72055c65717519b9b/fido2/utils.py
# Copyright (c) 2013 Yubico AB
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
class Timeout(object):
"""Utility class for adding a timeout to an event.
:param time_or_event: A number, in seconds, or a threading.Event object.
:ivar event: The Event associated with the Timeout.
:ivar timer: The Timer associated with the Timeout, if any.
"""
def __init__(self, time_or_event):
if isinstance(time_or_event, Number):
self.event = Event()
self.timer = Timer(time_or_event, self.event.set)
else:
self.event = time_or_event
self.timer = None
def __enter__(self):
if self.timer:
self.timer.start()
return self.event
def __exit__(self, exc_type, exc_val, exc_tb):
if self.timer:
self.timer.cancel()
self.timer.join()