def test_u2f(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") app_id = b"https://example.com" server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://example.com", ) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, )
def test_u2f(self): rp = RelyingParty('example.com', 'Example', 'http://example.com/icon.svg') app_id = b'https://example.com' server = U2FFido2Server(app_id=app_id.decode('ascii'), rp=rp) state = { 'challenge': 'GAZPACHO!', 'user_verification': USER_VERIFICATION.PREFERRED } client_data_dict = { 'challenge': 'GAZPACHO!', 'origin': 'https://example.com', 'type': WEBAUTHN_TYPE.GET_ASSERTION } client_data = ClientData(json.dumps(client_data_dict).encode('utf-8')) param = b'TOMATO GIVES ' device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete(state, [auth_data], device.credential_id, client_data, authenticator_data, signature)
def test_u2f(self): rp = RelyingParty("example.com", "Example", "http://example.com/icon.svg") app_id = b"https://example.com" server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp) state = { "challenge": "GAZPACHO!", "user_verification": USER_VERIFICATION.PREFERRED, } client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, )
def test_u2f_facets(self): rp = RelyingParty("example.com", "Example", "http://example.com/icon.svg") app_id = b"https://www.example.com/facets.json" def verify_u2f_origin(origin): return origin in ("https://oauth.example.com", "https://admin.example.com") server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp, verify_u2f_origin=verify_u2f_origin) state = { "challenge": "GAZPACHO!", "user_verification": USER_VERIFICATION.PREFERRED, } client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://oauth.example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) # Now with something not whitelisted client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://publicthingy.example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) authenticator_data, signature = device.sign(client_data) with six.assertRaisesRegex(self, ValueError, "Invalid origin in " "ClientData."): server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, )
def __init__(self, authenticator=None, status=EnrollmentStatus.EXISTING): super().__init__(authenticator, status) self.webauthn_authentication_server = U2FFido2Server( app_id=self.u2f_app_id, rp={ "id": self.rp_id, "name": "Sentry" })
def test_u2f_facets(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") app_id = b"https://www.example.com/facets.json" def verify_u2f_origin(origin): return origin in ("https://oauth.example.com", "https://admin.example.com") server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp, verify_u2f_origin=verify_u2f_origin) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://oauth.example.com", ) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) # Now with something not whitelisted client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://publicthingy.example.com", ) authenticator_data, signature = device.sign(client_data) with self.assertRaisesRegex(ValueError, "Invalid origin in CollectedClientData."): server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, )
def setUp(self): self.u2f = U2fInterface() self.login_as(user=self.user) rp = PublicKeyCredentialRpEntity("richardmasentry.ngrok.io", "Sentry") self.test_registration_server = Fido2Server(rp, verify_origin=verifiy_origin) self.test_authentication_server = U2FFido2Server( app_id="http://richardmasentry.ngrok.io/auth/2fa/u2fappid.json", rp={"id": "richardmasentry.ngrok.io", "name": "Sentry"}, verify_u2f_origin=verifiy_origin, )
def _get_fido2server(credentials, fido2rp): # See if any of the credentials is a legacy U2F credential with an app-id # (assume all app-ids are the same - authenticating with a mix of different # app-ids isn't supported in current Webauthn) app_id = None for k, v in credentials.items(): if v['app_id']: app_id = v['app_id'] break if app_id: return U2FFido2Server(app_id, fido2rp) return Fido2Server(fido2rp)
def test_u2f_facets(self): rp = RelyingParty('example.com', 'Example', 'http://example.com/icon.svg') app_id = b'https://www.example.com/facets.json' def verify_u2f_origin(origin): return origin in ('https://oauth.example.com', 'https://admin.example.com') server = U2FFido2Server(app_id=app_id.decode('ascii'), rp=rp, verify_u2f_origin=verify_u2f_origin) state = { 'challenge': 'GAZPACHO!', 'user_verification': USER_VERIFICATION.PREFERRED } client_data_dict = { 'challenge': 'GAZPACHO!', 'origin': 'https://oauth.example.com', 'type': WEBAUTHN_TYPE.GET_ASSERTION } client_data = ClientData(json.dumps(client_data_dict).encode('utf-8')) param = b'TOMATO GIVES ' device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete(state, [auth_data], device.credential_id, client_data, authenticator_data, signature) # Now with something not whitelisted client_data_dict = { 'challenge': 'GAZPACHO!', 'origin': 'https://publicthingy.example.com', 'type': WEBAUTHN_TYPE.GET_ASSERTION } client_data = ClientData(json.dumps(client_data_dict).encode('utf-8')) authenticator_data, signature = device.sign(client_data) with six.assertRaisesRegex(self, ValueError, 'Invalid origin in ' 'ClientData.'): server.authenticate_complete(state, [auth_data], device.credential_id, client_data, authenticator_data, signature)
def server(self): rp_id = getattr(settings, 'OTP_U2F_RP_ID', None) rp_name = getattr(settings, 'OTP_U2F_RP_NAME', None) app_id = getattr(settings, 'OTP_U2F_APP_ID', None) if rp_id is None: site = get_current_site(self.request) rp_id = site.domain rp_name = site.name if app_id is None: if self.request is not None: app_id = self.request.build_absolute_uri('/')[:-1] else: app_id = f'https://{rp_id}' return U2FFido2Server( app_id, rp=PublicKeyCredentialRpEntity(rp_id, rp_name), attestation='direct')
from fido2.server import U2FFido2Server, RelyingParty from fido2.ctap2 import AttestationObject, AuthenticatorData from fido2.ctap1 import RegistrationData from fido2.utils import sha256, websafe_encode from fido2 import cbor from flask import Flask, session, request, redirect, abort import os app = Flask(__name__, static_url_path='') app.secret_key = os.urandom(32) # Used for session. rp = RelyingParty('localhost', 'Demo server') # By using the U2FFido2Server class, we can support existing credentials # registered by the legacy u2f.register API for an appId. server = U2FFido2Server('https://*****:*****@app.route('/') def index(): return redirect('/index-u2f.html') @app.route('/api/register/begin', methods=['POST']) def register_begin(): registration_data, state = server.register_begin( {
def do_POST(self): origin = self.headers.get('Origin') host = origin[len('https://'):] rp = PublicKeyCredentialRpEntity(host, 'NGINX Auth Server') server = U2FFido2Server(origin, rp) if self.path == HTTP_PREFIX + "/get_challenge_for_new_key": registration_data, state = server.register_begin({ 'id': b'default', 'name': "Default user", 'displayName': "Default user" }) registration_data["publicKey"]["challenge"] = str(base64.b64encode(registration_data["publicKey"]["challenge"]), 'utf-8') registration_data["publicKey"]["user"]["id"] = str(base64.b64encode(registration_data["publicKey"]["user"]["id"]), 'utf-8') self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() # Save this challenge to a file so you can kill the host to add the lient via CLI with open(LASTCHALLENGE, 'w') as f: f.write(json.dumps(state)) self.wfile.write(bytes(json.dumps(registration_data), 'UTF-8')) return if self.path == HTTP_PREFIX + "/register": self.send_response(401) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(bytes(json.dumps({'error': 'not_configured'}), 'UTF-8')) return creds = [] files=glob.glob(CREDENTIALS_DIR + "/*") for file in files: public_key = "" with open(file, 'rb') as f: cred, _ = AttestedCredentialData.unpack_from(f.read()) # TODO add more public-keys which must be stored in AttestedCredentialData creds.append(cred) auth_data, state =server.authenticate_begin([cred]) public_key = str(base64.b64encode(auth_data["publicKey"]["allowCredentials"][0]["id"]))[2:-1] header_file = HEADERS_DIR + "/" + file.split("/")[-1] if os.path.exists(header_file): with open(header_file, 'r') as f: HEADER_MANAGER[public_key] = ["Fido-User", "\t".join(f.read().splitlines())] if self.path == HTTP_PREFIX + "/get_challenge_for_existing_key": auth_data, state = server.authenticate_begin(creds) auth_data["publicKey"]["challenge"] = str(base64.b64encode(auth_data["publicKey"]["challenge"]), 'utf-8') for el in auth_data["publicKey"]["allowCredentials"]: el["id"] = str(base64.b64encode(el["id"]), 'utf-8') #auth_data["publicKey"]["allowCredentials"][0]["id"] = str(base64.b64encode(auth_data["publicKey"]["allowCredentials"][0]["id"]), 'utf-8') CHALLENGE.update(state) self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(bytes(json.dumps(auth_data), 'UTF-8')) if self.path == HTTP_PREFIX + "/complete_challenge_for_existing_key": data = json.loads(self.rfile.read(int(self.headers.get('Content-Length')))) credential_id = base64.b64decode(data['id']) client_data = ClientData(base64.b64decode(data['clientDataJSON'])) auth_data = AuthenticatorData(base64.b64decode(data['authenticatorData'])) signature = base64.b64decode(data['signature']) with open(LASTCHALLENGE) as f: server.authenticate_complete( CHALLENGE, creds, credential_id, client_data, auth_data, signature ) cookie = http.cookies.SimpleCookie() header = HEADER_MANAGER.get(data['id'], ["", ""]) token = TOKEN_MANAGER.generate() cookie["token"] = token TOKEN_MANAGER.set_header(token, header) cookie["token"]["path"] = "/" cookie["token"]["secure"] = True self.send_response(200) self.send_header('Set-Cookie', cookie.output(header='')) if header != 0: self.send_header(header[0], header[1]) #self.send_header('Fido-User', "test") # TODO sem připsat hlavičku s uživatelem self.end_headers() self.wfile.write(bytes(json.dumps({'status': 'ok'}), 'UTF-8'))
self.send_response(200) self.send_header('Set-Cookie', cookie.output(header='')) if header != 0: self.send_header(header[0], header[1]) #self.send_header('Fido-User', "test") # TODO sem připsat hlavičku s uživatelem self.end_headers() self.wfile.write(bytes(json.dumps({'status': 'ok'}), 'UTF-8')) if len(sys.argv) > 1 and sys.argv[1] == "save-client": host = sys.argv[2] client_data = ClientData(base64.b64decode(sys.argv[3])) attestation_object = AttestationObject(base64.b64decode(sys.argv[4])) rp = PublicKeyCredentialRpEntity(host, 'NGINX Auth Server') server = U2FFido2Server('https://' + host, rp) with open(LASTCHALLENGE) as f: auth_data = server.register_complete(json.loads(f.read()), client_data, attestation_object) with open(CREDENTIALS_DIR + "/" + sys.argv[5], 'wb') as f: f.write(auth_data.credential_data) print("Credentials saved successfully") else: socketserver.TCPServer.allow_reuse_address = True httpd = socketserver.TCPServer(("", PORT), AuthHandler) try: print("serving at port", PORT) httpd.serve_forever() finally:
) from fido2.server import U2FFido2Server from fido2.ctap1 import RegistrationData from fido2.utils import sha256, websafe_encode, websafe_decode from fido2 import cbor from flask import Flask, session, request, redirect, abort import os app = Flask(__name__, static_url_path="") app.secret_key = os.urandom(32) # Used for session. rp = PublicKeyCredentialRpEntity(name="Demo server", id="localhost") # By using the U2FFido2Server class, we can support existing credentials # registered by the legacy u2f.register API for an appId. server = U2FFido2Server("https://*****:*****@app.route("/") def index(): return redirect("/index-u2f.html") @app.route("/api/register/begin", methods=["POST"]) def register_begin(): registration_data, state = server.register_begin( {
from fido2 import cbor from fido2.client import ClientData from fido2.ctap2 import AttestedCredentialData, AuthenticatorData from fido2.server import U2FFido2Server from fido2.utils import websafe_decode from fido2.webauthn import PublicKeyCredentialRpEntity from assemblyline.common.security import get_totp_token from assemblyline_ui.config import config, APP_ID from assemblyline_ui.http_exceptions import AuthenticationException rp = PublicKeyCredentialRpEntity(config.ui.fqdn, "Assemblyline server") server = U2FFido2Server(f"https://{config.ui.fqdn}", rp) # noinspection PyBroadException def validate_2fa(username, otp_token, state, webauthn_auth_resp, storage): security_token_enabled = False otp_enabled = False security_token_error = False otp_error = False report_errors = False # Get user user_data = storage.user.get(username) # Test Security Tokens if config.auth.allow_security_tokens: security_tokens = user_data.security_tokens credentials = [AttestedCredentialData(websafe_decode(x)) for x in security_tokens.values()]
class U2FDevice(Device): version = models.TextField(default="U2F_V2") public_key = models.TextField() credential_id = models.TextField() aaguid = models.TextField() counter = models.IntegerField(default=0) # server = Fido2Server(PublicKeyCredentialRpEntity(settings.SSO_DOMAIN.lower().split(':')[0], # f'{settings.SSO_SITE_NAME} Server')) u2f_app_id = f"{'https' if settings.SSO_USE_HTTPS else 'http'}://{settings.SSO_DOMAIN.lower().split(':')[0]}" server = U2FFido2Server( u2f_app_id, PublicKeyCredentialRpEntity(settings.SSO_DOMAIN.lower().split(':')[0], f'{settings.SSO_SITE_NAME} Server')) WEB_AUTHN_SALT = 'sso.auth.models.U2FDevice' device_id = 1 Device.devices.add((__qualname__, device_id)) class Meta: ordering = ['order', 'name'] def __str__(self): return "%s:%s:%s" % (self.__class__.__name__, self.user_id, self.pk) @classmethod def get_device_id(cls): return cls.device_id @classmethod def detail_template(cls): return 'sso_auth/u2f/detail.html' @classmethod def register_begin(cls, request): user = request.user credentials = U2FDevice.credentials(user) icon = absolute_url( request, get_thumbnail(user.picture, "60x60", crop="center").url) if user.picture else None registration_data, state = cls.server.register_begin( { "id": user.uuid.bytes, "name": user.username, "displayName": user.get_full_name(), "icon": icon }, credentials, user_verification="discouraged") u2f_request = { 'req': b64encode(cbor.encode(registration_data)).decode(), 'state': signing.dumps(state, salt=U2FDevice.WEB_AUTHN_SALT) } return u2f_request @classmethod def register_complete(cls, name, response_data, state_data, user): data = cbor.decode(b64decode(response_data)) state = signing.loads(state_data, salt=U2FDevice.WEB_AUTHN_SALT) client_data = ClientData(data["clientDataJSON"]) att_obj = AttestationObject(data["attestationObject"]) logger.debug("clientData", client_data) logger.debug("AttestationObject:", att_obj) auth_data = cls.server.register_complete(state, client_data, att_obj) logger.debug(auth_data) public_key = websafe_encode( cbor.encode(auth_data.credential_data.public_key)) aaguid = websafe_encode(auth_data.credential_data.aaguid) credential_id = websafe_encode(auth_data.credential_data.credential_id) device = U2FDevice.objects.create(name=name, user=user, public_key=public_key, credential_id=credential_id, aaguid=aaguid, confirmed=True, version="fido2") return device @classmethod def authenticate_complete(cls, response_data, state_data, user): response = cbor.decode(b64decode(response_data)) state = signing.loads(state_data, salt=U2FDevice.WEB_AUTHN_SALT) credential_id = response["credentialId"] client_data = ClientData(response["clientDataJSON"]) auth_data = AuthenticatorData(response["authenticatorData"]) signature = response["signature"] credentials = U2FDevice.credentials(user) cred = cls.server.authenticate_complete( state, credentials, credential_id, client_data, auth_data, signature, ) credential_id = websafe_encode(cred.credential_id) device = U2FDevice.objects.get(user=user, credential_id=credential_id) if auth_data.counter <= device.counter: # verify counter is increasing raise ValueError( f"login counter is not increasing. {auth_data.counter} <= {device.counter} " ) device.last_used = timezone.now() device.counter = auth_data.counter device.save(update_fields=["last_used", "counter"]) return device @classmethod def credentials(cls, user): u2f_devices = cls.objects.filter(user=user, confirmed=True) return [ AttestedCredentialData.create( aaguid=websafe_decode(d.aaguid), credential_id=websafe_decode(d.credential_id), public_key=CoseKey.parse( cbor.decode(websafe_decode(d.public_key)))) for d in u2f_devices ] @classmethod def challenges(cls, user): credentials = U2FDevice.credentials(user) req, state = cls.server.authenticate_begin( credentials=credentials, user_verification=UserVerificationRequirement.DISCOURAGED) sign_request = { 'req': b64encode(cbor.encode(req)).decode(), 'state': signing.dumps(state, salt=cls.WEB_AUTHN_SALT) } return json.dumps(sign_request) @classmethod def image(cls): return 'img/u2f_yubikey.png' @classmethod def login_form_class(cls): return U2FForm @classmethod def login_form_templates(cls): return 'sso_auth/u2f/verify.html' @classmethod def login_text(cls): return _( 'Please touch the flashing U2F device now. You may be prompted to allow the site permission ' 'to access your security keys. After granting permission, the device will start to blink.' ) @classmethod def default_name(cls): return _('FIDO2 or U2F Device')