root/trunk/twisted/conch/ssh/keys.py

Revision 33046, 27.8 KB (checked in by exarkun, 7 months ago)

Merge pubkey-check-5349

Author: antoine
Reviewer: exarkun
Fixes: #5349

Explicitly use the module name of the class of the key object
to determine what type of key it is, instead of relying on the
string representation of the class object.

This avoids a problem on Python 3 where the PyCrypto key class
becomes new-style and has a different string representation.

Line 
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 warnings
14import itertools
15
16# external library imports
17from Crypto.Cipher import DES3
18from Crypto.PublicKey import RSA, DSA
19from Crypto import Util
20from pyasn1.type import univ
21from pyasn1.codec.ber import decoder as berDecoder
22from pyasn1.codec.ber import encoder as berEncoder
23
24# twisted
25from twisted.python import randbytes
26from twisted.python.hashlib import md5, sha1
27
28# sibling imports
29from twisted.conch.ssh import common, sexpy
30
31
32class BadKeyError(Exception):
33    """
34    Raised when a key isn't what we expected from it.
35
36    XXX: we really need to check for bad keys
37    """
38
39class EncryptedKeyError(Exception):
40    """
41    Raised when an encrypted key is presented to fromString/fromFile without
42    a password.
43    """
44
45class Key(object):
46    """
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().
52
53    @ivar keyObject: The C{Crypto.PublicKey.pubkey.pubkey} object that
54                  operations are performed with.
55    """
56
57    def fromFile(Class, filename, type=None, passphrase=None):
58        """
59        Return a Key object corresponding to the data in filename.  type
60        and passphrase function as they do in fromString.
61        """
62        return Class.fromString(file(filename, 'rb').read(), type, passphrase)
63    fromFile = classmethod(fromFile)
64
65    def fromString(Class, data, type=None, passphrase=None):
66        """
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
71        the decryption key.
72
73        @type data: C{str}
74        @type type: C{None}/C{str}
75        @type passphrase: C{None}/C{str}
76        @rtype: C{Key}
77        """
78        if type is None:
79            type = Class._guessStringType(data)
80        if type is None:
81            raise BadKeyError('cannot guess the type of %r' % data)
82        method = getattr(Class, '_fromString_%s' % type.upper(), None)
83        if method is None:
84            raise BadKeyError('no _fromString method for %s' % type)
85        if method.func_code.co_argcount == 2: # no passphrase
86            if passphrase:
87                raise BadKeyError('key not encrypted')
88            return method(data)
89        else:
90            return method(data, passphrase)
91    fromString = classmethod(fromString)
92
93    def _fromString_BLOB(Class, blob):
94        """
95        Return a public key object corresponding to this public key blob.
96        The format of a RSA public key blob is::
97            string 'ssh-rsa'
98            integer e
99            integer n
100
101        The format of a DSA public key blob is::
102            string 'ssh-dss'
103            integer p
104            integer q
105            integer g
106            integer y
107
108        @type blob: C{str}
109        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
110        @raises BadKeyError: if the key type (the first string) is unknown.
111        """
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)))
119        else:
120            raise BadKeyError('unknown blob type: %s' % keyType)
121    _fromString_BLOB = classmethod(_fromString_BLOB)
122
123    def _fromString_PRIVATE_BLOB(Class, blob):
124        """
125        Return a private key object corresponding to this private key blob.
126        The blob formats are as follows:
127
128        RSA keys::
129            string 'ssh-rsa'
130            integer n
131            integer e
132            integer d
133            integer u
134            integer p
135            integer q
136
137        DSA keys::
138            string 'ssh-dss'
139            integer p
140            integer q
141            integer g
142            integer y
143            integer x
144
145        @type blob: C{str}
146        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
147        @raises BadKeyError: if the key type (the first string) is unknown.
148        """
149        keyType, rest = common.getNS(blob)
150
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)))
154            return rsakey
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)))
158            return dsakey
159        else:
160            raise BadKeyError('unknown blob type: %s' % keyType)
161    _fromString_PRIVATE_BLOB = classmethod(_fromString_PRIVATE_BLOB)
162
163    def _fromString_PUBLIC_OPENSSH(Class, data):
164        """
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>
168
169        @type data: C{str}
170        @return: A {Crypto.PublicKey.pubkey.pubkey} object
171        @raises BadKeyError: if the blob type is unknown.
172        """
173        blob = base64.decodestring(data.split()[1])
174        return Class._fromString_BLOB(blob)
175    _fromString_PUBLIC_OPENSSH = classmethod(_fromString_PUBLIC_OPENSSH)
176
177    def _fromString_PRIVATE_OPENSSH(Class, data, passphrase):
178        """
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.
182
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------
189
190        The ASN.1 structure of a RSA key is::
191            (0, n, e, d, p, q)
192
193        The ASN.1 structure of a DSA key is::
194            (0, p, q, g, y, x)
195
196        @type data: C{str}
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
203        """
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,
209                len(ivdata), 2)])
210            if not passphrase:
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]
219        else:
220            b64Data = ''.join(lines[1:-1])
221            keyData = base64.decodestring(b64Data)
222        try:
223            decodedKey = berDecoder.decode(keyData)[0]
224        except Exception, e:
225            raise BadKeyError, 'something wrong with decode'
226        if kind == 'RSA':
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
233                p, q = q, p
234            return Class(RSA.construct((n, e, d, p, q)))
235        elif kind == 'DSA':
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)
241
242    def _fromString_PUBLIC_LSH(Class, data):
243        """
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>)+))>
247
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.
250
251        @type data: C{str}
252        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
253        @raises BadKeyError: if the key type is unknown
254        """
255        sexp = sexpy.parse(base64.decodestring(data[1:-1]))
256        assert sexp[0] == 'public-key'
257        kd = {}
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'])))
264        else:
265            raise BadKeyError('unknown lsh key type %s' % sexp[1][0])
266    _fromString_PUBLIC_LSH = classmethod(_fromString_PUBLIC_LSH)
267
268    def _fromString_PRIVATE_LSH(Class, data):
269        """
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>)+))>
273
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.
276
277        @type data: C{str}
278        @return: a {Crypto.PublicKey.pubkey.pubkey} object
279        @raises BadKeyError: if the key type is unknown
280        """
281        sexp = sexpy.parse(data)
282        assert sexp[0] == 'private-key'
283        kd = {}
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'],
289                kd['q'], kd['x'])))
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'],
295                kd['p'], kd['q'])))
296        else:
297            raise BadKeyError('unknown lsh key type %s' % sexp[1][0])
298    _fromString_PRIVATE_LSH = classmethod(_fromString_PRIVATE_LSH)
299
300    def _fromString_AGENTV3(Class, data):
301        """
302        Return a private key object corresponsing to the Secure Shell Key
303        Agent v3 format.
304
305        The SSH Key Agent v3 format for a RSA key is::
306            string 'ssh-rsa'
307            integer e
308            integer d
309            integer n
310            integer u
311            integer p
312            integer q
313
314        The SSH Key Agent v3 format for a DSA key is::
315            string 'ssh-dss'
316            integer p
317            integer q
318            integer g
319            integer y
320            integer x
321
322        @type data: C{str}
323        @return: a C{Crypto.PublicKey.pubkey.pubkey} object
324        @raises BadKeyError: if the key type (the first string) is unknown
325        """
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)))
342        else:
343            raise BadKeyError("unknown key type %s" % keyType)
344    _fromString_AGENTV3 = classmethod(_fromString_AGENTV3)
345
346    def _guessStringType(Class, data):
347        """
348        Guess the type of key in data.  The types map to _fromString_*
349        methods.
350        """
351        if data.startswith('ssh-'):
352            return 'public_openssh'
353        elif data.startswith('-----BEGIN'):
354            return 'private_openssh'
355        elif data.startswith('{'):
356            return 'public_lsh'
357        elif data.startswith('('):
358            return 'private_lsh'
359        elif data.startswith('\x00\x00\x00\x07ssh-'):
360            ignored, rest = common.getNS(data)
361            count = 0
362            while rest:
363                count += 1
364                ignored, rest = common.getMP(rest)
365            if count > 4:
366                return 'agentv3'
367            else:
368                return 'blob'
369    _guessStringType = classmethod(_guessStringType)
370
371    def __init__(self, keyObject):
372        """
373        Initialize a PublicKey with a C{Crypto.PublicKey.pubkey.pubkey}
374        object.
375
376        @type keyObject: C{Crypto.PublicKey.pubkey.pubkey}
377        """
378        self.keyObject = keyObject
379
380    def __eq__(self, other):
381        """
382        Return True if other represents an object with the same key.
383        """
384        if type(self) == type(other):
385            return self.type() == other.type() and self.data() == other.data()
386        else:
387            return NotImplemented
388
389    def __ne__(self, other):
390        """
391        Return True if other represents anything other than this key.
392        """
393        result = self.__eq__(other)
394        if result == NotImplemented:
395            return result
396        return not result
397
398    def __repr__(self):
399        """
400        Return a pretty representation of this object.
401        """
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:]
408            while by:
409                m = by[:15]
410                by = by[15:]
411                o = ''
412                for c in m:
413                    o = o + '%02x:' % ord(c)
414                if len(m) < 15:
415                    o = o[:-1]
416                lines.append('\t' + o)
417        lines[-1] = lines[-1] + '>'
418        return '\n'.join(lines)
419
420    def isPublic(self):
421        """
422        Returns True if this Key is a public key.
423        """
424        return not self.keyObject.has_private()
425
426    def public(self):
427        """
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
430        as self.
431        """
432        return Key(self.keyObject.publickey())
433
434
435    def fingerprint(self):
436        """
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>}::
440
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
446            by colons.
447
448        @since: 8.2
449
450        @return: the user presentation of this L{Key}'s fingerprint, as a
451        string.
452
453        @rtype: L{str}
454        """
455        return ':'.join([x.encode('hex') for x in md5(self.blob()).digest()])
456
457
458    def type(self):
459        """
460        Return the type of the object we wrap.  Currently this can only be
461        'RSA' or 'DSA'.
462        """
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]
467        else:
468            raise RuntimeError('unknown type of object: %r' % self.keyObject)
469        if type in ('RSA', 'DSA'):
470            return type
471        else:
472            raise RuntimeError('unknown type of key: %s' % type)
473
474    def sshType(self):
475        """
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'.
478        """
479        return {'RSA':'ssh-rsa', 'DSA':'ssh-dss'}[self.type()]
480
481    def data(self):
482        """
483        Return the values of the public key as a dictionary.
484
485        @rtype: C{dict}
486        """
487        keyData = {}
488        for name in self.keyObject.keydata:
489            value = getattr(self.keyObject, name, None)
490            if value is not None:
491                keyData[name] = value
492        return keyData
493
494    def blob(self):
495        """
496        Return the public key blob for this key.  The blob is the
497        over-the-wire format for public keys:
498
499        RSA keys::
500            string  'ssh-rsa'
501            integer e
502            integer n
503
504        DSA keys::
505            string  'ssh-dss'
506            integer p
507            integer q
508            integer g
509            integer y
510
511        @rtype: C{str}
512        """
513        type = self.type()
514        data = self.data()
515        if type == 'RSA':
516            return (common.NS('ssh-rsa') + common.MP(data['e']) +
517                    common.MP(data['n']))
518        elif type == 'DSA':
519            return (common.NS('ssh-dss') + common.MP(data['p']) +
520                    common.MP(data['q']) + common.MP(data['g']) +
521                    common.MP(data['y']))
522
523    def privateBlob(self):
524        """
525        Return the private key blob for this key.  The blob is the
526        over-the-wire format for private keys:
527
528        RSA keys::
529            string 'ssh-rsa'
530            integer n
531            integer e
532            integer d
533            integer u
534            integer p
535            integer q
536
537        DSA keys::
538            string 'ssh-dss'
539            integer p
540            integer q
541            integer g
542            integer y
543            integer x
544        """
545        type = self.type()
546        data = self.data()
547        if type == 'RSA':
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']))
552        elif type == 'DSA':
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']))
556
557    def toString(self, type, extra=None):
558        """
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.
562
563        @param type: The type of string to emit.  Currently supported values
564            are C{'OPENSSH'}, C{'LSH'}, and C{'AGENTV3'}.
565        @type type: L{str}
566
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
570            encrypt with.
571        @type extra: L{str} or L{NoneType}
572
573        @rtype: L{str}
574        """
575        method = getattr(self, '_toString_%s' % type.upper(), None)
576        if method is None:
577            raise BadKeyError('unknown type: %s' % type)
578        if method.func_code.co_argcount == 2:
579            return method(extra)
580        else:
581            return method()
582
583    def _toString_OPENSSH(self, extra):
584        """
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.
589
590        @type extra: C{str}
591        @rtype: C{str}
592        """
593        data = self.data()
594        if self.isPublic():
595            b64Data = base64.encodestring(self.blob()).replace('\n', '')
596            if not extra:
597                extra = ''
598            return ('%s %s %s' % (self.sshType(), b64Data, extra)).strip()
599        else:
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),
605                        data['u'])
606            else:
607                objData = (0, data['p'], data['q'], data['g'], data['y'],
608                    data['x'])
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)
613            if extra:
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)
629
630    def _toString_LSH(self):
631        """
632        Return a public or private LSH key.  See _fromString_PUBLIC_LSH and
633        _fromString_PRIVATE_LSH for the key formats.
634
635        @rtype: C{str}
636        """
637        data = self.data()
638        if self.isPublic():
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', '') + '}'
650        else:
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:]]]]])
669
670    def _toString_AGENTV3(self):
671        """
672        Return a private Secure Shell Agent v3 key.  See
673        _fromString_AGENTV3 for the key format.
674
675        @rtype: C{str}
676        """
677        data = self.data()
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'],
684                        data['x'])
685            return common.NS(self.sshType()) + ''.join(map(common.MP, values))
686
687
688    def sign(self, data):
689        """
690        Returns a signature with this Key.
691
692        @type data: C{str}
693        @rtype: C{str}
694        """
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
710
711    def verify(self, signature, data):
712        """
713        Returns true if the signature for data is valid for this Key.
714
715        @type signature: C{str}
716        @type data: C{str}
717        @rtype: C{bool}
718        """
719        signatureType, signature = common.getNS(signature)
720        if signatureType != self.sshType():
721            return False
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],
728                    signature[20:]]
729            digest = sha1(data).digest()
730        return self.keyObject.verify(digest, numbers)
731
732
733def objectType(obj):
734    """
735    Return the SSH key type corresponding to a C{Crypto.PublicKey.pubkey.pubkey}
736    object.
737
738    @type obj:  C{Crypto.PublicKey.pubkey.pubkey}
739    @rtype:     C{str}
740    """
741    keyDataMapping = {
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'
745    }
746    try:
747        return keyDataMapping[tuple(obj.keydata)]
748    except (KeyError, AttributeError):
749        raise BadKeyError("invalid key object", obj)
750
751def pkcs1Pad(data, messageLength):
752    """
753    Pad out data to messageLength according to the PKCS#1 standard.
754    @type data: C{str}
755    @type messageLength: C{int}
756    """
757    lenPad = messageLength - 2 - len(data)
758    return '\x01' + ('\xff' * lenPad) + '\x00' + data
759
760def pkcs1Digest(data, messageLength):
761    """
762    Create a message digest using the SHA1 hash algorithm according to the
763    PKCS#1 standard.
764    @type data: C{str}
765    @type messageLength: C{str}
766    """
767    digest = sha1(data).digest()
768    return pkcs1Pad(ID_SHA1+digest, messageLength)
769
770def lenSig(obj):
771    """
772    Return the length of the signature in bytes for a key object.
773
774    @type obj: C{Crypto.PublicKey.pubkey.pubkey}
775    @rtype: C{long}
776    """
777    return obj.size()/8
778
779
780ID_SHA1 = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
Note: See TracBrowser for help on using the browser.