Skip to content

Commit

Permalink
Switched everything to use cryptography
Browse files Browse the repository at this point in the history
  • Loading branch information
alex committed Sep 15, 2014
1 parent f74c7d7 commit 56a4739
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 147 deletions.
4 changes: 2 additions & 2 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ channels to remote services across the encrypted tunnel (this is how sftp
works, for example).

it is written entirely in python (no C or platform-dependent code) and is
released under the GNU LGPL (lesser GPL).
released under the GNU LGPL (lesser GPL).

the package and its API is fairly well documented in the "doc/" folder
that should have come with this archive.
Expand All @@ -36,7 +36,7 @@ Requirements

- Python 2.6 or better <http://www.python.org/> - this includes Python
3.2 and higher as well.
- pycrypto 2.1 or better <https://www.dlitz.net/software/pycrypto/>
- Cryptography 0.5.4 or better <https://cryptography.io>
- ecdsa 0.9 or better <https://pypi.python.org/pypi/ecdsa>

If you have setuptools, you can build and install paramiko and all its
Expand Down
89 changes: 65 additions & 24 deletions paramiko/dsskey.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,31 @@
"""

import os
from hashlib import sha1

from Crypto.PublicKey import DSA
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa

from pyasn1.codec.der import encoder, decoder
from pyasn1.type import namedtype, univ

from paramiko import util
from paramiko.common import zero_byte
from paramiko.py3compat import long
from paramiko.ssh_exception import SSHException
from paramiko.message import Message
from paramiko.ber import BER, BERException
from paramiko.pkey import PKey


class DSSKey (PKey):
class _DSSSigValue(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('r', univ.Integer()),
namedtype.NamedType('s', univ.Integer())
)


class DSSKey(PKey):
"""
Representation of a DSS key which can be used to sign an verify SSH2
data.
Expand Down Expand Up @@ -98,20 +109,27 @@ def can_sign(self):
return self.x is not None

def sign_ssh_data(self, data):
digest = sha1(data).digest()
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x)))
# generate a suitable k
qsize = len(util.deflate_long(self.q, 0))
while True:
k = util.inflate_long(os.urandom(qsize), 1)
if (k > 2) and (k < self.q):
break
r, s = dss.sign(util.inflate_long(digest, 1), k)
key = dsa.DSAPrivateNumbers(
x=self.x,
public_numbers=dsa.DSAPublicNumbers(
y=self.y,
parameter_numbers=dsa.DSAParameterNumbers(
p=self.p,
q=self.q,
g=self.g
)
)
).private_key(backend=default_backend())
signer = key.signer(hashes.SHA1())
signer.update(data)
signature = signer.finalize()
(r, s), _ = decoder.decode(signature)

m = Message()
m.add_string('ssh-dss')
# apparently, in rare cases, r or s may be shorter than 20 bytes!
rstr = util.deflate_long(r, 0)
sstr = util.deflate_long(s, 0)
rstr = util.deflate_long(int(r), 0)
sstr = util.deflate_long(int(s), 0)
if len(rstr) < 20:
rstr = zero_byte * (20 - len(rstr)) + rstr
if len(sstr) < 20:
Expand All @@ -132,10 +150,28 @@ def verify_ssh_sig(self, data, msg):
# pull out (r, s) which are NOT encoded as mpints
sigR = util.inflate_long(sig[:20], 1)
sigS = util.inflate_long(sig[20:], 1)
sigM = util.inflate_long(sha1(data).digest(), 1)

dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q)))
return dss.verify(sigM, (sigR, sigS))
sig_asn1 = _DSSSigValue()
sig_asn1.setComponentByName('r', sigR)
sig_asn1.setComponentByName('s', sigS)
signature = encoder.encode(sig_asn1)

key = dsa.DSAPublicNumbers(
y=self.y,
parameter_numbers=dsa.DSAParameterNumbers(
p=self.p,
q=self.q,
g=self.g
)
).public_key(backend=default_backend())
verifier = key.verifier(signature, hashes.SHA1())
verifier.update(data)
try:
verifier.verify()
except InvalidSignature:
return False
else:
return True

