def test_05_encode_decode(self):
     b_str = b'Hello World'
     self.assertEqual(to_unicode(b_str), b_str.decode('utf8'))
     u_str = u'Hello Wörld'
     self.assertEqual(to_unicode(u_str), u_str)
     self.assertEqual(to_bytes(b_str), b_str)
     self.assertEqual(to_bytes(u_str), u_str.encode('utf8'))
Beispiel #2
0
 def _on_Connection(self, server, user, password,
                    auto_bind=None, client_strategy=None,
                    authentication=None, check_names=None,
                    auto_referrals=None, receive_timeout=None):
     """
     We need to create a Connection object with
     methods:
         add()
         modify()
         search()
         unbind()
     and object
         response
     """
     # check the password
     correct_password = False
     # Anonymous bind
     # Reload the directory just in case a change has been made to
     # user credentials
     self.directory = self._load_data(DIRECTORY)
     if authentication == ldap3.ANONYMOUS and user == "":
         correct_password = True
     for entry in self.directory:
         if entry.get("dn") == user:
             pw = entry.get("attributes").get("userPassword")
             # password can be unicode
             if to_bytes(pw) == to_bytes(password):
                 correct_password = True
             elif pw.startswith('{SSHA}'):
                 correct_password = ldap_salted_sha1.verify(password, pw)
             else:
                 correct_password = False
     self.con_obj = Connection(self.directory)
     self.con_obj.bound = correct_password
     return self.con_obj
Beispiel #3
0
def check_response(user_pub_key, app_id, client_data, signature,
                   counter, user_presence_byte=b'\x01'):
    """
    Check the ECDSA Signature with the given pubkey.
    The signed data is constructed from
     * app_id
     * user_presence_byte
     * counter and
     * client_data

    :param user_pub_key: The Application specific public key
    :type user_pub_key: hex string
    :param app_id: The AppID for this challenge response
    :type app_id: str
    :param client_data: The ClientData
    :type client_data: str
    :param counter: A counter
    :type counter: int
    :param user_presence_byte: User presence byte
    :type user_presence_byte: byte
    :param signature: The signature of the authentication request
    :type signature: hex string
    :return:
    """
    res = True
    app_id_hash = sha256(to_bytes(app_id)).digest()
    client_data_hash = sha256(to_bytes(client_data)).digest()
    user_pub_key_bin = binascii.unhexlify(user_pub_key)
    counter_bin = struct.pack(">L", counter)
    signature_bin = binascii.unhexlify(signature)

    input_data = app_id_hash + user_presence_byte + counter_bin \
                 + client_data_hash

    # The first byte 0x04 only indicates, that the public key is in the
    # uncompressed format x: 32 byte, y: 32byte
    user_pub_key_bin = user_pub_key_bin[1:]
    signature_bin_asn = der_decode(signature_bin)
    vkey = ecdsa.VerifyingKey.from_string(user_pub_key_bin,
                                          curve=ecdsa.NIST256p,
                                          hashfunc=sha256)
    try:
        vkey.verify(signature_bin_asn, input_data)
    except ecdsa.BadSignatureError:
        log.error("Bad signature for app_id {0!s}".format(app_id))
        res = False
    return res
Beispiel #4
0
def encryptPin(cryptPin):
    """
    :param cryptPin: the pin to encrypt
    :type cryptPin: bytes or str
    :return: the encrypted pin
    :rtype: str
    """
    hsm = get_hsm()
    return to_unicode(hsm.encrypt_pin(to_bytes(cryptPin)))
    def test_01_check_reg_data(self):
        attestation_cert = "3082013c3081e4a003020102020a4790128000115595735230" \
                           "0a06082a8648ce3d0403023017311530130603550403130c" \
                           "476e756262792050696c6f74301e170d313230383134313" \
                           "8323933325a170d3133303831343138323933325a303131" \
                           "2f302d0603550403132650696c6f74476e756262792d302" \
                           "e342e312d34373930313238303030313135353935373335" \
                           "323059301306072a8648ce3d020106082a8648ce3d030107" \
                           "034200048d617e65c9508e64bcc5673ac82a6799da3c1446" \
                           "682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4" \
                           "199edd7862f23abaf0203b4b8911ba0569994e101300a06" \
                           "082a8648ce3d0403020347003044022060cdb6061e9c2226" \
                           "2d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa" \
                           "0220631b1459f09e6330055722c8d89b7f48883b9089b88d" \
                           "60d1d9795902b30410df"

        cdata_str = """{"typ": "navigator.id.finishEnrollment",
                        "challenge": "vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo",
                        "cid_pubkey": {
                            "kty": "EC",
                            "crv": "P-256",
                            "x": "HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8",
                            "y": "XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4"},
                        "origin": "http://example.com"}"""
        my_app_id = 'http://example.com'

        app_id_hash = hexlify_and_unicode(sha256(to_bytes(my_app_id)).digest())
        self.assertEqual(app_id_hash,
                         "f0e6a6a97042a4f1f1c87f5f7d44315b2d852c2df5c7991cc66241bf7072d1c4")
        attestation_cert = crypto.load_certificate(crypto.FILETYPE_ASN1,
                                                   binascii.unhexlify(attestation_cert))
        client_data_str = ''.join(cdata_str.split())
        client_data_hash = hexlify_and_unicode(sha256(to_bytes(client_data_str)).digest())
        self.assertEqual(client_data_hash,
                         "4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb")

        user_pub_key = "04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9"
        key_handle = "2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25"
        signature = "304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871"

        r = check_registration_data(attestation_cert, my_app_id, client_data_str,
                                    user_pub_key, key_handle, signature)
        self.assertEqual(r, True)
Beispiel #6
0
def sign_challenge(user_priv_key,
                   app_id,
                   client_data,
                   counter,
                   user_presence_byte=b'\x01'):
    """
    This creates a signature for the U2F data.
    Only used in test scenario

    The calculation of the signature is described here:
    https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#authentication-response-message-success

    The input_data is a concatenation of:
        * AppParameter: sha256(app_id)
        * The user presence [1byte]
        * counter [4byte]
        * ChallengeParameter: sha256(client_data)

    :param user_priv_key: The private key
    :type user_priv_key: hex string
    :param app_id: The application id
    :type app_id: str
    :param client_data: the stringified JSON
    :type client_data: str
    :param counter: the authentication counter
    :type counter: int
    :param user_presence_byte: one byte 0x01
    :type user_presence_byte: char
    :return: The DER encoded signature
    :rtype: hex string
    """
    app_id_hash = sha256(to_bytes(app_id)).digest()
    client_data_hash = sha256(to_bytes(client_data)).digest()
    counter_bin = struct.pack(">L", counter)
    input_data = app_id_hash + user_presence_byte + counter_bin + \
                 client_data_hash
    priv_key_bin = binascii.unhexlify(user_priv_key)
    sk = ecdsa.SigningKey.from_string(priv_key_bin,
                                      curve=ecdsa.NIST256p,
                                      hashfunc=sha256)
    signature = sk.sign(input_data)
    der_sig = der_encode(signature)
    return hexlify_and_unicode(der_sig)
Beispiel #7
0
    def test_25_encodings(self):
        u = u'Hello Wörld'
        b = b'Hello World'
        self.assertEquals(to_utf8(None), None)
        self.assertEquals(to_utf8(u), u.encode('utf8'))
        self.assertEquals(to_utf8(b), b)

        self.assertEquals(to_unicode(u), u)
        self.assertEquals(to_unicode(b), b.decode('utf8'))
        self.assertEquals(to_unicode(None), None)
        self.assertEquals(to_unicode(10), 10)

        self.assertEquals(to_bytes(u), u.encode('utf8'))
        self.assertEquals(to_bytes(b), b)
        self.assertEquals(to_bytes(10), 10)

        self.assertEquals(to_byte_string(u), u.encode('utf8'))
        self.assertEquals(to_byte_string(b), b)
        self.assertEquals(to_byte_string(10), b'10')
    def test_25_encodings(self):
        u = u'Hello Wörld'
        b = b'Hello World'
        self.assertEquals(to_utf8(None), None)
        self.assertEquals(to_utf8(u), u.encode('utf8'))
        self.assertEquals(to_utf8(b), b)

        self.assertEquals(to_unicode(u), u)
        self.assertEquals(to_unicode(b), b.decode('utf8'))
        self.assertEquals(to_unicode(None), None)
        self.assertEquals(to_unicode(10), 10)

        self.assertEquals(to_bytes(u), u.encode('utf8'))
        self.assertEquals(to_bytes(b), b)
        self.assertEquals(to_bytes(10), 10)

        self.assertEquals(to_byte_string(u), u.encode('utf8'))
        self.assertEquals(to_byte_string(b), b)
        self.assertEquals(to_byte_string(10), b'10')
Beispiel #9
0
 def _on_Connection(self,
                    server,
                    user,
                    password,
                    auto_bind=None,
                    client_strategy=None,
                    authentication=None,
                    check_names=None,
                    auto_referrals=None,
                    receive_timeout=None):
     """
     We need to create a Connection object with
     methods:
         add()
         modify()
         search()
         unbind()
     and object
         response
     """
     # check the password
     correct_password = False
     # Anonymous bind
     # Reload the directory just in case a change has been made to
     # user credentials
     self.directory = self._load_data(DIRECTORY)
     if authentication == ldap3.ANONYMOUS and user == "":
         correct_password = True
     for entry in self.directory:
         if entry.get("dn") == user:
             pw = entry.get("attributes").get("userPassword")
             # password can be unicode
             if to_bytes(pw) == to_bytes(password):
                 correct_password = True
             elif pw.startswith('{SSHA}'):
                 correct_password = ldap_salted_sha1.verify(password, pw)
             else:
                 correct_password = False
     self.con_obj = Connection(self.directory)
     self.con_obj.bound = correct_password
     return self.con_obj
