1 # -*- test-case-name: twisted.conch.test.test_keys -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Handling of RSA and DSA keys.
8 Maintainer: U{Paul Swartz}
11 # base library imports
16 # external library imports
17 from Crypto.Cipher import DES3
18 from Crypto.PublicKey import RSA, DSA
19 from Crypto import Util
20 from pyasn1.type import univ
21 from pyasn1.codec.ber import decoder as berDecoder
22 from pyasn1.codec.ber import encoder as berEncoder
25 from twisted.python import randbytes
26 from twisted.python.hashlib import md5, sha1
29 from twisted.conch.ssh import common, sexpy
32 class BadKeyError(Exception):
34 Raised when a key isn't what we expected from it.
36 XXX: we really need to check for bad keys
39 class EncryptedKeyError(Exception):
41 Raised when an encrypted key is presented to fromString/fromFile without
47 An object representing a key. A key can be either a public or
48 private key. A public key can verify a signature; a private key can
49 create or verify a signature. To generate a string that can be stored
50 on disk, use the toString method. If you have a private key, but want
51 the string representation of the public key, use Key.public().toString().
53 @ivar keyObject: The C{Crypto.PublicKey.pubkey.pubkey} object that
54 operations are performed with.
57 def fromFile(Class, filename, type=None, passphrase=None):
59 Return a Key object corresponding to the data in filename. type
60 and passphrase function as they do in fromString.
62 return Class.fromString(file(filename, 'rb').read(), type, passphrase)
63 fromFile = classmethod(fromFile)
65 def fromString(Class, data, type=None, passphrase=None):
67 Return a Key object corresponding to the string data.
68 type is optionally the type of string, matching a _fromString_*
69 method. Otherwise, the _guessStringType() classmethod will be used
70 to guess a type. If the key is encrypted, passphrase is used as
74 @type type: C{None}/C{str}
75 @type passphrase: C{None}/C{str}
79 type = Class._guessStringType(data)
81 raise BadKeyError('cannot guess the type of %r' % data)
82 method = getattr(Class, '_fromString_%s' % type.upper(), None)
84 raise BadKeyError('no _fromString method for %s' % type)
85 if method.func_code.co_argcount == 2: # no passphrase
87 raise BadKeyError('key not encrypted')
90 return method(data, passphrase)
91 fromString = classmethod(fromString)
93 def _fromString_BLOB(Class, blob):
95 Return a public key object corresponding to this public key blob.
96 The format of a RSA public key blob is::
101 The format of a DSA public key blob is::
109 @return: a C{Crypto.PublicKey.pubkey.pubkey} object
110 @raises BadKeyError: if the key type (the first string) is unknown.
112 keyType, rest = common.getNS(blob)
113 if keyType == 'ssh-rsa':
114 e, n, rest = common.getMP(rest, 2)
115 return Class(RSA.construct((n, e)))
116 elif keyType == 'ssh-dss':
117 p, q, g, y, rest = common.getMP(rest, 4)
118 return Class(DSA.construct((y, g, p, q)))
120 raise BadKeyError('unknown blob type: %s' % keyType)
121 _fromString_BLOB = classmethod(_fromString_BLOB)
123 def _fromString_PRIVATE_BLOB(Class, blob):
125 Return a private key object corresponding to this private key blob.
126 The blob formats are as follows:
146 @return: a C{Crypto.PublicKey.pubkey.pubkey} object
147 @raises BadKeyError: if the key type (the first string) is unknown.
149 keyType, rest = common.getNS(blob)
151 if keyType == 'ssh-rsa':
152 n, e, d, u, p, q, rest = common.getMP(rest, 6)
153 rsakey = Class(RSA.construct((n, e, d, p, q, u)))
155 elif keyType == 'ssh-dss':
156 p, q, g, y, x, rest = common.getMP(rest, 5)
157 dsakey = Class(DSA.construct((y, g, p, q, x)))
160 raise BadKeyError('unknown blob type: %s' % keyType)
161 _fromString_PRIVATE_BLOB = classmethod(_fromString_PRIVATE_BLOB)
163 def _fromString_PUBLIC_OPENSSH(Class, data):
165 Return a public key object corresponding to this OpenSSH public key
166 string. The format of an OpenSSH public key string is::
167 <key type> <base64-encoded public key blob>
170 @return: A {Crypto.PublicKey.pubkey.pubkey} object
171 @raises BadKeyError: if the blob type is unknown.
173 blob = base64.decodestring(data.split()[1])
174 return Class._fromString_BLOB(blob)
175 _fromString_PUBLIC_OPENSSH = classmethod(_fromString_PUBLIC_OPENSSH)
177 def _fromString_PRIVATE_OPENSSH(Class, data, passphrase):
179 Return a private key object corresponding to this OpenSSH private key
180 string. If the key is encrypted, passphrase MUST be provided.
181 Providing a passphrase for an unencrypted key is an error.
183 The format of an OpenSSH private key string is::
184 -----BEGIN <key type> PRIVATE KEY-----
185 [Proc-Type: 4,ENCRYPTED
186 DEK-Info: DES-EDE3-CBC,<initialization value>]
187 <base64-encoded ASN.1 structure>
188 ------END <key type> PRIVATE KEY------
190 The ASN.1 structure of a RSA key is::
193 The ASN.1 structure of a DSA key is::
197 @type passphrase: C{str}
198 @return: a C{Crypto.PublicKey.pubkey.pubkey} object
199 @raises BadKeyError: if
200 * a passphrase is provided for an unencrypted key
201 * a passphrase is not provided for an encrypted key
202 * the ASN.1 encoding is incorrect
204 lines = [x + '\n' for x in data.split('\n')]
205 kind = lines[0][11:14]
206 if lines[1].startswith('Proc-Type: 4,ENCRYPTED'): # encrypted key
207 ivdata = lines[2].split(',')[1][:-1]
208 iv = ''.join([chr(int(ivdata[i:i + 2], 16)) for i in range(0,
211 raise EncryptedKeyError('encrypted key with no passphrase')
212 ba = md5(passphrase + iv).digest()
213 bb = md5(ba + passphrase + iv).digest()
214 decKey = (ba + bb)[:24]
215 b64Data = base64.decodestring(''.join(lines[3:-1]))
216 keyData = DES3.new(decKey, DES3.MODE_CBC, iv).decrypt(b64Data)
217 removeLen = ord(keyData[-1])
218 keyData = keyData[:-removeLen]
220 b64Data = ''.join(lines[1:-1])
221 keyData = base64.decodestring(b64Data)
223 decodedKey = berDecoder.decode(keyData)[0]
225 raise BadKeyError, 'something wrong with decode'
227 if len(decodedKey) == 2: # alternate RSA key
228 decodedKey = decodedKey[0]
229 if len(decodedKey) < 6:
230 raise BadKeyError('RSA key failed to decode properly')
231 n, e, d, p, q = [long(value) for value in decodedKey[1:6]]
232 if p > q: # make p smaller than q
234 return Class(RSA.construct((n, e, d, p, q)))
236 p, q, g, y, x = [long(value) for value in decodedKey[1: 6]]
237 if len(decodedKey) < 6:
238 raise BadKeyError('DSA key failed to decode properly')
239 return Class(DSA.construct((y, g, p, q, x)))
240 _fromString_PRIVATE_OPENSSH = classmethod(_fromString_PRIVATE_OPENSSH)
242 def _fromString_PUBLIC_LSH(Class, data):
244 Return a public key corresponding to this LSH public key string.
245 The LSH public key string format is::
246 <s-expression: ('public-key', (<key type>, (<name, <value>)+))>
248 The names for a RSA (key type 'rsa-pkcs1-sha1') key are: n, e.
249 The names for a DSA (key type 'dsa') key are: y, g, p, q.
252 @return: a C{Crypto.PublicKey.pubkey.pubkey} object
253 @raises BadKeyError: if the key type is unknown
255 sexp = sexpy.parse(base64.decodestring(data[1:-1]))
256 assert sexp[0] == 'public-key'
258 for name, data in sexp[1][1:]:
259 kd[name] = common.getMP(common.NS(data))[0]
260 if sexp[1][0] == 'dsa':
261 return Class(DSA.construct((kd['y'], kd['g'], kd['p'], kd['q'])))
262 elif sexp[1][0] == 'rsa-pkcs1-sha1':
263 return Class(RSA.construct((kd['n'], kd['e'])))
265 raise BadKeyError('unknown lsh key type %s' % sexp[1][0])
266 _fromString_PUBLIC_LSH = classmethod(_fromString_PUBLIC_LSH)
268 def _fromString_PRIVATE_LSH(Class, data):
270 Return a private key corresponding to this LSH private key string.
271 The LSH private key string format is::
272 <s-expression: ('private-key', (<key type>, (<name>, <value>)+))>
274 The names for a RSA (key type 'rsa-pkcs1-sha1') key are: n, e, d, p, q.
275 The names for a DSA (key type 'dsa') key are: y, g, p, q, x.
278 @return: a {Crypto.PublicKey.pubkey.pubkey} object
279 @raises BadKeyError: if the key type is unknown
281 sexp = sexpy.parse(data)
282 assert sexp[0] == 'private-key'
284 for name, data in sexp[1][1:]:
285 kd[name] = common.getMP(common.NS(data))[0]
286 if sexp[1][0] == 'dsa':
287 assert len(kd) == 5, len(kd)
288 return Class(DSA.construct((kd['y'], kd['g'], kd['p'],
290 elif sexp[1][0] == 'rsa-pkcs1':
291 assert len(kd) == 8, len(kd)
292 if kd['p'] > kd['q']: # make p smaller than q
293 kd['p'], kd['q'] = kd['q'], kd['p']
294 return Class(RSA.construct((kd['n'], kd['e'], kd['d'],
297 raise BadKeyError('unknown lsh key type %s' % sexp[1][0])
298 _fromString_PRIVATE_LSH = classmethod(_fromString_PRIVATE_LSH)
300 def _fromString_AGENTV3(Class, data):
302 Return a private key object corresponsing to the Secure Shell Key
305 The SSH Key Agent v3 format for a RSA key is::
314 The SSH Key Agent v3 format for a DSA key is::
323 @return: a C{Crypto.PublicKey.pubkey.pubkey} object
324 @raises BadKeyError: if the key type (the first string) is unknown
326 keyType, data = common.getNS(data)
327 if keyType == 'ssh-dss':
328 p, data = common.getMP(data)
329 q, data = common.getMP(data)
330 g, data = common.getMP(data)
331 y, data = common.getMP(data)
332 x, data = common.getMP(data)
333 return Class(DSA.construct((y,g,p,q,x)))
334 elif keyType == 'ssh-rsa':
335 e, data = common.getMP(data)
336 d, data = common.getMP(data)
337 n, data = common.getMP(data)
338 u, data = common.getMP(data)
339 p, data = common.getMP(data)
340 q, data = common.getMP(data)
341 return Class(RSA.construct((n,e,d,p,q,u)))
343 raise BadKeyError("unknown key type %s" % keyType)
344 _fromString_AGENTV3 = classmethod(_fromString_AGENTV3)
346 def _guessStringType(Class, data):
348 Guess the type of key in data. The types map to _fromString_*
351 if data.startswith('ssh-'):
352 return 'public_openssh'
353 elif data.startswith('-----BEGIN'):
354 return 'private_openssh'
355 elif data.startswith('{'):
357 elif data.startswith('('):
359 elif data.startswith('\x00\x00\x00\x07ssh-'):
360 ignored, rest = common.getNS(data)
364 ignored, rest = common.getMP(rest)
369 _guessStringType = classmethod(_guessStringType)
371 def __init__(self, keyObject):
373 Initialize a PublicKey with a C{Crypto.PublicKey.pubkey.pubkey}
376 @type keyObject: C{Crypto.PublicKey.pubkey.pubkey}
378 self.keyObject = keyObject
380 def __eq__(self, other):
382 Return True if other represents an object with the same key.
384 if type(self) == type(other):
385 return self.type() == other.type() and self.data() == other.data()
387 return NotImplemented
389 def __ne__(self, other):
391 Return True if other represents anything other than this key.
393 result = self.__eq__(other)
394 if result == NotImplemented:
400 Return a pretty representation of this object.
402 lines = ['<%s %s (%s bits)' % (self.type(),
403 self.isPublic() and 'Public Key' or 'Private Key',
404 self.keyObject.size())]
405 for k, v in self.data().items():
406 lines.append('attr %s:' % k)
407 by = common.MP(v)[4:]
413 o = o + '%02x:' % ord(c)
416 lines.append('\t' + o)
417 lines[-1] = lines[-1] + '>'
418 return '\n'.join(lines)
422 Returns True if this Key is a public key.
424 return not self.keyObject.has_private()
428 Returns a version of this key containing only the public key data.
429 If this is a public key, this may or may not be the same object
432 return Key(self.keyObject.publickey())
435 def fingerprint(self):
437 Get the user presentation of the fingerprint of this L{Key}. As
438 described by U{RFC 4716 section
439 4<http://tools.ietf.org/html/rfc4716#section-4>}::
441 The fingerprint of a public key consists of the output of the MD5
442 message-digest algorithm [RFC1321]. The input to the algorithm is
443 the public key data as specified by [RFC4253]. (...) The output
444 of the (MD5) algorithm is presented to the user as a sequence of 16
445 octets printed as hexadecimal with lowercase letters and separated
450 @return: the user presentation of this L{Key}'s fingerprint, as a
455 return ':'.join([x.encode('hex') for x in md5(self.blob()).digest()])
460 Return the type of the object we wrap. Currently this can only be
463 # the class is Crypto.PublicKey.<type>.<stuff we don't care about>
464 mod = self.keyObject.__class__.__module__
465 if mod.startswith('Crypto.PublicKey'):
466 type = mod.split('.')[2]
468 raise RuntimeError('unknown type of object: %r' % self.keyObject)
469 if type in ('RSA', 'DSA'):
472 raise RuntimeError('unknown type of key: %s' % type)
476 Return the type of the object we wrap as defined in the ssh protocol.
477 Currently this can only be 'ssh-rsa' or 'ssh-dss'.
479 return {'RSA':'ssh-rsa', 'DSA':'ssh-dss'}[self.type()]
483 Return the values of the public key as a dictionary.
488 for name in self.keyObject.keydata:
489 value = getattr(self.keyObject, name, None)
490 if value is not None:
491 keyData[name] = value
496 Return the public key blob for this key. The blob is the
497 over-the-wire format for public keys:
516 return (common.NS('ssh-rsa') + common.MP(data['e']) +
517 common.MP(data['n']))
519 return (common.NS('ssh-dss') + common.MP(data['p']) +
520 common.MP(data['q']) + common.MP(data['g']) +
521 common.MP(data['y']))
523 def privateBlob(self):
525 Return the private key blob for this key. The blob is the
526 over-the-wire format for private keys:
548 return (common.NS('ssh-rsa') + common.MP(data['n']) +
549 common.MP(data['e']) + common.MP(data['d']) +
550 common.MP(data['u']) + common.MP(data['p']) +
551 common.MP(data['q']))
553 return (common.NS('ssh-dss') + common.MP(data['p']) +
554 common.MP(data['q']) + common.MP(data['g']) +
555 common.MP(data['y']) + common.MP(data['x']))
557 def toString(self, type, extra=None):
559 Create a string representation of this key. If the key is a private
560 key and you want the represenation of its public key, use
561 C{key.public().toString()}. type maps to a _toString_* method.
563 @param type: The type of string to emit. Currently supported values
564 are C{'OPENSSH'}, C{'LSH'}, and C{'AGENTV3'}.
567 @param extra: Any extra data supported by the selected format which
568 is not part of the key itself. For public OpenSSH keys, this is
569 a comment. For private OpenSSH keys, this is a passphrase to
571 @type extra: L{str} or L{NoneType}
575 method = getattr(self, '_toString_%s' % type.upper(), None)
577 raise BadKeyError('unknown type: %s' % type)
578 if method.func_code.co_argcount == 2:
583 def _toString_OPENSSH(self, extra):
585 Return a public or private OpenSSH string. See
586 _fromString_PUBLIC_OPENSSH and _fromString_PRIVATE_OPENSSH for the
587 string formats. If extra is present, it represents a comment for a
588 public key, or a passphrase for a private key.
595 b64Data = base64.encodestring(self.blob()).replace('\n', '')
598 return ('%s %s %s' % (self.sshType(), b64Data, extra)).strip()
600 lines = ['-----BEGIN %s PRIVATE KEY-----' % self.type()]
601 if self.type() == 'RSA':
602 p, q = data['p'], data['q']
603 objData = (0, data['n'], data['e'], data['d'], q, p,
604 data['d'] % (q - 1), data['d'] % (p - 1),
607 objData = (0, data['p'], data['q'], data['g'], data['y'],
609 asn1Sequence = univ.Sequence()
610 for index, value in itertools.izip(itertools.count(), objData):
611 asn1Sequence.setComponentByPosition(index, univ.Integer(value))
612 asn1Data = berEncoder.encode(asn1Sequence)
614 iv = randbytes.secureRandom(8)
615 hexiv = ''.join(['%02X' % ord(x) for x in iv])
616 lines.append('Proc-Type: 4,ENCRYPTED')
617 lines.append('DEK-Info: DES-EDE3-CBC,%s\n' % hexiv)
618 ba = md5(extra + iv).digest()
619 bb = md5(ba + extra + iv).digest()
620 encKey = (ba + bb)[:24]
621 padLen = 8 - (len(asn1Data) % 8)
622 asn1Data += (chr(padLen) * padLen)
623 asn1Data = DES3.new(encKey, DES3.MODE_CBC,
624 iv).encrypt(asn1Data)
625 b64Data = base64.encodestring(asn1Data).replace('\n', '')
626 lines += [b64Data[i:i + 64] for i in range(0, len(b64Data), 64)]
627 lines.append('-----END %s PRIVATE KEY-----' % self.type())
628 return '\n'.join(lines)
630 def _toString_LSH(self):
632 Return a public or private LSH key. See _fromString_PUBLIC_LSH and
633 _fromString_PRIVATE_LSH for the key formats.
639 if self.type() == 'RSA':
640 keyData = sexpy.pack([['public-key', ['rsa-pkcs1-sha1',
641 ['n', common.MP(data['n'])[4:]],
642 ['e', common.MP(data['e'])[4:]]]]])
643 elif self.type() == 'DSA':
644 keyData = sexpy.pack([['public-key', ['dsa',
645 ['p', common.MP(data['p'])[4:]],
646 ['q', common.MP(data['q'])[4:]],
647 ['g', common.MP(data['g'])[4:]],
648 ['y', common.MP(data['y'])[4:]]]]])
649 return '{' + base64.encodestring(keyData).replace('\n', '') + '}'
651 if self.type() == 'RSA':
652 p, q = data['p'], data['q']
653 return sexpy.pack([['private-key', ['rsa-pkcs1',
654 ['n', common.MP(data['n'])[4:]],
655 ['e', common.MP(data['e'])[4:]],
656 ['d', common.MP(data['d'])[4:]],
657 ['p', common.MP(q)[4:]],
658 ['q', common.MP(p)[4:]],
659 ['a', common.MP(data['d'] % (q - 1))[4:]],
660 ['b', common.MP(data['d'] % (p - 1))[4:]],
661 ['c', common.MP(data['u'])[4:]]]]])
662 elif self.type() == 'DSA':
663 return sexpy.pack([['private-key', ['dsa',
664 ['p', common.MP(data['p'])[4:]],
665 ['q', common.MP(data['q'])[4:]],
666 ['g', common.MP(data['g'])[4:]],
667 ['y', common.MP(data['y'])[4:]],
668 ['x', common.MP(data['x'])[4:]]]]])
670 def _toString_AGENTV3(self):
672 Return a private Secure Shell Agent v3 key. See
673 _fromString_AGENTV3 for the key format.
678 if not self.isPublic():
679 if self.type() == 'RSA':
680 values = (data['e'], data['d'], data['n'], data['u'],
681 data['p'], data['q'])
682 elif self.type() == 'DSA':
683 values = (data['p'], data['q'], data['g'], data['y'],
685 return common.NS(self.sshType()) + ''.join(map(common.MP, values))
688 def sign(self, data):
690 Returns a signature with this Key.
695 if self.type() == 'RSA':
696 digest = pkcs1Digest(data, self.keyObject.size()/8)
697 signature = self.keyObject.sign(digest, '')[0]
698 ret = common.NS(Util.number.long_to_bytes(signature))
699 elif self.type() == 'DSA':
700 digest = sha1(data).digest()
701 randomBytes = randbytes.secureRandom(19)
702 sig = self.keyObject.sign(digest, randomBytes)
703 # SSH insists that the DSS signature blob be two 160-bit integers
704 # concatenated together. The sig[0], [1] numbers from obj.sign
705 # are just numbers, and could be any length from 0 to 160 bits.
706 # Make sure they are padded out to 160 bits (20 bytes each)
707 ret = common.NS(Util.number.long_to_bytes(sig[0], 20) +
708 Util.number.long_to_bytes(sig[1], 20))
709 return common.NS(self.sshType()) + ret
711 def verify(self, signature, data):
713 Returns true if the signature for data is valid for this Key.
715 @type signature: C{str}
719 signatureType, signature = common.getNS(signature)
720 if signatureType != self.sshType():
722 if self.type() == 'RSA':
723 numbers = common.getMP(signature)
724 digest = pkcs1Digest(data, self.keyObject.size() / 8)
725 elif self.type() == 'DSA':
726 signature = common.getNS(signature)[0]
727 numbers = [Util.number.bytes_to_long(n) for n in signature[:20],
729 digest = sha1(data).digest()
730 return self.keyObject.verify(digest, numbers)
735 Return the SSH key type corresponding to a C{Crypto.PublicKey.pubkey.pubkey}
738 @type obj: C{Crypto.PublicKey.pubkey.pubkey}
742 ('n', 'e', 'd', 'p', 'q'): 'ssh-rsa',
743 ('n', 'e', 'd', 'p', 'q', 'u'): 'ssh-rsa',
744 ('y', 'g', 'p', 'q', 'x'): 'ssh-dss'
747 return keyDataMapping[tuple(obj.keydata)]
748 except (KeyError, AttributeError):
749 raise BadKeyError("invalid key object", obj)
751 def pkcs1Pad(data, messageLength):
753 Pad out data to messageLength according to the PKCS#1 standard.
755 @type messageLength: C{int}
757 lenPad = messageLength - 2 - len(data)
758 return '\x01' + ('\xff' * lenPad) + '\x00' + data
760 def pkcs1Digest(data, messageLength):
762 Create a message digest using the SHA1 hash algorithm according to the
765 @type messageLength: C{str}
767 digest = sha1(data).digest()
768 return pkcs1Pad(ID_SHA1+digest, messageLength)
772 Return the length of the signature in bytes for a key object.
774 @type obj: C{Crypto.PublicKey.pubkey.pubkey}
780 ID_SHA1 = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'