Skip to content

Commit e001c79

Browse files
added type hints
1 parent db481c5 commit e001c79

File tree

19 files changed

+394
-180
lines changed

19 files changed

+394
-180
lines changed

ssh_proxy_server/authentication.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,16 @@
3333
def probe_host(hostname_or_ip: Text, port: int, username: Text, public_key: paramiko.pkey.PublicBlob) -> bool:
3434

3535
@typechecked
36-
def valid(self, msg: paramiko.message.Message) -> None:
36+
def valid(self, msg: paramiko.message.Message) -> None: # type: ignore
3737
self.auth_event.set()
3838
self.authenticated = True
3939

4040
@typechecked
41-
def parse_service_accept(self, m: paramiko.message.Message) -> None:
41+
def parse_service_accept(self, m: paramiko.message.Message) -> None: # type: ignore
4242
# https://tools.ietf.org/html/rfc4252#section-7
4343
service = m.get_text()
4444
if not (service == "ssh-userauth" and self.auth_method == "publickey"):
45-
return self._parse_service_accept(m)
45+
return self._parse_service_accept(m) # type: ignore
4646
m = paramiko.message.Message()
4747
m.add_byte(paramiko.common.cMSG_USERAUTH_REQUEST)
4848
m.add_string(self.username)
@@ -83,16 +83,16 @@ class RemoteCredentials():
8383
@typechecked
8484
def __init__(
8585
self, *,
86-
username: str,
87-
password: Optional[str] = None,
88-
key=None,
89-
host: Optional[str] = None,
86+
username: Text,
87+
password: Optional[Text] = None,
88+
key: Optional[PKey]=None,
89+
host: Optional[Text] = None,
9090
port: Optional[int] = None
9191
) -> None:
92-
self.username: str = username
93-
self.password: Optional[str] = password
94-
self.key = key
95-
self.host: Optional[str] = host
92+
self.username: Text = username
93+
self.password: Optional[Text] = password
94+
self.key: Optional[PKey] = key
95+
self.host: Optional[Text] = host
9696
self.port: Optional[int] = port
9797

9898