Beispiel #10
0
def url_decode(url):
    """
    Decodes a base64 encoded, not padded string as used in FIDO U2F
    :param url: base64 urlsafe encoded string
    :type url: str
    :return: the decoded string
    :rtype: bytes
    """
    pad_len = len(url) % 4
    padding = pad_len * "="
    res = base64.urlsafe_b64decode(to_bytes(url + padding))
    return res
Beispiel #11
0
def url_decode(url):
    """
    Decodes a base64 encoded, not padded string as used in FIDO U2F
    :param url: base64 urlsafe encoded string
    :type url: str
    :return: the decoded string
    :rtype: bytes
    """
    pad_len = -len(url) % 4
    padding = pad_len * "="
    res = base64.urlsafe_b64decode(to_bytes(url + padding))
    return res
Beispiel #12
0
def create_key_from_password(password):
    """
    Create a key from the given password.
    This is used to encrypt and decrypt the enckey file.

    :param password:
    :type password: str or bytes
    :return: the generated key
    :rtype: bytes
    """
    key = sha256(to_bytes(password)).digest()[0:32]
    return key
Beispiel #13
0
def sign_challenge(user_priv_key, app_id, client_data, counter,
                   user_presence_byte=b'\x01'):
    """
    This creates a signature for the U2F data.
    Only used in test scenario

    The calculation of the signature is described here:
    https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#authentication-response-message-success

    The input_data is a concatenation of:
        * AppParameter: sha256(app_id)
        * The user presence [1byte]
        * counter [4byte]
        * ChallengeParameter: sha256(client_data)

    :param user_priv_key: The private key
    :type user_priv_key: hex string
    :param app_id: The application id
    :type app_id: str
    :param client_data: the stringified JSON
    :type client_data: str
    :param counter: the authentication counter
    :type counter: int
    :param user_presence_byte: one byte 0x01
    :type user_presence_byte: char
    :return: The DER encoded signature
    :rtype: hex string
    """
    app_id_hash = sha256(to_bytes(app_id)).digest()
    client_data_hash = sha256(to_bytes(client_data)).digest()
    counter_bin = struct.pack(">L", counter)
    input_data = app_id_hash + user_presence_byte + counter_bin + \
                 client_data_hash
    priv_key_bin = binascii.unhexlify(user_priv_key)
    sk = ecdsa.SigningKey.from_string(priv_key_bin, curve=ecdsa.NIST256p,
                                      hashfunc=sha256)
    signature = sk.sign(input_data)
    der_sig = der_encode(signature)
    return hexlify_and_unicode(der_sig)
Beispiel #14
0
def _build_verify_object(pubkey_pem):
    """
    Load the given stripped and urlsafe public key and return the verify object

    :param pubkey_pem:
    :return:
    """
    # The public key of the smartphone was probably sent as urlsafe:
    pubkey_pem = pubkey_pem.replace("-", "+").replace("_", "/")
    # The public key was sent without any header
    pubkey_pem = "-----BEGIN PUBLIC KEY-----\n{0!s}\n-----END PUBLIC " \
                 "KEY-----".format(pubkey_pem.strip().replace(" ", "+"))

    return serialization.load_pem_public_key(to_bytes(pubkey_pem), default_backend())
Beispiel #15
0
def _digi2daplug(normal_otp):
    """
    convert "497096" to 34 39 37 30 39 36, which is efekeiebekeh

    This function is only used for testing purposes
    :param normal_otp:
    :type normal_otp: bytes or str
    :return:
    """
    daplug_otp = ""
    hex_otp = hexlify_and_unicode(to_bytes(normal_otp))
    for i in hex_otp:
        daplug_otp += REVERSE_MAPPING.get(i)
    return daplug_otp
Beispiel #16
0
def _digi2daplug(normal_otp):
    """
    convert "497096" to 34 39 37 30 39 36, which is efekeiebekeh

    This function is only used for testing purposes
    :param normal_otp:
    :type normal_otp: bytes or str
    :return:
    """
    daplug_otp = ""
    hex_otp = hexlify_and_unicode(to_bytes(normal_otp))
    for i in hex_otp:
        daplug_otp += REVERSE_MAPPING.get(i)
    return daplug_otp
Beispiel #17
0
    def get_access_token(server=None, client=None, secret=None):

        auth = to_unicode(base64.b64encode(to_bytes(client + ':' + secret)))

        url = "{0!s}/oauth/token?grant_type=client_credentials".format(server)
        resp = requests.get(url, headers={'Authorization': 'Basic ' + auth})

        if resp.status_code != 200:
            info = "Could not get access token: {0!s}".format(resp.status_code)
            log.error(info)
            raise Exception(info)

        access_token = yaml.safe_load(resp.content).get('access_token')
        return access_token
Beispiel #18
0
def check_registration_data(attestation_cert, app_id,
                            client_data, user_pub_key,
                            key_handle, signature):
    """
    See example in fido spec
    https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#registration-example

    In case of signature error an exception is raised

    :param attestation_cert: The Attestation cert of the FIDO device
    :type attestation_cert: x509 Object
    :param app_id: The appId
    :type app_id: str
    :param client_data: The ClientData
    :type client_data: str
    :param user_pub_key: The public key for this AppID
    :type user_pub_key: hex string
    :param key_handle: The keyHandle on the FIDO device
    :type key_handle: hex string
    :param signature: The signature of the registration request
    :type signature: hex string
    :return: Bool
    """
    app_id_hash = sha256(to_bytes(app_id)).digest()
    client_data_hash = sha256(to_bytes(client_data)).digest()
    reg_data = b'\x00' + app_id_hash + client_data_hash \
               + binascii.unhexlify(key_handle) + binascii.unhexlify(user_pub_key)
    try:
        crypto.verify(attestation_cert,
                      binascii.unhexlify(signature),
                      reg_data,
                      "sha256")
    except Exception as exx:
        raise Exception("Error checking the signature of the registration "
                        "data. %s" % exx)
    return True
    def get_access_token(server=None, client=None, secret=None):

        auth = to_unicode(base64.b64encode(to_bytes(client + ':' + secret)))

        url = "{0!s}/oauth/token?grant_type=client_credentials".format(server)
        resp = requests.get(url,
                            headers={'Authorization': 'Basic ' + auth})

        if resp.status_code != 200:
            info = "Could not get access token: {0!s}".format(resp.status_code)
            log.error(info)
            raise Exception(info)

        access_token = yaml.safe_load(resp.content).get('access_token')
        return access_token
Beispiel #20
0
def _build_smartphone_data(serial, challenge, fb_gateway, pem_privkey,
                           options):
    """
    Create the dictionary to be send to the smartphone as challenge

    :param challenge: base32 encoded random data string
    :type challenge: str
    :param fb_gateway: the gateway object containing the firebase configuration
    :type fb_gateway: privacyidea.lib.smsprovider.SMSProvider.ISMSProvider
    :param options: the options dictionary
    :type options: dict
    :return: the created smartphone_data dictionary
    :rtype: dict
    """
    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")
    # We send the challenge to the Firebase service
    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": 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)

    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)
    return smartphone_data
        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({}))
Beispiel #22
0
        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({}))
Beispiel #23
0
def _build_smartphone_data(serial, challenge, registration_url, pem_privkey,
                           options):
    """
    Create the dictionary to be send to the smartphone as challenge

    :param challenge: base32 encoded random data string
    :type challenge: str
    :param registration_url: The privacyIDEA URL, to which the Push token communicates
    :type registration_url: str
    :param options: the options dictionary
    :type options: dict
    :return: the created smartphone_data dictionary
    :rtype: dict
    """
    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")
    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": serial,
        "title": title,
        "sslverify": sslverify,
        "url": registration_url
    }
    # Create the signature.
    # value to string
    sign_string = u"{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format(
        **smartphone_data)

    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)
    return smartphone_data
    def checkPass(self, uid, password):
        """
        This function checks the password for a given uid.
        returns true in case of success
        false if password does not match

        We do not support shadow passwords. so the seconds column
        of the passwd file needs to contain the encrypted password

        If the password is a unicode object, it is encoded according
        to ENCODING first.

        :param uid: The uid of the user
        :type uid: int
        :param password: The password in cleartext
        :type password: sting
        :return: True or False
        :rtype: bool
        """
        log.info("checking password for user uid {0!s}".format(uid))
        if six.PY2:
            # crypt needs bytes in python 2
            password = to_bytes(password)
        cryptedpasswd = self.passDict[uid]
        log.debug("We found the encrypted pass {0!s} for uid {1!s}".format(
            cryptedpasswd, uid))
        if cryptedpasswd:
            if cryptedpasswd in ['x', '*']:
                err = "Sorry, currently no support for shadow passwords"
                log.error("{0!s}".format(err))
                raise NotImplementedError(err)
            cp = crypt.crypt(password, cryptedpasswd)
            log.debug("encrypted pass is {0!s}".format(cp))
            if crypt.crypt(password, cryptedpasswd) == cryptedpasswd:
                log.info(
                    "successfully authenticated user uid {0!s}".format(uid))
                return True
            else:
                log.warning(
                    "user uid {0!s} failed to authenticate".format(uid))
                return False
        else:
            log.warning("Failed to verify password. No encrypted password "
                        "found in file")
            return False
