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