def setSoPin(self, soPin): # TODO: we could log the PIN here log.debug('setSoPin()') iv = geturandom(16) enc_soPin = encrypt(soPin, iv) self.privacyIDEATokenPinSO = unicode(binascii.hexlify(enc_soPin)) self.privacyIDEATokenPinSOIV = unicode(binascii.hexlify(iv))
def rand(): """ This endpoint can be used to retrieve random keys from privacyIDEA. In certain cases the client might need random data to initialize tokens on the client side. E.g. the command line client when initializing the yubikey or the WebUI when creating Client API keys for the yubikey. In this case, privacyIDEA can create the random data/keys. :queryparam len: The length of a symmetric key (byte) :queryparam encode: The type of encoding. Can be "hex" or "b64". :return: key material """ length = int(getParam(request.all_data, "len") or 20) encode = getParam(request.all_data, "encode") r = geturandom(length=length) if encode == "b64": res = b64encode_and_unicode(r) else: res = hexlify_and_unicode(r) g.audit_object.log({'success': res}) return send_result(res)
def get_init_detail(self, params=None, user=None): """ At the end of the initialization we ask the user to press the button """ response_detail = {} if self.init_step == 1: # This is the first step of the init request app_id = get_from_config("u2f.appId", "").strip("/") from privacyidea.lib.error import TokenAdminError if not app_id: raise TokenAdminError(_("You need to define the appId in the " "token config!")) nonce = urlsafe_b64encode_and_unicode(geturandom(32)) response_detail = TokenClass.get_init_detail(self, params, user) register_request = {"version": U2F_Version, "challenge": nonce, "appId": app_id} response_detail["u2fRegisterRequest"] = register_request self.add_tokeninfo("appId", app_id) elif self.init_step == 2: # This is the second step of the init request response_detail["u2fRegisterResponse"] = {"subject": self.token.description} return response_detail
def generate_otpkey(key_size=20): ''' generates the HMAC key of keysize. Should be 20 or 32 The key is returned as a hexlified string ''' log.debug("generating key of size %s" % key_size) return binascii.hexlify(geturandom(key_size))
def __init__(self, key=None, keylen=20, algo=None, digits=6, offset=0, jitter=0, timestep=60): ''' no key given - create one ''' if key is None: self.key = binascii.hexlify(geturandom(keylen)) else: self.key = key.decode('hex') keylen = len(self.key) if algo is None: if keylen == 20: algo = sha1 elif keylen == 32: algo = sha256 elif keylen == 64: algo = sha512 else: algo = algo self.offset = offset self.jitter = jitter self.timestep = timestep self.digits = digits self.hmacOtp = HmacOtp(self.key, digits=self.digits, hashfunc=algo) return
def hash_password(password, hashtype): """ Hash a password with phppass, SHA, SSHA, SSHA256, SSHA512, OTRS :param password: The password in plain text :param hashtype: One of the hash types as string :return: The hashed password """ hashtype = hashtype.upper() if hashtype == "PHPASS": PH = PasswordHash() password = PH.hash_password(password) elif hashtype == "SHA": password = hashlib.sha1(password).digest() password = "******" + b64encode(password) elif hashtype == "SSHA": salt = geturandom(20) hr = hashlib.sha1(password) hr.update(salt) pw = b64encode(hr.digest() + salt) return "{SSHA}" + pw elif hashtype == "SSHA256": salt = geturandom(32) hr = hashlib.sha256(password) hr.update(salt) pw = b64encode(hr.digest() + salt) return "{SSHA256}" + pw elif hashtype == "SSHA512": salt = geturandom(64) hr = hashlib.sha512(password) hr.update(salt) pw = b64encode(hr.digest() + salt) return "{SSHA512}" + pw elif hashtype == "OTRS": password = hashlib.sha256(password).hexdigest() elif hashtype == "MD5CRYPT": salt = geturandom(8, True) password = crypt.crypt(password, "$1$" + salt + "$") elif hashtype == "SHA512CRYPT": salt = geturandom(8, True) password = crypt.crypt(password, "$6$" + salt + "$") else: raise Exception("Unsupported password hashtype. Use PHPASS, SHA, " "SSHA, SSHA256, SSHA512, OTRS.") return password
def test_41_import_csv(self): # sha1 r = TokenClass.get_import_csv(["ser1", geturandom(20, True), "hotp", "6"]) self.assertEqual(r["hashlib"], "sha1") # sha224 r = TokenClass.get_import_csv(["ser1", geturandom(28, True), "hotp", "6"]) self.assertEqual(r["hashlib"], "sha224") # sha256 r = TokenClass.get_import_csv(["ser1", geturandom(32, True), "hotp", "8"]) self.assertEqual(r["hashlib"], "sha256") # sha384 r = TokenClass.get_import_csv(["ser1", geturandom(48, True), "totp", " 8 "]) self.assertEqual(r["hashlib"], "sha384") self.assertEqual(r["type"], "totp") self.assertEqual(r["otplen"], "8") # sha512 r = TokenClass.get_import_csv(["ser1", geturandom(64, True), "totp", "8"]) self.assertEqual(r["hashlib"], "sha512")
def generate_new_refilltoken(token_obj): """ Generate new refill token and store it in the tokeninfo of the token. :param token_obj: token in question :return: a string """ new_refilltoken = geturandom(REFILLTOKEN_LENGTH, hex=True) token_obj.add_tokeninfo("refilltoken", new_refilltoken) return new_refilltoken
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} message = get_action_values_from_options(SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(), ACTION.CHALLENGETEXT), options)or _(u'Please confirm with your U2F token ({0!s})').format( self.token.description) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) challenge = geturandom(32) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=hexlify_and_unicode(challenge), data=None, session=options.get("session"), validitytime=validity) db_challenge.save() sec_object = self.token.get_otpkey() key_handle_hex = sec_object.getKey() key_handle_bin = binascii.unhexlify(key_handle_hex) key_handle_url = url_encode(key_handle_bin) challenge_url = url_encode(challenge) u2f_sign_request = {"appId": self.get_tokeninfo("appId"), "version": U2F_Version, "challenge": challenge_url, "keyHandle": key_handle_url} image_url = IMAGES.get(self.token.description.lower().split()[0], "") response_details = {"u2fSignRequest": u2f_sign_request, "hideResponseInput": True, "img": image_url} return True, message, db_challenge.transaction_id, response_details
def setHKey(self, hOtpKey, reset_failcount=True): iv = geturandom(16) #bhOtpKey = binascii.unhexlify(hOtpKey) enc_otp_key = encrypt(hOtpKey, iv) self.privacyIDEAKeyEnc = unicode(binascii.hexlify(enc_otp_key)) self.privacyIDEAKeyIV = unicode(binascii.hexlify(iv)) self.privacyIDEACount = 0 if True == reset_failcount: self.privacyIDEAFailCount = 0
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} message = 'Please confirm with your U2F token ({0!s})'.format( \ self.token.description) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) challenge = geturandom(32) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=binascii.hexlify(challenge), data=None, session=options.get("session"), validitytime=validity) db_challenge.save() sec_object = self.token.get_otpkey() key_handle_hex = sec_object.getKey() key_handle_bin = binascii.unhexlify(key_handle_hex) key_handle_url = url_encode(key_handle_bin) challenge_url = url_encode(challenge) u2f_sign_request = {"appId": self.get_tokeninfo("appId"), "version": U2F_Version, "challenge": challenge_url, "keyHandle": key_handle_url} image_url = IMAGES.get(self.token.description.lower().split()[0], "") response_details = {"u2fSignRequest": u2f_sign_request, "hideResponseInput": True, "img": image_url} return True, message, db_challenge.transaction_id, response_details
def generate_otpkey(key_size=20): """ generates the HMAC key of keysize. Should be 20 or 32 The key is returned as a hexlified string :param key_size: The size of the key to generate :type key_size: int :return: hexlified key :rtype: string """ log.debug("generating key of size {0!s}".format(key_size)) return binascii.hexlify(geturandom(key_size))
def setHKey(self, hOtpKey, reset_failcount=True): iv = geturandom(16) enc_otp_key = encrypt(hOtpKey, iv) self.privacyIDEAKeyEnc = unicode(binascii.hexlify(enc_otp_key)) length = len(self.privacyIDEAKeyEnc) if length > 1024: log.error("Key %s exceeds database field %d!" % (self.getSerial(), length)) self.privacyIDEAKeyIV = unicode(binascii.hexlify(iv)) self.privacyIDEACount = 0 if True == reset_failcount: self.privacyIDEAFailCount = 0
def createtoken(role=ROLE.ADMIN): """ Create an API authentication token for administrative or validate use. Possible roles are "admin" or "validate". """ username = geturandom(hex=True) secret = app.config.get("SECRET_KEY") authtype = "API" validity = timedelta(days=365) token = jwt.encode({"username": username, "realm": "API", "nonce": geturandom(hex=True), "role": role, "authtype": authtype, "exp": datetime.datetime.utcnow() + validity, "rights": "TODO"}, secret) print "Username: %s" % username print "Role: %s" % role print "Auth-Token: %s" % token
def update(self, param, reset_failcount=True): """ process the initialization parameters We need to distinguish the first authentication step and the second authentication step. 1. step: parameter type contained. parameter genkey contained. 2. step: parameter serial contained parameter fbtoken contained parameter pubkey contained :param param: dict of initialization parameters :type param: dict :return: nothing """ upd_param = {} for k, v in param.items(): upd_param[k] = v if "serial" in upd_param and "fbtoken" in upd_param and "pubkey" in upd_param: # We are in step 2: if self.token.rollout_state != "clientwait": raise ParameterError("Invalid state! The token you want to enroll is not in the state 'clientwait'.") enrollment_credential = getParam(upd_param, "enrollment_credential", optional=False) if enrollment_credential != self.get_tokeninfo("enrollment_credential"): raise ParameterError("Invalid enrollment credential. You are not authorized to finalize this token.") self.del_tokeninfo("enrollment_credential") self.token.rollout_state = "enrolled" self.token.active = True self.add_tokeninfo(PUBLIC_KEY_SMARTPHONE, upd_param.get("pubkey")) self.add_tokeninfo("firebase_token", upd_param.get("fbtoken")) # create a keypair for the server side. pub_key, priv_key = generate_keypair(4096) self.add_tokeninfo(PUBLIC_KEY_SERVER, pub_key) self.add_tokeninfo(PRIVATE_KEY_SERVER, priv_key, "password") elif "genkey" in upd_param: # We are in step 1: upd_param["2stepinit"] = 1 self.add_tokeninfo("enrollment_credential", geturandom(20, hex=True)) # We also store the firebase config, that was used during the enrollment. self.add_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG, param.get(PUSH_ACTION.FIREBASE_CONFIG)) else: raise ParameterError("Invalid Parameters. Either provide (genkey) or (serial, fbtoken, pubkey).") TokenClass.update(self, upd_param, reset_failcount)
def hash_password(password, hashtype): """ Hash a password with phppass, SHA, SSHA, SSHA256, SSHA512, OTRS :param password: The password in plain text :param hashtype: One of the hash types as string :return: The hashed password """ hashtype = hashtype.upper() if hashtype == "PHPASS": PH = PasswordHash() password = PH.hash_password(password) elif hashtype == "SHA": password = hashlib.sha1(password).digest() password = "******" + b64encode(password) elif hashtype == "SSHA": salt = geturandom(20) hr = hashlib.sha1(password) hr.update(salt) pw = b64encode(hr.digest() + salt) return "{SSHA}" + pw elif hashtype == "SSHA256": salt = geturandom(32) hr = hashlib.sha256(password) hr.update(salt) pw = b64encode(hr.digest() + salt) return "{SSHA256}" + pw elif hashtype == "SSHA512": salt = geturandom(64) hr = hashlib.sha512(password) hr.update(salt) pw = b64encode(hr.digest() + salt) return "{SSHA512}" + pw elif hashtype == "OTRS": password = hashlib.sha256(password).hexdigest() else: raise Exception("Unsupported password hashtype. Use PHPASS, SHA, " "SSHA, SSHA256, SSHA512, OTRS.") return password
def test_06_password_encrypt_decrypt(self): res = DefaultSecurityModule.password_encrypt("secrettext", "password1") self.assertTrue(len(res) == len( "80f1833450a74224c32d03fe4161735c" ":c1944e8c0982d5c35992a9b25abad18a2" "8cac15585ed2fbab05bd2b1ea2cc44b"), res) res = DefaultSecurityModule.password_decrypt(res, "password1") self.assertTrue(res == "secrettext", res) # encrypt and decrypt binary data like the enckey enckey = geturandom(96) cipher = DefaultSecurityModule.password_encrypt(enckey, "top secret " "!!!") clear = DefaultSecurityModule.password_decrypt(cipher, "top secret " "!!!") self.assertTrue(enckey == clear, (enckey, clear)) # encrypt and decrypt binary data like the enckey enckey = geturandom(96) cipher = DefaultSecurityModule.password_encrypt(enckey, "topSecret123!") clear = DefaultSecurityModule.password_decrypt(cipher, "topSecret123!") self.assertTrue(enckey == clear, (enckey, clear))
def test_41_import_csv(self): # sha1 r = TokenClass.get_import_csv( ["ser1", geturandom(20, True), "hotp", "6"]) self.assertEqual(r["hashlib"], "sha1") # sha224 r = TokenClass.get_import_csv( ["ser1", geturandom(28, True), "hotp", "6"]) self.assertEqual(r["hashlib"], "sha224") # sha256 r = TokenClass.get_import_csv( ["ser1", geturandom(32, True), "hotp", "8"]) self.assertEqual(r["hashlib"], "sha256") # sha384 r = TokenClass.get_import_csv( ["ser1", geturandom(48, True), "totp", " 8 "]) self.assertEqual(r["hashlib"], "sha384") self.assertEqual(r["type"], "totp") self.assertEqual(r["otplen"], "8") # sha512 r = TokenClass.get_import_csv( ["ser1", geturandom(64, True), "totp", "8"]) self.assertEqual(r["hashlib"], "sha512")
def get_random_bytes(self, count): outp = '' try: outp = geturandom(count) except Exception as exx: # pragma: no cover log.debug("problem getting urandom: {0!s}".format(exx)) if len(outp) < count: # pragma: no cover outp = '' rem = count while rem > 0: self.random_state = hashlib.md5( str(time.time()) + self.random_state).hexdigest() outp += hashlib.md5(self.random_state).digest() rem -= 1 outp = outp[:count] return outp
def get_random_bytes(self, count): outp = '' try: outp = geturandom(count) except Exception as exx: # pragma: no cover log.debug("problem getting urandom: {0!s}".format(exx)) if len(outp) < count: # pragma: no cover outp = '' rem = count while rem > 0: self.random_state = hashlib.md5(str(time.time()) + self.random_state).hexdigest() outp += hashlib.md5(self.random_state).digest() rem -= 1 outp = outp[:count] return outp
def get_authentication_item(cls, token_type, serial, challenge=None, options=None, filter_param=None): """ :param token_type: the type of the token. At the moment we only support yubikeys, tokentype "TOTP". :param serial: the serial number of the token. The challenge response token needs to start with "UBOM". :param challenge: A challenge, for which a response get calculated. If none is presented, we create one. :type challenge: hex string :return auth_item: For Yubikey token type it returns a dictionary with a "challenge" and a "response". """ ret = {} options = options or {} if token_type.lower() == "totp" and serial.startswith("UBOM"): # create a challenge of 32 byte # Although the yubikey is capable of doing 64byte challenges # the hmac module calculates different responses for 64 bytes. if challenge is None: challenge = geturandom(32) challenge_hex = binascii.hexlify(challenge) else: challenge_hex = challenge ret["challenge"] = challenge_hex # create the response. We need to get # the HMAC key and calculate a HMAC response for # the challenge toks = get_tokens(serial=serial, active=True) if len(toks) == 1: # tokenclass is a TimeHmacTokenClass (_r, _p, otp, _c) = toks[0].get_otp(challenge=challenge_hex, do_truncation=False) ret["response"] = otp else: log.info("Token %r, type %r is not supported by" "LUKS application module" % (serial, token_type)) return ret
def get_authentication_item(token_type, serial, challenge=None, options=None, filter_param=None): """ :param token_type: the type of the token. At the moment we only support yubikeys, tokentype "TOTP". :param serial: the serial number of the token. The challenge response token needs to start with "UBOM". :param challenge: A challenge, for which a response get calculated. If none is presented, we create one. :type challenge: hex string :return auth_item: For Yubikey token type it returns a dictionary with a "challenge" and a "response". """ ret = {} options = options or {} if token_type.lower() == "totp" and serial.startswith("UBOM"): # create a challenge of 32 byte # Although the yubikey is capable of doing 64byte challenges # the hmac module calculates different responses for 64 bytes. if challenge is None: challenge = geturandom(32) challenge_hex = hexlify_and_unicode(challenge) else: challenge_hex = challenge ret["challenge"] = challenge_hex # create the response. We need to get # the HMAC key and calculate a HMAC response for # the challenge toks = get_tokens(serial=serial, active=True) if len(toks) == 1: # tokenclass is a TimeHmacTokenClass (_r, _p, otp, _c) = toks[0].get_otp(challenge=challenge_hex, do_truncation=False) ret["response"] = otp else: log.info("Token %r, type %r is not supported by" "LUKS application module" % (serial, token_type)) return ret
def create_challenge(self): """ Depending on the self.challenge_type and the self.challenge_length we create a challenge :return: a challenge string """ ret = None if self.challenge_type == "QH": ret = geturandom(length=self.challenge_length, hex=True) elif self.challenge_type == "QA": ret = get_alphanum_str(self.challenge_length) elif self.challenge_type == "QN": ret = get_rand_digit_str(length=self.challenge_length) if not ret: # pragma: no cover raise Exception("OCRA.create_challenge failed. Obviously no good " "challenge_type!") return ret
def _create_ssha(password): """ Encodes the given password as a base64 SSHA hash :param password: string to hash :type password: basestring :return: string encoded as a base64 SSHA hash """ salt = geturandom(4) # Hash password string and append the salt sha_hash = hashlib.sha1(password) sha_hash.update(salt) # Create a base64 encoded string digest_b64 = binascii.b2a_base64(sha_hash.digest() + salt).strip() # Tag it with SSHA tagged_digest = '{{SSHA}}{}'.format(digest_b64) return tagged_digest
def _attributes_to_db_columns(self, attributes): """ takes the attributes and maps them to the DB columns :param attributes: :return: dict with column name as keys and values """ columns = {} for fieldname in attributes.keys(): if self.map.get(fieldname): if fieldname == "password": password = attributes.get(fieldname) # Create a {SSHA256} password salt = geturandom(16) hr = hashlib.sha256(password) hr.update(salt) hash_bin = hr.digest() hash_b64 = b64encode(hash_bin + salt) columns[self.map.get(fieldname)] = "{SSHA256}" + hash_b64 else: columns[self.map.get(fieldname)] = attributes.get(fieldname) return columns
def password_encrypt(cls, text, password): """ Encrypt the given text with the password. A key is derived from the password and used to encrypt the text in AES MODE_CBC. The IV is returned togeather with the cipher text. <IV:Ciphter> :param text: The text to encrypt :param password: The password to derive a key from :return: IV and cipher text :rtype: basestring """ bkey = create_key_from_password(password) # convert input to ascii, so we can securely append bin data input_data = binascii.b2a_hex(text) input_data += u"\x01\x02" padding = (16 - len(input_data) % 16) % 16 input_data += padding * "\0" iv = geturandom(16) aes = AES.new(bkey, AES.MODE_CBC, iv) cipher = aes.encrypt(input_data) iv_hex = binascii.hexlify(iv) cipher_hex = binascii.hexlify(cipher) return "%s:%s" % (iv_hex, cipher_hex)
def password_encrypt(text, password): """ Encrypt the given text with the password. A key is derived from the password and used to encrypt the text in AES MODE_CBC. The IV is returned togeather with the cipher text. <IV:Ciphter> :param text: The text to encrypt :param password: The password to derive a key from :return: IV and cipher text :rtype: basestring """ bkey = create_key_from_password(password) # convert input to ascii, so we can securely append bin data input_data = binascii.b2a_hex(text) input_data += u"\x01\x02" padding = (16 - len(input_data) % 16) % 16 input_data += padding * "\0" iv = geturandom(16) aes = AES.new(bkey, AES.MODE_CBC, iv) cipher = aes.encrypt(input_data) iv_hex = binascii.hexlify(iv) cipher_hex = binascii.hexlify(cipher) return "{0!s}:{1!s}".format(iv_hex, cipher_hex)
def update(self, param, reset_failcount=True): if "tans" in param: # init tokens with tans tans = param.get("tans").split() tan_dict = {k: v for k, v in enumerate(tans)} # Avoid to generate TANs in the superclass PaperToken, since we get the tans from params param["papertoken_count"] = 0 # Determine the otplen from the TANs if len(tans) > 0: param["otplen"] = len(tans[0]) PaperTokenClass.update(self, param, reset_failcount=reset_failcount) else: # Init token without tans, so we create tans in the superclass PaperToken param["papertoken_count"] = param.get("tantoken_count") or DEFAULT_COUNT PaperTokenClass.update(self, param, reset_failcount=reset_failcount) # After this creation, the init_details contain the complete list of the TANs tan_dict = self.init_details.get("otps", {}) for tankey, tanvalue in tan_dict.items(): # Get a 4 byte salt from the crypto module salt = geturandom(SALT_LENGTH, hex=True) # Now we add all TANs to the tokeninfo of this token. hashed_tan = binascii.hexlify(hash(tanvalue, salt)) self.add_tokeninfo("tan.tan{0!s}".format(tankey), "{0}:{1}".format(salt, hashed_tan))
def create_subscription_request(): iv = geturandom(16) enc = encrypt("privacy IDEA", iv=iv) r = binascii.hexlify(enc + iv) return {"systemid": r}
def export_pskc(tokenobj_list, psk=None): """ Take a list of token objects and create a beautifulsoup xml object. If no preshared key is given, we create one and return it. :param tokenobj_list: list of token objects :param psk: pre-shared-key for AES-128-CBC in hex format :return: tuple of (psk, number of tokens, beautifulsoup) """ if psk: psk = binascii.unhexlify(psk) else: psk = geturandom(16) mackey = geturandom(20) encrypted_mackey = aes_encrypt_b64(psk, mackey) number_of_exported_tokens = 0 # define the header soup = BeautifulSoup( """<KeyContainer Version="1.0" xmlns="urn:ietf:params:xml:ns:keyprov:pskc" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <EncryptionKey> <ds:KeyName>Pre-shared-key</ds:KeyName> </EncryptionKey> <MACMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"> <MACKey> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/> <xenc:CipherData> <xenc:CipherValue>{encrypted_mackey}</xenc:CipherValue> </xenc:CipherData> </MACKey> </MACMethod> """.format(encrypted_mackey=encrypted_mackey), "html.parser") for tokenobj in tokenobj_list: if tokenobj.type.lower() not in ["totp", "hotp", "pw"]: continue type = tokenobj.type.lower() issuer = "privacyIDEA" try: manufacturer = tokenobj.token.description.encode("ascii") except UnicodeEncodeError: manufacturer = "deleted during export" serial = tokenobj.token.serial otplen = tokenobj.token.otplen counter = tokenobj.token.count suite = tokenobj.get_tokeninfo("hashlib", default="sha1") if type == "totp": timestep = tokenobj.get_tokeninfo("timeStep") timedrift = tokenobj.get_tokeninfo("timeShift") else: timestep = 0 timedrift = 0 otpkey = tokenobj.token.get_otpkey().getKey() try: encrypted_otpkey = aes_encrypt_b64(psk, binascii.unhexlify(otpkey)) except TypeError: # Some keys might be odd string length continue try: kp2 = BeautifulSoup( """<KeyPackage> <DeviceInfo> <Manufacturer>{manufacturer}</Manufacturer> <SerialNo>{serial}</SerialNo> </DeviceInfo> <Key Id="{serial}" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:{type}"> <Issuer>{issuer}</Issuer> <AlgorithmParameters> <ResponseFormat Length="{otplen}" Encoding="DECIMAL"/> <Suite hashalgo="{suite}" /> </AlgorithmParameters> <Data> <Secret> <EncryptedValue> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/> <xenc:CipherData> <xenc:CipherValue>{encrypted_otpkey}</xenc:CipherValue> </xenc:CipherData> </EncryptedValue> </Secret> <ValueMAC>TODOmissing</ValueMAC> <Time> <PlainValue>0</PlainValue> </Time> <TimeInterval> <PlainValue>{timestep}</PlainValue> </TimeInterval> <Counter> <PlainValue>{counter}</PlainValue> </Counter> <TimeDrift> <PlainValue>{timedrift}</PlainValue> </TimeDrift> </Data> </Key> </KeyPackage>""".format(serial=cgi.escape(serial), type=cgi.escape(type), otplen=otplen, issuer=cgi.escape(issuer), manufacturer=cgi.escape(manufacturer), counter=counter, timestep=timestep, encrypted_otpkey=encrypted_otpkey, timedrift=timedrift, suite=cgi.escape(suite)), "html.parser") soup.macmethod.insert_after(kp2) number_of_exported_tokens += 1 except Exception as e: log.warning(u"Failed to export the token {0!s}: {1!s}".format( serial, e)) return binascii.hexlify(psk), number_of_exported_tokens, soup
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} message = get_action_values_from_options( SCOPE.AUTH, ACTION.CHALLENGETEXT, options) or DEFAULT_CHALLENGE_TEXT attributes = None data = None fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if fb_identifier: challenge = b32encode_and_unicode(geturandom()) fb_gateway = create_sms_instance(fb_identifier) pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER) smartphone_data = _build_smartphone_data(self.token.serial, challenge, fb_gateway, pem_privkey, options) res = fb_gateway.submit_message( self.get_tokeninfo("firebase_token"), smartphone_data) if not res: raise ValidateError( "Failed to submit message to firebase service.") else: log.warning(u"The token {0!s} has no tokeninfo {1!s}. " u"The message could not be sent.".format( self.token.serial, PUSH_ACTION.FIREBASE_CONFIG)) raise ValidateError( "The token has no tokeninfo. Can not send via firebase service." ) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a PushChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=data, session=options.get("session"), validitytime=validity) db_challenge.save() self.challenge_janitor() return True, message, db_challenge.transaction_id, attributes
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ res = False options = options or {} message = get_action_values_from_options( SCOPE.AUTH, ACTION.CHALLENGETEXT, options) or DEFAULT_CHALLENGE_TEXT sslverify = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.SSL_VERIFY, options) or "1" sslverify = getParam({"sslverify": sslverify}, "sslverify", allowed_values=["0", "1"], default="1") attributes = None data = None challenge = b32encode_and_unicode(geturandom()) fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if fb_identifier: # We send the challenge to the Firebase service fb_gateway = create_sms_instance(fb_identifier) url = fb_gateway.smsgateway.option_dict.get( FIREBASE_CONFIG.REGISTRATION_URL) message_on_mobile = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.MOBILE_TEXT, options) or DEFAULT_MOBILE_TEXT title = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.MOBILE_TITLE, options) or "privacyIDEA" smartphone_data = { "nonce": challenge, "question": message_on_mobile, "serial": self.token.serial, "title": title, "sslverify": sslverify, "url": url } # Create the signature. # value to string sign_string = u"{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format( **smartphone_data) pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER) privkey_obj = serialization.load_pem_private_key( to_bytes(pem_privkey), None, default_backend()) # Sign the data with PKCS1 padding. Not all Androids support PSS padding. signature = privkey_obj.sign(sign_string.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) smartphone_data["signature"] = b32encode_and_unicode(signature) res = fb_gateway.submit_message( self.get_tokeninfo("firebase_token"), smartphone_data) if not res: raise ValidateError( "Failed to submit message to firebase service.") else: log.warning(u"The token {0!s} has no tokeninfo {1!s}. " u"The message could not be sent.".format( self.token.serial, PUSH_ACTION.FIREBASE_CONFIG)) raise ValidateError( "The token has no tokeninfo. Can not send via firebase service." ) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a PushChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=data, session=options.get("session"), validitytime=validity) db_challenge.save() self.challenge_janitor() return True, message, db_challenge.transaction_id, attributes
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ res = False options = options or {} message = get_action_values_from_options(SCOPE.AUTH, ACTION.CHALLENGETEXT, options) or DEFAULT_CHALLENGE_TEXT sslverify = get_action_values_from_options(SCOPE.AUTH, PUSH_ACTION.SSL_VERIFY, options) or "1" sslverify = getParam({"sslverify": sslverify}, "sslverify", allowed_values=["0", "1"], default="1") attributes = None data = None challenge = b32encode_and_unicode(geturandom()) fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if fb_identifier: # We send the challenge to the Firebase service fb_gateway = create_sms_instance(fb_identifier) url = fb_gateway.smsgateway.option_dict.get(FIREBASE_CONFIG.REGISTRATION_URL) message_on_mobile = get_action_values_from_options(SCOPE.AUTH, PUSH_ACTION.MOBILE_TEXT, options) or DEFAULT_MOBILE_TEXT title = get_action_values_from_options(SCOPE.AUTH, PUSH_ACTION.MOBILE_TITLE, options) or "privacyIDEA" smartphone_data = {"nonce": challenge, "question": message_on_mobile, "serial": self.token.serial, "title": title, "sslverify": sslverify, "url": url} # Create the signature. # value to string sign_string = u"{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format(**smartphone_data) pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER) privkey_obj = serialization.load_pem_private_key(to_bytes(pem_privkey), None, default_backend()) # Sign the data with PKCS1 padding. Not all Androids support PSS padding. signature = privkey_obj.sign(sign_string.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) smartphone_data["signature"] = b32encode_and_unicode(signature) res = fb_gateway.submit_message(self.get_tokeninfo("firebase_token"), smartphone_data) if not res: raise ValidateError("Failed to submit message to firebase service.") else: log.warning(u"The token {0!s} has no tokeninfo {1!s}. " u"The message could not be sent.".format(self.token.serial, PUSH_ACTION.FIREBASE_CONFIG)) raise ValidateError("The token has no tokeninfo. Can not send via firebase service.") validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a PushChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=data, session=options.get("session"), validitytime=validity) db_challenge.save() self.challenge_janitor() return True, message, db_challenge.transaction_id, attributes
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional challenge ``reply_dict``, which are displayed in the JSON challenges response. """ options = options or {} message = get_action_values_from_options( SCOPE.AUTH, ACTION.CHALLENGETEXT, options) or DEFAULT_CHALLENGE_TEXT reply_dict = {} data = None # Initially we assume there is no error from Firebase res = True fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if fb_identifier: challenge = b32encode_and_unicode(geturandom()) if fb_identifier != POLL_ONLY: # We only push to Firebase if this tokens does NOT POLL_ONLY. fb_gateway = create_sms_instance(fb_identifier) registration_url = get_action_values_from_options( SCOPE.ENROLL, PUSH_ACTION.REGISTRATION_URL, options=options) pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER) smartphone_data = _build_smartphone_data( self.token.serial, challenge, registration_url, pem_privkey, options) res = fb_gateway.submit_message( self.get_tokeninfo("firebase_token"), smartphone_data) # Create the challenge in the challenge table if either the message # was successfully submitted to the Firebase API or if polling is # allowed in general or for this specific token. allow_polling = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING, options=options) or PushAllowPolling.ALLOW if ((allow_polling == PushAllowPolling.ALLOW or (allow_polling == PushAllowPolling.TOKEN and is_true( self.get_tokeninfo(POLLING_ALLOWED, default='True')))) or res): validity = int( get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a PushChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=data, session=options.get("session"), validitytime=validity) db_challenge.save() self.challenge_janitor() # If sending the Push message failed, we still raise an error and a warning. if not res: log.warning( u"Failed to submit message to Firebase service for token {0!s}." .format(self.token.serial)) raise ValidateError( "Failed to submit message to Firebase service.") else: log.warning(u"The token {0!s} has no tokeninfo {1!s}. " u"The message could not be sent.".format( self.token.serial, PUSH_ACTION.FIREBASE_CONFIG)) raise ValidateError( "The token has no tokeninfo. Can not send via Firebase service." ) return True, message, db_challenge.transaction_id, reply_dict
def check_otp(self, anOtpVal, counter=None, window=None, options=None): """ Here we contact the Yubico Cloud server to validate the OtpVal. """ res = -1 apiId = get_from_config("yubico.id", DEFAULT_CLIENT_ID) apiKey = get_from_config("yubico.secret", DEFAULT_API_KEY) yubico_url = get_from_config("yubico.url", YUBICO_URL) if apiKey == DEFAULT_API_KEY or apiId == DEFAULT_CLIENT_ID: log.warning("Usage of default apiKey or apiId not recommended!") log.warning("Please register your own apiKey and apiId at " "yubico website!") log.warning("Configure of apiKey and apiId at the " "privacyidea manage config menu!") tokenid = self.get_tokeninfo("yubico.tokenid") if len(anOtpVal) < 12: log.warning("The otpval is too short: {0!r}".format(anOtpVal)) elif anOtpVal[:12] != tokenid: log.warning("The tokenid in the OTP value does not match " "the assigned token!") else: nonce = geturandom(20, hex=True) p = {'nonce': nonce, 'otp': anOtpVal, 'id': apiId} # Also send the signature to the yubico server p["h"] = yubico_api_signature(p, apiKey) try: r = requests.post(yubico_url, data=p) if r.status_code == requests.codes.ok: response = r.text elements = response.split() data = {} for elem in elements: k, v = elem.split("=", 1) data[k] = v result = data.get("status") return_nonce = data.get("nonce") # check signature: signature_valid = yubico_check_api_signature(data, apiKey) if not signature_valid: log.error( "The hash of the return from the yubico " "authentication server ({0!s}) " "does not match the data!".format(yubico_url)) if nonce != return_nonce: log.error("The returned nonce does not match " "the sent nonce!") if result == "OK": res = 1 if nonce != return_nonce or not signature_valid: log.warning("Nonce and Hash do not match.") res = -2 else: # possible results are listed here: # https://github.com/Yubico/yubikey-val/wiki/ValidationProtocolV20 log.warning("failed with {0!r}".format(result)) except Exception as ex: log.error("Error getting response from Yubico Cloud Server" " (%r): %r" % (yubico_url, ex)) log.debug("{0!s}".format(traceback.format_exc())) return res
def test_04_api_authenticate_smartphone(self): # Test the /validate/check endpoints and the smartphone endpoint /ttype/push # for authentication # get enrolled push token toks = get_tokens(tokentype="push") self.assertEqual(len(toks), 1) tokenobj = toks[0] # set PIN tokenobj.set_pin("pushpin") tokenobj.add_user(User("cornelius", self.realm1)) def check_firebase_params(request): payload = json.loads(request.body) # check the signature in the payload! data = payload.get("message").get("data") sign_string = u"{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format(**data) token_obj = get_tokens(serial=data.get("serial"))[0] pem_pubkey = token_obj.get_tokeninfo(PUBLIC_KEY_SERVER) pubkey_obj = load_pem_public_key(to_bytes(pem_pubkey), backend=default_backend()) signature = b32decode(data.get("signature")) # If signature does not match it will raise InvalidSignature exception pubkey_obj.verify(signature, sign_string.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'} return (200, headers, json.dumps({})) # We mock the ServiceAccountCredentials, since we can not directly contact the Google API with mock.patch('privacyidea.lib.smsprovider.FirebaseProvider.ServiceAccountCredentials') as mySA: # alternative: side_effect instead of return_value mySA.from_json_keyfile_name.return_value = myCredentials(myAccessTokenInfo("my_bearer_token")) # add responses, to simulate the communication to firebase responses.add_callback(responses.POST, 'https://fcm.googleapis.com/v1/projects/test-123456/messages:send', callback=check_firebase_params, content_type="application/json") # Send the first authentication request to trigger the challenge with self.app.test_request_context('/validate/check', method='POST', data={"user": "******", "realm": self.realm1, "pass": "******"}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = json.loads(res.data.decode('utf8')) self.assertFalse(jsonresp.get("result").get("value")) self.assertTrue(jsonresp.get("result").get("status")) self.assertEqual(jsonresp.get("detail").get("serial"), tokenobj.token.serial) self.assertTrue("transaction_id" in jsonresp.get("detail")) transaction_id = jsonresp.get("detail").get("transaction_id") self.assertEqual(jsonresp.get("detail").get("message"), DEFAULT_CHALLENGE_TEXT) # The challenge is sent to the smartphone via the Firebase service, so we do not know # the challenge from the /validate/check API. # So lets read the challenge from the database! challengeobject_list = get_challenges(serial=tokenobj.token.serial, transaction_id=transaction_id) challenge = challengeobject_list[0].challenge # Incomplete request fails with HTTP400 with self.app.test_request_context('/ttype/push', method='POST', data={"serial": tokenobj.token.serial, "nonce": challenge}): res = self.app.full_dispatch_request() self.assertEquals(res.status_code, 400) # This is what the smartphone answers. # create the signature: sign_data = "{0!s}|{1!s}".format(challenge, tokenobj.token.serial) signature = b32encode_and_unicode( self.smartphone_private_key.sign(sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) # Try an invalid signature first wrong_sign_data = "{}|{}".format(challenge, tokenobj.token.serial[1:]) wrong_signature = b32encode_and_unicode( self.smartphone_private_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) # Signed the wrong data with self.app.test_request_context('/ttype/push', method='POST', data={"serial": tokenobj.token.serial, "nonce": challenge, "signature": wrong_signature}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, wrong challenge wrong_challenge = b32encode_and_unicode(geturandom()) wrong_sign_data = "{}|{}".format(wrong_challenge, tokenobj.token.serial) wrong_signature = b32encode_and_unicode( self.smartphone_private_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) with self.app.test_request_context('/ttype/push', method='POST', data={"serial": tokenobj.token.serial, "nonce": wrong_challenge, "signature": wrong_signature}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, empty nonce with self.app.test_request_context('/ttype/push', method='POST', data={"serial": tokenobj.token.serial, "nonce": "", "signature": signature}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, wrong private key wrong_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) wrong_sign_data = "{}|{}".format(challenge, tokenobj.token.serial) wrong_signature = b32encode_and_unicode( wrong_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) with self.app.test_request_context('/ttype/push', method='POST', data={"serial": tokenobj.token.serial, "nonce": challenge, "signature": wrong_signature}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Result value is still false with self.app.test_request_context('/validate/check', method='POST', data={"user": "******", "realm": self.realm1, "pass": "", "state": transaction_id}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertFalse(res.json['result']['value']) # Now the correct request with self.app.test_request_context('/ttype/push', method='POST', data={"serial": tokenobj.token.serial, "nonce": challenge, "signature": signature}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertTrue(res.json['result']['value']) with self.app.test_request_context('/validate/check', method='POST', data={"user": "******", "realm": self.realm1, "pass": "", "state": transaction_id}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = json.loads(res.data.decode('utf8')) # Result-Value is True self.assertTrue(jsonresp.get("result").get("value"))
def check_otp(self, anOtpVal, counter=None, window=None, options=None): """ Here we contact the Yubico Cloud server to validate the OtpVal. """ res = -1 apiId = get_from_config("yubico.id", DEFAULT_CLIENT_ID) apiKey = get_from_config("yubico.secret", DEFAULT_API_KEY) yubico_url = get_from_config("yubico.url", YUBICO_URL) if apiKey == DEFAULT_API_KEY or apiId == DEFAULT_CLIENT_ID: log.warning("Usage of default apiKey or apiId not recommended!") log.warning("Please register your own apiKey and apiId at " "yubico website!") log.warning("Configure of apiKey and apiId at the " "privacyidea manage config menu!") tokenid = self.get_tokeninfo("yubico.tokenid") if len(anOtpVal) < 12: log.warning("The otpval is too short: {0!r}".format(anOtpVal)) elif anOtpVal[:12] != tokenid: log.warning("The tokenid in the OTP value does not match " "the assigned token!") else: nonce = geturandom(20, hex=True) p = {'nonce': nonce, 'otp': anOtpVal, 'id': apiId} # Also send the signature to the yubico server p["h"] = yubico_api_signature(p, apiKey) try: r = requests.post(yubico_url, data=p) if r.status_code == requests.codes.ok: response = r.text elements = response.split() data = {} for elem in elements: k, v = elem.split("=", 1) data[k] = v result = data.get("status") return_nonce = data.get("nonce") # check signature: signature_valid = yubico_check_api_signature(data, apiKey) if not signature_valid: log.error("The hash of the return from the yubico " "authentication server ({0!s}) " "does not match the data!".format(yubico_url)) if nonce != return_nonce: log.error("The returned nonce does not match " "the sent nonce!") if result == "OK": res = 1 if nonce != return_nonce or not signature_valid: log.warning("Nonce and Hash do not match.") res = -2 else: # possible results are listed here: # https://github.com/Yubico/yubikey-val/wiki/ValidationProtocolV20 log.warning("failed with {0!r}".format(result)) except Exception as ex: log.error("Error getting response from Yubico Cloud Server" " (%r): %r" % (yubico_url, ex)) log.debug("{0!s}".format(traceback.format_exc())) return res
def setHashedPin(self, pin): log.debug('setHashedPin()') seed = geturandom(16) self.privacyIDEASeed = unicode(binascii.hexlify(seed)) self.privacyIDEAPinHash = unicode(binascii.hexlify(hash(pin, seed))) return self.privacyIDEAPinHash
def test_04_api_authenticate_smartphone(self): # Test the /validate/check endpoints and the smartphone endpoint /ttype/push # for authentication # get enrolled push token toks = get_tokens(tokentype="push") self.assertEqual(len(toks), 1) tokenobj = toks[0] # set PIN tokenobj.set_pin("pushpin") tokenobj.add_user(User("cornelius", self.realm1)) def check_firebase_params(request): payload = json.loads(request.body) # check the signature in the payload! data = payload.get("message").get("data") sign_string = u"{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format( **data) token_obj = get_tokens(serial=data.get("serial"))[0] pem_pubkey = token_obj.get_tokeninfo(PUBLIC_KEY_SERVER) pubkey_obj = load_pem_public_key(to_bytes(pem_pubkey), backend=default_backend()) signature = b32decode(data.get("signature")) # If signature does not match it will raise InvalidSignature exception pubkey_obj.verify(signature, sign_string.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'} return (200, headers, json.dumps({})) # We mock the ServiceAccountCredentials, since we can not directly contact the Google API with mock.patch( 'privacyidea.lib.smsprovider.FirebaseProvider.ServiceAccountCredentials' ) as mySA: # alternative: side_effect instead of return_value mySA.from_json_keyfile_name.return_value = myCredentials( myAccessTokenInfo("my_bearer_token")) # add responses, to simulate the communication to firebase responses.add_callback( responses.POST, 'https://fcm.googleapis.com/v1/projects/test-123456/messages:send', callback=check_firebase_params, content_type="application/json") # Send the first authentication request to trigger the challenge with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "******" }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = res.json self.assertFalse(jsonresp.get("result").get("value")) self.assertTrue(jsonresp.get("result").get("status")) self.assertEqual( jsonresp.get("detail").get("serial"), tokenobj.token.serial) self.assertTrue("transaction_id" in jsonresp.get("detail")) transaction_id = jsonresp.get("detail").get("transaction_id") self.assertEqual( jsonresp.get("detail").get("message"), DEFAULT_CHALLENGE_TEXT) # Our ServiceAccountCredentials mock has not been called because we use a cached token self.assertEqual(len(mySA.from_json_keyfile_name.mock_calls), 0) self.assertIn(FIREBASE_FILE, get_app_local_store()["firebase_token"]) # The challenge is sent to the smartphone via the Firebase service, so we do not know # the challenge from the /validate/check API. # So lets read the challenge from the database! challengeobject_list = get_challenges(serial=tokenobj.token.serial, transaction_id=transaction_id) challenge = challengeobject_list[0].challenge # Incomplete request fails with HTTP400 with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": challenge }): res = self.app.full_dispatch_request() self.assertEquals(res.status_code, 400) # This is what the smartphone answers. # create the signature: sign_data = "{0!s}|{1!s}".format(challenge, tokenobj.token.serial) signature = b32encode_and_unicode( self.smartphone_private_key.sign(sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) # Try an invalid signature first wrong_sign_data = "{}|{}".format(challenge, tokenobj.token.serial[1:]) wrong_signature = b32encode_and_unicode( self.smartphone_private_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) # Signed the wrong data with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": challenge, "signature": wrong_signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, wrong challenge wrong_challenge = b32encode_and_unicode(geturandom()) wrong_sign_data = "{}|{}".format(wrong_challenge, tokenobj.token.serial) wrong_signature = b32encode_and_unicode( self.smartphone_private_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": wrong_challenge, "signature": wrong_signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, empty nonce with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": "", "signature": signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, wrong private key wrong_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) wrong_sign_data = "{}|{}".format(challenge, tokenobj.token.serial) wrong_signature = b32encode_and_unicode( wrong_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": challenge, "signature": wrong_signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Result value is still false with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "", "state": transaction_id }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertFalse(res.json['result']['value']) # Now the correct request with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": challenge, "signature": signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertTrue(res.json['result']['value']) with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "", "state": transaction_id }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = res.json # Result-Value is True self.assertTrue(jsonresp.get("result").get("value"))
def export_pskc(tokenobj_list, psk=None): """ Take a list of token objects and create a beautifulsoup xml object. If no preshared key is given, we create one and return it. :param tokenobj_list: list of token objects :param psk: pre-shared-key for AES-128-CBC in hex format :return: tuple of (psk, number of tokens, beautifulsoup) """ if psk: psk = binascii.unhexlify(psk) else: psk = geturandom(16) mackey = geturandom(20) encrypted_mackey = aes_encrypt_b64(psk, mackey) number_of_exported_tokens = 0 # define the header soup = BeautifulSoup("""<KeyContainer Version="1.0" xmlns="urn:ietf:params:xml:ns:keyprov:pskc" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <EncryptionKey> <ds:KeyName>Pre-shared-key</ds:KeyName> </EncryptionKey> <MACMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"> <MACKey> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/> <xenc:CipherData> <xenc:CipherValue>{encrypted_mackey}</xenc:CipherValue> </xenc:CipherData> </MACKey> </MACMethod> """.format(encrypted_mackey=encrypted_mackey), "html.parser") for tokenobj in tokenobj_list: if tokenobj.type.lower() not in ["totp", "hotp", "pw"]: continue type = tokenobj.type.lower() issuer = "privacyIDEA" try: manufacturer = tokenobj.token.description.encode("ascii", "replace") manufacturer = to_unicode(manufacturer) except UnicodeEncodeError: manufacturer = "deleted during export" serial = tokenobj.token.serial otplen = tokenobj.token.otplen counter = tokenobj.token.count suite = tokenobj.get_tokeninfo("hashlib", default="sha1") if type == "totp": timestep = tokenobj.get_tokeninfo("timeStep") timedrift = tokenobj.get_tokeninfo("timeShift") else: timestep = 0 timedrift = 0 otpkey = tokenobj.token.get_otpkey().getKey() try: if tokenobj.type.lower() in ["totp", "hotp"]: encrypted_otpkey = aes_encrypt_b64(psk, binascii.unhexlify(otpkey)) elif tokenobj.type.lower() in ["pw"]: encrypted_otpkey = aes_encrypt_b64(psk, otpkey) else: encrypted_otpkey = aes_encrypt_b64(psk, otpkey) except TypeError: # Some keys might be odd string length continue try: kp2 = BeautifulSoup("""<KeyPackage> <DeviceInfo> <Manufacturer>{manufacturer}</Manufacturer> <SerialNo>{serial}</SerialNo> </DeviceInfo> <Key Id="{serial}" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:{type}"> <Issuer>{issuer}</Issuer> <AlgorithmParameters> <ResponseFormat Length="{otplen}" Encoding="DECIMAL"/> <Suite hashalgo="{suite}" /> </AlgorithmParameters> <Data> <Secret> <EncryptedValue> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/> <xenc:CipherData> <xenc:CipherValue>{encrypted_otpkey}</xenc:CipherValue> </xenc:CipherData> </EncryptedValue> </Secret> <ValueMAC>TODOmissing</ValueMAC> <Time> <PlainValue>0</PlainValue> </Time> <TimeInterval> <PlainValue>{timestep}</PlainValue> </TimeInterval> <Counter> <PlainValue>{counter}</PlainValue> </Counter> <TimeDrift> <PlainValue>{timedrift}</PlainValue> </TimeDrift> </Data> </Key> </KeyPackage>""".format(serial=cgi.escape(serial), type=cgi.escape(type), otplen=otplen, issuer=cgi.escape(issuer), manufacturer=cgi.escape(manufacturer), counter=counter, timestep=timestep, encrypted_otpkey=encrypted_otpkey, timedrift=timedrift, suite=cgi.escape(suite)), "html.parser") soup.macmethod.insert_after(kp2) number_of_exported_tokens += 1 except Exception as e: log.warning(u"Failed to export the token {0!s}: {1!s}".format(serial, e)) tb = traceback.format_exc() log.debug(tb) return hexlify_and_unicode(psk), number_of_exported_tokens, soup
def make_app(global_conf, full_stack=True, static_files=True, **app_conf): """ Create a Pylons WSGI application and return it ``global_conf`` The inherited configuration for this application. Normally from the [DEFAULT] section of the Paste ini file. ``full_stack`` Whether this application provides a full WSGI stack (by default, meaning it handles its own exceptions and errors). Disable full_stack when this application is "managed" by another WSGI middleware. ``static_files`` Whether this application serves its own static files; disable when another web server is responsible for serving them. ``app_conf`` The application's local configuration. Normally specified in the [app:<name>] section of the Paste ini file (where <name> defaults to main). """ # Configure the Pylons environment load_environment(global_conf, app_conf) # The Pylons WSGI app app = PylonsApp() # Profiling Middleware if profile_load: if asbool(config['profile']): app = AccumulatingProfileMiddleware( app, log_filename='/var/log/privacyidea/profiling.log', cachegrind_filename='/var/log/privacyidea/cachegrind.out', discard_first_request=True, flush_at_shutdown=True, path='/__profile__' ) # Routing/Session/Cache Middleware app = RoutesMiddleware(app, config['routes.map']) # We do not use beaker sessions! Keep the environment smaller. #app = SessionMiddleware(app, config) app = CacheMiddleware(app, config) # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares) if asbool(full_stack): # Handle Python exceptions app = ErrorHandler(app, global_conf, **config['pylons.errorware']) # Display error documents for 401, 403, 404 status codes (and # 500 when debug is disabled) if asbool(config['debug']): app = StatusCodeRedirect(app) else: app = StatusCodeRedirect(app, [400, 401, 403, 404, 500]) # Establish the Registry for this application app = RegistryManager(app) if asbool(static_files): # Serve static files static_app = StaticURLParser(config['pylons.paths']['static_files']) app = Cascade([static_app, app]) # Add the repoze.who middleware # with a cookie encryption key, that is generated at every server start! cookie_timeout = int(global_conf.get("privacyIDEASessionTimeout", COOKIE_TIMEOUT)) cookie_reissue_time = int(cookie_timeout / 2) cookie_key = geturandom(32) privacyidea_auth = auth_privacy_plugin() basicauth = BasicAuthPlugin('repoze.who') privacyidea_md = auth_privacy_plugin() auth_tkt = AuthTktCookiePlugin(cookie_key, cookie_name='privacyidea_session', secure=True, include_ip=False, timeout=cookie_timeout, reissue_time=cookie_reissue_time) form = make_redirecting_plugin(login_form_url="/account/login", login_handler_path='/account/dologin', logout_handler_path='/account/logout', rememberer_name="auth_tkt") # For authentication for browsers form.classifications = {IIdentifier: ['browser'], IChallenger: ['browser']} # basic authentication only for API calls basicauth.classifications = {IIdentifier: ['basic'], IChallenger: ['basic']} identifiers = [('form', form), ('auth_tkt', auth_tkt), ('basicauth', basicauth)] authenticators = [('privacyidea.lib.repoze_auth:UserModelPlugin', privacyidea_auth)] challengers = [('form', form), ('basicauth', basicauth)] mdproviders = [('privacyidea.lib.repoze_auth:UserModelPlugin', privacyidea_md)] #app = make_who_with_config(app, global_conf, app_conf['who.config_file'], # app_conf['who.log_file'], app_conf['who.log_level']) log_file = app_conf.get("who.log_file") if log_file is not None: if log_file.lower() == 'stdout': log_stream = None else: log_stream = open(log_file, 'wb') log_level = app_conf.get("who.log_level") if log_level is None: log_level = logging.INFO else: log_level = _LEVELS[log_level.lower()] app = PluggableAuthenticationMiddleware( app, identifiers, authenticators, challengers, mdproviders, request_classifier, default_challenge_decider, log_stream, log_level ) return app
def update(self, param, reset_failcount=True): """ process the initialization parameters We need to distinguish the first authentication step and the second authentication step. 1. step: ``param`` contains: - ``type`` - ``genkey`` 2. step: ``param`` contains: - ``serial`` - ``fbtoken`` - ``pubkey`` :param param: dict of initialization parameters :type param: dict :return: nothing """ upd_param = {} for k, v in param.items(): upd_param[k] = v if "serial" in upd_param and "fbtoken" in upd_param and "pubkey" in upd_param: # We are in step 2: if self.token.rollout_state != ROLLOUTSTATE.CLIENTWAIT: raise ParameterError( "Invalid state! The token you want to enroll is not in the state 'clientwait'." ) enrollment_credential = getParam(upd_param, "enrollment_credential", optional=False) if enrollment_credential != self.get_tokeninfo( "enrollment_credential"): raise ParameterError( "Invalid enrollment credential. You are not authorized to finalize this token." ) self.del_tokeninfo("enrollment_credential") self.token.rollout_state = "enrolled" self.token.active = True self.add_tokeninfo(PUBLIC_KEY_SMARTPHONE, upd_param.get("pubkey")) self.add_tokeninfo("firebase_token", upd_param.get("fbtoken")) # create a keypair for the server side. pub_key, priv_key = generate_keypair(4096) self.add_tokeninfo(PUBLIC_KEY_SERVER, pub_key) self.add_tokeninfo(PRIVATE_KEY_SERVER, priv_key, "password") elif "genkey" in upd_param: # We are in step 1: upd_param["2stepinit"] = 1 self.add_tokeninfo("enrollment_credential", geturandom(20, hex=True)) # We also store the Firebase config, that was used during the enrollment. self.add_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG, param.get(PUSH_ACTION.FIREBASE_CONFIG)) else: raise ParameterError( "Invalid Parameters. Either provide (genkey) or (serial, fbtoken, pubkey)." ) TokenClass.update(self, upd_param, reset_failcount)
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional challenge ``reply_dict``, which are displayed in the JSON challenges response. """ options = options or {} message = get_action_values_from_options( SCOPE.AUTH, "{0!s}_{1!s}".format( self.get_class_type(), ACTION.CHALLENGETEXT), options) or _( u'Please confirm with your U2F token ({0!s})').format( self.token.description) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # if a transaction id is given, check if there are other u2f token and # reuse the challenge challenge = None if transactionid: for c in get_challenges(transaction_id=transactionid): if get_tokens(serial=c.serial, tokentype=self.get_class_type(), count=True): challenge = c.challenge break if not challenge: nonce = geturandom(32) challenge = hexlify_and_unicode(nonce) else: nonce = binascii.unhexlify(challenge) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=None, session=options.get("session"), validitytime=validity) db_challenge.save() sec_object = self.token.get_otpkey() key_handle_hex = sec_object.getKey() key_handle_bin = binascii.unhexlify(key_handle_hex) key_handle_url = url_encode(key_handle_bin) challenge_url = url_encode(nonce) u2f_sign_request = { "appId": self.get_tokeninfo("appId"), "version": U2F_Version, "challenge": challenge_url, "keyHandle": key_handle_url } image_url = IMAGES.get(self.token.description.lower().split()[0], "") reply_dict = { "attributes": { "u2fSignRequest": u2f_sign_request, "hideResponseInput": self.client_mode != CLIENTMODE.INTERACTIVE, "img": image_url }, "image": image_url } return True, message, db_challenge.transaction_id, reply_dict
def get_auth_token(): """ This call verifies the credentials of the user and issues an authentication token, that is used for the later API calls. The authentication token has a validity, that is usually 1 hour. :jsonparam username: The username of the user who wants to authenticate to the API. :jsonparam password: The password/credentials of the user who wants to authenticate to the API. :return: A json response with an authentication token, that needs to be used in any further request. :status 200: in case of success :status 401: if authentication fails **Example Authentication Request**: .. sourcecode:: http POST /auth HTTP/1.1 Host: example.com Accept: application/json username=admin password=topsecret **Example Authentication Response**: .. sourcecode:: http HTTP/1.0 200 OK Content-Length: 354 Content-Type: application/json { "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": { "token": "eyJhbGciOiJIUz....jdpn9kIjuGRnGejmbFbM" } }, "version": "privacyIDEA unknown" } **Response for failed authentication**: .. sourcecode:: http HTTP/1.1 401 UNAUTHORIZED Content-Type: application/json Content-Length: 203 { "id": 1, "jsonrpc": "2.0", "result": { "error": { "code": -401, "message": "missing Authorization header" }, "status": false }, "version": "privacyIDEA unknown", "config": { "logout_time": 30 } } """ validity = timedelta(hours=1) username = getParam(request.all_data, "username") password = getParam(request.all_data, "password") realm = getParam(request.all_data, "realm") details = {} if username is None: raise AuthError(_("Authentication failure. Missing Username"), id=ERROR.AUTHENTICATE_MISSING_USERNAME) if realm: username = username + "@" + realm # Failsafe to have the user attempt in the log, whatever happens # This can be overwritten later g.audit_object.log({"user": username, "realm": realm}) secret = current_app.secret_key superuser_realms = current_app.config.get("SUPERUSER_REALM", []) # This is the default role for the logged in user. # The role privileges may be risen to "admin" role = ROLE.USER # The way the user authenticated. This could be # "password" = The admin user DB or the user store # "pi" = The admin or the user is authenticated against privacyIDEA # "remote_user" = authenticated by webserver authtype = "password" # Verify the password admin_auth = False user_auth = False loginname, realm = split_user(username) realm = realm or get_default_realm() user_obj = User() # Check if the remote user is allowed if (request.remote_user == username) and is_remote_user_allowed(request): # Authenticated by the Web Server # Check if the username exists # 1. in local admins # 2. in a realm # 2a. is an admin realm authtype = "remote_user " if db_admin_exist(username): role = ROLE.ADMIN admin_auth = True g.audit_object.log({ "success": True, "user": "", "administrator": username, "info": "internal admin" }) else: # check, if the user exists user_obj = User(loginname, realm) g.audit_object.log({ "user": user_obj.login, "realm": user_obj.realm, "info": log_used_user(user_obj) }) if user_obj.exist(): user_auth = True if user_obj.realm in superuser_realms: role = ROLE.ADMIN admin_auth = True elif verify_db_admin(username, password): role = ROLE.ADMIN admin_auth = True # This admin is not in the default realm! realm = "" g.audit_object.log({ "success": True, "user": "", "administrator": username, "info": "internal admin" }) else: # The user could not be identified against the admin database, # so we do the rest of the check options = {"g": g, "clientip": g.client_ip} for key, value in request.all_data.items(): if value and key not in ["g", "clientip"]: options[key] = value user_obj = User(loginname, realm) user_auth, role, details = check_webui_user( user_obj, password, options=options, superuser_realms=superuser_realms) details = details or {} if role == ROLE.ADMIN: g.audit_object.log({ "user": "", "administrator": user_obj.login, "realm": user_obj.realm, "resolver": user_obj.resolver, "serial": details.get('serial', None), "info": u"{0!s}|loginmode={1!s}".format(log_used_user(user_obj), details.get("loginmode")) }) else: g.audit_object.log({ "user": user_obj.login, "realm": user_obj.realm, "resolver": user_obj.resolver, "serial": details.get('serial', None), "info": u"{0!s}|loginmode={1!s}".format(log_used_user(user_obj), details.get("loginmode")) }) if not admin_auth and not user_auth: raise AuthError(_("Authentication failure. Wrong credentials"), id=ERROR.AUTHENTICATE_WRONG_CREDENTIALS, details=details or {}) else: g.audit_object.log({"success": True}) request.User = user_obj # If the HSM is not ready, we need to create the nonce in another way! hsm = init_hsm() if hsm.is_ready: nonce = geturandom(hex=True) # Add the role to the JWT, so that we can verify it internally # Add the authtype to the JWT, so that we could use it for access # definitions rights = g.policy_object.ui_get_rights(role, realm, loginname, g.client_ip) menus = g.policy_object.ui_get_main_menus( { "username": loginname, "role": role, "realm": realm }, g.client_ip) else: import os nonce = hexlify_and_unicode(os.urandom(20)) rights = [] menus = [] # What is the log level? log_level = current_app.config.get("PI_LOGLEVEL", 30) token = jwt.encode( { "username": loginname, "realm": realm, "nonce": nonce, "role": role, "authtype": authtype, "exp": datetime.utcnow() + validity, "rights": rights }, secret, algorithm='HS256').decode('utf8') # Add the role to the response, so that the WebUI can make decisions # based on this (only show selfservice, not the admin part) return send_result( { "token": token, "role": role, "username": loginname, "realm": realm, "log_level": log_level, "rights": rights, "menus": menus }, details=details)
def setUserPin(self, userPin): iv = geturandom(16) enc_userPin = encrypt(userPin, iv) self.privacyIDEATokenPinUser = unicode(binascii.hexlify(enc_userPin)) self.privacyIDEATokenPinUserIV = unicode(binascii.hexlify(iv))