Merge lp:~cjwatson/launchpad/ssh-ecdsa-keys into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18721
Proposed branch: lp:~cjwatson/launchpad/ssh-ecdsa-keys
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/lazr.sshserver-0.1.8
Diff against target: 449 lines (+152/-56)
7 files modified
lib/lp/registry/interfaces/ssh.py (+20/-10)
lib/lp/registry/model/person.py (+26/-7)
lib/lp/registry/scripts/listteammembers.py (+3/-8)
lib/lp/registry/stories/person/xx-add-sshkey.txt (+32/-5)
lib/lp/registry/templates/person-editsshkeys.pt (+2/-1)
lib/lp/registry/tests/test_ssh.py (+32/-4)
lib/lp/testing/factory.py (+37/-21)
To merge this branch: bzr merge lp:~cjwatson/launchpad/ssh-ecdsa-keys
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+348848@code.launchpad.net

Commit message

Add support for SSH ECDSA keys.

Description of the change

The main difficulty is that there are multiple public key algorithm names for ECDSA, so we need to stop having data structures laid out on the assumption that there's a bijection between public key algorithm names and SSHKeyType enumeration items. (The alternative was to have one SSHKeyType item for each curve size, but that seemed a bit silly.)

The prerequisite branch and the corresponding branches of txpkgupload and turnip must all be deployed to production before this is landed, to avoid the situation where people can upload keys they can't use.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/registry/interfaces/ssh.py'
--- lib/lp/registry/interfaces/ssh.py 2018-06-25 15:14:38 +0000
+++ lib/lp/registry/interfaces/ssh.py 2018-07-02 14:38:50 +0000
@@ -8,7 +8,6 @@
8__all__ = [8__all__ = [
9 'ISSHKey',9 'ISSHKey',
10 'ISSHKeySet',10 'ISSHKeySet',
11 'SSH_KEY_TYPE_TO_TEXT',
12 'SSH_TEXT_TO_KEY_TYPE',11 'SSH_TEXT_TO_KEY_TYPE',
13 'SSHKeyAdditionError',12 'SSHKeyAdditionError',
14 'SSHKeyType',13 'SSHKeyType',
@@ -39,7 +38,7 @@
39class SSHKeyType(DBEnumeratedType):38class SSHKeyType(DBEnumeratedType):
40 """SSH key type39 """SSH key type
4140
42 SSH (version 2) can use RSA or DSA keys for authentication. See41 SSH (version 2) can use RSA, DSA, or ECDSA keys for authentication. See
43 OpenSSH's ssh-keygen(1) man page for details.42 OpenSSH's ssh-keygen(1) man page for details.
44 """43 """
4544
@@ -55,14 +54,20 @@
55 DSA54 DSA
56 """)55 """)
5756
5857 ECDSA = DBItem(3, """
59SSH_KEY_TYPE_TO_TEXT = {58 ECDSA
60 SSHKeyType.RSA: "ssh-rsa",59
61 SSHKeyType.DSA: "ssh-dss",60 ECDSA
62}61 """)
6362
6463
65SSH_TEXT_TO_KEY_TYPE = {v: k for k, v in SSH_KEY_TYPE_TO_TEXT.items()}64SSH_TEXT_TO_KEY_TYPE = {
65 "ssh-rsa": SSHKeyType.RSA,
66 "ssh-dss": SSHKeyType.DSA,
67 "ecdsa-sha2-nistp256": SSHKeyType.ECDSA,
68 "ecdsa-sha2-nistp384": SSHKeyType.ECDSA,
69 "ecdsa-sha2-nistp521": SSHKeyType.ECDSA,
70 }
6671
6772
68class ISSHKey(Interface):73class ISSHKey(Interface):
@@ -139,6 +144,11 @@
139 if 'kind' in kwargs:144 if 'kind' in kwargs:
140 kind = kwargs.pop('kind')145 kind = kwargs.pop('kind')
141 msg = "Invalid SSH key type: '%s'" % kind146 msg = "Invalid SSH key type: '%s'" % kind
147 if 'type_mismatch' in kwargs:
148 keytype, keydatatype = kwargs.pop('type_mismatch')
149 msg = (
150 "Invalid SSH key data: key type '%s' does not match key data "
151 "type '%s'" % (keytype, keydatatype))
142 if 'exception' in kwargs:152 if 'exception' in kwargs:
143 exception = kwargs.pop('exception')153 exception = kwargs.pop('exception')
144 try:154 try:
145155
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2018-06-19 14:46:05 +0000
+++ lib/lp/registry/model/person.py 2018-07-02 14:38:50 +0000
@@ -29,6 +29,7 @@
29 'WikiNameSet',29 'WikiNameSet',
30 ]30 ]
3131
32import base64
32from datetime import (33from datetime import (
33 datetime,34 datetime,
34 timedelta,35 timedelta,
@@ -81,6 +82,7 @@
81 Store,82 Store,
82 )83 )
83import transaction84import transaction
85from twisted.conch.ssh.common import getNS
84from twisted.conch.ssh.keys import Key86from twisted.conch.ssh.keys import Key
85from zope.component import (87from zope.component import (
86 adapter,88 adapter,
@@ -205,7 +207,6 @@
205from lp.registry.interfaces.ssh import (207from lp.registry.interfaces.ssh import (
206 ISSHKey,208 ISSHKey,
207 ISSHKeySet,209 ISSHKeySet,
208 SSH_KEY_TYPE_TO_TEXT,
209 SSH_TEXT_TO_KEY_TYPE,210 SSH_TEXT_TO_KEY_TYPE,
210 SSHKeyAdditionError,211 SSHKeyAdditionError,
211 SSHKeyType,212 SSHKeyType,
@@ -4122,8 +4123,23 @@
4122 super(SSHKey, self).destroySelf()4123 super(SSHKey, self).destroySelf()
41234124
4124 def getFullKeyText(self):4125 def getFullKeyText(self):
4125 return "%s %s %s" % (4126 try:
4126 SSH_KEY_TYPE_TO_TEXT[self.keytype], self.keytext, self.comment)4127 ssh_keytype = getNS(base64.b64decode(self.keytext))[0]
4128 except Exception as e:
4129 # We didn't always validate keys, so there might be some that
4130 # can't be loaded this way.
4131 if self.keytype == SSHKeyType.RSA:
4132 ssh_keytype = "ssh-rsa"
4133 elif self.keytype == SSHKeyType.DSA:
4134 ssh_keytype = "ssh-dss"
4135 else:
4136 # There's no single ssh_keytype for ECDSA keys (it depends
4137 # on the curve), and we've always validated these so this
4138 # shouldn't happen.
4139 raise ValueError(
4140 "SSH key of type %s has invalid data '%s'" %
4141 (self.keytype, self.keytext))
4142 return "%s %s %s" % (ssh_keytype, self.keytext, self.comment)
41274143
41284144
4129@implementer(ISSHKeySet)4145@implementer(ISSHKeySet)
@@ -4131,13 +4147,16 @@
41314147
4132 def new(self, person, sshkey, check_key=True, send_notification=True,4148 def new(self, person, sshkey, check_key=True, send_notification=True,
4133 dry_run=False):4149 dry_run=False):
4134 keytype, keytext, comment = self._extract_ssh_key_components(sshkey)4150 kind, keytype, keytext, comment = self._extract_ssh_key_components(
4151 sshkey)
41354152
4136 if check_key:4153 if check_key:
4137 try:4154 try:
4138 Key.fromString(sshkey)4155 key = Key.fromString(sshkey)
4139 except Exception as e:4156 except Exception as e:
4140 raise SSHKeyAdditionError(key=sshkey, exception=e)4157 raise SSHKeyAdditionError(key=sshkey, exception=e)
4158 if kind != key.sshType():
4159 raise SSHKeyAdditionError(type_mismatch=(kind, key.sshType()))
41414160
4142 if send_notification:4161 if send_notification:
4143 person.security_field_changed(4162 person.security_field_changed(
@@ -4161,7 +4180,7 @@
4161 """ % sqlvalues([person.id for person in people]))4180 """ % sqlvalues([person.id for person in people]))
41624181
4163 def getByPersonAndKeyText(self, person, sshkey):4182 def getByPersonAndKeyText(self, person, sshkey):
4164 keytype, keytext, comment = self._extract_ssh_key_components(sshkey)4183 _, keytype, keytext, comment = self._extract_ssh_key_components(sshkey)
4165 return IStore(SSHKey).find(4184 return IStore(SSHKey).find(
4166 SSHKey,4185 SSHKey,
4167 person=person, keytype=keytype, keytext=keytext, comment=comment)4186 person=person, keytype=keytype, keytext=keytext, comment=comment)
@@ -4178,7 +4197,7 @@
4178 keytype = SSH_TEXT_TO_KEY_TYPE.get(kind)4197 keytype = SSH_TEXT_TO_KEY_TYPE.get(kind)
4179 if keytype is None:4198 if keytype is None:
4180 raise SSHKeyAdditionError(kind=kind)4199 raise SSHKeyAdditionError(kind=kind)
4181 return keytype, keytext, comment4200 return kind, keytype, keytext, comment
41824201
41834202
4184@implementer(IWikiName)4203@implementer(IWikiName)
41854204
=== modified file 'lib/lp/registry/scripts/listteammembers.py'
--- lib/lp/registry/scripts/listteammembers.py 2016-05-23 03:17:16 +0000
+++ lib/lp/registry/scripts/listteammembers.py 2018-07-02 14:38:50 +0000
@@ -11,7 +11,6 @@
11from zope.component import getUtility11from zope.component import getUtility
1212
13from lp.registry.interfaces.person import IPersonSet13from lp.registry.interfaces.person import IPersonSet
14from lp.registry.interfaces.ssh import SSH_KEY_TYPE_TO_TEXT
1514
1615
17OUTPUT_TEMPLATES = {16OUTPUT_TEMPLATES = {
@@ -30,11 +29,8 @@
30bad_ssh_pattern = re.compile('[\r\n\f]')29bad_ssh_pattern = re.compile('[\r\n\f]')
3130
3231
33def make_sshkey_params(member, type_name, key):32def make_sshkey_params(member, key):
34 sshkey = "%s %s %s" % (33 sshkey = bad_ssh_pattern.sub('', key.getFullKeyText()).strip()
35 type_name,
36 bad_ssh_pattern.sub('', key.keytext),
37 bad_ssh_pattern.sub('', key.comment).strip())
38 return dict(name=member.name, sshkey=sshkey)34 return dict(name=member.name, sshkey=sshkey)
3935
4036
@@ -62,8 +58,7 @@
62 sshkey = '--none--'58 sshkey = '--none--'
63 if display_option == 'sshkeys':59 if display_option == 'sshkeys':
64 for key in member.sshkeys:60 for key in member.sshkeys:
65 type_name = SSH_KEY_TYPE_TO_TEXT[key.keytype]61 params = make_sshkey_params(member, key)
66 params = make_sshkey_params(member, type_name, key)
67 output.append(template % params)62 output.append(template % params)
68 # Ubuntite63 # Ubuntite
69 ubuntite = "no"64 ubuntite = "no"
7065
=== modified file 'lib/lp/registry/stories/person/xx-add-sshkey.txt'
--- lib/lp/registry/stories/person/xx-add-sshkey.txt 2017-01-11 18:45:55 +0000
+++ lib/lp/registry/stories/person/xx-add-sshkey.txt 2018-07-02 14:38:50 +0000
@@ -57,8 +57,9 @@
57 Change your SSH keys...57 Change your SSH keys...
5858
59Any key must be of the form "keytype keytext comment", where keytype must be59Any key must be of the form "keytype keytext comment", where keytype must be
60either ssh-rsa or ssh-dss. If the key doesn't match the expected format, an60one of ssh-rsa, ssh-dss, ecdsa-sha2-nistp256, ecdsa-sha2-nistp284, or
61error message will be shown.61ecdsa-sha2-nistp521. If the key doesn't match the expected format, an error
62message will be shown.
6263
63 >>> sshkey = "ssh-rsa "64 >>> sshkey = "ssh-rsa "
64 >>> browser.getControl(name='sshkey').value = sshkey65 >>> browser.getControl(name='sshkey').value = sshkey
@@ -82,7 +83,7 @@
82 Invalid public key83 Invalid public key
8384
8485
85Now, Salgado will upload one RSA and one DSA keys, matching the expected86Now, Salgado will upload one of each type of key, matching the expected
86format.87format.
8788
88 >>> sshkey = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6VVQrIoBhxSB7duD69PRzYfdJz3QNUky5lSOpl6a9hEP9iAU72RK3fr4uaYiEEjr70EDAROCimi/rtkBuWCRmPJbQDpzBoZ7PDW/jF5tWAuC4+5z/fy05HOhHRH8WGzeEuWn5HBflcx1QasMD95oDiiEuQbF/kGxBM5/no/4FeJU3fgc+1XQNH7tMDQIcaqoHarc2kefGC8/sbRwbzajhg9yeqskgs6o6y+7931/bcZSLZ/wU53m5nB7eVkkVihk7KD+sf9jKG91LnaRW1IjBgo8AAbXl+e556XkwIwVoieKNYW2Fvw8ybcW5rCTvJ1e/3Cvo2hw8ZsDMRofSifiKw== salgado@canario"89 >>> sshkey = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6VVQrIoBhxSB7duD69PRzYfdJz3QNUky5lSOpl6a9hEP9iAU72RK3fr4uaYiEEjr70EDAROCimi/rtkBuWCRmPJbQDpzBoZ7PDW/jF5tWAuC4+5z/fy05HOhHRH8WGzeEuWn5HBflcx1QasMD95oDiiEuQbF/kGxBM5/no/4FeJU3fgc+1XQNH7tMDQIcaqoHarc2kefGC8/sbRwbzajhg9yeqskgs6o6y+7931/bcZSLZ/wU53m5nB7eVkkVihk7KD+sf9jKG91LnaRW1IjBgo8AAbXl+e556XkwIwVoieKNYW2Fvw8ybcW5rCTvJ1e/3Cvo2hw8ZsDMRofSifiKw== salgado@canario"
@@ -101,6 +102,30 @@
101 ... print tag.renderContents()102 ... print tag.renderContents()
102 SSH public key added.103 SSH public key added.
103104
105 >>> sshkey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJseCUmxVG7D6qh4JmhLp0Du4kScScJ9PtZ0LGHYHaURnRw9tbX1wwURAio8og6dbnT75CQ3TbUE/xJhxI0aFXE= salgado@canario"
106 >>> browser.getControl(name='sshkey').value = sshkey
107 >>> browser.getControl('Import Public Key').click()
108 >>> soup = find_main_content(browser.contents)
109 >>> for tag in soup('p', 'informational message'):
110 ... print tag.renderContents()
111 SSH public key added.
112
113 >>> sshkey = "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBDUR0E0zCHRHJER6uzjfE/o0HAHFLcq/n8lp0duThpeIPsmo+wr3vHHuAAyOddOgkuQC8Lj8FzHlrOEYgXL6qa7FvpviE9YWUgmqVDa/yJbL/m6Mg8fvSIXlDJKmvOSv6g== salgado@canario"
114 >>> browser.getControl(name='sshkey').value = sshkey
115 >>> browser.getControl('Import Public Key').click()
116 >>> soup = find_main_content(browser.contents)
117 >>> for tag in soup('p', 'informational message'):
118 ... print tag.renderContents()
119 SSH public key added.
120
121 >>> sshkey = "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAB3rpD+Ozb/kwUOqCZUXSiruAkIx6sNZLJyjJ0zxVTZSannaysCLxMQ/IiVxCd59+U2NaLduMzd93JcYDRlX3M5+AApY+3JjfSPo01Sb17HTLNSYU3RZWx0A3XJxm/YN+x/iuYZ3IziuAKeYMsNsdfHlO4/IWjw4Ruy0enW+QhWaY2qAQ== salgado@canario"
122 >>> browser.getControl(name='sshkey').value = sshkey
123 >>> browser.getControl('Import Public Key').click()
124 >>> soup = find_main_content(browser.contents)
125 >>> for tag in soup('p', 'informational message'):
126 ... print tag.renderContents()
127 SSH public key added.
128
104Launchpad administrators are not allowed to poke at other user's ssh keys.129Launchpad administrators are not allowed to poke at other user's ssh keys.
105130
106 >>> login(ANONYMOUS)131 >>> login(ANONYMOUS)
@@ -118,11 +143,13 @@
118 >>> browser.open('http://launchpad.dev/~salgado')143 >>> browser.open('http://launchpad.dev/~salgado')
119 >>> print browser.title144 >>> print browser.title
120 Guilherme Salgado in Launchpad145 Guilherme Salgado in Launchpad
121 >>> print extract_text(146 >>> print extract_text(find_tag_by_id(browser.contents, 'sshkeys'))
122 ... find_tag_by_id(browser.contents, 'sshkeys'))
123 SSH keys: Update SSH keys147 SSH keys: Update SSH keys
124 salgado@canario148 salgado@canario
125 salgado@canario149 salgado@canario
150 salgado@canario
151 salgado@canario
152 salgado@canario
126 >>> browser.getLink('Update SSH keys').click()153 >>> browser.getLink('Update SSH keys').click()
127 >>> print browser.title154 >>> print browser.title
128 Change your SSH keys...155 Change your SSH keys...
129156
=== modified file 'lib/lp/registry/templates/person-editsshkeys.pt'
--- lib/lp/registry/templates/person-editsshkeys.pt 2012-05-31 02:20:41 +0000
+++ lib/lp/registry/templates/person-editsshkeys.pt 2018-07-02 14:38:50 +0000
@@ -47,7 +47,8 @@
47 <label>Public key line</label>47 <label>Public key line</label>
48 <div class="formHelp">48 <div class="formHelp">
49 Insert the contents of your public key (usually49 Insert the contents of your public key (usually
50 <code>~/.ssh/id_dsa.pub</code> or <code>~/.ssh/id_rsa.pub</code>).50 <code>~/.ssh/id_rsa.pub</code>, <code>~/.ssh/id_dsa.pub</code>, or
51 <code>~/.ssh/id_ecdsa.pub</code>).
51 Only SSH v2 keys are supported.52 Only SSH v2 keys are supported.
52 <a href="https://help.launchpad.net/YourAccount/CreatingAnSSHKeyPair">53 <a href="https://help.launchpad.net/YourAccount/CreatingAnSSHKeyPair">
53 How do I create a public key?54 How do I create a public key?
5455
=== modified file 'lib/lp/registry/tests/test_ssh.py'
--- lib/lp/registry/tests/test_ssh.py 2018-06-25 15:14:38 +0000
+++ lib/lp/registry/tests/test_ssh.py 2018-07-02 14:38:50 +0000
@@ -12,7 +12,6 @@
12 ISSHKeySet,12 ISSHKeySet,
13 SSH_TEXT_TO_KEY_TYPE,13 SSH_TEXT_TO_KEY_TYPE,
14 SSHKeyAdditionError,14 SSHKeyAdditionError,
15 SSHKeyType,
16 )15 )
17from lp.testing import (16from lp.testing import (
18 admin_logged_in,17 admin_logged_in,
@@ -31,17 +30,38 @@
31 def test_getFullKeyText_for_rsa_key(self):30 def test_getFullKeyText_for_rsa_key(self):
32 person = self.factory.makePerson()31 person = self.factory.makePerson()
33 with person_logged_in(person):32 with person_logged_in(person):
34 key = self.factory.makeSSHKey(person, SSHKeyType.RSA)33 key = self.factory.makeSSHKey(person, "ssh-rsa")
35 expected = "ssh-rsa %s %s" % (key.keytext, key.comment)34 expected = "ssh-rsa %s %s" % (key.keytext, key.comment)
36 self.assertEqual(expected, key.getFullKeyText())35 self.assertEqual(expected, key.getFullKeyText())
3736
38 def test_getFullKeyText_for_dsa_key(self):37 def test_getFullKeyText_for_dsa_key(self):
39 person = self.factory.makePerson()38 person = self.factory.makePerson()
40 with person_logged_in(person):39 with person_logged_in(person):
41 key = self.factory.makeSSHKey(person, SSHKeyType.DSA)40 key = self.factory.makeSSHKey(person, "ssh-dss")
42 expected = "ssh-dss %s %s" % (key.keytext, key.comment)41 expected = "ssh-dss %s %s" % (key.keytext, key.comment)
43 self.assertEqual(expected, key.getFullKeyText())42 self.assertEqual(expected, key.getFullKeyText())
4443
44 def test_getFullKeyText_for_ecdsa_nistp256_key(self):
45 person = self.factory.makePerson()
46 with person_logged_in(person):
47 key = self.factory.makeSSHKey(person, "ecdsa-sha2-nistp256")
48 expected = "ecdsa-sha2-nistp256 %s %s" % (key.keytext, key.comment)
49 self.assertEqual(expected, key.getFullKeyText())
50
51 def test_getFullKeyText_for_ecdsa_nistp384_key(self):
52 person = self.factory.makePerson()
53 with person_logged_in(person):
54 key = self.factory.makeSSHKey(person, "ecdsa-sha2-nistp384")
55 expected = "ecdsa-sha2-nistp384 %s %s" % (key.keytext, key.comment)
56 self.assertEqual(expected, key.getFullKeyText())
57
58 def test_getFullKeyText_for_ecdsa_nistp521_key(self):
59 person = self.factory.makePerson()
60 with person_logged_in(person):
61 key = self.factory.makeSSHKey(person, "ecdsa-sha2-nistp521")
62 expected = "ecdsa-sha2-nistp521 %s %s" % (key.keytext, key.comment)
63 self.assertEqual(expected, key.getFullKeyText())
64
45 def test_destroySelf_sends_notification_by_default(self):65 def test_destroySelf_sends_notification_by_default(self):
46 person = self.factory.makePerson()66 person = self.factory.makePerson()
47 with person_logged_in(person):67 with person_logged_in(person):
@@ -58,7 +78,7 @@
58 % key.comment)78 % key.comment)
59 )79 )
6080
61 def test_destroySelf_notications_can_be_supressed(self):81 def test_destroySelf_notifications_can_be_suppressed(self):
62 person = self.factory.makePerson()82 person = self.factory.makePerson()
63 with person_logged_in(person):83 with person_logged_in(person):
64 key = self.factory.makeSSHKey(person, send_notification=False)84 key = self.factory.makeSSHKey(person, send_notification=False)
@@ -179,6 +199,14 @@
179 )199 )
180 self.assertRaisesWithContent(200 self.assertRaisesWithContent(
181 SSHKeyAdditionError,201 SSHKeyAdditionError,
202 "Invalid SSH key data: key type 'ssh-rsa' does not match key "
203 "data type 'ssh-dss'",
204 keyset.new,
205 person,
206 'ssh-rsa ' + self.factory.makeSSHKeyText(key_type='ssh-dss')[8:]
207 )
208 self.assertRaisesWithContent(
209 SSHKeyAdditionError,
182 "Invalid SSH key data: 'None'",210 "Invalid SSH key data: 'None'",
183 keyset.new,211 keyset.new,
184 person, None212 person, None
185213
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2018-06-30 16:09:44 +0000
+++ lib/lp/testing/factory.py 2018-07-02 14:38:50 +0000
@@ -20,6 +20,7 @@
20 ]20 ]
2121
22import base6422import base64
23from cryptography.utils import int_to_bytes
23from datetime import (24from datetime import (
24 datetime,25 datetime,
25 timedelta,26 timedelta,
@@ -228,11 +229,7 @@
228 SourcePackageUrgency,229 SourcePackageUrgency,
229 )230 )
230from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet231from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
231from lp.registry.interfaces.ssh import (232from lp.registry.interfaces.ssh import ISSHKeySet
232 ISSHKeySet,
233 SSH_KEY_TYPE_TO_TEXT,
234 SSHKeyType,
235 )
236from lp.registry.model.commercialsubscription import CommercialSubscription233from lp.registry.model.commercialsubscription import CommercialSubscription
237from lp.registry.model.karma import KarmaTotalCache234from lp.registry.model.karma import KarmaTotalCache
238from lp.registry.model.milestone import Milestone235from lp.registry.model.milestone import Milestone
@@ -4311,37 +4308,56 @@
4311 return getUtility(IHWSubmissionDeviceSet).create(4308 return getUtility(IHWSubmissionDeviceSet).create(
4312 device_driver_link, submission, parent, hal_device_id)4309 device_driver_link, submission, parent, hal_device_id)
43134310
4314 def makeSSHKeyText(self, key_type=SSHKeyType.RSA, comment=None):4311 def makeSSHKeyText(self, key_type="ssh-rsa", comment=None):
4315 """Create new SSH public key text.4312 """Create new SSH public key text.
43164313
4317 :param key_type: If specified, the type of SSH key to generate. Must be4314 :param key_type: If specified, the type of SSH key to generate, as a
4318 a member of SSHKeyType. If unspecified, SSHKeyType.RSA is used.4315 public key algorithm name
4316 (https://www.iana.org/assignments/ssh-parameters/). Must be a
4317 member of SSH_TEXT_TO_KEY_TYPE. If unspecified, "ssh-rsa" is
4318 used.
4319 """4319 """
4320 key_type_string = SSH_KEY_TYPE_TO_TEXT.get(key_type)4320 parameters = None
4321 if key_type is None:4321 if key_type == "ssh-rsa":
4322 raise AssertionError(
4323 "key_type must be a member of SSHKeyType, not %r" % key_type)
4324 if key_type == SSHKeyType.RSA:
4325 parameters = [MP(keydata.RSAData[param]) for param in ("e", "n")]4322 parameters = [MP(keydata.RSAData[param]) for param in ("e", "n")]
4326 elif key_type == SSHKeyType.DSA:4323 elif key_type == "ssh-dss":
4327 parameters = [4324 parameters = [
4328 MP(keydata.DSAData[param]) for param in ("p", "q", "g", "y")]4325 MP(keydata.DSAData[param]) for param in ("p", "q", "g", "y")]
4329 else:4326 elif key_type.startswith("ecdsa-sha2-"):
4327 curve = key_type[len("ecdsa-sha2-"):]
4328 key_size, curve_data = {
4329 "nistp256": (256, keydata.ECDatanistp256),
4330 "nistp384": (384, keydata.ECDatanistp384),
4331 "nistp521": (521, keydata.ECDatanistp521),
4332 }.get(curve, (None, None))
4333 if curve_data is not None:
4334 key_byte_length = (key_size + 7) // 8
4335 parameters = [
4336 NS(curve_data["curve"][-8:]),
4337 NS(b"\x04" +
4338 int_to_bytes(curve_data["x"], key_byte_length) +
4339 int_to_bytes(curve_data["y"], key_byte_length)),
4340 ]
4341 if parameters is None:
4330 raise AssertionError(4342 raise AssertionError(
4331 "key_type must be a member of SSHKeyType, not %r" % key_type)4343 "key_type must be a member of SSH_TEXT_TO_KEY_TYPE, not %r" %
4332 key_text = base64.b64encode(NS(key_type_string) + b"".join(parameters))4344 key_type)
4345 key_text = base64.b64encode(NS(key_type) + b"".join(parameters))
4333 if comment is None:4346 if comment is None:
4334 comment = self.getUniqueString()4347 comment = self.getUniqueString()
4335 return "%s %s %s" % (key_type_string, key_text, comment)4348 return "%s %s %s" % (key_type, key_text, comment)
43364349
4337 def makeSSHKey(self, person=None, key_type=SSHKeyType.RSA,4350 def makeSSHKey(self, person=None, key_type="ssh-rsa",
4338 send_notification=True):4351 send_notification=True):
4339 """Create a new SSHKey.4352 """Create a new SSHKey.
43404353
4341 :param person: If specified, the person to attach the key to. If4354 :param person: If specified, the person to attach the key to. If
4342 unspecified, a person is created.4355 unspecified, a person is created.
4343 :param key_type: If specified, the type of SSH key to generate. Must be4356 :param key_type: If specified, the type of SSH key to generate, as a
4344 a member of SSHKeyType. If unspecified, SSHKeyType.RSA is used.4357 public key algorithm name
4358 (https://www.iana.org/assignments/ssh-parameters/). Must be a
4359 member of SSH_TEXT_TO_KEY_TYPE. If unspecified, "ssh-rsa" is
4360 used.
4345 """4361 """
4346 if person is None:4362 if person is None:
4347 person = self.makePerson()4363 person = self.makePerson()