Beispiel #25
0
def encryptPassword(password):
    """
    Encrypt given password with hsm

    This function returns a unicode string with a
    hexlified contents of the IV and the encrypted data separated by a
    colon like u"4956:44415441"

    :param password: the password
    :type password: bytes or str
    :return: the encrypted password, hexlified
    :rtype: str
    """
    hsm = get_hsm()
    try:
        ret = hsm.encrypt_password(to_bytes(password))
    except Exception as exx:  # pragma: no cover
        log.warning(exx)
        ret = "FAILED TO ENCRYPT PASSWORD!"
    return ret
Beispiel #26
0
    def _encrypt_value(self, value, slot_id):
        """
        base method to encrypt a value
        - uses one slot id to encrypt a string
        returns as string with leading iv, separated by ':'

        :param value: the value that is to be encrypted
        :type value: str

        :param slot_id: slot of the key array
        :type slot_id: int

        :return: encrypted data with leading iv and separator ':'
        :rtype: str
        """
        iv = self.random(16)
        v = self.encrypt(to_bytes(value), iv, slot_id)

        cipher_value = binascii.hexlify(iv) + b':' + binascii.hexlify(v)
        return cipher_value.decode("utf-8")
Beispiel #27
0
    def sign(self, s):
        """
        Create a signature of the string s

        :param s: String to sign
        :type s: str
        :return: The hexlified and versioned signature of the string
        :rtype: str
        """
        if not self.private:
            log.info('Could not sign message {0!s}, no private key!'.format(s))
            # TODO: should we throw an exception in this case?
            return ''

        signature = self.private.sign(
            to_bytes(s),
            asym_padding.PSS(mgf=asym_padding.MGF1(hashes.SHA256()),
                             salt_length=asym_padding.PSS.MAX_LENGTH),
            hashes.SHA256())
        res = ':'.join([self.sig_ver, hexlify_and_unicode(signature)])
        return res
Beispiel #28
0
        def check_password(self, password):
            """

            :param password:
            :type password: str
            :return: result of password check: 0 if success, -1 if failed
            :rtype: int
            """
            res = -1

            key = self.secretObject.getKey()
            # getKey() returns bytes and since we can not assume, that the
            # password only contains printable characters, we need to compare
            # bytes strings here. This also avoids making another copy of 'key'.
            if key == to_bytes(password):
                res = 0

            zerome(key)
            del key

            return res
Beispiel #29
0
    def calcOtp(self, counter, key=None, pin=None):
        '''
        calculate an otp value from counter/time, key and pin
        
        :param counter:    counter/time to be checked
        :type counter:     int
        :param key:        the secret key
        :type key:         str
        :param pin:        the secret pin
        :type pin:         str
        
        :return:           the otp value
        :rtype:            str
        '''
        ## ref impl from https://github.com/szimszon/motpy/blob/master/motpy
        if pin is None:
            pin = self.pin
        if key is None:
            key = self.key

        vhash = u"{0:d}{1!s}{2!s}".format(counter, key, pin)
        motp = md5(to_bytes(vhash)).hexdigest()[:self.digits]
        return to_unicode(motp)
Beispiel #30
0
    def calcOtp(self, counter, key=None, pin=None):
        '''
        calculate an otp value from counter/time, key and pin
        
        :param counter:    counter/time to be checked
        :type counter:     int
        :param key:        the secret key
        :type key:         str
        :param pin:        the secret pin
        :type pin:         str
        
        :return:           the otp value
        :rtype:            str
        '''
        ## ref impl from https://github.com/szimszon/motpy/blob/master/motpy
        if pin is None:
            pin = self.pin
        if key is None:
            key = self.key

        vhash = u"{0:d}{1!s}{2!s}".format(counter, key, pin)
        motp = md5(to_bytes(vhash)).hexdigest()[:self.digits]
        return to_unicode(motp)
Beispiel #31
0
def yubico_api_signature(data, api_key):
    """
    Get a dictionary "data", sort the dictionary by the keys
    and sign it HMAC-SHA1 with the api_key

    :param data: The data to be signed
    :type data: dict
    :param api_key: base64 encoded API key
    :type api_key: basestring
    :return: base64 encoded signature
    """
    r = dict(data)
    if 'h' in r:
        del r['h']
    keys = sorted(r.keys())
    data_string = ""
    for key in keys:
        data_string += "{0!s}={1!s}&".format(key, r.get(key))
    data_string = data_string.strip("&")
    api_key_bin = base64.b64decode(api_key)
    # generate the signature
    h = hmac.new(api_key_bin, to_bytes(data_string), sha1).digest()
    h_b64 = b64encode_and_unicode(h)
    return h_b64
Beispiel #32
0
def yubico_api_signature(data, api_key):
    """
    Get a dictionary "data", sort the dictionary by the keys
    and sign it HMAC-SHA1 with the api_key

    :param data: The data to be signed
    :type data: dict
    :param api_key: base64 encoded API key
    :type api_key: basestring
    :return: base64 encoded signature
    """
    r = dict(data)
    if 'h' in r:
        del r['h']
    keys = sorted(r.keys())
    data_string = ""
    for key in keys:
        data_string += "{0!s}={1!s}&".format(key, r.get(key))
    data_string = data_string.strip("&")
    api_key_bin = base64.b64decode(api_key)
    # generate the signature
    h = hmac.new(api_key_bin, to_bytes(data_string), sha1).digest()
    h_b64 = b64encode_and_unicode(h)
    return h_b64
    def test_02_api_enroll(self):
        self.authenticate()

        # Failed enrollment due to missing policy
        with self.app.test_request_context('/token/init',
                                           method='POST',
                                           data={"type": "push",
                                                 "genkey": 1},
                                           headers={'Authorization': self.at}):
            res = self.app.full_dispatch_request()
            self.assertNotEqual(res.status_code,  200)
            error = json.loads(res.data.decode("utf8")).get("result").get("error")
            self.assertEqual(error.get("message"), "Missing enrollment policy for push token: push_firebase_configuration")
            self.assertEqual(error.get("code"), 303)

        r = set_smsgateway(self.firebase_config_name, u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider', "myFB",
                           {FIREBASE_CONFIG.REGISTRATION_URL: "http://test/ttype/push",
                            FIREBASE_CONFIG.TTL: 10,
                            FIREBASE_CONFIG.API_KEY: "1",
                            FIREBASE_CONFIG.APP_ID: "2",
                            FIREBASE_CONFIG.PROJECT_NUMBER: "3",
                            FIREBASE_CONFIG.PROJECT_ID: "test-123456",
                            FIREBASE_CONFIG.JSON_CONFIG: FIREBASE_FILE})
        self.assertTrue(r > 0)
        set_policy("push1", scope=SCOPE.ENROLL,
                   action="{0!s}={1!s}".format(PUSH_ACTION.FIREBASE_CONFIG,
                                               self.firebase_config_name))

        # 1st step
        with self.app.test_request_context('/token/init',
                                           method='POST',
                                           data={"type": "push",
                                                 "genkey": 1},
                                           headers={'Authorization': self.at}):
            res = self.app.full_dispatch_request()
            self.assertEqual(res.status_code,  200)
            detail = json.loads(res.data.decode('utf8')).get("detail")
            serial = detail.get("serial")
            self.assertEqual(detail.get("rollout_state"), "clientwait")
            self.assertTrue("pushurl" in detail)
            # check that the new URL contains the serial number
            self.assertTrue("&serial=PIPU" in detail.get("pushurl").get("value"))
            self.assertTrue("appid=" in detail.get("pushurl").get("value"))
            self.assertTrue("appidios=" in detail.get("pushurl").get("value"))
            self.assertTrue("apikeyios=" in detail.get("pushurl").get("value"))
            self.assertFalse("otpkey" in detail)
            enrollment_credential = detail.get("enrollment_credential")

        # 2nd step. Failing with wrong serial number
        with self.app.test_request_context('/ttype/push',
                                           method='POST',
                                           data={"serial": "wrongserial",
                                                 "pubkey": self.smartphone_public_key_pem_urlsafe,
                                                 "fbtoken": "firebaseT"}):
            res = self.app.full_dispatch_request()
            self.assertTrue(res.status_code == 404, res)
            status = json.loads(res.data.decode('utf8')).get("result").get("status")
            self.assertFalse(status)
            error = json.loads(res.data.decode('utf8')).get("result").get("error")
            self.assertEqual(error.get("message"),
                             "No token with this serial number in the rollout state 'clientwait'.")

        # 2nd step. Fails with missing enrollment credential
        with self.app.test_request_context('/ttype/push',
                                           method='POST',
                                           data={"serial": serial,
                                                 "pubkey": self.smartphone_public_key_pem_urlsafe,
                                                 "fbtoken": "firebaseT",
                                                 "enrollment_credential": "WRonG"}):
            res = self.app.full_dispatch_request()
            self.assertTrue(res.status_code == 400, res)
            status = json.loads(res.data.decode('utf8')).get("result").get("status")
            self.assertFalse(status)
            error = json.loads(res.data.decode('utf8')).get("result").get("error")
            self.assertEqual(error.get("message"),
                             "ERR905: Invalid enrollment credential. You are not authorized to finalize this token.")

        # 2nd step: as performed by the smartphone
        with self.app.test_request_context('/ttype/push',
                                           method='POST',
                                           data={"enrollment_credential": enrollment_credential,
                                                 "serial": serial,
                                                 "pubkey": self.smartphone_public_key_pem_urlsafe,
                                                 "fbtoken": "firebaseT"}):
            res = self.app.full_dispatch_request()
            self.assertTrue(res.status_code == 200, res)
            detail = json.loads(res.data.decode('utf8')).get("detail")
            # still the same serial number
            self.assertEqual(serial, detail.get("serial"))
            self.assertEqual(detail.get("rollout_state"), "enrolled")
            # Now the smartphone gets a public key from the server
            augmented_pubkey = "-----BEGIN RSA PUBLIC KEY-----\n{}\n-----END RSA PUBLIC KEY-----\n".format(
                detail.get("public_key"))
            parsed_server_pubkey = serialization.load_pem_public_key(
                to_bytes(augmented_pubkey),
                default_backend())
            self.assertIsInstance(parsed_server_pubkey, RSAPublicKey)
            pubkey = detail.get("public_key")

            # Now check, what is in the token in the database
            toks = get_tokens(serial=serial)
            self.assertEqual(len(toks), 1)
            token_obj = toks[0]
            self.assertEqual(token_obj.token.rollout_state, u"enrolled")
            self.assertTrue(token_obj.token.active)
            tokeninfo = token_obj.get_tokeninfo()
            self.assertEqual(tokeninfo.get("public_key_smartphone"), self.smartphone_public_key_pem_urlsafe)
            self.assertEqual(tokeninfo.get("firebase_token"), u"firebaseT")
            self.assertEqual(tokeninfo.get("public_key_server").strip().strip("-BEGIN END RSA PUBLIC KEY-").strip(), pubkey)
            # The token should also contain the firebase config
            self.assertEqual(tokeninfo.get(PUSH_ACTION.FIREBASE_CONFIG), self.firebase_config_name)
