1# -*- test-case-name: twisted.conch.test.test_keys -*-
2# Copyright (c) Twisted Matrix Laboratories.
3# See LICENSE for details.
4
5"""
6Handling of RSA and DSA keys.
7
8Maintainer: U{Paul Swartz}
9"""
10
11# base library imports
12import base64
13import itertools
14from hashlib import md5, sha1
15
16# external library imports
17from Crypto.Cipher import DES3, AES
18from Crypto.PublicKey import RSA, DSA
19from Crypto import Util
20from pyasn1.error import PyAsn1Error
21from pyasn1.type import univ
22from pyasn1.codec.ber import decoder as berDecoder
23from pyasn1.codec.ber import encoder as berEncoder
24
25# twisted
26from twisted.python import randbytes
27
28# sibling imports
29from twisted.conch.ssh import common, sexpy
30
31
32
33class BadKeyError(Exception):
34    """
35    Raised when a key isn't what we expected from it.
36
37    XXX: we really need to check for bad keys
38    """
39
40
41
42class EncryptedKeyError(Exception):
43    """
44    Raised when an encrypted key is presented to fromString/fromFile without
45    a password.
46    """
47
48
49
50class Key(object):
51    """
52    An object representing a key.  A key can be either a public or
53    private key.  A public key can verify a signature; a private key can
54    create or verify a signature.  To generate a string that can be stored
55    on disk, use the toString method.  If you have a private key, but want
56    the string representation of the public key, use Key.public().toString().
57
58    @ivar keyObject: The C{Crypto.PublicKey.pubkey.pubkey} object that
59                  operations are performed with.
60    """
61
62    def fromFile(Class, filename, type=None, passphrase=None):
63        """
64        Return a Key object corresponding to the data in filename.  type
65        and passphrase function as they do in fromString.
66        """
67        return Class.fromString(file(filename, 'rb').read(), type, passphrase)
68    fromFile = classmethod(fromFile)
69
70
71    def fromString(Class, data, type=None, passphrase=None):
72        """
73        Return a Key object corresponding to the string data.
74        type is optionally the type of string, matching a _fromString_*
75        method.  Otherwise, the _guessStringType() classmethod will be used
76        to guess a type.  If the key is encrypted, passphrase is used as
77        the decryption key.
78
79        @type data: C{str}
80        @type type: C{None}/C{str}
81        @type passphrase: C{None}/C{str}
82        @rtype: C{Key}
83        """
84        if type is None:
85            type = Class._guessStringType(data)
86        if type is None:
87            raise BadKeyError('cannot guess the type of %r' % data)
88        method = getattr(Class, '_fromString_%s' % type.upper(), None)
89        if method is None:
90            raise BadKeyError('no _fromString method for %s' % type)
91        if method.func_code.co_argcount == 2:  # no passphrase
92            if passphrase:
93                raise BadKeyError('key not encrypted')
94            return method(data)
95        else:
96            return method(data, passphrase)
97    fromString = classmethod(fromString)
98
99
100    def _fromString_BLOB(Class, blob):
101        """
102        Return a public key object corresponding to this public key blob.
103        The format of a RSA public key blob is::
104            string 'ssh-rsa'
105            integer e
106            integer n
107
108        The format of a DSA public key blob is::
109            string 'ssh-dss'
110            integer p
111            integer q
112            integer g
113            integer y
114
115        @type blob: C{str}
116        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
117        @raises BadKeyError: if the key type (the first string) is unknown.
118        """
119        keyType, rest = common.getNS(blob)
120        if keyType == 'ssh-rsa':
121            e, n, rest = common.getMP(rest, 2)
122            return Class(RSA.construct((n, e)))
123        elif keyType == 'ssh-dss':
124            p, q, g, y, rest = common.getMP(rest, 4)
125            return Class(DSA.construct((y, g, p, q)))
126        else:
127            raise BadKeyError('unknown blob type: %s' % keyType)
128    _fromString_BLOB = classmethod(_fromString_BLOB)
129
130
131    def _fromString_PRIVATE_BLOB(Class, blob):
132        """
133        Return a private key object corresponding to this private key blob.
134        The blob formats are as follows:
135
136        RSA keys::
137            string 'ssh-rsa'
138            integer n
139            integer e
140            integer d
141            integer u
142            integer p
143            integer q
144
145        DSA keys::
146            string 'ssh-dss'
147            integer p
148            integer q
149            integer g
150            integer y
151            integer x
152
153        @type blob: C{str}
154        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
155        @raises BadKeyError: if the key type (the first string) is unknown.
156        """
157        keyType, rest = common.getNS(blob)
158
159        if keyType == 'ssh-rsa':
160            n, e, d, u, p, q, rest = common.getMP(rest, 6)
161            rsakey = Class(RSA.construct((n, e, d, p, q, u)))
162            return rsakey
163        elif keyType == 'ssh-dss':
164            p, q, g, y, x, rest = common.getMP(rest, 5)
165            dsakey = Class(DSA.construct((y, g, p, q, x)))
166            return dsakey
167        else:
168            raise BadKeyError('unknown blob type: %s' % keyType)
169    _fromString_PRIVATE_BLOB = classmethod(_fromString_PRIVATE_BLOB)
170
171
172    def _fromString_PUBLIC_OPENSSH(Class, data):
173        """
174        Return a public key object corresponding to this OpenSSH public key
175        string.  The format of an OpenSSH public key string is::
176            <key type> <base64-encoded public key blob>
177
178        @type data: C{str}
179        @return: A {Crypto.PublicKey.pubkey.pubkey} object
180        @raises BadKeyError: if the blob type is unknown.
181        """
182        blob = base64.decodestring(data.split()[1])
183        return Class._fromString_BLOB(blob)
184    _fromString_PUBLIC_OPENSSH = classmethod(_fromString_PUBLIC_OPENSSH)
185
186
187    def _fromString_PRIVATE_OPENSSH(Class, data, passphrase):
188        """
189        Return a private key object corresponding to this OpenSSH private key
190        string.  If the key is encrypted, passphrase MUST be provided.
191        Providing a passphrase for an unencrypted key is an error.
192
193        The format of an OpenSSH private key string is::
194            -----BEGIN <key type> PRIVATE KEY-----
195            [Proc-Type: 4,ENCRYPTED
196            DEK-Info: DES-EDE3-CBC,<initialization value>]
197            <base64-encoded ASN.1 structure>
198            ------END <key type> PRIVATE KEY------
199
200        The ASN.1 structure of a RSA key is::
201            (0, n, e, d, p, q)
202
203        The ASN.1 structure of a DSA key is::
204            (0, p, q, g, y, x)
205
206        @type data: C{str}
207        @type passphrase: C{str}
208        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
209        @raises BadKeyError: if
210            * a passphrase is provided for an unencrypted key
211            * the ASN.1 encoding is incorrect
212        @raises EncryptedKeyError: if
213            * a passphrase is not provided for an encrypted key
214        """
215        lines = data.strip().split('\n')
216        kind = lines[0][11:14]
217        if lines[1].startswith('Proc-Type: 4,ENCRYPTED'):  # encrypted key
218            if not passphrase:
219                raise EncryptedKeyError('Passphrase must be provided '
220                                        'for an encrypted key')
221
222            # Determine cipher and initialization vector
223            try:
224                _, cipher_iv_info = lines[2].split(' ', 1)
225                cipher, ivdata = cipher_iv_info.rstrip().split(',', 1)
226            except ValueError:
227                raise BadKeyError('invalid DEK-info %r' % lines[2])
228
229            if cipher == 'AES-128-CBC':
230                CipherClass = AES
231                keySize = 16
232                if len(ivdata) != 32:
233                    raise BadKeyError('AES encrypted key with a bad IV')
234            elif cipher == 'DES-EDE3-CBC':
235                CipherClass = DES3
236                keySize = 24
237                if len(ivdata) != 16:
238                    raise BadKeyError('DES encrypted key with a bad IV')
239            else:
240                raise BadKeyError('unknown encryption type %r' % cipher)
241
242            # extract keyData for decoding
243            iv = ''.join([chr(int(ivdata[i:i + 2], 16))
244                          for i in range(0, len(ivdata), 2)])
245            ba = md5(passphrase + iv[:8]).digest()
246            bb = md5(ba + passphrase + iv[:8]).digest()
247            decKey = (ba + bb)[:keySize]
248            b64Data = base64.decodestring(''.join(lines[3:-1]))
249            keyData = CipherClass.new(decKey,
250                                      CipherClass.MODE_CBC,
251                                      iv).decrypt(b64Data)
252            removeLen = ord(keyData[-1])
253            keyData = keyData[:-removeLen]
254        else:
255            b64Data = ''.join(lines[1:-1])
256            keyData = base64.decodestring(b64Data)
257
258        try:
259            decodedKey = berDecoder.decode(keyData)[0]
260        except PyAsn1Error, e:
261            raise BadKeyError('Failed to decode key (Bad Passphrase?): %s' % e)
262
263        if kind == 'RSA':
264            if len(decodedKey) == 2:  # alternate RSA key
265                decodedKey = decodedKey[0]
266            if len(decodedKey) < 6:
267                raise BadKeyError('RSA key failed to decode properly')
268
269            n, e, d, p, q = [long(value) for value in decodedKey[1:6]]
270            if p > q:  # make p smaller than q
271                p, q = q, p
272            return Class(RSA.construct((n, e, d, p, q)))
273        elif kind == 'DSA':
274            p, q, g, y, x = [long(value) for value in decodedKey[1: 6]]
275            if len(decodedKey) < 6:
276                raise BadKeyError('DSA key failed to decode properly')
277            return Class(DSA.construct((y, g, p, q, x)))
278    _fromString_PRIVATE_OPENSSH = classmethod(_fromString_PRIVATE_OPENSSH)
279
280
281    def _fromString_PUBLIC_LSH(Class, data):
282        """
283        Return a public key corresponding to this LSH public key string.
284        The LSH public key string format is::
285            <s-expression: ('public-key', (<key type>, (<name, <value>)+))>
286
287        The names for a RSA (key type 'rsa-pkcs1-sha1') key are: n, e.
288        The names for a DSA (key type 'dsa') key are: y, g, p, q.
289
290        @type data: C{str}
291        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
292        @raises BadKeyError: if the key type is unknown
293        """
294        sexp = sexpy.parse(base64.decodestring(data[1:-1]))
295        assert sexp[0] == 'public-key'
296        kd = {}
297        for name, data in sexp[1][1:]:
298            kd[name] = common.getMP(common.NS(data))[0]
299        if sexp[1][0] == 'dsa':
300            return Class(DSA.construct((kd['y'], kd['g'], kd['p'], kd['q'])))
301        elif sexp[1][0] == 'rsa-pkcs1-sha1':
302            return Class(RSA.construct((kd['n'], kd['e'])))
303        else:
304            raise BadKeyError('unknown lsh key type %s' % sexp[1][0])
305    _fromString_PUBLIC_LSH = classmethod(_fromString_PUBLIC_LSH)
306
307
308    def _fromString_PRIVATE_LSH(Class, data):
309        """
310        Return a private key corresponding to this LSH private key string.
311        The LSH private key string format is::
312            <s-expression: ('private-key', (<key type>, (<name>, <value>)+))>
313
314        The names for a RSA (key type 'rsa-pkcs1-sha1') key are: n, e, d, p, q.
315        The names for a DSA (key type 'dsa') key are: y, g, p, q, x.
316
317        @type data: C{str}
318        @return: a {Crypto.PublicKey.pubkey.pubkey} object
319        @raises BadKeyError: if the key type is unknown
320        """
321        sexp = sexpy.parse(data)
322        assert sexp[0] == 'private-key'
323        kd = {}
324        for name, data in sexp[1][1:]:
325            kd[name] = common.getMP(common.NS(data))[0]
326        if sexp[1][0] == 'dsa':
327            assert len(kd) == 5, len(kd)
328            return Class(DSA.construct((kd['y'], kd['g'], kd['p'],
329                                        kd['q'], kd['x'])))
330        elif sexp[1][0] == 'rsa-pkcs1':
331            assert len(kd) == 8, len(kd)
332            if kd['p'] > kd['q']:  # make p smaller than q
333                kd['p'], kd['q'] = kd['q'], kd['p']
334            return Class(RSA.construct((kd['n'], kd['e'], kd['d'],
335                                        kd['p'], kd['q'])))
336        else:
337            raise BadKeyError('unknown lsh key type %s' % sexp[1][0])
338    _fromString_PRIVATE_LSH = classmethod(_fromString_PRIVATE_LSH)
339
340
341    def _fromString_AGENTV3(Class, data):
342        """
343        Return a private key object corresponsing to the Secure Shell Key
344        Agent v3 format.
345
346        The SSH Key Agent v3 format for a RSA key is::
347            string 'ssh-rsa'
348            integer e
349            integer d
350            integer n
351            integer u
352            integer p
353            integer q
354
355        The SSH Key Agent v3 format for a DSA key is::
356            string 'ssh-dss'
357            integer p
358            integer q
359            integer g
360            integer y
361            integer x
362
363        @type data: C{str}
364        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
365        @raises BadKeyError: if the key type (the first string) is unknown
366        """
367        keyType, data = common.getNS(data)
368        if keyType == 'ssh-dss':
369            p, data = common.getMP(data)
370            q, data = common.getMP(data)
371            g, data = common.getMP(data)
372            y, data = common.getMP(data)
373            x, data = common.getMP(data)
374            return Class(DSA.construct((y, g, p, q, x)))
375        elif keyType == 'ssh-rsa':
376            e, data = common.getMP(data)
377            d, data = common.getMP(data)
378            n, data = common.getMP(data)
379            u, data = common.getMP(data)
380            p, data = common.getMP(data)
381            q, data = common.getMP(data)
382            return Class(RSA.construct((n, e, d, p, q, u)))
383        else:
384            raise BadKeyError("unknown key type %s" % keyType)
385    _fromString_AGENTV3 = classmethod(_fromString_AGENTV3)
386
387
388    def _guessStringType(Class, data):
389        """
390        Guess the type of key in data.  The types map to _fromString_*
391        methods.
392        """
393        if data.startswith('ssh-'):
394            return 'public_openssh'
395        elif data.startswith('-----BEGIN'):
396            return 'private_openssh'
397        elif data.startswith('{'):
398            return 'public_lsh'
399        elif data.startswith('('):
400            return 'private_lsh'
401        elif data.startswith('\x00\x00\x00\x07ssh-'):
402            ignored, rest = common.getNS(data)
403            count = 0
404            while rest:
405                count += 1
406                ignored, rest = common.getMP(rest)
407            if count > 4:
408                return 'agentv3'
409            else:
410                return 'blob'
411    _guessStringType = classmethod(_guessStringType)
412
413
414    def __init__(self, keyObject):
415        """
416        Initialize a PublicKey with a C{Crypto.PublicKey.pubkey.pubkey}
417        object.
418
419        @type keyObject: C{Crypto.PublicKey.pubkey.pubkey}
420        """
421        self.keyObject = keyObject
422
423
424    def __eq__(self, other):
425        """
426        Return True if other represents an object with the same key.
427        """
428        if type(self) == type(other):
429            return self.type() == other.type() and self.data() == other.data()
430        else:
431            return NotImplemented
432
433
434    def __ne__(self, other):
435        """
436        Return True if other represents anything other than this key.
437        """
438        result = self.__eq__(other)
439        if result == NotImplemented:
440            return result
441        return not result
442
443
444    def __repr__(self):
445        """
446        Return a pretty representation of this object.
447        """
448        lines = [
449            '<%s %s (%s bits)' % (
450                self.type(),
451                self.isPublic() and 'Public Key' or 'Private Key',
452                self.keyObject.size())]
453        for k, v in sorted(self.data().items()):
454            lines.append('attr %s:' % k)
455            by = common.MP(v)[4:]
456            while by:
457                m = by[:15]
458                by = by[15:]
459                o = ''
460                for c in m:
461                    o = o + '%02x:' % ord(c)
462                if len(m) < 15:
463                    o = o[:-1]
464                lines.append('\t' + o)
465        lines[-1] = lines[-1] + '>'
466        return '\n'.join(lines)
467
468
469    def isPublic(self):
470        """
471        Returns True if this Key is a public key.
472        """
473        return not self.keyObject.has_private()
474
475
476    def public(self):
477        """
478        Returns a version of this key containing only the public key data.
479        If this is a public key, this may or may not be the same object
480        as self.
481        """
482        return Key(self.keyObject.publickey())
483
484
485    def fingerprint(self):
486        """
487        Get the user presentation of the fingerprint of this L{Key}.  As
488        described by U{RFC 4716 section
489        4<http://tools.ietf.org/html/rfc4716#section-4>}::
490
491            The fingerprint of a public key consists of the output of the MD5
492            message-digest algorithm [RFC1321].  The input to the algorithm is
493            the public key data as specified by [RFC4253].  (...)  The output
494            of the (MD5) algorithm is presented to the user as a sequence of 16
495            octets printed as hexadecimal with lowercase letters and separated
496            by colons.
497
498        @since: 8.2
499
500        @return: the user presentation of this L{Key}'s fingerprint, as a
501        string.
502
503        @rtype: L{str}
504        """
505        return ':'.join([x.encode('hex') for x in md5(self.blob()).digest()])
506
507
508    def type(self):
509        """
510        Return the type of the object we wrap.  Currently this can only be
511        'RSA' or 'DSA'.
512        """
513        # the class is Crypto.PublicKey.<type>.<stuff we don't care about>
514        mod = self.keyObject.__class__.__module__
515        if mod.startswith('Crypto.PublicKey'):
516            type = mod.split('.')[2]
517        else:
518            raise RuntimeError('unknown type of object: %r' % self.keyObject)
519        if type in ('RSA', 'DSA'):
520            return type
521        else:
522            raise RuntimeError('unknown type of key: %s' % type)
523
524
525    def sshType(self):
526        """
527        Return the type of the object we wrap as defined in the ssh protocol.
528        Currently this can only be 'ssh-rsa' or 'ssh-dss'.
529        """
530        return {'RSA': 'ssh-rsa', 'DSA': 'ssh-dss'}[self.type()]
531
532
533    def data(self):
534        """
535        Return the values of the public key as a dictionary.
536
537        @rtype: C{dict}
538        """
539        keyData = {}
540        for name in self.keyObject.keydata:
541            value = getattr(self.keyObject, name, None)
542            if value is not None:
543                keyData[name] = value
544        return keyData
545
546
547    def blob(self):
548        """
549        Return the public key blob for this key.  The blob is the
550        over-the-wire format for public keys:
551
552        RSA keys::
553            string  'ssh-rsa'
554            integer e
555            integer n
556
557        DSA keys::
558            string  'ssh-dss'
559            integer p
560            integer q
561            integer g
562            integer y
563
564        @rtype: C{str}
565        """
566        type = self.type()
567        data = self.data()
568        if type == 'RSA':
569            return (common.NS('ssh-rsa') + common.MP(data['e']) +
570                    common.MP(data['n']))
571        elif type == 'DSA':
572            return (common.NS('ssh-dss') + common.MP(data['p']) +
573                    common.MP(data['q']) + common.MP(data['g']) +
574                    common.MP(data['y']))
575
576
577    def privateBlob(self):
578        """
579        Return the private key blob for this key.  The blob is the
580        over-the-wire format for private keys:
581
582        RSA keys::
583            string 'ssh-rsa'
584            integer n
585            integer e
586            integer d
587            integer u
588            integer p
589            integer q
590
591        DSA keys::
592            string 'ssh-dss'
593            integer p
594            integer q
595            integer g
596            integer y
597            integer x
598        """
599        type = self.type()
600        data = self.data()
601        if type == 'RSA':
602            return (common.NS('ssh-rsa') + common.MP(data['n']) +
603                    common.MP(data['e']) + common.MP(data['d']) +
604                    common.MP(data['u']) + common.MP(data['p']) +
605                    common.MP(data['q']))
606        elif type == 'DSA':
607            return (common.NS('ssh-dss') + common.MP(data['p']) +
608                    common.MP(data['q']) + common.MP(data['g']) +
609                    common.MP(data['y']) + common.MP(data['x']))
610
611
612    def toString(self, type, extra=None):
613        """
614        Create a string representation of this key.  If the key is a private
615        key and you want the represenation of its public key, use
616        C{key.public().toString()}.  type maps to a _toString_* method.
617
618        @param type: The type of string to emit.  Currently supported values
619            are C{'OPENSSH'}, C{'LSH'}, and C{'AGENTV3'}.
620        @type type: L{str}
621
622        @param extra: Any extra data supported by the selected format which
623            is not part of the key itself.  For public OpenSSH keys, this is
624            a comment.  For private OpenSSH keys, this is a passphrase to
625            encrypt with.
626        @type extra: L{str} or L{NoneType}
627
628        @rtype: L{str}
629        """
630        method = getattr(self, '_toString_%s' % type.upper(), None)
631        if method is None:
632            raise BadKeyError('unknown type: %s' % type)
633        if method.func_code.co_argcount == 2:
634            return method(extra)
635        else:
636            return method()
637
638
639    def _toString_OPENSSH(self, extra):
640        """
641        Return a public or private OpenSSH string.  See
642        _fromString_PUBLIC_OPENSSH and _fromString_PRIVATE_OPENSSH for the
643        string formats.  If extra is present, it represents a comment for a
644        public key, or a passphrase for a private key.
645
646        @param extra: Comment for a public key or passphrase for a
647            private key
648        @type extra: C{str}
649
650        @rtype: C{str}
651        """
652        data = self.data()
653        if self.isPublic():
654            b64Data = base64.encodestring(self.blob()).replace('\n', '')
655            if not extra:
656                extra = ''
657            return ('%s %s %s' % (self.sshType(), b64Data, extra)).strip()
658        else:
659            lines = ['-----BEGIN %s PRIVATE KEY-----' % self.type()]
660            if self.type() == 'RSA':
661                p, q = data['p'], data['q']
662                objData = (0, data['n'], data['e'], data['d'], q, p,
663                           data['d'] % (q - 1), data['d'] % (p - 1),
664                           data['u'])
665            else:
666                objData = (0, data['p'], data['q'], data['g'], data['y'],
667                           data['x'])
668            asn1Sequence = univ.Sequence()
669            for index, value in itertools.izip(itertools.count(), objData):
670                asn1Sequence.setComponentByPosition(index, univ.Integer(value))
671            asn1Data = berEncoder.encode(asn1Sequence)
672            if extra:
673                iv = randbytes.secureRandom(8)
674                hexiv = ''.join(['%02X' % ord(x) for x in iv])
675                lines.append('Proc-Type: 4,ENCRYPTED')
676                lines.append('DEK-Info: DES-EDE3-CBC,%s\n' % hexiv)
677                ba = md5(extra + iv).digest()
678                bb = md5(ba + extra + iv).digest()
679                encKey = (ba + bb)[:24]
680                padLen = 8 - (len(asn1Data) % 8)
681                asn1Data += (chr(padLen) * padLen)
682                asn1Data = DES3.new(encKey, DES3.MODE_CBC,
683                                    iv).encrypt(asn1Data)
684            b64Data = base64.encodestring(asn1Data).replace('\n', '')
685            lines += [b64Data[i:i + 64] for i in range(0, len(b64Data), 64)]
686            lines.append('-----END %s PRIVATE KEY-----' % self.type())
687            return '\n'.join(lines)
688
689
690    def _toString_LSH(self):
691        """
692        Return a public or private LSH key.  See _fromString_PUBLIC_LSH and
693        _fromString_PRIVATE_LSH for the key formats.
694
695        @rtype: C{str}
696        """
697        data = self.data()
698        if self.isPublic():
699            if self.type() == 'RSA':
700                keyData = sexpy.pack([['public-key',
701                                       ['rsa-pkcs1-sha1',
702                                        ['n', common.MP(data['n'])[4:]],
703                                        ['e', common.MP(data['e'])[4:]]]]])
704            elif self.type() == 'DSA':
705                keyData = sexpy.pack([['public-key',
706                                       ['dsa',
707                                        ['p', common.MP(data['p'])[4:]],
708                                        ['q', common.MP(data['q'])[4:]],
709                                        ['g', common.MP(data['g'])[4:]],
710                                        ['y', common.MP(data['y'])[4:]]]]])
711            return '{' + base64.encodestring(keyData).replace('\n', '') + '}'
712        else:
713            if self.type() == 'RSA':
714                p, q = data['p'], data['q']
715                return sexpy.pack([['private-key',
716                                    ['rsa-pkcs1',
717                                     ['n', common.MP(data['n'])[4:]],
718                                     ['e', common.MP(data['e'])[4:]],
719                                     ['d', common.MP(data['d'])[4:]],
720                                     ['p', common.MP(q)[4:]],
721                                     ['q', common.MP(p)[4:]],
722                                     ['a', common.MP(data['d'] % (q - 1))[4:]],
723                                     ['b', common.MP(data['d'] % (p - 1))[4:]],
724                                     ['c', common.MP(data['u'])[4:]]]]])
725            elif self.type() == 'DSA':
726                return sexpy.pack([['private-key',
727                                    ['dsa',
728                                     ['p', common.MP(data['p'])[4:]],
729                                     ['q', common.MP(data['q'])[4:]],
730                                     ['g', common.MP(data['g'])[4:]],
731                                     ['y', common.MP(data['y'])[4:]],
732                                     ['x', common.MP(data['x'])[4:]]]]])
733
734
735    def _toString_AGENTV3(self):
736        """
737        Return a private Secure Shell Agent v3 key.  See
738        _fromString_AGENTV3 for the key format.
739
740        @rtype: C{str}
741        """
742        data = self.data()
743        if not self.isPublic():
744            if self.type() == 'RSA':
745                values = (data['e'], data['d'], data['n'], data['u'],
746                          data['p'], data['q'])
747            elif self.type() == 'DSA':
748                values = (data['p'], data['q'], data['g'], data['y'],
749                          data['x'])
750            return common.NS(self.sshType()) + ''.join(map(common.MP, values))
751
752
753    def sign(self, data):
754        """
755        Returns a signature with this Key.
756
757        @type data: C{str}
758        @rtype: C{str}
759        """
760        if self.type() == 'RSA':
761            digest = pkcs1Digest(data, self.keyObject.size() / 8)
762            signature = self.keyObject.sign(digest, '')[0]
763            ret = common.NS(Util.number.long_to_bytes(signature))
764        elif self.type() == 'DSA':
765            digest = sha1(data).digest()
766            randomBytes = randbytes.secureRandom(19)
767            sig = self.keyObject.sign(digest, randomBytes)
768            # SSH insists that the DSS signature blob be two 160-bit integers
769            # concatenated together. The sig[0], [1] numbers from obj.sign
770            # are just numbers, and could be any length from 0 to 160 bits.
771            # Make sure they are padded out to 160 bits (20 bytes each)
772            ret = common.NS(Util.number.long_to_bytes(sig[0], 20) +
773                            Util.number.long_to_bytes(sig[1], 20))
774        return common.NS(self.sshType()) + ret
775
776
777    def verify(self, signature, data):
778        """
779        Returns true if the signature for data is valid for this Key.
780
781        @type signature: C{str}
782        @type data: C{str}
783        @rtype: C{bool}
784        """
785        if len(signature) == 40:
786            # DSA key with no padding
787            signatureType, signature = 'ssh-dss', common.NS(signature)
788        else:
789            signatureType, signature = common.getNS(signature)
790        if signatureType != self.sshType():
791            return False
792        if self.type() == 'RSA':
793            numbers = common.getMP(signature)
794            digest = pkcs1Digest(data, self.keyObject.size() / 8)
795        elif self.type() == 'DSA':
796            signature = common.getNS(signature)[0]
797            numbers = [Util.number.bytes_to_long(n) for n in signature[:20],
798                       signature[20:]]
799            digest = sha1(data).digest()
800        return self.keyObject.verify(digest, numbers)
801
802
803
804def objectType(obj):
805    """
806    Return the SSH key type corresponding to a
807    C{Crypto.PublicKey.pubkey.pubkey} object.
808
809    @type obj:  C{Crypto.PublicKey.pubkey.pubkey}
810    @rtype:     C{str}
811    """
812    keyDataMapping = {
813        ('n', 'e', 'd', 'p', 'q'): 'ssh-rsa',
814        ('n', 'e', 'd', 'p', 'q', 'u'): 'ssh-rsa',
815        ('y', 'g', 'p', 'q', 'x'): 'ssh-dss'
816    }
817    try:
818        return keyDataMapping[tuple(obj.keydata)]
819    except (KeyError, AttributeError):
820        raise BadKeyError("invalid key object", obj)
821
822
823
824def pkcs1Pad(data, messageLength):
825    """
826    Pad out data to messageLength according to the PKCS#1 standard.
827    @type data: C{str}
828    @type messageLength: C{int}
829    """
830    lenPad = messageLength - 2 - len(data)
831    return '\x01' + ('\xff' * lenPad) + '\x00' + data
832
833
834
835def pkcs1Digest(data, messageLength):
836    """
837    Create a message digest using the SHA1 hash algorithm according to the
838    PKCS#1 standard.
839    @type data: C{str}
840    @type messageLength: C{str}
841    """
842    digest = sha1(data).digest()
843    return pkcs1Pad(ID_SHA1 + digest, messageLength)
844
845
846
847def lenSig(obj):
848    """
849    Return the length of the signature in bytes for a key object.
850
851    @type obj: C{Crypto.PublicKey.pubkey.pubkey}
852    @rtype: C{long}
853    """
854    return obj.size() / 8
855
856
857ID_SHA1 = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
858