def _get_user_credentials(user): res = {} for this in user.credentials.filter(U2F).to_list(): acd = AttestedCredentialData.from_ctap1(websafe_decode(this.keyhandle), websafe_decode(this.public_key)) res[this.key] = {'u2f': {'version': this.version, 'keyHandle': this.keyhandle, 'publicKey': this.public_key, }, 'webauthn': acd, 'app_id': this.app_id, } for this in user.credentials.filter(Webauthn).to_list(): keyhandle = this.keyhandle cred_data = base64.urlsafe_b64decode(this.credential_data.encode('ascii')) credential_data, rest = AttestedCredentialData.unpack_from(cred_data) version = 'webauthn' res[this.key] = {'u2f': {'version': version, 'keyHandle': keyhandle, 'publicKey': credential_data.public_key, }, 'webauthn': credential_data, 'app_id': '', } return res
def test_sign_await_touch(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = [ ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), SIG_DATA, ] event = Event() event.wait = mock.MagicMock() resp = client.sign( APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}], event=event, ) event.wait.assert_called() client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called() client_param, app_param, key_handle = client.ctap.authenticate.call_args[0] self.assertEqual(client_param, sha256(websafe_decode(resp["clientData"]))) self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b"key") self.assertEqual(websafe_decode(resp["signatureData"]), SIG_DATA)
def test_register_await_touch(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA) client.ctap.register.side_effect = [ ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), REG_DATA, ] event = Event() event.wait = mock.MagicMock() resp = client.register( APP_ID, [{"version": "U2F_V2", "challenge": "foobar"}], [{"version": "U2F_V2", "keyHandle": "a2V5"}], event=event, ) event.wait.assert_called() client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client.ctap.register.assert_called() client_param, app_param = client.ctap.register.call_args[0] self.assertEqual(sha256(websafe_decode(resp["clientData"])), client_param) self.assertEqual(websafe_decode(resp["registrationData"]), REG_DATA) self.assertEqual(sha256(APP_ID.encode()), app_param)
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 ]
def makeSignature(challenge,cred_id,rp_id): allow_list = [{ 'type': 'public-key', 'id': websafe_decode(cred_id) }] sys.stderr.write('\nTouch your authenticator device now...\n') # sys.stderr.write(challenge+"\n") # sys.stderr.write(cred_id+"\n") # sys.stderr.write(rp_id+"\n") try: assertions, client_data = client.get_assertion( rp_id, challenge, allow_list) except ValueError: assertions, client_data = client.get_assertion( rp_id, challenge, allow_list) sys.stderr.write('Credential authenticated!') assertion = assertions[0] # Only one cred in allowList, only one response. # print('ASSERTIONS : ', assertions) # print() # print('CLIENT DATA:', client_data) # print() # print('ASSERTION DATA:', assertion) # print() return str(cbor.decode_from(assertion.signature))
def authenticate_complete(request): credentials = [] username=request.session.get("base_username",request.user.username) server=getServer() credentials=getUserCredentials(username) data = cbor.loads(request.body)[0] credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] cred = server.authenticate_complete( request.session.pop('fido_state'), credentials, credential_id, client_data, auth_data, signature ) keys = User_Keys.objects.filter(username=username, key_type="FIDO2",enabled=1) import random for k in keys: if AttestedCredentialData(websafe_decode(k.properties["device"])).credential_id == cred.credential_id: k.last_used = timezone.now() k.save() mfa = {"verified": True, "method": "FIDO2"} if getattr(settings, "MFA_RECHECK", False): mfa["next_check"] = int((datetime.datetime.now()+ datetime.timedelta( seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s")) request.session["mfa"] = mfa res=login(request) return HttpResponse(simplejson.dumps({'status':"OK","redirect":res["location"]}),content_type="application/json") return HttpResponse(simplejson.dumps({'status': "err"}))
def authenticate_begin(username, **_): """ Begin authentication procedure Variables: username user name of the user you want to login with Arguments: None Data Block: None Result example: <WEBAUTHN_AUTHENTICATION_DATA> """ user = STORAGE.user.get(username, as_obj=False) if not user: return make_api_response({'success': False}, err="Bad Request", status_code=400) session.pop('state', None) security_tokens = user.get('security_tokens', {}) or {} credentials = [AttestedCredentialData(websafe_decode(x)) for x in security_tokens.values()] auth_data, state = server.authenticate_begin(credentials) session['state'] = state return make_api_response(list(cbor.encode(auth_data)))
def work(self, client): try: pin = None if client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") request_options = PublicKeyCredentialRequestOptions( challenge=utils.websafe_decode(self._challenge), rp_id=self._rp['id'], allow_credentials=self._allow_list) self._assertions, self._client_data = client.get_assertion( request_options, on_keepalive=self.on_keepalive, event=self._cancel, pin=pin) except ClientError as e: if e.code == ClientError.ERR.DEVICE_INELIGIBLE: self.ui.info( 'Security key is ineligible') # TODO extract key info return elif e.code != ClientError.ERR.TIMEOUT: raise else: return self._cancel.set()
def test_get_by_username(self): Authenticator.objects.create(user=self.user, attestation_data=ATTESTATION_OBJECT) response = self.client.get(self.url, data={'username': self.user.username}) self.assertEqual(response.status_code, 200) # Check response state = self.client.session[FIDO2_REQUEST_SESSION_KEY] challenge = websafe_decode(state['challenge']) fido2_request = { 'publicKey': { 'rpId': 'testserver', 'challenge': base64.b64encode(challenge).decode('utf-8'), 'allowCredentials': [{ 'id': CREDENTIAL_ID, 'type': 'public-key' }], 'timeout': 30000 } } if fido2.__version__ < '0.8': fido2_request['publicKey']['userVerification'] = 'preferred' self.assertEqual(response.json(), fido2_request)
def authenticate_complete(request): server = get_server() data = cbor.decode(request.body) credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] cred = server.authenticate_complete(request.session.pop('fido_state'), get_user_credentials(request), credential_id, client_data, auth_data, signature) keys = UserKey.objects.filter( user=request.user, key_type=KEY_TYPE_FIDO2, enabled=True, ) for key in keys: if AttestedCredentialData(websafe_decode( key.properties["device"])).credential_id == cred.credential_id: write_session(request, key) res = login(request) return JsonResponse({'status': "OK", "redirect": res["location"]}) return JsonResponse({'status': "err"})
def authenticate_complete(request): credentials = [] username = request.session.get("base_username", request.user.get_username()) server = get_server() credentials = get_user_credentials(username) data = cbor.decode(request.body) credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] cred = server.authenticate_complete(request.session.pop('fido_state'), credentials, credential_id, client_data, auth_data, signature) for k in UserKey.objects.filter(username=username, key_type="FIDO2", enabled=1): if AttestedCredentialData(websafe_decode( k.properties["device"])).credential_id == cred.credential_id: k.last_used = timezone.now() k.save() mfa = {"verified": True, "method": "FIDO2", 'id': k.id} if getattr(settings, "MFA_RECHECK", False): mfa["next_check"] = next_check() request.session["mfa"] = mfa res = login(request) return JsonResponse({'status': "OK", "redirect": res["location"]}) return JsonResponse({'status': "err"})
def _build_factor_name(self, factor): """ Build the display name for a MFA factor based on the factor type""" if factor['provider'] == 'DUO': return factor['factorType'] + ": " + factor['provider'].capitalize() elif factor['factorType'] == 'push': return "Okta Verify App: " + factor['profile']['deviceType'] + ": " + factor['profile']['name'] elif factor['factorType'] == 'sms': return factor['factorType'] + ": " + factor['profile']['phoneNumber'] elif factor['factorType'] == 'call': return factor['factorType'] + ": " + factor['profile']['phoneNumber'] elif factor['factorType'] == 'token:software:totp': return factor['factorType'] + "( " + factor['provider'] + " ) : " + factor['profile']['credentialId'] elif factor['factorType'] == 'token': return factor['factorType'] + ": " + factor['profile']['credentialId'] elif factor['factorType'] == 'u2f': return factor['factorType'] + ": " + factor['factorType'] elif factor['factorType'] == 'webauthn': factor_name = None try: registered_authenticators = RegisteredAuthenticators(self.ui) credential_id = websafe_decode(factor['profile']['credentialId']) factor_name = registered_authenticators.get_authenticator_user(credential_id) except Exception: pass default_factor_name = factor['profile'].get('authenticatorName') or factor['factorType'] factor_name = factor_name or default_factor_name return factor['factorType'] + ": " + factor_name elif factor['factorType'] == 'token:hardware': return factor['factorType'] + ": " + factor['provider'] else: return "Unknown MFA type: " + factor['factorType']
def try_enroll( self, enrollment_data, response_data, has_webauthn_register, device_name=None, state=None ): if has_webauthn_register: data = json.loads(response_data) client_data = ClientData(websafe_decode(data["response"]["clientDataJSON"])) att_obj = base.AttestationObject(websafe_decode(data["response"]["attestationObject"])) binding = self.webauthn_registration_server.register_complete( state, client_data, att_obj ) else: data, cert = u2f.complete_registration(enrollment_data, response_data, self.u2f_facets) binding = dict(data) devices = self.config.setdefault("devices", []) devices.append( {"name": device_name or "Security Key", "ts": int(time()), "binding": binding} )
def okta_mfa_webauthn(conf, s, factor, state_token): # type: (Conf, requests.Session, Dict[str, str], str) -> Optional[Dict[str, Any]] if not have_fido: err('Need fido2 package(s) for webauthn. Consider doing `pip install fido2` (or similar)' ) devices = list(CtapHidDevice.list_devices()) if not devices: err('webauthn configured, but no U2F devices found') provider = factor.get('provider', '') log('mfa {0} challenge request [okta_url]'.format(provider)) data = {'stateToken': state_token} _, _h, j = send_json_req(conf, s, 'webauthn mfa challenge', factor.get('url', ''), data, expected_url=conf.okta_url, verify=conf.get_cert('okta_url', True)) rfactor = j['_embedded']['factor'] profile = rfactor['profile'] purl = parse_url(conf.okta_url) origin = '{0}://{1}'.format(purl[0], purl[1]) challenge = rfactor['_embedded']['challenge']['challenge'] credentialId = websafe_decode(profile['credentialId']) allow_list = [{'type': 'public-key', 'id': credentialId}] for dev in devices: client = Fido2Client(dev, origin) print('!!! Touch the flashing U2F device to authenticate... !!!') try: result = client.get_assertion(purl[1], challenge, allow_list) dbg(conf.debug, 'assertion.result', result) break except Exception: traceback.print_exc(file=sys.stderr) result = None if not result: return None assertion, client_data = result[0][0], result[ 1] # only one cred in allowList, so only one response. data = { 'stateToken': state_token, 'clientData': to_n((base64.b64encode(client_data)).decode('ascii')), 'signatureData': to_n((base64.b64encode(assertion.signature)).decode('ascii')), 'authenticatorData': to_n((base64.b64encode(assertion.auth_data)).decode('ascii')) } log('mfa {0} signature request [okta_url]'.format(provider)) _, _h, j = send_json_req(conf, s, 'uf2 mfa signature', j['_links']['next']['href'], data, expected_url=conf.okta_url, verify=conf.get_cert('okta_url', True)) return j
def test_sign(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.return_value = SIG_DATA resp = client.sign( APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}] ) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client_param, app_param, key_handle = client.ctap.authenticate.call_args[0] self.assertEqual(client_param, sha256(websafe_decode(resp["clientData"]))) self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b"key") self.assertEqual(websafe_decode(resp["signatureData"]), SIG_DATA)
def test_websafe_decode(self): self.assertEqual(websafe_decode(b''), b'') self.assertEqual(websafe_decode(b'Zg'), b'f') self.assertEqual(websafe_decode(b'Zm8'), b'fo') self.assertEqual(websafe_decode(b'Zm9v'), b'foo') self.assertEqual(websafe_decode(b'Zm9vYg'), b'foob') self.assertEqual(websafe_decode(b'Zm9vYmE'), b'fooba') self.assertEqual(websafe_decode(b'Zm9vYmFy'), b'foobar')
def test_websafe_decode(self): self.assertEqual(websafe_decode(b""), b"") self.assertEqual(websafe_decode(b"Zg"), b"f") self.assertEqual(websafe_decode(b"Zm8"), b"fo") self.assertEqual(websafe_decode(b"Zm9v"), b"foo") self.assertEqual(websafe_decode(b"Zm9vYg"), b"foob") self.assertEqual(websafe_decode(b"Zm9vYmE"), b"fooba") self.assertEqual(websafe_decode(b"Zm9vYmFy"), b"foobar")
def test_get(self): self.client.force_login(self.user) response = self.client.get(self.url) self.assertEqual(response.status_code, 200) # Check response state = self.client.session[FIDO2_REQUEST_SESSION_KEY] challenge = websafe_decode(state['challenge']) self.assertEqual(response.json(), self._get_fido2_request(challenge, []))
def get_user_credentials(request): return [ AttestedCredentialData(websafe_decode(uk.properties["device"])) for uk in UserKey.objects.filter( user=request.user, key_type=KEY_TYPE_FIDO2, properties__contains=f'"domain":"{request.get_host()}"', enabled=True, ) ]
def validate_response(self, request: Request, challenge, response): try: credentials = [] for device in self.get_u2f_devices(): if type(device) == AuthenticatorData: credentials.append(device.credential_data) else: credentials.append(create_credential_object(device)) self.webauthn_authentication_server.authenticate_complete( state=request.session["webauthn_authentication_state"], credentials=credentials, credential_id=websafe_decode(response["keyHandle"]), client_data=ClientData(websafe_decode(response["clientData"])), auth_data=AuthenticatorData( websafe_decode(response["authenticatorData"])), signature=websafe_decode(response["signatureData"]), ) except (InvalidSignature, InvalidKey, StopIteration): return False return True
def forwards_func(apps, schema_editor): # We get the model from the versioned app registry; U2FDevice = apps.get_model("sso_auth", "U2FDevice") db_alias = schema_editor.connection.alias for d in U2FDevice.objects.using(db_alias).all(): credential_data = AttestedCredentialData.from_ctap1(websafe_decode(d.key_handle), websafe_decode(d.public_key_old)) d.public_key = websafe_encode(cbor.encode(credential_data.public_key)) d.aaguid = websafe_encode(credential_data.aaguid) d.credential_id = websafe_encode(credential_data.credential_id) d.save()
def test_get_registered_keys(self): Authenticator.objects.create(user=self.user, attestation_data=ATTESTATION_OBJECT) self.client.force_login(self.user) response = self.client.get(self.url) self.assertEqual(response.status_code, 200) # Check response contains the same request as session state = self.client.session[FIDO2_REQUEST_SESSION_KEY] challenge = websafe_decode(state['challenge']) credentials = [{'id': CREDENTIAL_ID, 'type': 'public-key'}] self.assertEqual(response.json(), self._get_fido2_request(challenge, credentials))
def __init__(self, ui, okta_org_url, challenge, credential_id=None, timeout_ms=30_000): """ :param okta_org_url: Base URL string for Okta IDP. :param challenge: Challenge :param credential_id: FIDO credential ID """ self.ui = ui self._okta_org_url = okta_org_url self._clients = None self._has_prompted = False self._challenge = websafe_decode(challenge) self._timeout_ms = timeout_ms self._event = Event() self._assertions = None self._client_data = None self._rp = {'id': okta_org_url[8:], 'name': okta_org_url[8:]} if credential_id: self._allow_list = [ PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, websafe_decode(credential_id)) ]
def u2f_complete(): data = cbor.decode(request.get_data()) reg_data = RegistrationData.from_b64(data["registrationData"]) print("clientData", websafe_decode(data["clientData"])) print("U2F RegistrationData:", reg_data) att_obj = AttestationObject.from_ctap1(sha256(b"https://localhost:5000"), reg_data) print("AttestationObject:", att_obj) auth_data = att_obj.auth_data credentials.append(auth_data.credential_data) print("REGISTERED U2F CREDENTIAL:", auth_data.credential_data) return cbor.encode({"status": "OK"})
def test_register(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = 'U2F_V2' client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA) client.ctap.register.return_value = REG_DATA resp = client.register(APP_ID, [{ 'version': 'U2F_V2', 'challenge': 'foobar' }], [{ 'version': 'U2F_V2', 'keyHandle': 'a2V5' }]) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client.ctap.register.assert_called_once() client_param, app_param = client.ctap.register.call_args[0] self.assertEqual(sha256(websafe_decode(resp['clientData'])), client_param) self.assertEqual(websafe_decode(resp['registrationData']), REG_DATA) self.assertEqual(sha256(APP_ID.encode()), app_param)
def __init__(self, ui, appId, nonce, credentialId): """ :param appId: Base URL string for Okta IDP e.g. https://xxxx.okta.com' :param nonce: nonce :param credentialid: credentialid """ self.ui = ui self._clients = None self._has_prompted = False self._cancel = Event() self._credentialId = websafe_decode(credentialId) self._appId = sha256(appId.encode()) self._version = 'U2F_V2' self._signature = None self._clientData = json.dumps({ "challenge": nonce, "origin": appId, "typ": "navigator.id.getAssertion" }).encode() self._nonce = sha256(self._clientData)
def register_begin(**kwargs): """ Begin registration of a security token Variables: None Arguments: None Data Block: None Result example: <WEBAUTHN_REGISTRATION_DATA> """ uname = kwargs['user']['uname'] user = STORAGE.user.get(uname, as_obj=False) if user['otp_sk'] is None: return make_api_response(None, err="OTP must be setup before adding security tokens", status_code=403) session.pop('state', None) security_tokens = user.get('security_tokens', {}) or {} registration_data, state = server.register_begin( dict( id=user['uname'].encode('utf-8'), name=user['uname'], displayName=user['name'], icon=f"https://{config.ui.fqdn}/static/images/favicon.ico" ), credentials=[AttestedCredentialData(websafe_decode(x)) for x in security_tokens.values()] ) session['state'] = state return make_api_response(list(cbor.encode(registration_data)))
def test_get(self): Authenticator.objects.create(user=self.user, attestation_data=ATTESTATION_OBJECT) session = self.client.session session[AUTHENTICATION_USER_SESSION_KEY] = self.user.pk session.save() response = self.client.get(self.url) self.assertEqual(response.status_code, 200) # Check response state = self.client.session[FIDO2_REQUEST_SESSION_KEY] challenge = websafe_decode(state['challenge']) fido2_request = { 'publicKey': { 'rpId': 'testserver', 'challenge': base64.b64encode(challenge).decode('utf-8'), 'allowCredentials': [{ 'id': CREDENTIAL_ID, 'type': 'public-key' }] } } self.assertEqual(response.json(), fido2_request)
def getUserCredentials(username): credentials = [] for uk in User_Keys.objects.filter(username=username, key_type="FIDO2"): credentials.append( AttestedCredentialData(websafe_decode(uk.properties["device"]))) return credentials
argv = sys.argv[1:] try: opts, args = getopt.getopt(argv, "i:n:s:c:", []) except getopt.GetoptError: print('test.py -i <inputfile> -o <outputfile>') sys.exit(2) for opt, arg in opts: if opt in ("-i"): user_id = arg elif opt in ("-n"): user_name = arg elif opt in ("-s"): salt = bytes(arg, encoding='utf-8') elif opt in ("-c"): credential = AttestedCredentialData(websafe_decode(arg)) try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev
def authenticate_complete(request): try: credentials = [] username = request.session.get("base_username", request.user.username) server = getServer() credentials = getUserCredentials(username) data = cbor.decode(request.body) credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] try: cred = server.authenticate_complete( request.session.pop('fido_state'), credentials, credential_id, client_data, auth_data, signature) except ValueError: return HttpResponse(simplejson.dumps({ 'status': "ERR", "message": "Wrong challenge received, make sure that this is your security and try again." }), content_type="application/json") except Exception as excep: try: from raven.contrib.django.raven_compat.models import client client.captureException() except: pass return HttpResponse(simplejson.dumps({ 'status': "ERR", "message": excep.message }), content_type="application/json") if request.session.get("mfa_recheck", False): import time request.session["mfa"]["rechecked_at"] = time.time() return HttpResponse(simplejson.dumps({'status': "OK"}), content_type="application/json") else: import random keys = User_Keys.objects.filter(username=username, key_type="FIDO2", enabled=1) for k in keys: if AttestedCredentialData( websafe_decode(k.properties["device"]) ).credential_id == cred.credential_id: k.last_used = timezone.now() k.save() mfa = {"verified": True, "method": "FIDO2", 'id': k.id} if getattr(settings, "MFA_RECHECK", False): mfa["next_check"] = int( (datetime.datetime.now() + datetime.timedelta(seconds=random.randint( settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s")) request.session["mfa"] = mfa try: authenticated = request.user.is_authenticated except: authenticated = request.user.is_authenticated() if not authenticated: res = login(request) if not "location" in res: return reset_cookie(request) return HttpResponse(simplejson.dumps({ 'status': "OK", "redirect": res["location"] }), content_type="application/json") return HttpResponse(simplejson.dumps({'status': "OK"}), content_type="application/json") except Exception as exp: return HttpResponse(simplejson.dumps({ 'status': "ERR", "message": exp.message }), content_type="application/json")