[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:
Robey Pointer 2004-09-25 21:28:23 +00:00
parent 0737ea2ca4
commit 12287b3e0e
3 changed files with 145 additions and 133 deletions

View File

@ -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 vals is not None:
self.p, self.q, self.g, self.y = vals
else:
if (msg is None) or (msg.get_string() != 'ssh-dss'):
return
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 = len(util.deflate_long(self.p, 0))
self.valid = True
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)

View File

@ -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}
@ -151,39 +174,20 @@ class PKey (object):
"""
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.
"""

View 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 vals is not None:
self.e, self.n = vals
else:
if (msg is None) or (msg.get_string() != 'ssh-rsa'):
return
raise SSHException('Invalid key')
self.e = msg.get_mpint()
self.n = msg.get_mpint()
self.size = len(util.deflate_long(self.n, 0))
self.valid = True
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)