summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2004-09-25 21:28:23 +0000
committerRobey Pointer <robey@lag.net>2004-09-25 21:28:23 +0000
commit12287b3e0e95664a383902b69a0881775b550feb (patch)
treebb72bc18bec9b02f09c705fd11e7659fe38d0e98
parent0737ea2ca410a70f0aa574daa3c4d2b5b1a1226e (diff)
[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.
-rw-r--r--paramiko/dsskey.py84
-rw-r--r--paramiko/pkey.py95
-rw-r--r--paramiko/rsakey.py97
3 files changed, 144 insertions, 132 deletions
diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py
index eaefba3f..09952b9d 100644
--- a/paramiko/dsskey.py
+++ b/paramiko/dsskey.py
@@ -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)
+
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index 8faacc5b..a7dc8a8f 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -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):
+
+ def from_private_key_file(cl, filename, password=None):
"""
- Read private key contents from a file into this object. If the private
+ 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).
-
- @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.
+ 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.
"""
diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py
index aa85724c..e9e7d01a 100644
--- a/paramiko/rsakey.py
+++ b/paramiko/rsakey.py
@@ -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)
+