Beispiel #34
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
Beispiel #35
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 answer the challenge'
        attributes = {}

        # Get ValidityTime=120s. Maybe there is a OCRAChallengeValidityTime...
        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))

        # Get the OCRASUITE from the token information
        ocrasuite = self.get_tokeninfo("ocrasuite") or OCRA_DEFAULT_SUITE

        challenge = options.get("challenge")
        # TODO: we could add an additional parameter to hash the challenge
        # cleartext -> sha1
        if not challenge:
            # If no challenge is given in the Request, we create a random
            # challenge based on the OCRA-SUITE
            os = OCRASuite(ocrasuite)
            challenge = os.create_challenge()
        else:
            # Add a random challenge
            if options.get("addrandomchallenge"):
                challenge += get_alphanum_str(int(options.get(
                    "addrandomchallenge")))
            attributes["original_challenge"] = challenge
            attributes["qrcode"] = create_img(challenge)
            if options.get("hashchallenge", "").lower() == "sha256":
                challenge = hexlify_and_unicode(hashlib.sha256(to_bytes(challenge)).digest())
            elif options.get("hashchallenge", "").lower() == "sha512":
                challenge = hexlify_and_unicode(hashlib.sha512(to_bytes(challenge)).digest())
            elif options.get("hashchallenge"):
                challenge = hexlify_and_unicode(hashlib.sha1(to_bytes(challenge)).digest())


        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=None,
                                 challenge=challenge,
                                 data=None,
                                 session=None,
                                 validitytime=validity)
        db_challenge.save()

        attributes["challenge"] = challenge

        return True, message, db_challenge.transaction_id, attributes
Beispiel #36
0
    def api_endpoint(cls, request, g):
        """
        This provides a function which is called by the API endpoint
        /ttype/push which is defined in api/ttype.py

        The method returns
            return "json", {}

        This endpoint is used for the 2nd enrollment step of the smartphone.
        Parameters sent:
            * serial
            * fbtoken
            * pubkey

        This endpoint is also used, if the smartphone sends the signed response
        to the challenge during authentication
        Parameters sent:
            * serial
            * nonce (which is the challenge)
            * signature (which is the signed nonce)


        :param request: The Flask request
        :param g: The Flask global object g
        :return: dictionary
        """
        details = {}
        result = False
        serial = getParam(request.all_data, "serial", optional=False)

        if serial and "fbtoken" in request.all_data and "pubkey" in request.all_data:
            # Do the 2nd step of the enrollment
            try:
                token_obj = get_one_token(serial=serial,
                                          tokentype="push",
                                          rollout_state="clientwait")
                token_obj.update(request.all_data)
            except ResourceNotFoundError:
                raise ResourceNotFoundError(
                    "No token with this serial number in the rollout state 'clientwait'."
                )
            init_detail_dict = request.all_data

            details = token_obj.get_init_detail(init_detail_dict)
            result = True
        elif serial and "nonce" in request.all_data and "signature" in request.all_data:
            challenge = getParam(request.all_data, "nonce")
            serial = getParam(request.all_data, "serial")
            signature = getParam(request.all_data, "signature")

            # get the token_obj for the given serial:
            token_obj = get_one_token(serial=serial, tokentype="push")
            pubkey_pem = token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)
            # The public key of the smartphone was probably sent as urlsafe:
            pubkey_pem = pubkey_pem.replace("-", "+").replace("_", "/")
            # The public key was sent without any header
            pubkey_pem = "-----BEGIN PUBLIC KEY-----\n{0!s}\n-----END PUBLIC KEY-----".format(
                pubkey_pem.strip().replace(" ", "+"))
            # Do the 2nd step of the authentication
            # Find valid challenges
            challengeobject_list = get_challenges(serial=serial,
                                                  challenge=challenge)

            if challengeobject_list:
                # There are valid challenges, so we check this signature
                for chal in challengeobject_list:
                    # verify the signature of the nonce
                    pubkey_obj = serialization.load_pem_public_key(
                        to_bytes(pubkey_pem), default_backend())
                    sign_data = u"{0!s}|{1!s}".format(challenge, serial)
                    try:
                        pubkey_obj.verify(b32decode(signature),
                                          sign_data.encode("utf8"),
                                          padding.PKCS1v15(), hashes.SHA256())
                        # The signature was valid
                        chal.set_otp_status(True)
                        result = True
                    except InvalidSignature as e:
                        pass

        else:
            raise ParameterError("Missing parameters!")

        return "json", prepare_result(result, details=details)
    def test_01_create_token(self):
        db_token = Token(self.serial1, tokentype="push")
        db_token.save()
        token = PushTokenClass(db_token)
        self.assertEqual(token.token.serial, self.serial1)
        self.assertEqual(token.token.tokentype, "push")
        self.assertEqual(token.type, "push")
        class_prefix = token.get_class_prefix()
        self.assertEqual(class_prefix, "PIPU")
        self.assertEqual(token.get_class_type(), "push")

        # Test to do the 2nd step, although the token is not yet in clientwait
        self.assertRaises(ParameterError, token.update, {"otpkey": "1234", "pubkey": "1234", "serial": self.serial1})

        # Run enrollment step 1
        token.update({"genkey": 1})

        # Now the token is in the state clientwait, but insufficient parameters would still fail
        self.assertRaises(ParameterError, token.update, {"otpkey": "1234"})
        self.assertRaises(ParameterError, token.update, {"otpkey": "1234", "pubkey": "1234"})

        # Unknown config
        self.assertRaises(ParameterError, token.get_init_detail, params={"firebase_config": "bla"})

        fb_config = {FIREBASE_CONFIG.REGISTRATION_URL: "http://test/ttype/push",
                     FIREBASE_CONFIG.JSON_CONFIG: CLIENT_FILE,
                     FIREBASE_CONFIG.TTL: 10,
                     FIREBASE_CONFIG.API_KEY: "1",
                     FIREBASE_CONFIG.APP_ID: "2",
                     FIREBASE_CONFIG.PROJECT_NUMBER: "3",
                     FIREBASE_CONFIG.PROJECT_ID: "4"}

        # Wrong JSON file
        self.assertRaises(ConfigAdminError, set_smsgateway,
                          "fb1", u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider', "myFB",
                          fb_config)

        # Wrong Project number
        fb_config[FIREBASE_CONFIG.JSON_CONFIG] = FIREBASE_FILE
        self.assertRaises(ConfigAdminError, set_smsgateway,
                          "fb1", u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider', "myFB",
                          fb_config)

        # Missing APP_ID
        self.assertRaises(ConfigAdminError, set_smsgateway,
                          "fb1", u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider', "myFB",
                          {FIREBASE_CONFIG.REGISTRATION_URL: "http://test/ttype/push",
                           FIREBASE_CONFIG.JSON_CONFIG: CLIENT_FILE,
                           FIREBASE_CONFIG.TTL: 10,
                           FIREBASE_CONFIG.API_KEY: "1",
                           FIREBASE_CONFIG.PROJECT_NUMBER: "3",
                           FIREBASE_CONFIG.PROJECT_ID: "4"})

        # Missing API_KEY_IOS
        self.assertRaises(ConfigAdminError, set_smsgateway,
                          "fb1", u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider', "myFB",
                          {FIREBASE_CONFIG.REGISTRATION_URL: "http://test/ttype/push",
                           FIREBASE_CONFIG.JSON_CONFIG: CLIENT_FILE,
                           FIREBASE_CONFIG.TTL: 10,
                           FIREBASE_CONFIG.APP_ID_IOS: "1",
                           FIREBASE_CONFIG.PROJECT_NUMBER: "3",
                           FIREBASE_CONFIG.PROJECT_ID: "4"})

        # Everything is fine
        fb_config[FIREBASE_CONFIG.PROJECT_ID] = "test-123456"
        r = set_smsgateway("fb1", u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider', "myFB",
                           fb_config)
        self.assertTrue(r > 0)

        detail = token.get_init_detail(params={"firebase_config": self.firebase_config_name})
        self.assertEqual(detail.get("serial"), self.serial1)
        self.assertEqual(detail.get("rollout_state"), "clientwait")
        enrollment_credential = detail.get("enrollment_credential")
        self.assertTrue("pushurl" in detail)
        self.assertFalse("otpkey" in detail)

        # Run enrollment step 2
        token.update({"enrollment_credential": enrollment_credential,
                      "serial": self.serial1,
                      "fbtoken": "firebasetoken",
                      "pubkey": self.smartphone_public_key_pem_urlsafe})
        self.assertEqual(token.get_tokeninfo("firebase_token"), "firebasetoken")
        self.assertEqual(token.get_tokeninfo("public_key_smartphone"), self.smartphone_public_key_pem_urlsafe)
        self.assertTrue(token.get_tokeninfo("public_key_server").startswith(u"-----BEGIN RSA PUBLIC KEY-----\n"),
                        token.get_tokeninfo("public_key_server"))
        parsed_server_pubkey = serialization.load_pem_public_key(
            to_bytes(token.get_tokeninfo("public_key_server")),
            default_backend())
        self.assertIsInstance(parsed_server_pubkey, RSAPublicKey)
        self.assertTrue(token.get_tokeninfo("private_key_server").startswith(u"-----BEGIN RSA PRIVATE KEY-----\n"),
                        token.get_tokeninfo("private_key_server"))
        parsed_server_privkey = serialization.load_pem_private_key(
            to_bytes(token.get_tokeninfo("private_key_server")),
            None,
            default_backend())
        self.assertIsInstance(parsed_server_privkey, RSAPrivateKey)

        detail = token.get_init_detail()
        self.assertEqual(detail.get("rollout_state"), "enrolled")
        augmented_pubkey = "-----BEGIN RSA PUBLIC KEY-----\n{}\n-----END RSA PUBLIC KEY-----\n".format(
            detail.get("public_key"))
        parsed_stripped_server_pubkey = serialization.load_pem_public_key(
            to_bytes(augmented_pubkey),
            default_backend())
        self.assertEqual(parsed_server_pubkey.public_numbers(), parsed_stripped_server_pubkey.public_numbers())
        remove_token(self.serial1)