@@ -177,8 +177,8 @@ def __init__(self, session: 'ssh_proxy_server.session.Session') -> None:
177177
@typechecked
178178
def get_remote_host_credentials(
179179
self,
180-
username: str,
181-
password: Optional[str] = None,
180+
username: Text,
181+
password: Optional[Text] = None,
182182
key: Optional[PKey] = None
183183
) -> RemoteCredentials:
184184
if self.session.proxyserver.transparent:
@@ -199,7 +199,7 @@ def get_remote_host_credentials(
199199

200200
@classmethod
201201
@typechecked
202-
def get_auth_methods(cls, host: str, port: int) -> Optional[List[str]]:
202+
def get_auth_methods(cls, host: Text, port: int) -> Optional[List[Text]]:
203203
auth_methods = None
204204
t = paramiko.Transport((host, port))
205205
try:
@@ -218,8 +218,8 @@ def get_auth_methods(cls, host: str, port: int) -> Optional[List[str]]:
218218
@typechecked
219219
def authenticate(
220220
self,
221-
username: Optional[str] = None,
222-
password: Optional[str] = None,
221+
username: Optional[Text] = None,
222+
password: Optional[Text] = None,
223223
key: Optional[PKey] = None,
224224
store_credentials: bool = True
225225
) -> int:

ssh_proxy_server/clients/sftp.py

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,47 @@
11
import logging
22
import os
33
from typing import (
4-
Optional
4+
TYPE_CHECKING,
5+
Callable,
6+
List,
7+
Any,
8+
Optional,
9+
Tuple,
10+
Union,
11+
Text
512
)
613

714
import paramiko
15+
from paramiko.pkey import PKey
16+
from paramiko.sftp_attr import SFTPAttributes
17+
from paramiko.sftp_file import SFTPFile
18+
from typeguard import typechecked
819

9-
from ssh_proxy_server.clients.ssh import SSHClient
10-
20+
import ssh_proxy_server
21+
from ssh_proxy_server.clients.ssh import AuthenticationMethod, SSHClient
22+
if TYPE_CHECKING:
23+
from ssh_proxy_server.session import Session
1124

1225
class SFTPClient(SSHClient):
1326

14-
def __init__(self, host, port, method, password, user, key, session):
27+
@typechecked
28+
def __init__(
29+
self,
30+
host: Text,
31+
port: int,
32+
method: AuthenticationMethod,
33+
password: Optional[Text],
34+
user: Text,
35+
key: Optional[PKey],
36+
session: 'ssh_proxy_server.session.Session'
37+
) -> None:
1538
super().__init__(host, port, method, password, user, key, session)
1639
self._sftp: Optional[paramiko.SFTPClient] = None
1740
self.subsystem_count = 0
1841

1942
@classmethod
20-
def from_client(cls, ssh_client: Optional[SSHClient]):
43+
@typechecked
44+
def from_client(cls, ssh_client: Optional[SSHClient]) -> Optional['SFTPClient']:
2145
if ssh_client is None:
2246
logging.error('error creating sftp client - no ssh client!')
2347
return None
@@ -46,35 +70,47 @@ def from_client(cls, ssh_client: Optional[SSHClient]):
4670
return None
4771

4872
@property
49-
def running(self):
73+
def running(self) -> bool:
5074
return self.subsystem_count > 0
5175

52-
def connect(self):
76+
@typechecked
77+
def connect(self) -> bool:
5378
ret = super().connect()
5479
if not ret:
5580
return False
5681
if self._sftp is None:
57-
return paramiko.sftp.SFTP_FAILURE
82+
return False
5883
try:
84+
if self.transport is None:
85+
return False
5986
self._sftp = paramiko.SFTPClient.from_transport(self.transport)
6087
return True
6188
except Exception:
6289
logging.exception('error creating sftp client')
6390
return False
6491

65-
def chmod(self, path, mode):
92+
@typechecked
93+
def open(self, filename: Union[Text, bytes], mode: Text = 'r', bufsize: int = -1) -> SFTPFile:
94+
if self._sftp is None:
95+
raise paramiko.SFTPError("Expected handle")
96+
return self._sftp.open(filename, mode, bufsize)
97+
98+
@typechecked
99+
def chmod(self, path: Union[Text, bytes], mode: int) -> int:
66100
if self._sftp is None:
67101
return paramiko.sftp.SFTP_FAILURE
68102
self._sftp.chmod(path, mode)
69103
return paramiko.sftp.SFTP_OK
70104

71-
def chown(self, path, uid, gid):
105+
@typechecked
106+
def chown(self, path: Union[Text, bytes], uid: int, gid: int) -> int:
72107
if self._sftp is None:
73108
return paramiko.sftp.SFTP_FAILURE
74109
self._sftp.chown(path, uid, gid)
75110
return paramiko.sftp.SFTP_OK
76111

77-
def get(self, remotePath, localPath, callback=None):
112+
@typechecked
113+
def get(self, remotePath: Union[Text, bytes], localPath: Union[Text, bytes], callback: Optional[Callable[[int, int], Any]] = None) -> int:
78114
if self._sftp is None:
79115
return paramiko.sftp.SFTP_FAILURE
80116
try:
@@ -83,70 +119,84 @@ def get(self, remotePath, localPath, callback=None):
83119
except (IOError, OSError) as ex:
84120
logging.error(ex)
85121
os.remove(localPath)
86-
return paramiko.sftp.SFTP_FAILURE
122+
return paramiko.sftp.SFTP_FAILURE
87123

88-
def listdir_attr(self, path='.'):
124+
@typechecked
125+
def listdir_attr(self, path: Text = '.') -> Union[int, List[SFTPAttributes]]:
89126
if self._sftp is None:
90127
return paramiko.sftp.SFTP_FAILURE
91128
return self._sftp.listdir_attr(path)
92129

93-
def lstat(self, path):
130+
@typechecked
131+
def lstat(self, path: Union[Text, bytes]) -> Union[int, SFTPAttributes]:
94132
if self._sftp is None:
95133
return paramiko.sftp.SFTP_FAILURE
96134
return self._sftp.lstat(path)
97135

98-
def mkdir(self, path, mode=511):
136+
@typechecked
137+
def mkdir(self, path: Union[Text, bytes], mode: int = 511) -> int:
99138
if self._sftp is None:
100139
return paramiko.sftp.SFTP_FAILURE
101140
self._sftp.mkdir(path, mode)
102141
return paramiko.sftp.SFTP_OK
103142

104-
def put(self, localPath, remotePath, callback=None, confirm=True):
143+
@typechecked
144+
def put(self, localPath: Union[Text, bytes], remotePath: Union[Text, bytes], callback: Any = None, confirm: bool = True) -> None:
105145
raise NotImplementedError('put not implemented')
106146

107-
def readlink(self, path):
147+
@typechecked
148+
def readlink(self, path: Union[Text, bytes]) -> Union[int, Text]:
108149
if self._sftp is None:
109150
return paramiko.sftp.SFTP_FAILURE
110-
return self._sftp.readlink(path)
151+
return self._sftp.readlink(path) or paramiko.sftp.SFTP_FAILURE
111152

112-
def remove(self, path):
153+
@typechecked
154+
def remove(self, path: Union[Text, bytes]) -> int:
113155
if self._sftp is None:
114156
return paramiko.sftp.SFTP_FAILURE
115157
self._sftp.remove(path)
116158
return paramiko.sftp.SFTP_OK
117159

118-
def rename(self, oldpath, newpath):
160+
@typechecked
161+
def rename(self, oldpath: Union[Text, bytes], newpath: Union[Text, bytes]) -> int:
119162
if self._sftp is None:
120163
return paramiko.sftp.SFTP_FAILURE
121164
self._sftp.rename(oldpath, newpath)
122165
return paramiko.sftp.SFTP_OK
123166

124-
def rmdir(self, path):
167+
@typechecked
168+
def rmdir(self, path: Union[Text, bytes]) -> int:
125169
if self._sftp is None:
126170
return paramiko.sftp.SFTP_FAILURE
127171
self._sftp.rmdir(path)
128172
return paramiko.sftp.SFTP_OK
129173

130-
def stat(self, path):
174+
@typechecked
175+
def stat(self, path: Union[Text, bytes]) -> Union[int, SFTPAttributes]:
131176
if self._sftp is None:
132177
return paramiko.sftp.SFTP_FAILURE
133178
return self._sftp.stat(path)
134179

135-
def utime(self, path, times):
180+
@typechecked
181+
def utime(self, path: Union[Text, bytes], times: Tuple[float, float]) -> int:
136182
if self._sftp is None:
137183
return paramiko.sftp.SFTP_FAILURE
138-
return self._sftp.utime(path, times)
184+
self._sftp.utime(path, times)
185+
return paramiko.sftp.SFTP_OK
139186

140-
def symlink(self, source, dest):
187+
@typechecked
188+
def symlink(self, source: Union[Text, bytes], dest: Union[Text, bytes]) -> int:
141189
if self._sftp is None:
142190
return paramiko.sftp.SFTP_FAILURE
143191
self._sftp.symlink(source, dest)
144192
return paramiko.sftp.SFTP_OK
145193

146-
def close(self):
194+
@typechecked
195+
def close(self) -> int:
147196
if self._sftp is None:
148197
return paramiko.sftp.SFTP_FAILURE
149198
if not self.running:
150199
self._sftp.close()
151-
self.session.sftp_channel.close()
200+
if self.session.sftp_channel is not None:
201+
self.session.sftp_channel.close()
152202
return paramiko.sftp.SFTP_OK

ssh_proxy_server/clients/ssh.py

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from enhancements.modules import BaseModule
1717
import ssh_proxy_server
18+
from ssh_proxy_server.forwarders.agent import AgentProxy
1819
from ssh_proxy_server.exceptions import NoAgentKeys, InvalidHostKey
1920

2021
if TYPE_CHECKING:
@@ -35,7 +36,7 @@ class SSHClient(BaseSSHClient):
3536

3637
CIPHERS = None
3738

38-
#@typechecked
39+
@typechecked
3940
def __init__(
4041
self,
4142
host: Text,
@@ -46,18 +47,18 @@ def __init__(
4647
key: Optional[PKey],
4748
session: 'ssh_proxy_server.session.Session'
4849
) -> None:
49-
self.session = session
50-
self.host = host
51-
self.port = port
52-
self.method = method
53-
self.user = user
54-
self.password = password
55-
self.agent = session.agent
56-
self.key = key
57-
self.transport = None
58-
self.connected = False
59-
60-
def connect(self):
50+
self.session: 'ssh_proxy_server.session.Session' = session
51+
self.host: Text = host
52+
self.port: int = port
53+
self.method: AuthenticationMethod = method
54+
self.user: Text = user
55+
self.password: Optional[Text] = password
56+
self.agent: Optional[AgentProxy] = self.session.agent
57+
self.key: Optional[PKey] = key
58+
self.transport: Optional[paramiko.Transport] = None
59+
self.connected: bool = False
60+
61+
def connect(self) -> bool:
6162
message = None
6263

6364
self.transport = paramiko.Transport((self.host, self.port))
@@ -72,19 +73,20 @@ def connect(self):
7273
elif self.method is AuthenticationMethod.publickey:
7374
self.transport.connect(username=self.user, password=self.password, pkey=self.key)
7475
elif self.method is AuthenticationMethod.agent:
75-
keys = self.agent.get_keys()
76-
if not keys:
77-
raise NoAgentKeys()
78-
for k in keys:
79-
try:
80-
self.transport.connect(username=self.user, password=self.password, pkey=k)
81-
ssh_pub_key = SSHKey(f"{k.get_name()} {k.get_base64()}")
82-
ssh_pub_key.parse()
83-
logging.debug("ssh-mitm connected to remote host with username=%s, key=%s %s %sbits", self.user, k.get_name(), ssh_pub_key.hash_sha256(), ssh_pub_key.bits)
84-
break
85-
except paramiko.AuthenticationException:
86-
self.transport.close()
87-
self.transport = paramiko.Transport((self.host, self.port))
76+
if self.agent is not None:
77+
keys = self.agent.get_keys()
78+
if not keys:
79+
raise NoAgentKeys()
80+
for k in keys:
81+
try:
82+
self.transport.connect(username=self.user, password=self.password, pkey=k)
83+
ssh_pub_key = SSHKey(f"{k.get_name()} {k.get_base64()}")
84+
ssh_pub_key.parse()
85+
logging.debug("ssh-mitm connected to remote host with username=%s, key=%s %s %sbits", self.user, k.get_name(), ssh_pub_key.hash_sha256(), ssh_pub_key.bits)
86+
break
87+
except paramiko.AuthenticationException:
88+
self.transport.close()
89+
self.transport = paramiko.Transport((self.host, self.port))
8890

8991
else:
9092
logging.error('authentication method "%s" not supported!', self.method.value)
@@ -108,6 +110,6 @@ def connect(self):
108110

109111
return False
110112

111-
def check_host_key(self, hostname, keytype, key):
113+
def check_host_key(self, hostname: Text, keytype: Text, key: PKey) -> bool:
112114
"""checks the host key, default always returns true"""
113115
return True

ssh_proxy_server/forwarders/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import (
2-
TYPE_CHECKING
2+
TYPE_CHECKING,
3+
Optional
34
)
45

56
from enhancements.modules import BaseModule
@@ -28,7 +29,7 @@ def __init__(self, session: 'ssh_proxy_server.session.Session') -> None:
2829
self.server_channel: paramiko.Channel = session.ssh_client.transport.open_session()
2930
if session.agent is not None:
3031
session.agent.forward_agent(self.server_channel)
31-
self.channel: paramiko.Channel = None
32+
self.channel: Optional[paramiko.Channel] = None
3233
self.session: 'Session' = session
3334

3435
# pass environment variables from client to server

0 commit comments

Comments
 (0)