def _check_timestamp_in_range(timestamp, window): """ Check if the timestamp is a valid timestamp and if it matches the time window. If the check fails a privacyIDEA error is thrown. :param timestamp: A timestamp in iso format, either with a timezone or UTC is assumed :type timestamp: str :param window: Time window in minutes. The timestamp must lie within the range of -window to +window of the current time. :type window: int """ try: ts = isoparse(timestamp) except (ValueError, TypeError) as _e: log.debug('{0!s}'.format(traceback.format_exc())) raise privacyIDEAError('Could not parse timestamp {0!s}. ' 'ISO-Format required.'.format(timestamp)) td = timedelta(minutes=window) # We don't know if the passed timestamp is timezone aware. If no # timezone is passed, we assume UTC if ts.tzinfo: now = datetime.now(utc) else: now = datetime.utcnow() if not (now - td <= ts <= now + td): raise privacyIDEAError( 'Timestamp {0!s} not in valid range.'.format(timestamp))
def test_radius(identifier, server, secret, user, password, port=1812, description="", dictionary='/etc/privacyidea/dictionary', retries=3, timeout=5): """ This tests a RADIUS server configuration by sending an access request. :param identifier: The identifier or the name of the RADIUSServer definition :type identifier: basestring :param server: The FQDN or IP address of the RADIUS server :type server: basestring :param secret: The RADIUS secret :param user: the username to send :param password: the password to send :param port: the radius port :type port: int :param description: Human readable description of the RADIUS server definition :param dictionary: The RADIUS dictionary :return: The result of the access request """ cryptedSecret = encryptPassword(secret) if len(cryptedSecret) > 255: raise privacyIDEAError(description=_("The RADIUS secret is too long"), id=2234) s = RADIUSServerDB(identifier=identifier, server=server, port=port, secret=cryptedSecret, dictionary=dictionary, retries=retries, timeout=timeout, description=description) return RADIUSServer.request(s, user, password)
def add_radius(identifier, server, secret, port=1812, description="", dictionary='/etc/privacyidea/dictionary', retries=3, timeout=5): """ This adds a RADIUS server to the RADIUSServer database table. If the "identifier" already exists, the database entry is updated. :param identifier: The identifier or the name of the RADIUSServer definition. As the identifier is unique, providing an identifier will return a list with either one or no radius server :type identifier: basestring :param server: The FQDN or IP address of the RADIUS server :type server: basestring :param secret: The RADIUS secret :param port: the radius port :type port: int :param description: Human readable description of the RADIUS server definition :param dictionary: The RADIUS dictionary :return: The Id of the database object """ cryptedSecret = encryptPassword(secret) if len(cryptedSecret) > 255: raise privacyIDEAError(description=_("The RADIUS secret is too long"), id=2234) r = RADIUSServerDB(identifier=identifier, server=server, port=port, secret=cryptedSecret, description=description, dictionary=dictionary, retries=retries, timeout=timeout).save() return r
def add_user(self, attributes=None): """ Add a new user to the LDAP directory. The user can only be created in the LDAP using a DN. So we have to construct the DN out of the given attributes. attributes are these "username", "surname", "givenname", "email", "mobile", "phone", "password" :param attributes: Attributes according to the attribute mapping :type attributes: dict :return: The new UID of the user. The UserIdResolver needs to determine the way how to create the UID. """ # TODO: We still have some utf8 issues creating users with special characters. attributes = attributes or {} dn = self.dn_template dn = dn.replace("<basedn>", self.basedn) dn = dn.replace("<username>", attributes.get("username", "")) dn = dn.replace("<givenname>", attributes.get("givenname", "")) dn = dn.replace("<surname>", attributes.get("surname", "")) try: self._bind() params = self._attributes_to_ldap_attributes(attributes) self.l.add(dn, self.object_classes, params) except Exception as e: log.error("Error accessing LDAP server: {0!r}".format(e)) log.debug("{0}".format(traceback.format_exc())) raise privacyIDEAError(e) if self.l.result.get('result') != 0: log.error("Error during adding of user {0!r}: " "{1!r}".format(dn, self.l.result.get('message'))) raise privacyIDEAError(self.l.result.get('message')) return self.getUserId(attributes.get("username"))
def create_recoverycode(user, email=None, expiration_seconds=3600, recoverycode=None, base_url=""): """ Create and send a password recovery code :param user: User for whom the password reset code should be sent :type user: User Object :param email: The optional email of the user :param recoverycode: Only used for testing purpose :return: bool """ base_url = base_url.strip("recover") base_url += "#" recoverycode = recoverycode or generate_password(size=24) hash_code = hash_with_pepper(recoverycode) # send this recoverycode # pwreset = PasswordReset(hash_code, username=user.login, realm=user.realm, expiration_seconds=expiration_seconds) pwreset.save() res = False if not user: raise UserError("User required for recovery token.") user_email = user.info.get("email") if email and email.lower() != user_email.lower(): raise UserError("The email does not match the users email.") identifier = get_from_config("recovery.identifier") if identifier: # send email r = send_email_identifier( identifier, user_email, "Your password reset", BODY.format(base_url, user.login, user.realm, recoverycode)) if not r: raise privacyIDEAError("Failed to send email. {0!s}".format(r)) else: raise ConfigAdminError("Missing configuration " "recovery.identifier.") res = True return res
def create_recoverycode(user, email=None, expiration_seconds=3600, recoverycode=None, base_url=""): """ Create and send a password recovery code :param user: User for whom the password reset code should be sent :type user: User Object :param email: The optional email of the user :param recoverycode: Only used for testing purpose :return: bool """ base_url = base_url.strip("recover") base_url += "#" recoverycode = recoverycode or generate_password(size=24) hash_code = hash_with_pepper(recoverycode) # send this recoverycode # pwreset = PasswordReset(hash_code, username=user.login, realm=user.realm, expiration_seconds=expiration_seconds) pwreset.save() res = False if not user: raise UserError("User required for recovery token.") user_email = user.info.get("email") if email and email.lower() != user_email.lower(): raise UserError("The email does not match the users email.") identifier = get_from_config("recovery.identifier") if identifier: # send email r = send_email_identifier(identifier, user_email, "Your password reset", BODY.format(base_url, user.login, user.realm, recoverycode)) if not r: raise privacyIDEAError("Failed to send email. {0!s}".format(r)) else: raise ConfigAdminError("Missing configuration " "recovery.identifier.") res = True return res
def verify_certificate(certificate, chain): """ Verify a certificate against the certificate chain, which can be of any length The certificate chain starts with the root certificate and contains further intermediate certificates :param certificate: The certificate :type certificate: PEM encoded string :param chain: A list of PEM encoded certificates :type chain: list :return: raises an exception """ # first reverse the list, since it can be popped better chain = list(reversed(chain)) if not chain: raise privacyIDEAError( "Can not verify certificate against an empty chain.") certificate = load_pem_x509_certificate(to_byte_string(certificate), default_backend()) chain = [ load_pem_x509_certificate(to_byte_string(c), default_backend()) for c in chain ] # verify chain while chain: signer = chain.pop() if chain: # There is another element in the list, so we check the intermediate: signee = chain.pop() signer.public_key().verify(signee.signature, signee.tbs_certificate_bytes, padding.PKCS1v15(), signee.signature_hash_algorithm) signer = signee # This was the last certificate in the chain, so we check the certificate signer.public_key().verify(certificate.signature, certificate.tbs_certificate_bytes, padding.PKCS1v15(), certificate.signature_hash_algorithm)
def api_endpoint(cls, request, g): """ This provides a function which is called by the API endpoint ``/ttype/push`` which is defined in :doc:`../../api/ttype` The method returns a tuple ``("json", {})`` This endpoint provides several functionalities: - It is used for the 2nd enrollment step of the smartphone. It accepts the following parameters: .. sourcecode:: http POST /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver serial=<token serial> fbtoken=<Firebase token> pubkey=<public key> - It is also used when the smartphone sends the signed response to the challenge during authentication. The following parameters are accepted: .. sourcecode:: http POST /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver serial=<token serial> nonce=<the actual challenge> signature=<the signed nonce> - In some cases the Firebase service changes the token of a device. This needs to be communicated to privacyIDEA through this endpoint (https://github.com/privacyidea/privacyidea/wiki/concept%3A-pushtoken-poll#update -firebase-token): .. sourcecode:: http POST /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver new_fb_token=<new Firebase token> serial=<token serial> timestamp=<timestamp> signature=SIGNATURE(<new_fb_token>|<serial>|<timestamp>) - And it also acts as an endpoint for polling challenges: .. sourcecode:: http GET /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver serial=<tokenserial> timestamp=<timestamp> signature=SIGNATURE(<tokenserial>|<timestamp>) More on polling can be found here: https://github.com/privacyidea/privacyidea/wiki/concept%3A-pushtoken-poll :param request: The Flask request :param g: The Flask global object g :return: The json string representing the result dictionary :rtype: tuple("json", str) """ details = {} if request.method == 'POST': result, details = cls._api_endpoint_post(request.all_data) elif request.method == 'GET': result = cls._api_endpoint_get(g, request.all_data) else: raise privacyIDEAError( 'Method {0!s} not allowed in \'api_endpoint\' ' 'for push token.'.format(request.method)) return "json", prepare_result(result, details=details)
def _api_endpoint_get(cls, g, request_data): """ Handle all GET requests to the api endpoint. Currently this is only used for polling. :param g: The Flask context :param request_data: Dictionary containing the parameters of the request :type request_data: dict :returns: Result of the polling operation, 'True' if an unanswered and matching challenge exists, 'False' otherwise. :rtype: bool """ # By default we allow polling if the policy is not set. allow_polling = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING, options={'g': g}) or PushAllowPolling.ALLOW if allow_polling == PushAllowPolling.DENY: raise PolicyError('Polling not allowed!') serial = getParam(request_data, "serial", optional=False) timestamp = getParam(request_data, 'timestamp', optional=False) signature = getParam(request_data, 'signature', optional=False) # first check if the timestamp is in the required span cls._check_timestamp_in_range(timestamp, POLL_TIME_WINDOW) # now check the signature # first get the token try: tok = get_one_token(serial=serial, tokentype=cls.get_class_type()) # If the push_allow_polling policy is set to "token" we also # need to check the POLLING_ALLOWED tokeninfo. If it evaluated # to 'False', polling is not allowed for this token. If the # tokeninfo value evaluates to 'True' or is not set at all, # polling is allowed for this token. if allow_polling == PushAllowPolling.TOKEN: if not is_true( tok.get_tokeninfo(POLLING_ALLOWED, default='True')): log.debug('Polling not allowed for pushtoken {0!s} due to ' 'tokeninfo.'.format(serial)) raise PolicyError('Polling not allowed!') pubkey_obj = _build_verify_object( tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)) sign_data = u"{serial}|{timestamp}".format(**request_data) pubkey_obj.verify(b32decode(signature), sign_data.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) # The signature was valid now check for an open challenge # we need the private server key to sign the smartphone data pem_privkey = tok.get_tokeninfo(PRIVATE_KEY_SERVER) # We need the registration URL for the challenge registration_url = get_action_values_from_options( SCOPE.ENROLL, PUSH_ACTION.REGISTRATION_URL, options={'g': g}) if not registration_url: raise ResourceNotFoundError( 'There is no registration_url defined for the ' ' pushtoken {0!s}. You need to define a push_registration_url ' 'in an enrollment policy.'.format(serial)) options = {'g': g} challenges = [] challengeobject_list = get_challenges(serial=serial) for chal in challengeobject_list: # check if the challenge is active and not already answered _cnt, answered = chal.get_otp_status() if not answered and chal.is_valid(): # then return the necessary smartphone data to answer # the challenge sp_data = _build_smartphone_data(serial, chal.challenge, registration_url, pem_privkey, options) challenges.append(sp_data) # return the challenges as a list in the result value result = challenges except (ResourceNotFoundError, ParameterError, InvalidSignature, ConfigAdminError, BinasciiError) as e: # to avoid disclosing information we always fail with an invalid # signature error even if the token with the serial could not be found log.debug('{0!s}'.format(traceback.format_exc())) log.info('The following error occurred during the signature ' 'check: "{0!r}"'.format(e)) raise privacyIDEAError('Could not verify signature!') return result
def _api_endpoint_post(cls, request_data): """ Handle all POST requests to the api endpoint :param request_data: Dictionary containing the parameters of the request :type request_data: dict :returns: The result of handling the request and a dictionary containing the details of the request handling :rtype: (bool, dict) """ details = {} result = False serial = getParam(request_data, "serial", optional=False) if all(k in request_data for k in ("fbtoken", "pubkey")): log.debug("Do the 2nd step of the enrollment.") try: token_obj = get_one_token( serial=serial, tokentype="push", rollout_state=ROLLOUTSTATE.CLIENTWAIT) token_obj.update(request_data) except ResourceNotFoundError: raise ResourceNotFoundError( "No token with this serial number " "in the rollout state 'clientwait'.") init_detail_dict = request_data details = token_obj.get_init_detail(init_detail_dict) result = True elif all(k in request_data for k in ("nonce", "signature")): log.debug( "Handling the authentication response from the smartphone.") challenge = getParam(request_data, "nonce") signature = getParam(request_data, "signature") # get the token_obj for the given serial: token_obj = get_one_token(serial=serial, tokentype="push") pubkey_obj = _build_verify_object( token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)) # 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 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 elif all(k in request_data for k in ('new_fb_token', 'timestamp', 'signature')): timestamp = getParam(request_data, 'timestamp', optional=False) signature = getParam(request_data, 'signature', optional=False) # first check if the timestamp is in the required span cls._check_timestamp_in_range(timestamp, UPDATE_FB_TOKEN_WINDOW) try: tok = get_one_token(serial=serial, tokentype=cls.get_class_type()) pubkey_obj = _build_verify_object( tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)) sign_data = u"{new_fb_token}|{serial}|{timestamp}".format( **request_data) pubkey_obj.verify(b32decode(signature), sign_data.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) # If the timestamp and signature are valid we update the token tok.add_tokeninfo('firebase_token', request_data['new_fb_token']) result = True except (ResourceNotFoundError, ParameterError, TypeError, InvalidSignature, ConfigAdminError, BinasciiError) as e: # to avoid disclosing information we always fail with an invalid # signature error even if the token with the serial could not be found log.debug('{0!s}'.format(traceback.format_exc())) log.info('The following error occurred during the signature ' 'check: "{0!r}"'.format(e)) raise privacyIDEAError('Could not verify signature!') else: raise ParameterError("Missing parameters!") return result, details
def api_endpoint(cls, request, g): """ This provides a function which is called by the API endpoint ``/ttype/push`` which is defined in :doc:`../../api/ttype` The method returns a tuple ``("json", {})`` This endpoint provides several functionalities: - It is used for the 2nd enrollment step of the smartphone. It accepts the following parameters: .. sourcecode:: http POST /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver serial=<token serial> fbtoken=<firebase token> pubkey=<public key> - It is also used when the smartphone sends the signed response to the challenge during authentication. The following parameters ar accepted: .. sourcecode:: http POST /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver serial=<token serial> nonce=<the actual challenge> signature=<the signed nonce> - And it also acts as an endpoint for polling challenges: .. sourcecode:: http GET /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver serial=<tokenserial> timestamp=<timestamp> signature=SIGNATURE(<tokenserial>|<timestamp>) More on polling can be found here: https://github.com/privacyidea/privacyidea/wiki/concept%3A-pushtoken-poll :param request: The Flask request :param g: The Flask global object g :return: The json string representing the result dictionary :rtype: tuple("json", str) """ details = {} result = False if request.method == 'POST': 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_obj = _build_verify_object( token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)) # 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 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!") elif request.method == 'GET': # This is only used for polling # By default we allow polling if the policy is not set. allow_polling = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING, options={'g': g}) or PushAllowPolling.ALLOW if allow_polling == PushAllowPolling.DENY: raise PolicyError('Polling not allowed!') serial = getParam(request.all_data, "serial", optional=False) timestamp = getParam(request.all_data, 'timestamp', optional=False) signature = getParam(request.all_data, 'signature', optional=False) # first check if the timestamp is in the required span try: ts = isoparse(timestamp) except (ValueError, TypeError) as _e: log.debug('{0!s}'.format(traceback.format_exc())) raise privacyIDEAError( 'Could not parse timestamp {0!s}. ' 'ISO-Format required.'.format(timestamp)) # TODO: make time delta configurable td = timedelta(minutes=POLL_TIME_WINDOW) # We don't know if the passed timestamp is timezone aware. If no # timezone is passed, we assume UTC if ts.tzinfo: now = datetime.now(utc) else: now = datetime.utcnow() if not (now - td <= ts <= now + td): raise privacyIDEAError( 'Timestamp {0!s} not in valid range.'.format(timestamp)) # now check the signature # first get the token try: tok = get_one_token(serial=serial, tokentype=cls.get_class_type()) # If the push_allow_polling policy is set to "token" we also # need to check the POLLING_ALLOWED tokeninfo. If it evaluated # to 'False', polling is not allowed for this token. If the # tokeninfo value evaluates to 'True' or is not set at all, # polling is allowed for this token. if allow_polling == PushAllowPolling.TOKEN: if not is_true( tok.get_tokeninfo(POLLING_ALLOWED, default='True')): log.debug( 'Polling not allowed for pushtoken {0!s} due to ' 'tokeninfo.'.format(serial)) raise PolicyError('Polling not allowed!') pubkey_obj = _build_verify_object( tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)) sign_data = u"{serial}|{timestamp}".format(**request.all_data) pubkey_obj.verify(b32decode(signature), sign_data.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) # The signature was valid now check for an open challenge # we need the private server key to sign the smartphone data pem_privkey = tok.get_tokeninfo(PRIVATE_KEY_SERVER) # we also need the FirebaseGateway for this token fb_identifier = tok.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if not fb_identifier: raise ResourceNotFoundError( 'The pushtoken {0!s} has no Firebase configuration ' 'assigned.'.format(serial)) fb_gateway = create_sms_instance(fb_identifier) options = {'g': g} challenges = [] challengeobject_list = get_challenges(serial=serial) for chal in challengeobject_list: # check if the challenge is active and not already answered _cnt, answered = chal.get_otp_status() if not answered and chal.is_valid(): # then return the necessary smartphone data to answer # the challenge sp_data = _build_smartphone_data( serial, chal.challenge, fb_gateway, pem_privkey, options) challenges.append(sp_data) # return the challenges as a list in the result value result = challenges except (ResourceNotFoundError, ParameterError, InvalidSignature, ConfigAdminError, BinasciiError) as e: # to avoid disclosing information we always fail with an invalid # signature error even if the token with the serial could not be found log.debug('{0!s}'.format(traceback.format_exc())) log.info('The following error occurred during the signature ' 'check: "{0!r}"'.format(e)) raise privacyIDEAError('Could not verify signature!') else: raise privacyIDEAError( 'Method {0!s} not allowed in \'api_endpoint\' ' 'for push token.'.format(request.method)) return "json", prepare_result(result, details=details)
def update(self, param): """ This method is called during the initialization process. :param param: parameters from the token init :type param: dict :return: None """ TokenClass.update(self, param) request = getParam(param, "request", optional) spkac = getParam(param, "spkac", optional) certificate = getParam(param, "certificate", optional) generate = getParam(param, "genkey", optional) template_name = getParam(param, "template", optional) if request or generate: # If we do not upload a user certificate, then we need a CA do # sign the uploaded request or generated certificate. ca = getParam(param, "ca", required) self.add_tokeninfo("CA", ca) cacon = get_caconnector_object(ca) if request: if not spkac: # We only do the whole attestation checking in case we have no SPKAC request_csr = load_pem_x509_csr(to_byte_string(request), default_backend()) if not request_csr.is_signature_valid: raise privacyIDEAError("request has invalid signature.") # If a request is sent, we can have an attestation certificate attestation = getParam(param, "attestation", optional) verify_attestation = getParam(param, "verify_attestation", optional) if attestation: request_numbers = request_csr.public_key().public_numbers() attestation_cert = load_pem_x509_certificate( to_byte_string(attestation), default_backend()) attestation_numbers = attestation_cert.public_key( ).public_numbers() if request_numbers != attestation_numbers: log.warning( "certificate request does not match attestation certificate." ) raise privacyIDEAError( "certificate request does not match attestation certificate." ) try: verified = verify_certificate_path( attestation, param.get(ACTION.TRUSTED_CA_PATH)) except Exception as exx: # We could have file system errors during verification. log.debug("{0!s}".format(traceback.format_exc())) verified = False if not verified: log.warning( "Failed to verify certificate chain of attestation certificate." ) if verify_attestation: raise privacyIDEAError( "Failed to verify certificate chain of attestation certificate." ) # During the initialization process, we need to create the # certificate x509object = cacon.sign_request(request, options={ "spkac": spkac, "template": template_name }) certificate = crypto.dump_certificate(crypto.FILETYPE_PEM, x509object) elif generate: # Create the certificate on behalf of another user. # Now we need to create the key pair, # the request # and the certificate # We need the user for whom the certificate should be created user = get_user_from_param(param, optionalOrRequired=required) keysize = getParam(param, "keysize", optional, 2048) key = crypto.PKey() key.generate_key(crypto.TYPE_RSA, keysize) req = crypto.X509Req() req.get_subject().CN = user.login # Add email to subject if user.info.get("email"): req.get_subject().emailAddress = user.info.get("email") req.get_subject().organizationalUnitName = user.realm # TODO: Add Country, Organization, Email # req.get_subject().countryName = 'xxx' # req.get_subject().stateOrProvinceName = 'xxx' # req.get_subject().localityName = 'xxx' # req.get_subject().organizationName = 'xxx' req.set_pubkey(key) req.sign(key, "sha256") csr = to_unicode( crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)) x509object = cacon.sign_request( csr, options={"template": template_name}) certificate = crypto.dump_certificate(crypto.FILETYPE_PEM, x509object) # Save the private key to the encrypted key field of the token s = crypto.dump_privatekey(crypto.FILETYPE_PEM, key) self.add_tokeninfo("privatekey", s, value_type="password") if "pin" in param: self.set_pin(param.get("pin"), encrypt=True) if certificate: self.add_tokeninfo("certificate", certificate)