Beispiel #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, reply_dict)
        :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 = 'Please answer the challenge'
        attributes = {}

        # Get ValidityTime=120s. Maybe there is a OCRAChallengeValidityTime...
        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))

        # Get the OCRASUITE from the token information
        ocrasuite = self.get_tokeninfo("ocrasuite") or OCRA_DEFAULT_SUITE

        challenge = options.get("challenge")
        # TODO: we could add an additional parameter to hash the challenge
        # cleartext -> sha1
        if not challenge:
            # If no challenge is given in the Request, we create a random
            # challenge based on the OCRA-SUITE
            os = OCRASuite(ocrasuite)
            challenge = os.create_challenge()
        else:
            # Add a random challenge
            if options.get("addrandomchallenge"):
                challenge += get_alphanum_str(
                    int(options.get("addrandomchallenge")))
            attributes["original_challenge"] = challenge
            attributes["qrcode"] = create_img(challenge)
            if options.get("hashchallenge", "").lower() == "sha256":
                challenge = hexlify_and_unicode(
                    hashlib.sha256(to_bytes(challenge)).digest())
            elif options.get("hashchallenge", "").lower() == "sha512":
                challenge = hexlify_and_unicode(
                    hashlib.sha512(to_bytes(challenge)).digest())
            elif options.get("hashchallenge"):
                challenge = hexlify_and_unicode(
                    hashlib.sha1(to_bytes(challenge)).digest())

        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=None,
                                 challenge=challenge,
                                 data=None,
                                 session=None,
                                 validitytime=validity)
        db_challenge.save()

        attributes["challenge"] = challenge
        reply_dict = {"attributes": attributes}

        return True, message, db_challenge.transaction_id, reply_dict
Beispiel #39
0
    def _check_radius(self, otpval, options=None, radius_state=None):
        """
        run the RADIUS request against the RADIUS server

        :param otpval: the OTP value
        :param options: additional token specific options
        :type options: dict
        :return: counter of the matching OTP value.
        :rtype: AccessAccept, AccessReject, AccessChallenge
        """
        result = AccessReject
        radius_message = None
        if options is None:
            options = {}

        radius_dictionary = None
        radius_identifier = self.get_tokeninfo("radius.identifier")
        radius_user = self.get_tokeninfo("radius.user")
        system_radius_settings = self.get_tokeninfo("radius.system_settings")
        radius_timeout = 5
        radius_retries = 3
        if radius_identifier:
            # New configuration
            radius_server_object = get_radius(radius_identifier)
            radius_server = radius_server_object.config.server
            radius_port = radius_server_object.config.port
            radius_server = u"{0!s}:{1!s}".format(radius_server, radius_port)
            radius_secret = radius_server_object.get_secret()
            radius_dictionary = radius_server_object.config.dictionary
            radius_timeout = int(radius_server_object.config.timeout or 10)
            radius_retries = int(radius_server_object.config.retries or 1)
        elif system_radius_settings:
            # system configuration
            radius_server = get_from_config("radius.server")
            radius_secret = get_from_config("radius.secret")
        else:
            # individual token settings
            radius_server = self.get_tokeninfo("radius.server")
            # Read the secret
            secret = self.token.get_otpkey()
            radius_secret = binascii.unhexlify(secret.getKey())

        # here we also need to check for radius.user
        log.debug(u"checking OTP len:{0!s} on radius server: "
                  u"{1!s}, user: {2!r}".format(len(otpval), radius_server,
                                               radius_user))

        try:
            # pyrad does not allow to set timeout and retries.
            # it defaults to retries=3, timeout=5

            # TODO: At the moment we support only one radius server.
            # No round robin.
            server = radius_server.split(':')
            r_server = server[0]
            r_authport = 1812
            if len(server) >= 2:
                r_authport = int(server[1])
            nas_identifier = get_from_config("radius.nas_identifier",
                                             "privacyIDEA")
            if not radius_dictionary:
                radius_dictionary = get_from_config(
                    "radius.dictfile", "/etc/privacyidea/dictionary")
            log.debug(u"NAS Identifier: %r, "
                      u"Dictionary: %r" % (nas_identifier, radius_dictionary))
            log.debug(u"constructing client object "
                      u"with server: %r, port: %r, secret: %r" %
                      (r_server, r_authport, to_unicode(radius_secret)))

            srv = Client(server=r_server,
                         authport=r_authport,
                         secret=to_bytes(radius_secret),
                         dict=Dictionary(radius_dictionary))

            # Set retries and timeout of the client
            srv.timeout = radius_timeout
            srv.retries = radius_retries

            req = srv.CreateAuthPacket(
                code=pyrad.packet.AccessRequest,
                User_Name=radius_user.encode('utf-8'),
                NAS_Identifier=nas_identifier.encode('ascii'))

            req["User-Password"] = req.PwCrypt(otpval)

            if radius_state:
                req["State"] = radius_state
                log.info(
                    u"Sending saved challenge to radius server: {0!r} ".format(
                        radius_state))

            try:
                response = srv.SendPacket(req)
            except Timeout:
                log.warning(
                    u"The remote RADIUS server {0!s} timeout out for user {1!s}."
                    .format(r_server, radius_user))
                return AccessReject

            # handle the RADIUS challenge
            if response.code == pyrad.packet.AccessChallenge:
                # now we map this to a privacyidea challenge
                if "State" in response:
                    radius_state = response["State"][0]
                if "Reply-Message" in response:
                    radius_message = response["Reply-Message"][0]

                result = AccessChallenge
            elif response.code == pyrad.packet.AccessAccept:
                radius_state = '<SUCCESS>'
                radius_message = 'RADIUS authentication succeeded'
                log.info(u"RADIUS server {0!s} granted "
                         u"access to user {1!s}.".format(
                             r_server, radius_user))
                result = AccessAccept
            else:
                radius_state = '<REJECTED>'
                radius_message = 'RADIUS authentication failed'
                log.debug(u'radius response code {0!s}'.format(response.code))
                log.info(u"Radiusserver {0!s} "
                         u"rejected access to user {1!s}.".format(
                             r_server, radius_user))
                result = AccessReject

        except Exception as ex:  # pragma: no cover
            log.error("Error contacting radius Server: {0!r}".format((ex)))
            log.info("{0!s}".format(traceback.format_exc()))

        options.update({'radius_result': result})
        options.update({'radius_state': radius_state})
        options.update({'radius_message': radius_message})
        return result