def _encode_key(self):
if self.x is None:
Expand All @@ -160,14 +196,19 @@ def generate(bits=1024, progress_func=None):
generate a new host key or authentication key.
:param int bits: number of bits the generated key should be.
:param function progress_func:
an optional function to call at key points in key generation (used
by ``pyCrypto.PublicKey``).
:param function progress_func: Unused
:return: new `.DSSKey` private key
"""
dsa = DSA.generate(bits, os.urandom, progress_func)
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
key.x = dsa.x
numbers = dsa.generate_private_key(
bits, backend=default_backend()
).private_numbers()
key = DSSKey(vals=(
numbers.public_numbers.parameter_numbers.p,
numbers.public_numbers.parameter_numbers.q,
numbers.public_numbers.parameter_numbers.g,
numbers.public_numbers.y
))
key.x = numbers.x
return key
generate = staticmethod(generate)

Expand Down
4 changes: 1 addition & 3 deletions paramiko/ecdsakey.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,7 @@ def generate(curve=curves.NIST256p, progress_func=None):
@param bits: number of bits the generated key should be.
@type bits: int
@param progress_func: an optional function to call at key points in
key generation (used by C{pyCrypto.PublicKey}).
@type progress_func: function
@param progress_func: Unused.
@return: new private key
@rtype: L{RSAKey}
"""
Expand Down
1 change: 0 additions & 1 deletion paramiko/kex_gss.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"""


from Crypto.Hash import SHA
from paramiko.common import *
from paramiko import util
from paramiko.message import Message
Expand Down
6 changes: 3 additions & 3 deletions paramiko/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def send_message(self, data):
self._log(DEBUG, 'Write packet <%s>, length %d' % (cmd_name, orig_len))
self._log(DEBUG, util.format_binary(packet, 'OUT: '))
if self.__block_engine_out is not None:
out = self.__block_engine_out.encrypt(packet)
out = self.__block_engine_out.update(packet)
else:
out = packet
# + mac
Expand Down Expand Up @@ -340,7 +340,7 @@ def read_message(self):
"""
header = self.read_all(self.__block_size_in, check_rekey=True)
if self.__block_engine_in is not None:
header = self.__block_engine_in.decrypt(header)
header = self.__block_engine_in.update(header)
if self.__dump_packets:
self._log(DEBUG, util.format_binary(header, 'IN: '))
packet_size = struct.unpack('>I', header[:4])[0]
Expand All @@ -352,7 +352,7 @@ def read_message(self):
packet = buf[:packet_size - len(leftover)]
post_packet = buf[packet_size - len(leftover):]
if self.__block_engine_in is not None:
packet = self.__block_engine_in.decrypt(packet)
packet = self.__block_engine_in.update(packet)
if self.__dump_packets:
self._log(DEBUG, util.format_binary(packet, 'IN: '))
packet = leftover + packet
Expand Down
29 changes: 23 additions & 6 deletions paramiko/pkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,34 @@
import os
from hashlib import md5

from Crypto.Cipher import DES3, AES
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import algorithms, modes, Cipher

from paramiko import util
from paramiko.common import o600, zero_byte
from paramiko.py3compat import u, encodebytes, decodebytes, b
from paramiko.ssh_exception import SSHException, PasswordRequiredException


class PKey (object):
class PKey(object):
"""
Base class for public keys.
"""

# known encryption types for private key files:
_CIPHER_TABLE = {
'AES-128-CBC': {'cipher': AES, 'keysize': 16, 'blocksize': 16, 'mode': AES.MODE_CBC},
'DES-EDE3-CBC': {'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC},
'AES-128-CBC': {
'cipher': algorithms.AES,
'keysize': 16,
'blocksize': 16,
'mode': modes.CBC
},
'DES-EDE3-CBC': {
'cipher': algorithms.TripleDES,
'keysize': 24,
'blocksize': 8,
'mode': modes.CBC
},
}

def __init__(self, msg=None, data=None):
Expand Down Expand Up @@ -300,7 +311,10 @@ def _read_private_key(self, tag, f, password=None):
mode = self._CIPHER_TABLE[encryption_type]['mode']
salt = unhexlify(b(saltstr))
key = util.generate_key_bytes(md5, salt, password, keysize)
return cipher.new(key, mode, salt).decrypt(data)
decryptor = Cipher(
cipher(key), mode(salt), backend=default_backend()
).decryptor()
return decryptor.update(data) + decryptor.finalize()

def _write_private_key_file(self, tag, filename, data, password=None):
"""
Expand Down Expand Up @@ -336,7 +350,10 @@ def _write_private_key(self, tag, f, data, password=None):
#data += os.urandom(n)
# that would make more sense ^, but it confuses openssh.
data += zero_byte * n
data = cipher.new(key, mode, salt).encrypt(data)
encryptor = Cipher(
cipher(key), mode(salt), backend=default_backend()
).encryptor()
data = encryptor.update(data) + encryptor.finalize()
f.write('Proc-Type: 4,ENCRYPTED\n')
f.write('DEK-Info: %s,%s\n' % (cipher_name, u(hexlify(salt)).upper()))
f.write('\n')
Expand Down
Loading

0 comments on commit 56a4739

Please sign in to comment.