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 |