Beispiel #40
0
    def create_data_input(self,
                          question,
                          pin=None,
                          pin_hash=None,
                          counter=None,
                          timesteps=None):
        """
        Create the data_input to be used in the HMAC function
        In case of QN the question would be "111111"
        In case of QA the question would be "123ASD"
        In case of QH the question would be "BEEF"

        The question is transformed internally.

        :param question: The question can be
        :type question: str

        :param pin_hash: The hash of the pin
        :type pin_hash: basestring (hex)
        :param timesteps: timestemps
        :type timesteps: hex string
        :return: data_input
        :rtype: bytes
        """
        # In case the ocrasuite comes as a unicode (like from the webui) we
        # need to convert it!
        data_input = to_bytes(self.ocrasuite) + b'\0'
        # Check for counter
        if self.ocrasuite_obj.counter == "C":
            if counter:
                counter = int(counter)
                counter = struct.pack('>Q', int(counter))
                data_input += counter
            else:
                raise Exception(
                    "The ocrasuite {0!s} requires a counter".format(
                        self.ocrasuite))
        # Check for Question
        if self.ocrasuite_obj.challenge_type == "QN":
            # question contains only numeric values
            hex_q = '{0:x}'.format(int(question))
            hex_q += '0' * (len(hex_q) % 2)
            bin_q = binascii.unhexlify(hex_q)
            bin_q += b'\x00' * (128 - len(bin_q))
            data_input += bin_q
        elif self.ocrasuite_obj.challenge_type == "QA":
            # question contains alphanumeric characters
            bin_q = to_bytes(question)
            bin_q += b'\x00' * (128 - len(bin_q))
            data_input += bin_q
        elif self.ocrasuite_obj.challenge_type == "QH":
            # qustion contains hex values
            bin_q = binascii.unhexlify(question)
            bin_q += b'\x00' * (128 - len(bin_q))
            data_input += bin_q

        # in case of PIN
        if self.ocrasuite_obj.signature_type == "P":
            if pin_hash:
                data_input += binascii.unhexlify(pin_hash)
            elif pin:
                pin_hash = SHA_FUNC.get(self.ocrasuite_obj.signature_hash)(
                    to_bytes(pin)).digest()
                data_input += pin_hash
            else:
                raise Exception("The ocrasuite {0!s} requires a PIN!".format(
                    self.ocrasuite))
        elif self.ocrasuite_obj.signature_type == "T":
            if not timesteps:
                raise Exception(
                    "The ocrasuite {0!s} requires timesteps".format(
                        self.ocrasuite))
            # In case of Time
            timesteps = int(timesteps, 16)
            timesteps = struct.pack('>Q', int(timesteps))
            data_input += timesteps
        elif self.ocrasuite_obj.signature_type == "S":  # pragma: no cover
            # In case of session
            # TODO: Session not yet implemented
            raise NotImplementedError("OCRA Session not implemented, yet.")
        return data_input
Beispiel #41
0
    def check_otp(self, otpval, counter=None, window=None, options=None):
        """
        run the RADIUS request against the RADIUS server

        :param otpval: the OTP value
        :param counter: The counter for counter based otp values
        :type counter: int
        :param window: a counter window
        :type counter: int
        :param options: additional token specific options
        :type options: dict
        :return: counter of the matching OTP value.
        :rtype: int
        """
        otp_count = -1
        options = options or {}

        radius_dictionary = None
        radius_identifier = self.get_tokeninfo("radius.identifier")
        radius_user = self.get_tokeninfo("radius.user")
        system_radius_settings = self.get_tokeninfo("radius.system_settings")
        if radius_identifier:
            # New configuration
            radius_server_object = get_radius(radius_identifier)
            radius_server = radius_server_object.config.server
            radius_port = radius_server_object.config.port
            radius_server = u"{0!s}:{1!s}".format(radius_server, radius_port)
            radius_secret = radius_server_object.get_secret()
            radius_dictionary = radius_server_object.config.dictionary

        elif system_radius_settings:
            # system configuration
            radius_server = get_from_config("radius.server")
            radius_secret = get_from_config("radius.secret")
        else:
            # individual token settings
            radius_server = self.get_tokeninfo("radius.server")
            # Read the secret
            secret = self.token.get_otpkey()
            radius_secret = binascii.unhexlify(secret.getKey())

        # here we also need to check for radius.user
        log.debug(u"checking OTP len:{0!s} on radius server: "
                  u"{1!s}, user: {2!r}".format(len(otpval), radius_server,
                                               radius_user))

        try:
            # pyrad does not allow to set timeout and retries.
            # it defaults to retries=3, timeout=5

            # TODO: At the moment we support only one radius server.
            # No round robin.
            server = radius_server.split(':')
            r_server = server[0]
            r_authport = 1812
            if len(server) >= 2:
                r_authport = int(server[1])
            nas_identifier = get_from_config("radius.nas_identifier",
                                             "privacyIDEA")
            if not radius_dictionary:
                radius_dictionary = get_from_config("radius.dictfile",
                                                    "/etc/privacyidea/dictionary")
            log.debug(u"NAS Identifier: %r, "
                      u"Dictionary: %r" % (nas_identifier, radius_dictionary))
            log.debug(u"constructing client object "
                      u"with server: %r, port: %r, secret: %r" %
                      (r_server, r_authport, to_unicode(radius_secret)))

            srv = Client(server=r_server,
                         authport=r_authport,
                         secret=to_bytes(radius_secret),
                         dict=Dictionary(radius_dictionary))

            req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
                                       User_Name=radius_user.encode('utf-8'),
                                       NAS_Identifier=nas_identifier.encode('ascii'))

            req["User-Password"] = req.PwCrypt(otpval)
            if "transactionid" in options:
                req["State"] = str(options.get("transactionid"))

            response = srv.SendPacket(req)
            # TODO: handle the RADIUS challenge
            """
            if response.code == pyrad.packet.AccessChallenge:
                opt = {}
                for attr in response.keys():
                    opt[attr] = response[attr]
                res = False
                log.debug("challenge returned %r " % opt)
                # now we map this to a privacyidea challenge
                if "State" in opt:
                    reply["transactionid"] = opt["State"][0]
                if "Reply-Message" in opt:
                    reply["message"] = opt["Reply-Message"][0]
            """
            if response.code == pyrad.packet.AccessAccept:
                log.info("Radiusserver %s granted "
                         "access to user %s." % (r_server, radius_user))
                otp_count = 0
            else:
                log.warning("Radiusserver %s rejected "
                            "access to user %s." % (r_server, radius_user))

        except Exception as ex:  # pragma: no cover
            log.error("Error contacting radius Server: {0!r}".format((ex)))
            log.debug("{0!s}".format(traceback.format_exc()))

        return otp_count
