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