prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Support other keytypes with SSHKey Token
  We would like to import other public key types like ecdsa into the SSHKey token type.
  
  * [x] The UI and the /token/init API needs to allow this.
  * [x] We also need to check, if the keytype is saved and returned correctly by GET /authitem
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `privacyidea/lib/tokens/sshkeytoken.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 #
  3 #  privacyIDEA
  4 #  Jul 18, 2014 Cornelius Kölbel
  5 #  License:  AGPLv3
  6 #  contact:  http://www.privacyidea.org
  7 #
  8 # This code is free software; you can redistribute it and/or
  9 # modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10 # License as published by the Free Software Foundation; either
  11 # version 3 of the License, or any later version.
  12 #
  13 # This code is distributed in the hope that it will be useful,
  14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 # GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17 #
  18 # You should have received a copy of the GNU Affero General Public
  19 # License along with this program.  If not, see <http://www.gnu.org/licenses/>.
  20 #
  21 __doc__="""The SSHKeyTokenClass provides a TokenClass that stores the public
  22 SSH key and can give the public SSH key via the getotp function.
  23 This can be used to manage SSH keys and retrieve the public ssh key
  24 to import it to authorized keys files.
  25 
  26 The code is tested in tests/test_lib_tokens_ssh
  27 """
  28 
  29 import logging
  30 from privacyidea.lib import _
  31 from privacyidea.api.lib.utils import getParam
  32 from privacyidea.lib.log import log_with
  33 from privacyidea.lib.tokenclass import TokenClass
  34 from privacyidea.lib.policy import SCOPE, ACTION, GROUP
  35 
  36 log = logging.getLogger(__name__)
  37 
  38 
  39 optional = True
  40 required = False
  41 
  42 
  43 ##TODO: We should save a fingerprint of the SSH Key in the encrypted OTP
  44 # field, so that we can be sure, that the public ssh key was not changed in
  45 # the database!
  46 
  47 
  48 class SSHkeyTokenClass(TokenClass):
  49     """
  50     The SSHKeyTokenClass provides a TokenClass that stores the public
  51     SSH key and can give the public SSH key via the getotp function.
  52     This can be used to manage SSH keys and retrieve the public ssh key
  53     to import it to authorized keys files.
  54     """
  55     mode = ['authenticate']
  56     using_pin = False
  57 
  58     def __init__(self, db_token):
  59         TokenClass.__init__(self, db_token)
  60         self.set_type(u"sshkey")
  61 
  62     @staticmethod
  63     def get_class_type():
  64         return "sshkey"
  65 
  66     @staticmethod
  67     def get_class_prefix():
  68         return "SSHK"
  69 
  70     @staticmethod
  71     @log_with(log)
  72     def get_class_info(key=None, ret='all'):
  73         """
  74         returns a subtree of the token definition
  75 
  76         :param key: subsection identifier
  77         :type key: string
  78         :param ret: default return value, if nothing is found
  79         :type ret: user defined
  80         :return: subsection if key exists or user defined
  81         :rtype: dictionary
  82         """
  83         res = {'type': 'sshkey',
  84                'title': 'SSHkey Token',
  85                'description': _('SSH Public Key: The public SSH key.'),
  86                'config': {},
  87                'user': ['enroll'],
  88                # This tokentype is enrollable in the UI for...
  89                'ui_enroll': ["admin", "user"],
  90                'policy': {
  91                    SCOPE.ENROLL: {
  92                        ACTION.MAXTOKENUSER: {
  93                            'type': 'int',
  94                            'desc': _("The user may only have this maximum number of SSH keys assigned."),
  95                            'group': GROUP.TOKEN
  96                        },
  97                        ACTION.MAXACTIVETOKENUSER: {
  98                            'type': 'int',
  99                            'desc': _(
  100                                "The user may only have this maximum number of active SSH keys assigned."),
  101                            'group': GROUP.TOKEN
  102                        }
  103                    }
  104                },
  105                }
  106         if key:
  107             ret = res.get(key, {})
  108         else:
  109             if ret == 'all':
  110                 ret = res
  111 
  112         return ret
  113 
  114     def update(self, param):
  115         """
  116         The key holds the public ssh key and this is required
  117         
  118         The key probably is of the form "ssh-rsa BASE64 comment"
  119         """
  120         # We need to save the token, so that we can later add the tokeninfo
  121         # Otherwise we might not have created the DB entry, yet and we would
  122         # be missing the token.id
  123         self.token.save()
  124 
  125         getParam(param, "sshkey", required)
  126             
  127         key_elem = param.get("sshkey").split(" ", 2)
  128         if len(key_elem) != 3 or key_elem[0] != "ssh-rsa":
  129             raise Exception("The key must consist of 'ssh-rsa BASE64 comment'")
  130 
  131         key_type = key_elem[0]
  132         key = key_elem[1]
  133         key_comment = key_elem[2]
  134         
  135         # convert key to hex
  136         self.add_tokeninfo("ssh_key", key, value_type="password")
  137         self.add_tokeninfo("ssh_type", key_type)
  138         self.add_tokeninfo("ssh_comment", key_comment)
  139 
  140         # call the parents function
  141         TokenClass.update(self, param)
  142         
  143     @log_with(log)
  144     def get_sshkey(self):
  145         """
  146         returns the public SSH key
  147         
  148         :return: SSH pub key
  149         :rtype: string
  150         """
  151         ti = self.get_tokeninfo()
  152         key_type = ti.get("ssh_type")
  153         key_comment = ti.get("ssh_comment")
  154         # get the ssh key directly, otherwise it will not be decrypted
  155         sshkey = self.get_tokeninfo("ssh_key")
  156         return u"{0!s} {1!s} {2!s}".format(key_type, sshkey, key_comment)
  157
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/privacyidea/lib/tokens/sshkeytoken.py b/privacyidea/lib/tokens/sshkeytoken.py
  --- a/privacyidea/lib/tokens/sshkeytoken.py
  +++ b/privacyidea/lib/tokens/sshkeytoken.py
  @@ -125,8 +125,10 @@
           getParam(param, "sshkey", required)
               
           key_elem = param.get("sshkey").split(" ", 2)
  -        if len(key_elem) != 3 or key_elem[0] != "ssh-rsa":
  -            raise Exception("The key must consist of 'ssh-rsa BASE64 comment'")
  +        if len(key_elem) != 3:
  +            raise Exception("The key must consist of 'ssh-keytype BASE64 comment'")
  +        if key_elem[0] not in ["ssh-rsa", "ssh-ed25519", "ecdsa-sha2-nistp256"]:
  +            raise Exception("The keytype you specified is not supported.")
   
           key_type = key_elem[0]
           key = key_elem[1]
  
verification_info: {"golden_diff": "diff --git a/privacyidea/lib/tokens/sshkeytoken.py b/privacyidea/lib/tokens/sshkeytoken.py\n--- a/privacyidea/lib/tokens/sshkeytoken.py\n+++ b/privacyidea/lib/tokens/sshkeytoken.py\n@@ -125,8 +125,10 @@\n         getParam(param, \"sshkey\", required)\n             \n         key_elem = param.get(\"sshkey\").split(\" \", 2)\n-        if len(key_elem) != 3 or key_elem[0] != \"ssh-rsa\":\n-            raise Exception(\"The key must consist of 'ssh-rsa BASE64 comment'\")\n+        if len(key_elem) != 3:\n+            raise Exception(\"The key must consist of 'ssh-keytype BASE64 comment'\")\n+        if key_elem[0] not in [\"ssh-rsa\", \"ssh-ed25519\", \"ecdsa-sha2-nistp256\"]:\n+            raise Exception(\"The keytype you specified is not supported.\")\n \n         key_type = key_elem[0]\n         key = key_elem[1]\n", "issue": "Support other keytypes with SSHKey Token\nWe would like to import other public key types like ecdsa into the SSHKey token type.\r\n\r\n* [x] The UI and the /token/init API needs to allow this.\r\n* [x] We also need to check, if the keytype is saved and returned correctly by GET /authitem\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n#  privacyIDEA\n#  Jul 18, 2014 Cornelius K\u00f6lbel\n#  License:  AGPLv3\n#  contact:  http://www.privacyidea.org\n#\n# This code is free software; you can redistribute it and/or\n# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE\n# License as published by the Free Software Foundation; either\n# version 3 of the License, or any later version.\n#\n# This code is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU AFFERO GENERAL PUBLIC LICENSE for more details.\n#\n# You should have received a copy of the GNU Affero General Public\n# License along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n__doc__=\"\"\"The SSHKeyTokenClass provides a TokenClass that stores the public\nSSH key and can give the public SSH key via the getotp function.\nThis can be used to manage SSH keys and retrieve the public ssh key\nto import it to authorized keys files.\n\nThe code is tested in tests/test_lib_tokens_ssh\n\"\"\"\n\nimport logging\nfrom privacyidea.lib import _\nfrom privacyidea.api.lib.utils import getParam\nfrom privacyidea.lib.log import log_with\nfrom privacyidea.lib.tokenclass import TokenClass\nfrom privacyidea.lib.policy import SCOPE, ACTION, GROUP\n\nlog = logging.getLogger(__name__)\n\n\noptional = True\nrequired = False\n\n\n##TODO: We should save a fingerprint of the SSH Key in the encrypted OTP\n# field, so that we can be sure, that the public ssh key was not changed in\n# the database!\n\n\nclass SSHkeyTokenClass(TokenClass):\n    \"\"\"\n    The SSHKeyTokenClass provides a TokenClass that stores the public\n    SSH key and can give the public SSH key via the getotp function.\n    This can be used to manage SSH keys and retrieve the public ssh key\n    to import it to authorized keys files.\n    \"\"\"\n    mode = ['authenticate']\n    using_pin = False\n\n    def __init__(self, db_token):\n        TokenClass.__init__(self, db_token)\n        self.set_type(u\"sshkey\")\n\n    @staticmethod\n    def get_class_type():\n        return \"sshkey\"\n\n    @staticmethod\n    def get_class_prefix():\n        return \"SSHK\"\n\n    @staticmethod\n    @log_with(log)\n    def get_class_info(key=None, ret='all'):\n        \"\"\"\n        returns a subtree of the token definition\n\n        :param key: subsection identifier\n        :type key: string\n        :param ret: default return value, if nothing is found\n        :type ret: user defined\n        :return: subsection if key exists or user defined\n        :rtype: dictionary\n        \"\"\"\n        res = {'type': 'sshkey',\n               'title': 'SSHkey Token',\n               'description': _('SSH Public Key: The public SSH key.'),\n               'config': {},\n               'user': ['enroll'],\n               # This tokentype is enrollable in the UI for...\n               'ui_enroll': [\"admin\", \"user\"],\n               'policy': {\n                   SCOPE.ENROLL: {\n                       ACTION.MAXTOKENUSER: {\n                           'type': 'int',\n                           'desc': _(\"The user may only have this maximum number of SSH keys assigned.\"),\n                           'group': GROUP.TOKEN\n                       },\n                       ACTION.MAXACTIVETOKENUSER: {\n                           'type': 'int',\n                           'desc': _(\n                               \"The user may only have this maximum number of active SSH keys assigned.\"),\n                           'group': GROUP.TOKEN\n                       }\n                   }\n               },\n               }\n        if key:\n            ret = res.get(key, {})\n        else:\n            if ret == 'all':\n                ret = res\n\n        return ret\n\n    def update(self, param):\n        \"\"\"\n        The key holds the public ssh key and this is required\n        \n        The key probably is of the form \"ssh-rsa BASE64 comment\"\n        \"\"\"\n        # We need to save the token, so that we can later add the tokeninfo\n        # Otherwise we might not have created the DB entry, yet and we would\n        # be missing the token.id\n        self.token.save()\n\n        getParam(param, \"sshkey\", required)\n            \n        key_elem = param.get(\"sshkey\").split(\" \", 2)\n        if len(key_elem) != 3 or key_elem[0] != \"ssh-rsa\":\n            raise Exception(\"The key must consist of 'ssh-rsa BASE64 comment'\")\n\n        key_type = key_elem[0]\n        key = key_elem[1]\n        key_comment = key_elem[2]\n        \n        # convert key to hex\n        self.add_tokeninfo(\"ssh_key\", key, value_type=\"password\")\n        self.add_tokeninfo(\"ssh_type\", key_type)\n        self.add_tokeninfo(\"ssh_comment\", key_comment)\n\n        # call the parents function\n        TokenClass.update(self, param)\n        \n    @log_with(log)\n    def get_sshkey(self):\n        \"\"\"\n        returns the public SSH key\n        \n        :return: SSH pub key\n        :rtype: string\n        \"\"\"\n        ti = self.get_tokeninfo()\n        key_type = ti.get(\"ssh_type\")\n        key_comment = ti.get(\"ssh_comment\")\n        # get the ssh key directly, otherwise it will not be decrypted\n        sshkey = self.get_tokeninfo(\"ssh_key\")\n        return u\"{0!s} {1!s} {2!s}\".format(key_type, sshkey, key_comment)\n", "path": "privacyidea/lib/tokens/sshkeytoken.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n#  privacyIDEA\n#  Jul 18, 2014 Cornelius K\u00f6lbel\n#  License:  AGPLv3\n#  contact:  http://www.privacyidea.org\n#\n# This code is free software; you can redistribute it and/or\n# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE\n# License as published by the Free Software Foundation; either\n# version 3 of the License, or any later version.\n#\n# This code is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU AFFERO GENERAL PUBLIC LICENSE for more details.\n#\n# You should have received a copy of the GNU Affero General Public\n# License along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n__doc__=\"\"\"The SSHKeyTokenClass provides a TokenClass that stores the public\nSSH key and can give the public SSH key via the getotp function.\nThis can be used to manage SSH keys and retrieve the public ssh key\nto import it to authorized keys files.\n\nThe code is tested in tests/test_lib_tokens_ssh\n\"\"\"\n\nimport logging\nfrom privacyidea.lib import _\nfrom privacyidea.api.lib.utils import getParam\nfrom privacyidea.lib.log import log_with\nfrom privacyidea.lib.tokenclass import TokenClass\nfrom privacyidea.lib.policy import SCOPE, ACTION, GROUP\n\nlog = logging.getLogger(__name__)\n\n\noptional = True\nrequired = False\n\n\n##TODO: We should save a fingerprint of the SSH Key in the encrypted OTP\n# field, so that we can be sure, that the public ssh key was not changed in\n# the database!\n\n\nclass SSHkeyTokenClass(TokenClass):\n    \"\"\"\n    The SSHKeyTokenClass provides a TokenClass that stores the public\n    SSH key and can give the public SSH key via the getotp function.\n    This can be used to manage SSH keys and retrieve the public ssh key\n    to import it to authorized keys files.\n    \"\"\"\n    mode = ['authenticate']\n    using_pin = False\n\n    def __init__(self, db_token):\n        TokenClass.__init__(self, db_token)\n        self.set_type(u\"sshkey\")\n\n    @staticmethod\n    def get_class_type():\n        return \"sshkey\"\n\n    @staticmethod\n    def get_class_prefix():\n        return \"SSHK\"\n\n    @staticmethod\n    @log_with(log)\n    def get_class_info(key=None, ret='all'):\n        \"\"\"\n        returns a subtree of the token definition\n\n        :param key: subsection identifier\n        :type key: string\n        :param ret: default return value, if nothing is found\n        :type ret: user defined\n        :return: subsection if key exists or user defined\n        :rtype: dictionary\n        \"\"\"\n        res = {'type': 'sshkey',\n               'title': 'SSHkey Token',\n               'description': _('SSH Public Key: The public SSH key.'),\n               'config': {},\n               'user': ['enroll'],\n               # This tokentype is enrollable in the UI for...\n               'ui_enroll': [\"admin\", \"user\"],\n               'policy': {\n                   SCOPE.ENROLL: {\n                       ACTION.MAXTOKENUSER: {\n                           'type': 'int',\n                           'desc': _(\"The user may only have this maximum number of SSH keys assigned.\"),\n                           'group': GROUP.TOKEN\n                       },\n                       ACTION.MAXACTIVETOKENUSER: {\n                           'type': 'int',\n                           'desc': _(\n                               \"The user may only have this maximum number of active SSH keys assigned.\"),\n                           'group': GROUP.TOKEN\n                       }\n                   }\n               },\n               }\n        if key:\n            ret = res.get(key, {})\n        else:\n            if ret == 'all':\n                ret = res\n\n        return ret\n\n    def update(self, param):\n        \"\"\"\n        The key holds the public ssh key and this is required\n        \n        The key probably is of the form \"ssh-rsa BASE64 comment\"\n        \"\"\"\n        # We need to save the token, so that we can later add the tokeninfo\n        # Otherwise we might not have created the DB entry, yet and we would\n        # be missing the token.id\n        self.token.save()\n\n        getParam(param, \"sshkey\", required)\n            \n        key_elem = param.get(\"sshkey\").split(\" \", 2)\n        if len(key_elem) != 3:\n            raise Exception(\"The key must consist of 'ssh-keytype BASE64 comment'\")\n        if key_elem[0] not in [\"ssh-rsa\", \"ssh-ed25519\", \"ecdsa-sha2-nistp256\"]:\n            raise Exception(\"The keytype you specified is not supported.\")\n\n        key_type = key_elem[0]\n        key = key_elem[1]\n        key_comment = key_elem[2]\n        \n        # convert key to hex\n        self.add_tokeninfo(\"ssh_key\", key, value_type=\"password\")\n        self.add_tokeninfo(\"ssh_type\", key_type)\n        self.add_tokeninfo(\"ssh_comment\", key_comment)\n\n        # call the parents function\n        TokenClass.update(self, param)\n        \n    @log_with(log)\n    def get_sshkey(self):\n        \"\"\"\n        returns the public SSH key\n        \n        :return: SSH pub key\n        :rtype: string\n        \"\"\"\n        ti = self.get_tokeninfo()\n        key_type = ti.get(\"ssh_type\")\n        key_comment = ti.get(\"ssh_comment\")\n        # get the ssh key directly, otherwise it will not be decrypted\n        sshkey = self.get_tokeninfo(\"ssh_key\")\n        return u\"{0!s} {1!s} {2!s}\".format(key_type, sshkey, key_comment)\n", "path": "privacyidea/lib/tokens/sshkeytoken.py"}]}