Beispiel #42
0
    def create_data_input(self, question, pin=None, pin_hash=None,
                          counter=None, timesteps=None):
        """
        Create the data_input to be used in the HMAC function
        In case of QN the question would be "111111"
        In case of QA the question would be "123ASD"
        In case of QH the question would be "BEEF"

        The question is transformed internally.

        :param question: The question can be
        :type question: str

        :param pin_hash: The hash of the pin
        :type pin_hash: basestring (hex)
        :param timesteps: timestemps
        :type timesteps: hex string
        :return: data_input
        :rtype: bytes
        """
        # In case the ocrasuite comes as a unicode (like from the webui) we
        # need to convert it!
        data_input = to_bytes(self.ocrasuite) + b'\0'
        # Check for counter
        if self.ocrasuite_obj.counter == "C":
            if counter:
                counter = int(counter)
                counter = struct.pack('>Q', int(counter))
                data_input += counter
            else:
                raise Exception("The ocrasuite {0!s} requires a counter".format(
                                self.ocrasuite))
        # Check for Question
        if self.ocrasuite_obj.challenge_type == "QN":
            # question contains only numeric values
            hex_q = '{0:x}'.format(int(question))
            hex_q += '0' * (len(hex_q) % 2)
            bin_q = binascii.unhexlify(hex_q)
            bin_q += b'\x00' * (128-len(bin_q))
            data_input += bin_q
        elif self.ocrasuite_obj.challenge_type == "QA":
            # question contains alphanumeric characters
            bin_q = to_bytes(question)
            bin_q += b'\x00' * (128-len(bin_q))
            data_input += bin_q
        elif self.ocrasuite_obj.challenge_type == "QH":
            # qustion contains hex values
            bin_q = binascii.unhexlify(question)
            bin_q += b'\x00' * (128-len(bin_q))
            data_input += bin_q

        # in case of PIN
        if self.ocrasuite_obj.signature_type == "P":
            if pin_hash:
                data_input += binascii.unhexlify(pin_hash)
            elif pin:
                pin_hash = SHA_FUNC.get(self.ocrasuite_obj.signature_hash)(
                    to_bytes(pin)).digest()
                data_input += pin_hash
            else:
                raise Exception("The ocrasuite {0!s} requires a PIN!".format(
                                self.ocrasuite))
        elif self.ocrasuite_obj.signature_type == "T":
            if not timesteps:
                raise Exception("The ocrasuite {0!s} requires timesteps".format(
                                self.ocrasuite))
            # In case of Time
            timesteps = int(timesteps, 16)
            timesteps = struct.pack('>Q', int(timesteps))
            data_input += timesteps
        elif self.ocrasuite_obj.signature_type == "S":  # pragma: no cover
            # In case of session
            # TODO: Session not yet implemented
            raise NotImplementedError("OCRA Session not implemented, yet.")
        return data_input
    def test_01_create_token(self):
        db_token = Token(self.serial1, tokentype="push")
        db_token.save()
        token = PushTokenClass(db_token)
        self.assertEqual(token.token.serial, self.serial1)
        self.assertEqual(token.token.tokentype, "push")
        self.assertEqual(token.type, "push")
        class_prefix = token.get_class_prefix()
        self.assertEqual(class_prefix, "PIPU")
        self.assertEqual(token.get_class_type(), "push")

        # Test to do the 2nd step, although the token is not yet in clientwait
        self.assertRaises(ParameterError, token.update, {
            "otpkey": "1234",
            "pubkey": "1234",
            "serial": self.serial1
        })

        # Run enrollment step 1
        token.update({"genkey": 1})

        # Now the token is in the state clientwait, but insufficient parameters would still fail
        self.assertRaises(ParameterError, token.update, {"otpkey": "1234"})
        self.assertRaises(ParameterError, token.update, {
            "otpkey": "1234",
            "pubkey": "1234"
        })

        # Unknown config
        self.assertRaises(ParameterError,
                          token.get_init_detail,
                          params={"firebase_config": "bla"})

        fb_config = {
            FIREBASE_CONFIG.REGISTRATION_URL: "http://test/ttype/push",
            FIREBASE_CONFIG.JSON_CONFIG: CLIENT_FILE,
            FIREBASE_CONFIG.TTL: 10,
            FIREBASE_CONFIG.API_KEY: "1",
            FIREBASE_CONFIG.APP_ID: "2",
            FIREBASE_CONFIG.PROJECT_NUMBER: "3",
            FIREBASE_CONFIG.PROJECT_ID: "4"
        }

        # Wrong JSON file
        self.assertRaises(
            ConfigAdminError, set_smsgateway, "fb1",
            u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
            "myFB", fb_config)

        # Wrong Project number
        fb_config[FIREBASE_CONFIG.JSON_CONFIG] = FIREBASE_FILE
        self.assertRaises(
            ConfigAdminError, set_smsgateway, "fb1",
            u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
            "myFB", fb_config)

        # Missing APP_ID
        self.assertRaises(
            ConfigAdminError, set_smsgateway, "fb1",
            u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
            "myFB", {
                FIREBASE_CONFIG.REGISTRATION_URL: "http://test/ttype/push",
                FIREBASE_CONFIG.JSON_CONFIG: CLIENT_FILE,
                FIREBASE_CONFIG.TTL: 10,
                FIREBASE_CONFIG.API_KEY: "1",
                FIREBASE_CONFIG.PROJECT_NUMBER: "3",
                FIREBASE_CONFIG.PROJECT_ID: "4"
            })

        # Missing API_KEY_IOS
        self.assertRaises(
            ConfigAdminError, set_smsgateway, "fb1",
            u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
            "myFB", {
                FIREBASE_CONFIG.REGISTRATION_URL: "http://test/ttype/push",
                FIREBASE_CONFIG.JSON_CONFIG: CLIENT_FILE,
                FIREBASE_CONFIG.TTL: 10,
                FIREBASE_CONFIG.APP_ID_IOS: "1",
                FIREBASE_CONFIG.PROJECT_NUMBER: "3",
                FIREBASE_CONFIG.PROJECT_ID: "4"
            })

        # Everything is fine
        fb_config[FIREBASE_CONFIG.PROJECT_ID] = "test-123456"
        r = set_smsgateway(
            "fb1",
            u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
            "myFB", fb_config)
        self.assertTrue(r > 0)

        detail = token.get_init_detail(
            params={"firebase_config": self.firebase_config_name})
        self.assertEqual(detail.get("serial"), self.serial1)
        self.assertEqual(detail.get("rollout_state"), "clientwait")
        enrollment_credential = detail.get("enrollment_credential")
        self.assertTrue("pushurl" in detail)
        self.assertFalse("otpkey" in detail)

        # Run enrollment step 2
        token.update({
            "enrollment_credential": enrollment_credential,
            "serial": self.serial1,
            "fbtoken": "firebasetoken",
            "pubkey": self.smartphone_public_key_pem_urlsafe
        })
        self.assertEqual(token.get_tokeninfo("firebase_token"),
                         "firebasetoken")
        self.assertEqual(token.get_tokeninfo("public_key_smartphone"),
                         self.smartphone_public_key_pem_urlsafe)
        self.assertTrue(
            token.get_tokeninfo("public_key_server").startswith(
                u"-----BEGIN RSA PUBLIC KEY-----\n"),
            token.get_tokeninfo("public_key_server"))
        parsed_server_pubkey = serialization.load_pem_public_key(
            to_bytes(token.get_tokeninfo("public_key_server")),
            default_backend())
        self.assertIsInstance(parsed_server_pubkey, RSAPublicKey)
        self.assertTrue(
            token.get_tokeninfo("private_key_server").startswith(
                u"-----BEGIN RSA PRIVATE KEY-----\n"),
            token.get_tokeninfo("private_key_server"))
        parsed_server_privkey = serialization.load_pem_private_key(
            to_bytes(token.get_tokeninfo("private_key_server")), None,
            default_backend())
        self.assertIsInstance(parsed_server_privkey, RSAPrivateKey)

        detail = token.get_init_detail()
        self.assertEqual(detail.get("rollout_state"), "enrolled")
        augmented_pubkey = "-----BEGIN RSA PUBLIC KEY-----\n{}\n-----END RSA PUBLIC KEY-----\n".format(
            detail.get("public_key"))
        parsed_stripped_server_pubkey = serialization.load_pem_public_key(
            to_bytes(augmented_pubkey), default_backend())
        self.assertEqual(parsed_server_pubkey.public_numbers(),
                         parsed_stripped_server_pubkey.public_numbers())
        remove_token(self.serial1)
    def test_02_api_enroll(self):
        self.authenticate()

        # Failed enrollment due to missing policy
        with self.app.test_request_context('/token/init',
                                           method='POST',
                                           data={
                                               "type": "push",
                                               "genkey": 1
                                           },
                                           headers={'Authorization': self.at}):
            res = self.app.full_dispatch_request()
            self.assertNotEqual(res.status_code, 200)
            error = res.json.get("result").get("error")
            self.assertEqual(
                error.get("message"),
                "Missing enrollment policy for push token: push_firebase_configuration"
            )
            self.assertEqual(error.get("code"), 303)

        r = set_smsgateway(
            self.firebase_config_name,
            u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
            "myFB", {
                FIREBASE_CONFIG.REGISTRATION_URL: "http://test/ttype/push",
                FIREBASE_CONFIG.TTL: 10,
                FIREBASE_CONFIG.API_KEY: "1",
                FIREBASE_CONFIG.APP_ID: "2",
                FIREBASE_CONFIG.PROJECT_NUMBER: "3",
                FIREBASE_CONFIG.PROJECT_ID: "test-123456",
                FIREBASE_CONFIG.JSON_CONFIG: FIREBASE_FILE
            })
        self.assertTrue(r > 0)
        set_policy("push1",
                   scope=SCOPE.ENROLL,
                   action="{0!s}={1!s}".format(PUSH_ACTION.FIREBASE_CONFIG,
                                               self.firebase_config_name))

        # 1st step
        with self.app.test_request_context('/token/init',
                                           method='POST',
                                           data={
                                               "type": "push",
                                               "genkey": 1
                                           },
                                           headers={'Authorization': self.at}):
            res = self.app.full_dispatch_request()
            self.assertEqual(res.status_code, 200)
            detail = res.json.get("detail")
            serial = detail.get("serial")
            self.assertEqual(detail.get("rollout_state"), "clientwait")
            self.assertTrue("pushurl" in detail)
            # check that the new URL contains the serial number
            self.assertTrue(
                "&serial=PIPU" in detail.get("pushurl").get("value"))
            self.assertTrue("appid=" in detail.get("pushurl").get("value"))
            self.assertTrue("appidios=" in detail.get("pushurl").get("value"))
            self.assertTrue("apikeyios=" in detail.get("pushurl").get("value"))
            self.assertFalse("otpkey" in detail)
            enrollment_credential = detail.get("enrollment_credential")

        # 2nd step. Failing with wrong serial number
        with self.app.test_request_context(
                '/ttype/push',
                method='POST',
                data={
                    "serial": "wrongserial",
                    "pubkey": self.smartphone_public_key_pem_urlsafe,
                    "fbtoken": "firebaseT"
                }):
            res = self.app.full_dispatch_request()
            self.assertTrue(res.status_code == 404, res)
            status = res.json.get("result").get("status")
            self.assertFalse(status)
            error = res.json.get("result").get("error")
            self.assertEqual(
                error.get("message"),
                "No token with this serial number in the rollout state 'clientwait'."
            )

        # 2nd step. Fails with missing enrollment credential
        with self.app.test_request_context(
                '/ttype/push',
                method='POST',
                data={
                    "serial": serial,
                    "pubkey": self.smartphone_public_key_pem_urlsafe,
                    "fbtoken": "firebaseT",
                    "enrollment_credential": "WRonG"
                }):
            res = self.app.full_dispatch_request()
            self.assertTrue(res.status_code == 400, res)
            status = res.json.get("result").get("status")
            self.assertFalse(status)
            error = res.json.get("result").get("error")
            self.assertEqual(
                error.get("message"),
                "ERR905: Invalid enrollment credential. You are not authorized to finalize this token."
            )

        # 2nd step: as performed by the smartphone
        with self.app.test_request_context(
                '/ttype/push',
                method='POST',
                data={
                    "enrollment_credential": enrollment_credential,
                    "serial": serial,
                    "pubkey": self.smartphone_public_key_pem_urlsafe,
                    "fbtoken": "firebaseT"
                }):
            res = self.app.full_dispatch_request()
            self.assertTrue(res.status_code == 200, res)
            detail = res.json.get("detail")
            # still the same serial number
            self.assertEqual(serial, detail.get("serial"))
            self.assertEqual(detail.get("rollout_state"), "enrolled")
            # Now the smartphone gets a public key from the server
            augmented_pubkey = "-----BEGIN RSA PUBLIC KEY-----\n{}\n-----END RSA PUBLIC KEY-----\n".format(
                detail.get("public_key"))
            parsed_server_pubkey = serialization.load_pem_public_key(
                to_bytes(augmented_pubkey), default_backend())
            self.assertIsInstance(parsed_server_pubkey, RSAPublicKey)
            pubkey = detail.get("public_key")

            # Now check, what is in the token in the database
            toks = get_tokens(serial=serial)
            self.assertEqual(len(toks), 1)
            token_obj = toks[0]
            self.assertEqual(token_obj.token.rollout_state, u"enrolled")
            self.assertTrue(token_obj.token.active)
            tokeninfo = token_obj.get_tokeninfo()
            self.assertEqual(tokeninfo.get("public_key_smartphone"),
                             self.smartphone_public_key_pem_urlsafe)
            self.assertEqual(tokeninfo.get("firebase_token"), u"firebaseT")
            self.assertEqual(
                tokeninfo.get("public_key_server").strip().strip(
                    "-BEGIN END RSA PUBLIC KEY-").strip(), pubkey)
            # The token should also contain the firebase config
            self.assertEqual(tokeninfo.get(PUSH_ACTION.FIREBASE_CONFIG),
                             self.firebase_config_name)
