def _getEmailSubject(self, user=""): """ Could be used to implement some more complex logic similar to the SMS token where the SMS text is read from a policy. :return: The message that is sent to the user. It should contain at least the placeholder <otp> :rtype: string """ subject = '' if not user: return subject realm = user.realm login = user.login policies = get_client_policy(context['Client'], scope="authentication", realm=realm, user=login, action="emailsubject") if policies: subject = getPolicyActionValue(policies, "emailsubject", is_string=True) return subject
def _lookup_provider_policies(provider_type): """ helper, to prevent deleting a provider while it is still used in a policy :param provider_type: the type of provider: sms or email :return: a dictionary with provider names as key and list of policy names """ provider_policies = {} # lookup the policy action name provider_action_name = Policy_action_name.get(provider_type) if not provider_action_name: raise Exception('unknown provider_type for policy lookup! %r' % provider_type) # now have a look at all authentication policies policies = getPolicy({'scope': 'authentication', "action": provider_action_name, }) for policy in policies: provider_name = getPolicyActionValue(policies, provider_action_name, is_string=True) if provider_name not in provider_policies: provider_policies[provider_name] = [] provider_policies[provider_name].append(policy) return provider_policies
def get_voice_language(user="", realm=""): """ This function returns the voice language as defined in the policy authentication/voice_language. If no such policy is defined, the function returns the fallback message "en" :return: string """ voice_language = "en" pol = get_client_policy(context['Client'], scope="authentication", realm=realm, user=user, action="voice_language") if len(pol) > 0: voice_language = getPolicyActionValue(pol, "voice_language", is_string=True) log.debug("[get_voice_language] got the voice_language = %s", voice_language) return voice_language
def _is_valid_facet(self, origin): """ check if origin is in the valid facets if the u2f_valid_facets policy is set. Otherwise check if the origin matches the previously saved origin :return: boolean - True if supported, False if unsupported """ is_valid = False # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm valid_facets_action_value = "" realms = self.token.getRealmNames() if len(realms) > 0: get_policy_params = {"action": "u2f_valid_facets", "scope": "enrollment", "realm": realms[0]} policies = getPolicy(get_policy_params, context=self.context) valid_facets_action_value = getPolicyActionValue(policies, "u2f_valid_facets", String=True) if valid_facets_action_value != "": # 'u2f_valid_facets' policy is set - check if origin is in valid facets list valid_facets = valid_facets_action_value.split(";") for facet in valid_facets: facet = facet.strip() if origin in valid_facets: is_valid = True else: # 'u2f_valid_facets' policy is empty or not set # check if origin matches the origin stored in the token info or save it if no origin # is stored yet appId = self._get_app_id() if appId == origin: is_valid = True return is_valid
def _lookup_provider_policies(provider_type): """ helper, to prevent deleting a provider while it is still used in a policy :param provider_type: the type of provider: sms or email :return: a dictionary with provider names as key and list of policy names """ provider_policies = {} # lookup the policy action name provider_action_name = Policy_action_name.get(provider_type) if not provider_action_name: raise Exception('unknown provider_type for policy lookup! %r' % provider_type) # now have a look at all authentication policies policies = getPolicy({ 'scope': 'authentication', "action": provider_action_name, }) for policy in policies: provider_name = getPolicyActionValue(policies, provider_action_name, is_string=True) if provider_name not in provider_policies: provider_policies[provider_name] = [] provider_policies[provider_name].append(policy) return provider_policies
def get_auth_smstext(user="", realm="", context=None): ''' this function checks the policy scope=authentication, action=smstext This is a string policy The function returns the tuple (bool, string), bool: If a policy is defined string: the string to use ''' # the default string is the OTP value ret = False smstext = "<otp>" pol = getPolicy( { 'scope': 'authentication', 'realm': realm, "action": "smstext" }, context=context) if len(pol) > 0: smstext = getPolicyActionValue(pol, "smstext", String=True) log.debug("[get_auth_smstext] got the smstext = %s" % smstext) ret = True return ret, smstext
def get_mobile_number(self, user=None): ''' get the mobile number - from the token info or - if the policy allowes it, from the user info ''' if not user: return self._getPhone() pol = get_client_policy(context['Client'], scope="authentication", user=user, action="sms_dynamic_mobile_number") if not pol: return self._getPhone() get_dynamic = getPolicyActionValue(pol, "sms_dynamic_mobile_number", is_string=True) if not get_dynamic: return self._getPhone() user_detail = getUserDetail(user) return user_detail.get('mobile', self._getPhone())
def _getEmailSubject(self, user=""): """ Could be used to implement some more complex logic similar to the SMS token where the SMS text is read from a policy. :return: The message that is sent to the user. It should contain at least the placeholder <otp> :rtype: string """ subject = '' if not user: return subject realm = user.realm login = user.login policies = getPolicy({'scope': 'authentication', 'realm': realm, "action": "emailsubject", "user": login}, context=self.context ) if policies: subject = getPolicyActionValue(policies, "emailsubject", is_string=True) return subject
def valid_facets(self, realm=None): """ Show the JSON output for the valid facets configured by the enrollment policy 'u2f_valid_facets'. The form of the JSON output is specified by the FIDO Alliance. """ if realm is None: realm = getDefaultRealm() # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm get_policy_params = { 'action': 'u2f_valid_facets', 'scope': 'enrollment', 'realm': realm } valid_facets_action_value = getPolicyActionValue(getPolicy(get_policy_params), 'u2f_valid_facets', String=True ) # the action value contains the semicolon-separated list of valid facets valid_facets = valid_facets_action_value.split(';') # Prepare the response response.content_type = 'application/fido.trusted-Âapps+json' # as specified by FIDO response_dict = { "trustedFacets": [{ "version": {"major": 1, "minor": 0}, "ids": [] }] } for facet in valid_facets: facet = facet.strip() response_dict['trustedFacets'][0]['ids'].append(facet) return json.dumps(response_dict)
def _get_email_address(self, user=None): ''' get the email address - from the token info or - if the policy allowes it, from the user info ''' if not user: return self._email_address pol = get_client_policy( context['Client'], scope="authentication", user=user, action="dynamic_email_address") if not pol: return self._email_address get_dynamic = getPolicyActionValue( pol, "dynamic_email_address", is_string=True) if not get_dynamic: return self._email_address user_detail = getUserDetail(user) return user_detail.get('email', self._email_address)
def valid_facets(self, realm=None): """ Show the JSON output for the valid facets configured by the enrollment policy 'u2f_valid_facets'. The form of the JSON output is specified by the FIDO Alliance. """ if realm is None: realm = getDefaultRealm() # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm get_policy_params = { 'action': 'u2f_valid_facets', 'scope': 'enrollment', 'realm': realm } valid_facets_action_value = getPolicyActionValue( getPolicy(get_policy_params), 'u2f_valid_facets', is_string=True) # the action value contains the semicolon-separated list of valid facets valid_facets = valid_facets_action_value.split(';') # Prepare the response response.content_type = 'application/fido.trusted-Âapps+json' # as specified by FIDO response_dict = { "trustedFacets": [{ "version": {"major": 1, "minor": 0}, "ids": [] }] } for facet in valid_facets: facet = facet.strip() response_dict['trustedFacets'][0]['ids'].append(facet) return json.dumps(response_dict)
def get_provider_from_policy(provider_type, realm=None, user=None, scope='authentication', action=None): """ interface for the provider user like email token or sms token :param provider_type: 'push', 'email' or 'sms :param user: the user, who should receive the message, used for the policy lookup :return: the list of all identified providers by name """ # check if the provider is defined in a policy provider_name = None # lookup the policy action name provider_action_name = Policy_action_name.get(provider_type) if not provider_action_name: raise Exception('unknown provider_type for policy lookup! %r' % provider_type) if user is None: raise Exception('unknown user for policy lookup! %r' % user) if user and user.login: realm = user.realm if not action: action = provider_action_name policies = get_client_policy(request_context['Client'], scope=scope, action=action, realm=realm, user=user.login) if not policies: default_provider = _get_default_provider_name(provider_type) if default_provider: return [default_provider] return [] provider_names = getPolicyActionValue(policies, action, is_string=True) providers = [] for entry in [x.strip() for x in provider_names.split(' ')]: if entry: providers.append(entry) return providers
def get_provider_from_policy(provider_type, realm=None, user=None): """ interface for the provider user like email token or sms token :param provider_type: 'push', 'email' or 'sms :param user: the user, who should receive the message, used for the policy lookup :return: the list of all identified providers by name """ # check if the provider is defined in a policy provider_name = None # lookup the policy action name provider_action_name = Policy_action_name.get(provider_type) if not provider_action_name: raise Exception('unknown provider_type for policy lookup! %r' % provider_type) if user is None: raise Exception('unknown user for policy lookup! %r' % user) if user and user.login: realm = user.realm policies = get_client_policy(request_context['Client'], scope='authentication', action=provider_action_name, realm=realm, user=user.login) if not policies: default_provider = _get_default_provider_name(provider_type) if default_provider: return [default_provider] return [] provider_names = getPolicyActionValue(policies, provider_action_name, is_string=True) providers = [] for entry in [x.strip() for x in provider_names.split(' ')]: if entry: providers.append(entry) return providers
def get_single_auth_policy(policy_name, user=None, realms=None): """ Retrieves a policy value and checks if the value is consistent across realms. :param policy_name: the name of the policy, e.g: * qrtoken_pairing_callback_url * qrtoken_pairing_callback_sms * qrtoken_challenge_response_url * qrtoken_challenge_response_sms :param realms: the realms that his policy should be effective in """ action_values = [] login = None ret = None if user and user.login and user.realm: realms = [user.realm] login = user.login if realms is None or len(realms) == 0: realms = ['/:no realm:/'] params = {"scope": "authentication", 'action': policy_name} for realm in realms: params['realm'] = realm if login: params['user'] = login policy = getPolicy(params) action_value = getPolicyActionValue(policy, policy_name, is_string=True) if action_value: action_values.append(action_value) if len(action_values) > 1: for value in action_values: if value != action_values[0]: raise Exception('conflicting policy values %r found for ' 'realm set: %r' % (action_values, realms)) if action_values: ret = action_values[0] return ret
def is_phone_editable(user=""): """ this function checks the policy scope=selfservice, action=edit_sms This is a int policy, while the '0' is a deny """ # the default string is the OTP value ret = True realm = user.realm login = user.login policies = getPolicy({"scope": "selfservice", "realm": realm, "action": "edit_sms", "user": login}) if policies: edit_sms = getPolicyActionValue(policies, "edit_sms") if edit_sms == 0: ret = False return ret
def notify_user(user, action, info, required=False): """ notify user via email, sms or other method (http/whatsapp...) :param user: the user who should be notified :param action: action is currently the notification action like enrollment, setPin, which are defined in the notification policies :param info: generic dict which is action specific :param required: if True an exception is raised if no notification could be send eg if no provider is defined or could be found :return: boolean - true if notification is enabled """ policies = get_client_policy(request_context['Client'], scope='notification', action=action, realm=user.realm, user=user.login) provider_specs = getPolicyActionValue(policies, action, is_string=True) if not isinstance(provider_specs, list): provider_specs = [provider_specs] # TODO: use the ResouceSchduler to handle failover for provider_spec in provider_specs: provider_type, _sep, provider_name = provider_spec.partition('::') if provider_type == 'email': notify_user_by_email(provider_name, user, action, info) return True # elif provider_type == 'sms': # notify_user_by_email(provider_name, user, action, info) log.info('Failed to notify user %r', user) if required: raise NotificationException( 'No notification has been sent - %r provider defined?' % action) return False
def _is_valid_facet(self, origin): """ check if origin is in the valid facets if the u2f_valid_facets policy is set. Otherwise check if the origin matches the previously saved origin :return: boolean - True if supported, False if unsupported """ is_valid = False # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm valid_facets_action_value = '' realms = self.token.getRealmNames() if len(realms) > 0: get_policy_params = { 'action': 'u2f_valid_facets', 'scope': 'enrollment', 'realm': realms[0] } valid_facets_action_value = getPolicyActionValue(getPolicy(get_policy_params), 'u2f_valid_facets', String=True ) if valid_facets_action_value != '': # 'u2f_valid_facets' policy is set - check if origin is in valid facets list valid_facets = valid_facets_action_value.split(';') for facet in valid_facets: facet = facet.strip() if origin in valid_facets: is_valid = True else: # 'u2f_valid_facets' policy is empty or not set # check if origin matches the origin stored in the token info or save it if no origin # is stored yet appId = self.getFromTokenInfo('appId', None) if appId is None: self.addToTokenInfo('appId', origin) is_valid = True else: if origin == appId: is_valid = True return is_valid
def _is_valid_facet(self, origin): """ check if origin is in the valid facets if the u2f_valid_facets policy is set. Otherwise check if the origin matches the previously saved origin :return: boolean - True if supported, False if unsupported """ is_valid = False # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm valid_facets_action_value = '' realms = self.token.getRealmNames() if len(realms) > 0: get_policy_params = { 'action': 'u2f_valid_facets', 'scope': 'enrollment', 'realm': realms[0] } valid_facets_action_value = getPolicyActionValue( getPolicy(get_policy_params), 'u2f_valid_facets', String=True) if valid_facets_action_value != '': # 'u2f_valid_facets' policy is set - check if origin is in valid facets list valid_facets = valid_facets_action_value.split(';') for facet in valid_facets: facet = facet.strip() if origin in valid_facets: is_valid = True else: # 'u2f_valid_facets' policy is empty or not set # check if origin matches the origin stored in the token info or save it if no origin # is stored yet appId = self.getFromTokenInfo('appId', None) if appId is None: self.addToTokenInfo('appId', origin) is_valid = True else: if origin == appId: is_valid = True return is_valid
def get_auth_smstext(user="", realm=""): """ this function checks the policy scope=authentication, action=smstext This is a string policy The function returns the tuple (bool, string), bool: If a policy is defined string: the string to use """ # the default string is the OTP value ret = False smstext = "<otp>" pol = getPolicy({"scope": "authentication", "realm": realm, "action": "smstext"}) if len(pol) > 0: smstext = getPolicyActionValue(pol, "smstext", String=True) log.debug("[get_auth_smstext] got the smstext = %s" % smstext) ret = True return ret, smstext
def is_email_editable(user=""): ''' this function checks the policy scope=selfservice, action=edit_email This is a int policy, while the '0' is a deny ''' ret = True realm = user.realm login = user.login policies = getPolicy({'scope': 'selfservice', 'realm': realm, "action": "edit_email", "user": login}, ) if policies: edit_email = getPolicyActionValue(policies, "edit_email") if edit_email == 0: ret = False return ret
def is_email_editable(user=""): ''' this function checks the policy scope=selfservice, action=edit_email This is a int policy, while the '0' is a deny ''' ret = True realm = user.realm login = user.login policies = getPolicy({'scope': 'selfservice', 'realm': realm, "action": "edit_email", "user": login},) if policies: edit_email = getPolicyActionValue(policies, "edit_email") if edit_email == 0: ret = False return ret
def is_phone_editable(user=""): ''' this function checks the policy scope=selfservice, action=edit_sms This is a int policy, while the '0' is a deny ''' # the default string is the OTP value ret = True realm = user.realm login = user.login policies = getPolicy({'scope': 'selfservice', 'realm': realm, "action": "edit_sms", "user": login}) if policies: edit_sms = getPolicyActionValue(policies, "edit_sms") if edit_sms == 0: ret = False return ret
def get_auth_smstext(user="", realm=""): ''' this function checks the policy scope=authentication, action=smstext This is a string policy The function returns the tuple (bool, string), bool: If a policy is defined string: the string to use ''' # the default string is the OTP value ret = False smstext = "<otp>" pol = get_client_policy(context['Client'], scope="authentication", realm=realm, user=user, action="smstext") if len(pol) > 0: smstext = getPolicyActionValue(pol, "smstext", is_string=True) log.debug("[get_auth_smstext] got the smstext = %s" % smstext) ret = True return ret, smstext
def is_phone_editable(user="", context=None): ''' this function checks the policy scope=selfservice, action=edit_sms This is a int policy, while the '0' is a deny ''' # the default string is the OTP value ret = True realm = user.realm login = user.login policies = getPolicy({'scope': 'selfservice', 'realm': realm, "action": "edit_sms", "user": login}, context=context) if policies: edit_sms = getPolicyActionValue(policies, "edit_sms") if edit_sms == 0: ret = False return ret
def enforce_smstext(user="", realm=""): ''' this function checks the boolean policy scope=authentication, action=enforce_smstext The function returns true if the smstext should be used instead of the challenge data :return: bool ''' pol = get_client_policy(context['Client'], scope="authentication", realm=realm, user=user, action="enforce_smstext") if len(pol) > 0: enforce_smstext = getPolicyActionValue(pol, "enforce_smstext") log.debug("got enforce_smstext = %r" % enforce_smstext) return enforce_smstext or False return False
def loadProviderFromPolicy(provider_type, user=None): """ interface for the provider user like email token or sms token :param provider_type: 'email' or 'sms :param user: the user, who should receive the message, used for the policy lookup :return: the instantiated provider with already loaded config """ # check if the provider is defined in a policy provider_name = None # lookup the policy action name provider_action_name = Policy_action_name.get(provider_type) if not provider_action_name: raise Exception('unknown provider_type for policy lookup! %r' % provider_type) if not user: raise Exception('unknown user for policy lookup! %r' % user) realm = user.realm login = user.login policies = getPolicy({'scope': 'authentication', 'realm': realm, "action": provider_action_name, "user": login}, ) if policies: provider_name = getPolicyActionValue(policies, provider_action_name, is_string=True) return loadProvider(provider_type, provider_name)
def get_auth_smstext(user="", realm="", context=None): ''' this function checks the policy scope=authentication, action=smstext This is a string policy The function returns the tuple (bool, string), bool: If a policy is defined string: the string to use ''' # the default string is the OTP value ret = False smstext = "<otp>" pol = getPolicy({'scope': 'authentication', 'realm': realm, "action": "smstext"}, context=context) if len(pol) > 0: smstext = getPolicyActionValue(pol, "smstext", is_string=True) log.debug("[get_auth_smstext] got the smstext = %s" % smstext) ret = True return ret, smstext
def loadProviderFromPolicy(provider_type, realm=None, user=None): """ interface for the provider user like email token or sms token :param provider_type: 'push', 'email' or 'sms :param user: the user, who should receive the message, used for the policy lookup :return: the instantiated provider with already loaded config """ # check if the provider is defined in a policy provider_name = None # lookup the policy action name provider_action_name = Policy_action_name.get(provider_type) if not provider_action_name: raise Exception('unknown provider_type for policy lookup! %r' % provider_type) if user is None: raise Exception('unknown user for policy lookup! %r' % user) if user and user.login: realm = user.realm policies = get_client_policy(request_context['Client'], scope='authentication', action=provider_action_name, realm=realm, user=user.login) if policies: provider_name = getPolicyActionValue(policies, provider_action_name, is_string=True) return loadProvider(provider_type, provider_name)
def loadProviderFromPolicy(provider_type, realm=None, user=None): """ interface for the provider user like email token or sms token :param provider_type: 'push', 'email' or 'sms :param user: the user, who should receive the message, used for the policy lookup :return: the instantiated provider with already loaded config """ # check if the provider is defined in a policy provider_name = None # lookup the policy action name provider_action_name = Policy_action_name.get(provider_type) if not provider_action_name: raise Exception('unknown provider_type for policy lookup! %r' % provider_type) if not user: raise Exception('unknown user for policy lookup! %r' % user) if user and user.login: realm = user.realm policies = get_client_policy(request_context['Client'], scope='authentication', action=provider_action_name, realm=realm, user=user.login) if policies: provider_name = getPolicyActionValue(policies, provider_action_name, is_string=True) return loadProvider(provider_type, provider_name)
def getInitDetail(self, params, user=None): """ to complete the token normalisation, the response of the initialisation should be built by the token specific method, the getInitDetails """ response_detail = {} info = self.getInfo() response_detail.update(info) response_detail['serial'] = self.getSerial() # get requested phase requested_phase = getParam(params, "phase", optional=False) if requested_phase == "registration1": # We are in registration phase 1 # We create a 32 bytes otp key (from urandom) # which is used as the registration challenge challenge = base64.urlsafe_b64encode(binascii.unhexlify(self._genOtpKey_(32))) self.addToTokenInfo('challenge', challenge) # save the appId to the TokenInfo # An appId passed as parameter is preferred over an appId defined in a policy appId = '' if 'appid' in params: appId = params.get('appid') else: # No appId passed as parameter - fall back to the policy # Get the appId as specified in the enrollment policy 'u2f_app_id' # for the specific realm # If the token has multiple realms, the appIds are checked for conflicts. # It could be discussed whether the token should use the appId of the default # realm, when the token is not attached to any realms realms = self.token.getRealmNames() for realm in realms: get_policy_params = { 'action': 'u2f_app_id', 'scope': 'enrollment', 'realm': realm } policy_value = getPolicyActionValue(getPolicy(get_policy_params), 'u2f_app_id', String=True ) # Check for appId conflicts if appId and policy_value: if appId != policy_value: log.error("Conflicting appId values in u2f policies.") raise Exception("Conflicting appId values in u2f policies.") appId = policy_value if not appId: log.error("No appId defined.") raise Exception("No appId defined.") self.addToTokenInfo('appId', appId) # create U2F RegisterRequest object and append it to the response as 'message' appId = self._get_app_id() register_request = {'challenge': challenge, 'version': 'U2F_V2', 'appId': appId } response_detail['registerrequest'] = register_request elif requested_phase == "registration2": # We are in registration phase 2 # process the data generated by the u2f compatible token device registerResponse = "" otpkey = None if 'otpkey' in params: otpkey = params.get('otpkey') if otpkey is not None: # otpkey holds the JSON RegisterResponse object as specified by the FIDO Alliance try: registerResponse = json.loads(otpkey) except ValueError as ex: log.error("Invalid JSON format - value error %r", (ex)) raise Exception('Invalid JSON format') try: registrationData = registerResponse['registrationData'] clientData = registerResponse['clientData'] except AttributeError as ex: log.error( "Couldn't find keyword in JSON object - attribute error %r ", (ex)) raise Exception("Couldn't find keyword in JSON object") # registrationData and clientData are urlsafe base64 encoded # correct padding errors (length should be multiples of 4) # fill up the registrationData with '=' to the correct padding registrationData = registrationData + \ ('=' * (4 - (len(registrationData) % 4))) clientData = clientData + ('=' * (4 - (len(clientData) % 4))) registrationData = base64.urlsafe_b64decode( registrationData.encode('ascii')) clientData = base64.urlsafe_b64decode(clientData.encode('ascii')) # parse the raw registrationData according to the specification (userPublicKey, keyHandle, X509cert, signature) = \ self._parseRegistrationData(registrationData) # check the received clientData object if not self._checkClientData( clientData, 'registration', self.getFromTokenInfo('challenge', None)): raise ValueError("Received invalid clientData object. Aborting...") # prepare the applicationParameter and challengeParameter needed for # verification of the registration signature appId = self._get_app_id() applicationParameter = sha256(appId).digest() challengeParameter = sha256(clientData).digest() # verify the registration signature self._validateRegistrationSignature(applicationParameter, challengeParameter, keyHandle, userPublicKey, X509cert, signature ) # save the key handle and the user public key in the Tokeninfo field for # future use self.addToTokenInfo('keyHandle', base64.urlsafe_b64encode(keyHandle)) self.addToTokenInfo('publicKey', base64.urlsafe_b64encode(userPublicKey)) self.addToTokenInfo('counter', '0') self.addToTokenInfo('phase', 'authentication') # remove the registration challenge from the token info self.removeFromTokenInfo('challenge') # Activate the token self.token.LinOtpIsactive = True else: log.error("No otpkey set!") raise ValueError("No otpkey set") else: log.error("Unsupported phase: %s", requested_phase) raise Exception("Unsupported phase: %s", requested_phase) return response_detail
def getInitDetail(self, params, user=None): """ to complete the token normalisation, the response of the initialisation should be built by the token specific method, the getInitDetails """ response_detail = {} info = self.getInfo() response_detail.update(info) response_detail['serial'] = self.getSerial() # get requested phase requested_phase = getParam(params, "phase", optional=False) if requested_phase == "registration1": # We are in registration phase 1 # We create a 32 bytes otp key (from urandom) # which is used as the registration challenge challenge = base64.urlsafe_b64encode(binascii.unhexlify(self._genOtpKey_(32))) self.addToTokenInfo('challenge', challenge) # save the appId to the TokenInfo # An appId passed as parameter is preferred over an appId defined in a policy appId = '' if 'appid' in params: appId = params.get('appid') else: # No appId passed as parameter - fall back to the policy # Get the appId as specified in the enrollment policy 'u2f_app_id' # for the specific realm # If the token has multiple realms, the appIds are checked for conflicts. # It could be discussed whether the token should use the appId of the default # realm, when the token is not attached to any realms realms = self.token.getRealmNames() for realm in realms: get_policy_params = { 'action': 'u2f_app_id', 'scope': 'enrollment', 'realm': realm } policies = getPolicy(get_policy_params, context=self.context) policy_value = getPolicyActionValue(policies, 'u2f_app_id', String=True ) # Check for appId conflicts if appId and policy_value: if appId != policy_value: log.error("Conflicting appId values in u2f policies.") raise Exception("Conflicting appId values in u2f policies.") appId = policy_value if not appId: log.error("No appId defined.") raise Exception("No appId defined.") self.addToTokenInfo('appId', appId) # create U2F RegisterRequest object and append it to the response as 'message' appId = self._get_app_id() register_request = {'challenge': challenge, 'version': 'U2F_V2', 'appId': appId } response_detail['registerrequest'] = register_request elif requested_phase == "registration2": # We are in registration phase 2 # process the data generated by the u2f compatible token device registerResponse = "" otpkey = None if 'otpkey' in params: otpkey = params.get('otpkey') if otpkey is not None: # otpkey holds the JSON RegisterResponse object as specified by the FIDO Alliance try: registerResponse = json.loads(otpkey) except ValueError as ex: log.exception("Invalid JSON format - value error %r", (ex)) raise Exception('Invalid JSON format') self._handle_client_errors(registerResponse) try: registrationData = registerResponse['registrationData'] clientData = registerResponse['clientData'] except AttributeError as ex: log.exception( "Couldn't find keyword in JSON object - attribute error %r ", (ex)) raise Exception("Couldn't find keyword in JSON object") # registrationData and clientData are urlsafe base64 encoded # correct padding errors (length should be multiples of 4) # fill up the registrationData with '=' to the correct padding registrationData = registrationData + \ ('=' * (4 - (len(registrationData) % 4))) clientData = clientData + ('=' * (4 - (len(clientData) % 4))) registrationData = base64.urlsafe_b64decode( registrationData.encode('ascii')) clientData = base64.urlsafe_b64decode(clientData.encode('ascii')) # parse the raw registrationData according to the specification (userPublicKey, keyHandle, X509cert, signature) = \ self._parseRegistrationData(registrationData) # check the received clientData object if not self._checkClientData( clientData, 'registration', self.getFromTokenInfo('challenge', None)): raise ValueError("Received invalid clientData object. Aborting...") # prepare the applicationParameter and challengeParameter needed for # verification of the registration signature appId = self._get_app_id() applicationParameter = sha256(appId).digest() challengeParameter = sha256(clientData).digest() # verify the registration signature self._validateRegistrationSignature(applicationParameter, challengeParameter, keyHandle, userPublicKey, X509cert, signature ) # save the key handle and the user public key in the Tokeninfo field for # future use self.addToTokenInfo('keyHandle', base64.urlsafe_b64encode(keyHandle)) self.addToTokenInfo('publicKey', base64.urlsafe_b64encode(userPublicKey)) self.addToTokenInfo('counter', '0') self.addToTokenInfo('phase', 'authentication') # remove the registration challenge from the token info self.removeFromTokenInfo('challenge') # Activate the token self.token.LinOtpIsactive = True else: log.error("No otpkey set!") raise ValueError("No otpkey set") else: log.error("Unsupported phase: %s", requested_phase) raise Exception("Unsupported phase: %s", requested_phase) return response_detail