예제 #1
0
 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))
예제 #2
0
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)
예제 #3
0
    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
예제 #4
0
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))
예제 #5
0
    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
예제 #6
0
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")
예제 #8
0
 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
예제 #9
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 = 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
예제 #10
0
 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
예제 #11
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
예제 #12
0
파일: utils.py 프로젝트: STRML/privacyidea
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))
예제 #13
0
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))
예제 #14
0
 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
예제 #15
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
예제 #16
0
    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)
예제 #17
0
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
예제 #18
0
    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))
예제 #19
0
 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")
예제 #20
0
    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))
예제 #21
0
 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
예제 #22
0
 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
예제 #23
0
    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
예제 #24
0
    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
예제 #25
0
    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
예제 #26
0
파일: ocra.py 프로젝트: STRML/privacyidea
    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
예제 #27
0
    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
예제 #28
0
 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
예제 #29
0
 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
예제 #30
0
    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
예제 #31
0
    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)
예제 #32
0
    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)
예제 #33
0
 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}
예제 #35
0
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
예제 #36
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 = 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
예제 #37
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.
        """
        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
예제 #38
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.
        """
        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
예제 #39
0
def create_subscription_request():
    iv = geturandom(16)
    enc = encrypt("privacy IDEA", iv=iv)
    r = binascii.hexlify(enc + iv)
    return {"systemid": r}
예제 #40
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 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
예제 #41
0
    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"))
예제 #43
0
    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
예제 #44
0
 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"))
예제 #46
0
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
예제 #47
0
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
예제 #48
0
    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)
예제 #49
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 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
예제 #50
0
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)
예제 #51
0
 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))