1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Tests for L{twisted.conch.ssh.keys}.
9 import Crypto.Cipher.DES3
11 # we'll have to skip these tests without PyCypto and pyasn1
20 from twisted.conch.ssh import keys, common, sexpy
23 from twisted.conch.test import keydata
24 from twisted.python import randbytes
25 from twisted.python.hashlib import sha1
26 from twisted.trial import unittest
29 class HelpersTestCase(unittest.TestCase):
32 skip = "cannot run w/o PyCrypto"
34 skip = "Cannot run without PyASN1"
37 self._secureRandom = randbytes.secureRandom
38 randbytes.secureRandom = lambda x: '\x55' * x
41 randbytes.secureRandom = self._secureRandom
42 self._secureRandom = None
46 Test Public Key Cryptographic Standard #1 functions.
50 self.assertEqual(keys.pkcs1Pad(data, messageSize),
52 hash = sha1().digest()
54 self.assertEqual(keys.pkcs1Digest('', messageSize),
55 '\x01\xff\xff\xff\x00' + keys.ID_SHA1 + hash)
57 def _signRSA(self, data):
58 key = keys.Key.fromString(keydata.privateRSA_openssh)
60 return key.keyObject, sig
62 def _signDSA(self, data):
63 key = keys.Key.fromString(keydata.privateDSA_openssh)
65 return key.keyObject, sig
67 def test_signRSA(self):
69 Test that RSA keys return appropriate signatures.
72 key, sig = self._signRSA(data)
73 sigData = keys.pkcs1Digest(data, keys.lenSig(key))
74 v = key.sign(sigData, '')[0]
75 self.assertEqual(sig, common.NS('ssh-rsa') + common.MP(v))
78 def test_signDSA(self):
80 Test that DSA keys return appropriate signatures.
83 key, sig = self._signDSA(data)
84 sigData = sha1(data).digest()
85 v = key.sign(sigData, '\x55' * 19)
86 self.assertEqual(sig, common.NS('ssh-dss') + common.NS(
87 Crypto.Util.number.long_to_bytes(v[0], 20) +
88 Crypto.Util.number.long_to_bytes(v[1], 20)))
92 def test_objectType(self):
94 Test that objectType, returns the correct type for objects.
96 self.assertEqual(keys.objectType(keys.Key.fromString(
97 keydata.privateRSA_openssh).keyObject), 'ssh-rsa')
98 self.assertEqual(keys.objectType(keys.Key.fromString(
99 keydata.privateDSA_openssh).keyObject), 'ssh-dss')
100 self.assertRaises(keys.BadKeyError, keys.objectType, None)
103 class KeyTestCase(unittest.TestCase):
106 skip = "cannot run w/o PyCrypto"
108 skip = "Cannot run without PyASN1"
111 self.rsaObj = Crypto.PublicKey.RSA.construct((1L, 2L, 3L, 4L, 5L))
112 self.dsaObj = Crypto.PublicKey.DSA.construct((1L, 2L, 3L, 4L, 5L))
113 self.rsaSignature = ('\x00\x00\x00\x07ssh-rsa\x00'
114 '\x00\x00`N\xac\xb4@qK\xa0(\xc3\xf2h \xd3\xdd\xee6Np\x9d_'
115 '\xb0>\xe3\x0c(L\x9d{\txUd|!\xf6m\x9c\xd3\x93\x842\x7fU'
116 '\x05\xf4\xf7\xfaD\xda\xce\x81\x8ea\x7f=Y\xed*\xb7\xba\x81'
117 '\xf2\xad\xda\xeb(\x97\x03S\x08\x81\xc7\xb1\xb7\xe6\xe3'
118 '\xcd*\xd4\xbd\xc0wt\xf7y\xcd\xf0\xb7\x7f\xfb\x1e>\xf9r'
120 self.dsaSignature = ('\x00\x00\x00\x07ssh-dss\x00\x00'
121 '\x00(\x18z)H\x8a\x1b\xc6\r\xbbq\xa2\xd7f\x7f$\xa7\xbf'
122 '\xe8\x87\x8c\x88\xef\xd9k\x1a\x98\xdd{=\xdec\x18\t\xe3'
123 '\x87\xa9\xc72h\x95')
124 self.oldSecureRandom = randbytes.secureRandom
125 randbytes.secureRandom = lambda x: '\xff' * x
126 self.keyFile = self.mktemp()
127 file(self.keyFile, 'wb').write(keydata.privateRSA_lsh)
130 randbytes.secureRandom = self.oldSecureRandom
131 del self.oldSecureRandom
132 os.unlink(self.keyFile)
134 def test__guessStringType(self):
136 Test that the _guessStringType method guesses string types
139 self.assertEqual(keys.Key._guessStringType(keydata.publicRSA_openssh),
141 self.assertEqual(keys.Key._guessStringType(keydata.publicDSA_openssh),
143 self.assertEqual(keys.Key._guessStringType(
144 keydata.privateRSA_openssh), 'private_openssh')
145 self.assertEqual(keys.Key._guessStringType(
146 keydata.privateDSA_openssh), 'private_openssh')
147 self.assertEqual(keys.Key._guessStringType(keydata.publicRSA_lsh),
149 self.assertEqual(keys.Key._guessStringType(keydata.publicDSA_lsh),
151 self.assertEqual(keys.Key._guessStringType(keydata.privateRSA_lsh),
153 self.assertEqual(keys.Key._guessStringType(keydata.privateDSA_lsh),
155 self.assertEqual(keys.Key._guessStringType(
156 keydata.privateRSA_agentv3), 'agentv3')
157 self.assertEqual(keys.Key._guessStringType(
158 keydata.privateDSA_agentv3), 'agentv3')
159 self.assertEqual(keys.Key._guessStringType(
160 '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01\x01'),
162 self.assertEqual(keys.Key._guessStringType(
163 '\x00\x00\x00\x07ssh-dss\x00\x00\x00\x01\x01'),
165 self.assertEqual(keys.Key._guessStringType('not a key'),
168 def _testPublicPrivateFromString(self, public, private, type, data):
169 self._testPublicFromString(public, type, data)
170 self._testPrivateFromString(private, type, data)
172 def _testPublicFromString(self, public, type, data):
173 publicKey = keys.Key.fromString(public)
174 self.assertTrue(publicKey.isPublic())
175 self.assertEqual(publicKey.type(), type)
176 for k, v in publicKey.data().items():
177 self.assertEqual(data[k], v)
179 def _testPrivateFromString(self, private, type, data):
180 privateKey = keys.Key.fromString(private)
181 self.assertFalse(privateKey.isPublic())
182 self.assertEqual(privateKey.type(), type)
183 for k, v in data.items():
184 self.assertEqual(privateKey.data()[k], v)
186 def test_fromOpenSSH(self):
188 Test that keys are correctly generated from OpenSSH strings.
190 self._testPublicPrivateFromString(keydata.publicRSA_openssh,
191 keydata.privateRSA_openssh, 'RSA', keydata.RSAData)
192 self.assertEqual(keys.Key.fromString(
193 keydata.privateRSA_openssh_encrypted,
194 passphrase='encrypted'),
195 keys.Key.fromString(keydata.privateRSA_openssh))
196 self.assertEqual(keys.Key.fromString(
197 keydata.privateRSA_openssh_alternate),
198 keys.Key.fromString(keydata.privateRSA_openssh))
199 self._testPublicPrivateFromString(keydata.publicDSA_openssh,
200 keydata.privateDSA_openssh, 'DSA', keydata.DSAData)
202 def test_fromOpenSSH_with_whitespace(self):
204 If key strings have trailing whitespace, it should be ignored.
206 # from bug #3391, since our test key data doesn't have
207 # an issue with appended newlines
208 privateDSAData = """-----BEGIN DSA PRIVATE KEY-----
209 MIIBuwIBAAKBgQDylESNuc61jq2yatCzZbenlr9llG+p9LhIpOLUbXhhHcwC6hrh
210 EZIdCKqTO0USLrGoP5uS9UHAUoeN62Z0KXXWTwOWGEQn/syyPzNJtnBorHpNUT9D
211 Qzwl1yUa53NNgEctpo4NoEFOx8PuU6iFLyvgHCjNn2MsuGuzkZm7sI9ZpQIVAJiR
212 9dPc08KLdpJyRxz8T74b4FQRAoGAGBc4Z5Y6R/HZi7AYM/iNOM8su6hrk8ypkBwR
213 a3Dbhzk97fuV3SF1SDrcQu4zF7c4CtH609N5nfZs2SUjLLGPWln83Ysb8qhh55Em
214 AcHXuROrHS/sDsnqu8FQp86MaudrqMExCOYyVPE7jaBWW+/JWFbKCxmgOCSdViUJ
215 esJpBFsCgYEA7+jtVvSt9yrwsS/YU1QGP5wRAiDYB+T5cK4HytzAqJKRdC5qS4zf
216 C7R0eKcDHHLMYO39aPnCwXjscisnInEhYGNblTDyPyiyNxAOXuC8x7luTmwzMbNJ
217 /ow0IqSj0VF72VJN9uSoPpFd4lLT0zN8v42RWja0M8ohWNf+YNJluPgCFE0PT4Vm
219 -----END DSA PRIVATE KEY-----"""
220 self.assertEqual(keys.Key.fromString(privateDSAData),
221 keys.Key.fromString(privateDSAData + '\n'))
223 def test_fromLSH(self):
225 Test that keys are correctly generated from LSH strings.
227 self._testPublicPrivateFromString(keydata.publicRSA_lsh,
228 keydata.privateRSA_lsh, 'RSA', keydata.RSAData)
229 self._testPublicPrivateFromString(keydata.publicDSA_lsh,
230 keydata.privateDSA_lsh, 'DSA', keydata.DSAData)
231 sexp = sexpy.pack([['public-key', ['bad-key', ['p', '2']]]])
232 self.assertRaises(keys.BadKeyError, keys.Key.fromString,
233 data='{'+base64.encodestring(sexp)+'}')
234 sexp = sexpy.pack([['private-key', ['bad-key', ['p', '2']]]])
235 self.assertRaises(keys.BadKeyError, keys.Key.fromString,
238 def test_fromAgentv3(self):
240 Test that keys are correctly generated from Agent v3 strings.
242 self._testPrivateFromString(keydata.privateRSA_agentv3, 'RSA',
244 self._testPrivateFromString(keydata.privateDSA_agentv3, 'DSA',
246 self.assertRaises(keys.BadKeyError, keys.Key.fromString,
247 '\x00\x00\x00\x07ssh-foo'+'\x00\x00\x00\x01\x01'*5)
249 def test_fromStringErrors(self):
251 keys.Key.fromString should raise BadKeyError when the key is invalid.
253 self.assertRaises(keys.BadKeyError, keys.Key.fromString, '')
254 # no key data with a bad key type
255 self.assertRaises(keys.BadKeyError, keys.Key.fromString, '',
257 # trying to decrypt a key which doesn't support encryption
258 self.assertRaises(keys.BadKeyError, keys.Key.fromString,
259 keydata.publicRSA_lsh, passphrase = 'unencrypted')
260 # trying to decrypt an unencrypted key
261 self.assertRaises(keys.EncryptedKeyError, keys.Key.fromString,
262 keys.Key(self.rsaObj).toString('openssh', 'encrypted'))
263 # key with no key data
264 self.assertRaises(keys.BadKeyError, keys.Key.fromString,
265 '-----BEGIN RSA KEY-----\nwA==\n')
267 def test_fromFile(self):
269 Test that fromFile works correctly.
271 self.assertEqual(keys.Key.fromFile(self.keyFile),
272 keys.Key.fromString(keydata.privateRSA_lsh))
273 self.assertRaises(keys.BadKeyError, keys.Key.fromFile,
274 self.keyFile, 'bad_type')
275 self.assertRaises(keys.BadKeyError, keys.Key.fromFile,
276 self.keyFile, passphrase='unencrypted')
280 Test that the PublicKey object is initialized correctly.
282 obj = Crypto.PublicKey.RSA.construct((1L, 2L))
284 self.assertEqual(key.keyObject, obj)
286 def test_equal(self):
288 Test that Key objects are compared correctly.
290 rsa1 = keys.Key(self.rsaObj)
291 rsa2 = keys.Key(self.rsaObj)
292 rsa3 = keys.Key(Crypto.PublicKey.RSA.construct((1L, 2L)))
293 dsa = keys.Key(self.dsaObj)
294 self.assertTrue(rsa1 == rsa2)
295 self.assertFalse(rsa1 == rsa3)
296 self.assertFalse(rsa1 == dsa)
297 self.assertFalse(rsa1 == object)
298 self.assertFalse(rsa1 == None)
300 def test_notEqual(self):
302 Test that Key objects are not-compared correctly.
304 rsa1 = keys.Key(self.rsaObj)
305 rsa2 = keys.Key(self.rsaObj)
306 rsa3 = keys.Key(Crypto.PublicKey.RSA.construct((1L, 2L)))
307 dsa = keys.Key(self.dsaObj)
308 self.assertFalse(rsa1 != rsa2)
309 self.assertTrue(rsa1 != rsa3)
310 self.assertTrue(rsa1 != dsa)
311 self.assertTrue(rsa1 != object)
312 self.assertTrue(rsa1 != None)
316 Test that the type method returns the correct type for an object.
318 self.assertEqual(keys.Key(self.rsaObj).type(), 'RSA')
319 self.assertEqual(keys.Key(self.rsaObj).sshType(), 'ssh-rsa')
320 self.assertEqual(keys.Key(self.dsaObj).type(), 'DSA')
321 self.assertEqual(keys.Key(self.dsaObj).sshType(), 'ssh-dss')
322 self.assertRaises(RuntimeError, keys.Key(None).type)
323 self.assertRaises(RuntimeError, keys.Key(None).sshType)
324 self.assertRaises(RuntimeError, keys.Key(self).type)
325 self.assertRaises(RuntimeError, keys.Key(self).sshType)
327 def test_fromBlob(self):
329 Test that a public key is correctly generated from a public key blob.
331 rsaBlob = common.NS('ssh-rsa') + common.MP(2) + common.MP(3)
332 rsaKey = keys.Key.fromString(rsaBlob)
333 dsaBlob = (common.NS('ssh-dss') + common.MP(2) + common.MP(3) +
334 common.MP(4) + common.MP(5))
335 dsaKey = keys.Key.fromString(dsaBlob)
336 badBlob = common.NS('ssh-bad')
337 self.assertTrue(rsaKey.isPublic())
338 self.assertEqual(rsaKey.data(), {'e':2L, 'n':3L})
339 self.assertTrue(dsaKey.isPublic())
340 self.assertEqual(dsaKey.data(), {'p':2L, 'q':3L, 'g':4L, 'y':5L})
341 self.assertRaises(keys.BadKeyError,
342 keys.Key.fromString, badBlob)
345 def test_fromPrivateBlob(self):
347 Test that a private key is correctly generated from a private key blob.
349 rsaBlob = (common.NS('ssh-rsa') + common.MP(2) + common.MP(3) +
350 common.MP(4) + common.MP(5) + common.MP(6) + common.MP(7))
351 rsaKey = keys.Key._fromString_PRIVATE_BLOB(rsaBlob)
352 dsaBlob = (common.NS('ssh-dss') + common.MP(2) + common.MP(3) +
353 common.MP(4) + common.MP(5) + common.MP(6))
354 dsaKey = keys.Key._fromString_PRIVATE_BLOB(dsaBlob)
355 badBlob = common.NS('ssh-bad')
356 self.assertFalse(rsaKey.isPublic())
358 rsaKey.data(), {'n':2L, 'e':3L, 'd':4L, 'u':5L, 'p':6L, 'q':7L})
359 self.assertFalse(dsaKey.isPublic())
360 self.assertEqual(dsaKey.data(), {'p':2L, 'q':3L, 'g':4L, 'y':5L, 'x':6L})
362 keys.BadKeyError, keys.Key._fromString_PRIVATE_BLOB, badBlob)
367 Test that the Key object generates blobs correctly.
369 self.assertEqual(keys.Key(self.rsaObj).blob(),
370 '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01\x02'
371 '\x00\x00\x00\x01\x01')
372 self.assertEqual(keys.Key(self.dsaObj).blob(),
373 '\x00\x00\x00\x07ssh-dss\x00\x00\x00\x01\x03'
374 '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x02'
375 '\x00\x00\x00\x01\x01')
377 badKey = keys.Key(None)
378 self.assertRaises(RuntimeError, badKey.blob)
381 def test_privateBlob(self):
383 L{Key.privateBlob} returns the SSH protocol-level format of the private
384 key and raises L{RuntimeError} if the underlying key object is invalid.
386 self.assertEqual(keys.Key(self.rsaObj).privateBlob(),
387 '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01\x01'
388 '\x00\x00\x00\x01\x02\x00\x00\x00\x01\x03\x00'
389 '\x00\x00\x01\x04\x00\x00\x00\x01\x04\x00\x00'
391 self.assertEqual(keys.Key(self.dsaObj).privateBlob(),
392 '\x00\x00\x00\x07ssh-dss\x00\x00\x00\x01\x03'
393 '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x02\x00'
394 '\x00\x00\x01\x01\x00\x00\x00\x01\x05')
396 badKey = keys.Key(None)
397 self.assertRaises(RuntimeError, badKey.privateBlob)
400 def test_toOpenSSH(self):
402 Test that the Key object generates OpenSSH keys correctly.
404 key = keys.Key.fromString(keydata.privateRSA_lsh)
405 self.assertEqual(key.toString('openssh'), keydata.privateRSA_openssh)
406 self.assertEqual(key.toString('openssh', 'encrypted'),
407 keydata.privateRSA_openssh_encrypted)
408 self.assertEqual(key.public().toString('openssh'),
409 keydata.publicRSA_openssh[:-8]) # no comment
410 self.assertEqual(key.public().toString('openssh', 'comment'),
411 keydata.publicRSA_openssh)
412 key = keys.Key.fromString(keydata.privateDSA_lsh)
413 self.assertEqual(key.toString('openssh'), keydata.privateDSA_openssh)
414 self.assertEqual(key.public().toString('openssh', 'comment'),
415 keydata.publicDSA_openssh)
416 self.assertEqual(key.public().toString('openssh'),
417 keydata.publicDSA_openssh[:-8]) # no comment
419 def test_toLSH(self):
421 Test that the Key object generates LSH keys correctly.
423 key = keys.Key.fromString(keydata.privateRSA_openssh)
424 self.assertEqual(key.toString('lsh'), keydata.privateRSA_lsh)
425 self.assertEqual(key.public().toString('lsh'),
426 keydata.publicRSA_lsh)
427 key = keys.Key.fromString(keydata.privateDSA_openssh)
428 self.assertEqual(key.toString('lsh'), keydata.privateDSA_lsh)
429 self.assertEqual(key.public().toString('lsh'),
430 keydata.publicDSA_lsh)
432 def test_toAgentv3(self):
434 Test that the Key object generates Agent v3 keys correctly.
436 key = keys.Key.fromString(keydata.privateRSA_openssh)
437 self.assertEqual(key.toString('agentv3'), keydata.privateRSA_agentv3)
438 key = keys.Key.fromString(keydata.privateDSA_openssh)
439 self.assertEqual(key.toString('agentv3'), keydata.privateDSA_agentv3)
441 def test_toStringErrors(self):
443 Test that toString raises errors appropriately.
445 self.assertRaises(keys.BadKeyError, keys.Key(self.rsaObj).toString,
450 Test that the Key object generates correct signatures.
452 key = keys.Key.fromString(keydata.privateRSA_openssh)
453 self.assertEqual(key.sign(''), self.rsaSignature)
454 key = keys.Key.fromString(keydata.privateDSA_openssh)
455 self.assertEqual(key.sign(''), self.dsaSignature)
458 def test_verify(self):
460 Test that the Key object correctly verifies signatures.
462 key = keys.Key.fromString(keydata.publicRSA_openssh)
463 self.assertTrue(key.verify(self.rsaSignature, ''))
464 self.assertFalse(key.verify(self.rsaSignature, 'a'))
465 self.assertFalse(key.verify(self.dsaSignature, ''))
466 key = keys.Key.fromString(keydata.publicDSA_openssh)
467 self.assertTrue(key.verify(self.dsaSignature, ''))
468 self.assertFalse(key.verify(self.dsaSignature, 'a'))
469 self.assertFalse(key.verify(self.rsaSignature, ''))
473 Test the pretty representation of Key.
475 self.assertEqual(repr(keys.Key(self.rsaObj)),
476 """<RSA Private Key (0 bits)