[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-87]
clean up pkey interface change the pkey interface so that it's no longer possible to have a pkey that doesn't represent a valid key. (ie: no more "blank" key objects.) also add "get_bits" and "can_sign" methods to determine the key bit length and whether it can sign things (contains the "private parts") respectively.
This commit is contained in:
parent
0737ea2ca4
commit
12287b3e0e
|
@ -38,22 +38,24 @@ class DSSKey (PKey):
|
|||
data.
|
||||
"""
|
||||
|
||||
def __init__(self, msg=None, data=None):
|
||||
self.valid = False
|
||||
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None):
|
||||
if filename is not None:
|
||||
self._from_private_key_file(filename, password)
|
||||
return
|
||||
if (msg is None) and (data is not None):
|
||||
msg = Message(data)
|
||||
if (msg is None) or (msg.get_string() != 'ssh-dss'):
|
||||
return
|
||||
self.p = msg.get_mpint()
|
||||
self.q = msg.get_mpint()
|
||||
self.g = msg.get_mpint()
|
||||
self.y = msg.get_mpint()
|
||||
self.size = len(util.deflate_long(self.p, 0))
|
||||
self.valid = True
|
||||
if vals is not None:
|
||||
self.p, self.q, self.g, self.y = vals
|
||||
else:
|
||||
if (msg is None) or (msg.get_string() != 'ssh-dss'):
|
||||
raise SSHException('Invalid key')
|
||||
self.p = msg.get_mpint()
|
||||
self.q = msg.get_mpint()
|
||||
self.g = msg.get_mpint()
|
||||
self.y = msg.get_mpint()
|
||||
self.size = util.bit_length(self.p)
|
||||
|
||||
def __str__(self):
|
||||
if not self.valid:
|
||||
return ''
|
||||
m = Message()
|
||||
m.add_string('ssh-dss')
|
||||
m.add_mpint(self.p)
|
||||
|
@ -74,6 +76,12 @@ class DSSKey (PKey):
|
|||
def get_name(self):
|
||||
return 'ssh-dss'
|
||||
|
||||
def get_bits(self):
|
||||
return self.size
|
||||
|
||||
def can_sign(self):
|
||||
return hasattr(self, 'x')
|
||||
|
||||
def sign_ssh_data(self, randpool, data):
|
||||
hash = SHA.new(data).digest()
|
||||
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x)))
|
||||
|
@ -90,8 +98,6 @@ class DSSKey (PKey):
|
|||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if not self.valid:
|
||||
return 0
|
||||
if len(str(msg)) == 40:
|
||||
# spies.com bug: signature has no header
|
||||
sig = str(msg)
|
||||
|
@ -109,28 +115,7 @@ class DSSKey (PKey):
|
|||
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q)))
|
||||
return dss.verify(sigM, (sigR, sigS))
|
||||
|
||||
def read_private_key_file(self, filename, password=None):
|
||||
# private key file contains:
|
||||
# DSAPrivateKey = { version = 0, p, q, g, y, x }
|
||||
self.valid = False
|
||||
data = self._read_private_key_file('DSA', filename, password)
|
||||
try:
|
||||
keylist = BER(data).decode()
|
||||
except BERException, x:
|
||||
raise SSHException('Unable to parse key file: ' + str(x))
|
||||
if (type(keylist) is not list) or (len(keylist) < 6) or (keylist[0] != 0):
|
||||
raise SSHException('not a valid DSA private key file (bad ber encoding)')
|
||||
self.p = keylist[1]
|
||||
self.q = keylist[2]
|
||||
self.g = keylist[3]
|
||||
self.y = keylist[4]
|
||||
self.x = keylist[5]
|
||||
self.size = len(util.deflate_long(self.p, 0))
|
||||
self.valid = True
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
if not self.valid:
|
||||
raise SSHException('Invalid key')
|
||||
keylist = [ 0, self.p, self.q, self.g, self.y, self.x ]
|
||||
try:
|
||||
b = BER()
|
||||
|
@ -155,12 +140,29 @@ class DSSKey (PKey):
|
|||
@since: fearow
|
||||
"""
|
||||
dsa = DSA.generate(bits, randpool.get_bytes, progress_func)
|
||||
key = DSSKey()
|
||||
key.p = dsa.p
|
||||
key.q = dsa.q
|
||||
key.g = dsa.g
|
||||
key.y = dsa.y
|
||||
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
|
||||
key.x = dsa.x
|
||||
key.valid = True
|
||||
return key
|
||||
generate = staticmethod(generate)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _from_private_key_file(self, filename, password):
|
||||
# private key file contains:
|
||||
# DSAPrivateKey = { version = 0, p, q, g, y, x }
|
||||
data = self._read_private_key_file('DSA', filename, password)
|
||||
try:
|
||||
keylist = BER(data).decode()
|
||||
except BERException, x:
|
||||
raise SSHException('Unable to parse key file: ' + str(x))
|
||||
if (type(keylist) is not list) or (len(keylist) < 6) or (keylist[0] != 0):
|
||||
raise SSHException('not a valid DSA private key file (bad ber encoding)')
|
||||
self.p = keylist[1]
|
||||
self.q = keylist[2]
|
||||
self.g = keylist[3]
|
||||
self.y = keylist[4]
|
||||
self.x = keylist[5]
|
||||
self.size = util.bit_length(self.p)
|
||||
|
||||
|
|
|
@ -55,7 +55,10 @@ class PKey (object):
|
|||
type.
|
||||
@type msg: L{Message}
|
||||
@param data: an optional string containing a public key of this type
|
||||
@type data: string
|
||||
@type data: str
|
||||
|
||||
@raise SSHException: if a key cannot be created from the C{data} or
|
||||
C{msg} given, or no key was passed in.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -66,7 +69,7 @@ class PKey (object):
|
|||
re-create the key object later.
|
||||
|
||||
@return: string representation of an SSH key message.
|
||||
@rtype: string
|
||||
@rtype: str
|
||||
"""
|
||||
return ''
|
||||
|
||||
|
@ -94,10 +97,30 @@ class PKey (object):
|
|||
|
||||
@return: name of this private key type, in SSH terminology (for
|
||||
example, C{"ssh-rsa"}).
|
||||
@rtype: string
|
||||
@rtype: str
|
||||
"""
|
||||
return ''
|
||||
|
||||
def get_bits(self):
|
||||
"""
|
||||
Return the number of significant bits in this key. This is useful
|
||||
for judging the relative security of a key.
|
||||
|
||||
@return: bits in the key.
|
||||
@rtype: int
|
||||
"""
|
||||
return 0
|
||||
|
||||
def can_sign(self):
|
||||
"""
|
||||
Return C{True} if this key has the private part necessary for signing
|
||||
data.
|
||||
|
||||
@return: C{True} if this is a private key.
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_fingerprint(self):
|
||||
"""
|
||||
Return an MD5 fingerprint of the public part of this key. Nothing
|
||||
|
@ -105,7 +128,7 @@ class PKey (object):
|
|||
|
||||
@return: a 16-byte string (binary) of the MD5 fingerprint, in SSH
|
||||
format.
|
||||
@rtype: string
|
||||
@rtype: str
|
||||
"""
|
||||
return MD5.new(str(self)).digest()
|
||||
|
||||
|
@ -116,7 +139,7 @@ class PKey (object):
|
|||
public key files or recognized host keys.
|
||||
|
||||
@return: a base64 string containing the public part of the key.
|
||||
@rtype: string
|
||||
@rtype: str
|
||||
|
||||
@since: fearow
|
||||
"""
|
||||
|
@ -130,7 +153,7 @@ class PKey (object):
|
|||
@param randpool: a secure random number generator.
|
||||
@type randpool: L{Crypto.Util.randpool.RandomPool}
|
||||
@param data: the data to sign.
|
||||
@type data: string
|
||||
@type data: str
|
||||
@return: an SSH signature message.
|
||||
@rtype: L{Message}
|
||||
"""
|
||||
|
@ -142,7 +165,7 @@ class PKey (object):
|
|||
that data, verify that it was signed with this key.
|
||||
|
||||
@param data: the data that was signed.
|
||||
@type data: string
|
||||
@type data: str
|
||||
@param msg: an SSH signature message
|
||||
@type msg: L{Message}
|
||||
@return: C{True} if the signature verifies correctly; C{False}
|
||||
|
@ -150,40 +173,21 @@ class PKey (object):
|
|||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def read_private_key_file(self, filename, password=None):
|
||||
"""
|
||||
Read private key contents from a file into this object. If the private
|
||||
key is encrypted and C{password} is not C{None}, the given password
|
||||
will be used to decrypt the key (otherwise L{PasswordRequiredException}
|
||||
is thrown).
|
||||
|
||||
@param filename: name of the file to read.
|
||||
@type filename: string
|
||||
@param password: an optional password to use to decrypt the key file,
|
||||
if it's encrypted.
|
||||
@type password: string
|
||||
|
||||
@raise IOError: if there was an error reading the file.
|
||||
@raise PasswordRequiredException: if the private key file is
|
||||
encrypted, and C{password} is C{None}.
|
||||
@raise SSHException: if the key file is invalid.
|
||||
"""
|
||||
raise exception('Not implemented in PKey')
|
||||
|
||||
|
||||
def from_private_key_file(cl, filename, password=None):
|
||||
"""
|
||||
Create a key object by reading a private key file. This is roughly
|
||||
equivalent to creating a new key object and then calling
|
||||
L{read_private_key_file} on it. Through the magic of python, this
|
||||
factory method will exist in all subclasses of PKey (such as L{RSAKey}
|
||||
or L{DSSKey}), but is useless on the abstract PKey class.
|
||||
Create a key object by reading a private key file. If the private
|
||||
key is encrypted and C{password} is not C{None}, the given password
|
||||
will be used to decrypt the key (otherwise L{PasswordRequiredException}
|
||||
is thrown). Through the magic of python, this factory method will
|
||||
exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but
|
||||
is useless on the abstract PKey class.
|
||||
|
||||
@param filename: name of the file to read.
|
||||
@type filename: string
|
||||
@type filename: str
|
||||
@param password: an optional password to use to decrypt the key file,
|
||||
if it's encrypted
|
||||
@type password: string
|
||||
@type password: str
|
||||
@return: a new key object based on the given private key.
|
||||
@rtype: L{PKey}
|
||||
|
||||
|
@ -194,8 +198,7 @@ class PKey (object):
|
|||
|
||||
@since: fearow
|
||||
"""
|
||||
key = cl()
|
||||
key.read_private_key_file(filename, password)
|
||||
key = cl(filename=filename, password=password)
|
||||
return key
|
||||
from_private_key_file = classmethod(from_private_key_file)
|
||||
|
||||
|
@ -205,9 +208,9 @@ class PKey (object):
|
|||
C{None}, the key is encrypted before writing.
|
||||
|
||||
@param filename: name of the file to write.
|
||||
@type filename: string
|
||||
@type filename: str
|
||||
@param password: an optional password to use to encrypt the key file.
|
||||
@type password: string
|
||||
@type password: str
|
||||
|
||||
@raise IOError: if there was an error writing the file.
|
||||
@raise SSHException: if the key is invalid.
|
||||
|
@ -225,14 +228,14 @@ class PKey (object):
|
|||
the key (otherwise L{PasswordRequiredException} is thrown).
|
||||
|
||||
@param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
|
||||
@type tag: string
|
||||
@type tag: str
|
||||
@param filename: name of the file to read.
|
||||
@type filename: string
|
||||
@type filename: str
|
||||
@param password: an optional password to use to decrypt the key file,
|
||||
if it's encrypted.
|
||||
@type password: string
|
||||
@type password: str
|
||||
@return: data blob that makes up the private key.
|
||||
@rtype: string
|
||||
@rtype: str
|
||||
|
||||
@raise IOError: if there was an error reading the file.
|
||||
@raise PasswordRequiredException: if the private key file is
|
||||
|
@ -296,13 +299,13 @@ class PKey (object):
|
|||
a password is given, DES-EDE3-CBC is used.
|
||||
|
||||
@param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
|
||||
@type tag: string
|
||||
@type tag: str
|
||||
@param filename: name of the file to write.
|
||||
@type filename: string
|
||||
@type filename: str
|
||||
@param data: data blob that makes up the private key.
|
||||
@type data: string
|
||||
@type data: str
|
||||
@param password: an optional password to use to encrypt the file.
|
||||
@type password: string
|
||||
@type password: str
|
||||
|
||||
@raise IOError: if there was an error writing the file.
|
||||
"""
|
||||
|
|
|
@ -39,20 +39,22 @@ class RSAKey (PKey):
|
|||
data.
|
||||
"""
|
||||
|
||||
def __init__(self, msg=None, data=''):
|
||||
self.valid = False
|
||||
def __init__(self, msg=None, data='', filename=None, password=None, vals=None):
|
||||
if filename is not None:
|
||||
self._from_private_key_file(filename, password)
|
||||
return
|
||||
if (msg is None) and (data is not None):
|
||||
msg = Message(data)
|
||||
if (msg is None) or (msg.get_string() != 'ssh-rsa'):
|
||||
return
|
||||
self.e = msg.get_mpint()
|
||||
self.n = msg.get_mpint()
|
||||
self.size = len(util.deflate_long(self.n, 0))
|
||||
self.valid = True
|
||||
if vals is not None:
|
||||
self.e, self.n = vals
|
||||
else:
|
||||
if (msg is None) or (msg.get_string() != 'ssh-rsa'):
|
||||
raise SSHException('Invalid key')
|
||||
self.e = msg.get_mpint()
|
||||
self.n = msg.get_mpint()
|
||||
self.size = util.bit_length(self.n)
|
||||
|
||||
def __str__(self):
|
||||
if not self.valid:
|
||||
return ''
|
||||
m = Message()
|
||||
m.add_string('ssh-rsa')
|
||||
m.add_mpint(self.e)
|
||||
|
@ -68,14 +70,11 @@ class RSAKey (PKey):
|
|||
def get_name(self):
|
||||
return 'ssh-rsa'
|
||||
|
||||
def _pkcs1imify(self, data):
|
||||
"""
|
||||
turn a 20-byte SHA1 hash into a blob of data as large as the key's N,
|
||||
using PKCS1's \"emsa-pkcs1-v1_5\" encoding. totally bizarre.
|
||||
"""
|
||||
SHA1_DIGESTINFO = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
|
||||
filler = '\xff' * (self.size - len(SHA1_DIGESTINFO) - len(data) - 3)
|
||||
return '\x00\x01' + filler + '\x00' + SHA1_DIGESTINFO + data
|
||||
def get_bits(self):
|
||||
return self.size
|
||||
|
||||
def can_sign(self):
|
||||
return hasattr(self, 'd')
|
||||
|
||||
def sign_ssh_data(self, randpool, data):
|
||||
hash = SHA.new(data).digest()
|
||||
|
@ -87,7 +86,7 @@ class RSAKey (PKey):
|
|||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if (not self.valid) or (msg.get_string() != 'ssh-rsa'):
|
||||
if msg.get_string() != 'ssh-rsa':
|
||||
return False
|
||||
sig = util.inflate_long(msg.get_string(), 1)
|
||||
# verify the signature by SHA'ing the data and encrypting it using the
|
||||
|
@ -97,29 +96,7 @@ class RSAKey (PKey):
|
|||
rsa = RSA.construct((long(self.n), long(self.e)))
|
||||
return rsa.verify(hash, (sig,))
|
||||
|
||||
def read_private_key_file(self, filename, password=None):
|
||||
# private key file contains:
|
||||
# RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p }
|
||||
self.valid = False
|
||||
data = self._read_private_key_file('RSA', filename, password)
|
||||
try:
|
||||
keylist = BER(data).decode()
|
||||
except BERException:
|
||||
raise SSHException('Unable to parse key file')
|
||||
if (type(keylist) is not list) or (len(keylist) < 4) or (keylist[0] != 0):
|
||||
raise SSHException('Not a valid RSA private key file (bad ber encoding)')
|
||||
self.n = keylist[1]
|
||||
self.e = keylist[2]
|
||||
self.d = keylist[3]
|
||||
# not really needed
|
||||
self.p = keylist[4]
|
||||
self.q = keylist[5]
|
||||
self.size = len(util.deflate_long(self.n, 0))
|
||||
self.valid = True
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
if not self.valid:
|
||||
raise SSHException('Invalid key')
|
||||
keylist = [ 0, self.n, self.e, self.d, self.p, self.q,
|
||||
self.d % (self.p - 1), self.d % (self.q - 1),
|
||||
util.mod_inverse(self.q, self.p) ]
|
||||
|
@ -146,12 +123,42 @@ class RSAKey (PKey):
|
|||
@since: fearow
|
||||
"""
|
||||
rsa = RSA.generate(bits, randpool.get_bytes, progress_func)
|
||||
key = RSAKey()
|
||||
key.n = rsa.n
|
||||
key.e = rsa.e
|
||||
key = RSAKey(vals=(rsa.e, rsa.n))
|
||||
key.d = rsa.d
|
||||
key.p = rsa.p
|
||||
key.q = rsa.q
|
||||
key.valid = True
|
||||
return key
|
||||
generate = staticmethod(generate)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _pkcs1imify(self, data):
|
||||
"""
|
||||
turn a 20-byte SHA1 hash into a blob of data as large as the key's N,
|
||||
using PKCS1's \"emsa-pkcs1-v1_5\" encoding. totally bizarre.
|
||||
"""
|
||||
SHA1_DIGESTINFO = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
|
||||
size = len(util.deflate_long(self.n, 0))
|
||||
filler = '\xff' * (size - len(SHA1_DIGESTINFO) - len(data) - 3)
|
||||
return '\x00\x01' + filler + '\x00' + SHA1_DIGESTINFO + data
|
||||
|
||||
def _from_private_key_file(self, filename, password):
|
||||
# private key file contains:
|
||||
# RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p }
|
||||
data = self._read_private_key_file('RSA', filename, password)
|
||||
try:
|
||||
keylist = BER(data).decode()
|
||||
except BERException:
|
||||
raise SSHException('Unable to parse key file')
|
||||
if (type(keylist) is not list) or (len(keylist) < 4) or (keylist[0] != 0):
|
||||
raise SSHException('Not a valid RSA private key file (bad ber encoding)')
|
||||
self.n = keylist[1]
|
||||
self.e = keylist[2]
|
||||
self.d = keylist[3]
|
||||
# not really needed
|
||||
self.p = keylist[4]
|
||||
self.q = keylist[5]
|
||||
self.size = util.bit_length(self.n)
|
||||
|
||||
|
|
Loading…
Reference in New Issue