Beispiel #45
0
 def compare(self, key):
     bhOtpKey = binascii.unhexlify(key)
     enc_otp_key = to_bytes(encrypt(bhOtpKey, self.iv))
     return enc_otp_key == self.val
Beispiel #46
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
Beispiel #47
0
    def test_email(config,
                   recipient,
                   subject,
                   body,
                   sender=None,
                   reply_to=None,
                   mimetype="plain"):
        """
        Sends an email via the configuration.

        :param config: The email configuration
        :type config: dict
        :param recipient: The recipients of the email
        :type recipient: list
        :param subject: The subject of the email
        :type subject: basestring
        :param body: The body of the email
        :type body: basestring
        :param sender: An optional sender of the email. The SMTP database
            object has its own sender. This parameter can be used to override
            the internal sender.
        :type sender: basestring
        :param reply_to: The Reply-To parameter
        :type reply_to: basestring
        :param mimetype: The type of the email to send. Can by plain or html
        :return: True or False
        """
        if type(recipient) != list:
            recipient = [recipient]
        mail_from = sender or config['sender']
        reply_to = reply_to or mail_from
        msg = MIMEText(body.encode('utf-8'), mimetype, 'utf-8')
        msg['Subject'] = subject
        msg['From'] = mail_from
        msg['To'] = ",".join(recipient)
        msg['Date'] = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
        msg['Reply-To'] = reply_to

        mail = smtplib.SMTP(config['server'],
                            port=int(config['port']),
                            timeout=config.get('timeout', TIMEOUT))
        log.debug(u"submitting message to {0!s}".format(msg["To"]))
        log.debug("Saying EHLO to mailserver {0!s}".format(config['server']))
        r = mail.ehlo()
        log.debug("mailserver responded with {0!s}".format(r))
        # Start TLS if required
        if config.get('tls', False):
            log.debug("Trying to STARTTLS: {0!s}".format(config['tls']))
            mail.starttls()
        # Authenticate, if a username is given.
        if config.get('username', ''):
            log.debug("Doing authentication with {0!s}".format(
                config['username']))
            password = decryptPassword(config['password'])
            if password == FAILED_TO_DECRYPT_PASSWORD:
                password = config['password']
            # Under Python 2, we pass passwords as bytestrings to get CRAM-MD5 to work.
            # We add a safeguard config option to disable the conversion.
            # Under Python 3, we pass passwords as unicode.
            if six.PY2 and get_app_config_value("PI_SMTP_PASSWORD_AS_BYTES",
                                                True):
                password = to_bytes(password)
            mail.login(config['username'], password)
        r = mail.sendmail(mail_from, recipient, msg.as_string())
        log.info("Mail sent: {0!s}".format(r))
        # r is a dictionary like {"*****@*****.**": (200, 'OK')}
        # we change this to True or False
        success = True
        for one_recipient in recipient:
            res_id, res_text = r.get(one_recipient, (200, "OK"))
            if res_id != 200 and res_text != "OK":
                success = False
                log.error("Failed to send email to {0!r}: {1!r}, {2!r}".format(
                    one_recipient, res_id, res_text))
        mail.quit()
        log.debug("I am done sending your email.")
        return success
Beispiel #48
0
    def test_email(config, recipient, subject, body, sender=None,
                   reply_to=None, mimetype="plain"):
        """
        Sends an email via the configuration.

        :param config: The email configuration
        :type config: dict
        :param recipient: The recipients of the email
        :type recipient: list
        :param subject: The subject of the email
        :type subject: basestring
        :param body: The body of the email
        :type body: basestring
        :param sender: An optional sender of the email. The SMTP database
            object has its own sender. This parameter can be used to override
            the internal sender.
        :type sender: basestring
        :param reply_to: The Reply-To parameter
        :type reply_to: basestring
        :param mimetype: The type of the email to send. Can by plain or html
        :return: True or False
        """
        if type(recipient) != list:
            recipient = [recipient]
        mail_from = sender or config['sender']
        reply_to = reply_to or mail_from
        msg = MIMEText(body.encode('utf-8'), mimetype, 'utf-8')
        msg['Subject'] = subject
        msg['From'] = mail_from
        msg['To'] = ",".join(recipient)
        msg['Date'] = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
        msg['Reply-To'] = reply_to

        mail = smtplib.SMTP(config['server'], port=int(config['port']),
                            timeout=config.get('timeout', TIMEOUT))
        log.debug(u"submitting message to {0!s}".format(msg["To"]))
        log.debug("Saying EHLO to mailserver {0!s}".format(config['server']))
        r = mail.ehlo()
        log.debug("mailserver responded with {0!s}".format(r))
        # Start TLS if required
        if config.get('tls', False):
            log.debug("Trying to STARTTLS: {0!s}".format(config['tls']))
            mail.starttls()
        # Authenticate, if a username is given.
        if config.get('username', ''):
            log.debug("Doing authentication with {0!s}".format(config['username']))
            password = decryptPassword(config['password'])
            if password == FAILED_TO_DECRYPT_PASSWORD:
                password = config['password']
            # Under Python 2, we pass passwords as bytestrings to get CRAM-MD5 to work.
            # We add a safeguard config option to disable the conversion.
            # Under Python 3, we pass passwords as unicode.
            if six.PY2 and get_app_config_value("PI_SMTP_PASSWORD_AS_BYTES", True):
                password = to_bytes(password)
            mail.login(config['username'], password)
        r = mail.sendmail(mail_from, recipient, msg.as_string())
        log.info("Mail sent: {0!s}".format(r))
        # r is a dictionary like {"*****@*****.**": (200, 'OK')}
        # we change this to True or False
        success = True
        for one_recipient in recipient:
            res_id, res_text = r.get(one_recipient, (200, "OK"))
            if res_id != 200 and res_text != "OK":
                success = False
                log.error("Failed to send email to {0!r}: {1!r}, {2!r}".format(one_recipient,
                                                                  res_id,
                                                                  res_text))
        mail.quit()
        log.debug("I am done sending your email.")
        return success
Beispiel #49
0
    def api_endpoint(cls, request, g):
        """
        This provides a function which is called by the API endpoint
        /ttype/push which is defined in api/ttype.py

        The method returns
            return "json", {}

        This endpoint is used for the 2nd enrollment step of the smartphone.
        Parameters sent:
            * serial
            * fbtoken
            * pubkey

        This endpoint is also used, if the smartphone sends the signed response
        to the challenge during authentication
        Parameters sent:
            * serial
            * nonce (which is the challenge)
            * signature (which is the signed nonce)


        :param request: The Flask request
        :param g: The Flask global object g
        :return: dictionary
        """
        details = {}
        result = False
        serial = getParam(request.all_data, "serial", optional=False)

        if serial and "fbtoken" in request.all_data and "pubkey" in request.all_data:
            log.debug("Do the 2nd step of the enrollment.")
            try:
                token_obj = get_one_token(serial=serial,
                                          tokentype="push",
                                          rollout_state="clientwait")
                token_obj.update(request.all_data)
            except ResourceNotFoundError:
                raise ResourceNotFoundError("No token with this serial number in the rollout state 'clientwait'.")
            init_detail_dict = request.all_data

            details = token_obj.get_init_detail(init_detail_dict)
            result = True
        elif serial and "nonce" in request.all_data and "signature" in request.all_data:
            log.debug("Handling the authentication response from the smartphone.")
            challenge = getParam(request.all_data, "nonce")
            serial = getParam(request.all_data, "serial")
            signature = getParam(request.all_data, "signature")

            # get the token_obj for the given serial:
            token_obj = get_one_token(serial=serial, tokentype="push")
            pubkey_pem = token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)
            # The public key of the smartphone was probably sent as urlsafe:
            pubkey_pem = pubkey_pem.replace("-", "+").replace("_", "/")
            # The public key was sent without any header
            pubkey_pem = "-----BEGIN PUBLIC KEY-----\n{0!s}\n-----END PUBLIC KEY-----".format(pubkey_pem.strip().replace(" ", "+"))
            # Do the 2nd step of the authentication
            # Find valid challenges
            challengeobject_list = get_challenges(serial=serial, challenge=challenge)

            if challengeobject_list:
                # There are valid challenges, so we check this signature
                for chal in challengeobject_list:
                    # verify the signature of the nonce
                    pubkey_obj = serialization.load_pem_public_key(to_bytes(pubkey_pem), default_backend())
                    sign_data = u"{0!s}|{1!s}".format(challenge, serial)
                    try:
                        pubkey_obj.verify(b32decode(signature),
                                          sign_data.encode("utf8"),
                                          padding.PKCS1v15(),
                                          hashes.SHA256())
                        # The signature was valid
                        log.debug("Found matching challenge {0!s}.".format(chal))
                        chal.set_otp_status(True)
                        chal.save()
                        result = True
                    except InvalidSignature as e:
                        pass

        else:
            raise ParameterError("Missing parameters!")

        return "json", prepare_result(result, details=details)