Skip to content

Commit

Permalink
update client/server -> deadlock pbs during connection establishment
Browse files Browse the repository at this point in the history
  • Loading branch information
ElNiak committed Dec 27, 2023
1 parent ac6739a commit 750e158
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 232 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ make env; make install;

```bash
./ssh3_env/bin/activate && python3 py-ssh3/server_cli.py --help
./ssh3_env/bin/activate && python3 py-ssh3/server_cli.py --generateSelfSignedCert --enablePasswordLogin --bind "127.0.0.1:4443" --urlPath "/my-secret-path" --verbose
./ssh3_env/bin/activate && python3 py-ssh3/server_cli.py --generateSelfSignedCert --enablePasswordLogin --bind "127.0.0.1:4443" --urlPath "/my-secret-path" --verbose --insecure
```

#### Authorized keys and authorized identities
Expand All @@ -30,7 +30,7 @@ TODO
```bash
./ssh3_env/bin/activate && python3 py-ssh3/client_cli.py --help
./ssh3_env/bin/activate && python3 py-ssh3/client_cli.py --url "127.0.0.1:4443/my-secret-path?user=elniak" --verbose --usePassword
./ssh3_env/bin/activate && python3 py-ssh3/client_cli.py --url "127.0.0.1:4443/my-secret-path?user=elniak" --verbose --privkey ~/.ssh/id_rsa
./ssh3_env/bin/activate && python3 py-ssh3/client_cli.py --url "127.0.0.1:4443/my-secret-path?user=elniak" --verbose --privkey ~/.ssh/id_rsa --insecure
```

#### Private-key authentication
Expand All @@ -50,4 +50,5 @@ TODO
- [ ] Add examples
- [ ] Add more features
- [ ] Add threading support
- [ ] Inspire more from [paramiko]
- [ ] Inspire more from [paramiko]
- [ ] Secure version
35 changes: 19 additions & 16 deletions py-ssh3/client_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,9 @@ async def main():
configuration = QuicConfiguration(
alpn_protocols=H3_ALPN,
is_client=True,
max_datagram_frame_size=65536,
max_datagram_size=30000
# TODO Invalid payload length
# max_datagram_frame_size=65536,
# max_datagram_size=30000
)
configuration.verify_mode = ssl.CERT_REQUIRED if not args.insecure else ssl.CERT_NONE
# load SSL certificate and key
Expand Down Expand Up @@ -302,7 +303,7 @@ async def dial_quic_host(hostname, port, quic_config, known_hosts_path):
pass
log.info(f"Connecting to {hostname}:{port}")
# Attempt to establish a QUIC connection
async with connect(hostname, port, configuration=quic_config) as client:
async with connect(hostname, port, configuration=quic_config, create_protocol=HttpClient) as client:
# Connection established
return client

Expand Down Expand Up @@ -360,17 +361,16 @@ async def dial_quic_host(hostname, port, quic_config, known_hosts_path):
if not client or client == -1:
return exit(-1)

tls_state = client.connection.tls.state
conv = new_client_conversation(30000,10, tls_state)
tls_state = client._quic.tls.state
log.info(f"TLS state is {tls_state}")
conv = await new_client_conversation(30000,10, tls_state)

log.info(f"Conversation is {conv}")

# HTTP request over QUIC
# perform request
req = perform_http_request(
client=client,
url=url_from_param,
data="CONNECT",

)
req = HttpRequest(method="CONNECT", url=URL(url_from_param))
log.info(f"Request is {req}")
# await asyncio.gather(*coros)
# req.Proto = "ssh3" # TODO
# process http pushes
Expand Down Expand Up @@ -415,14 +415,17 @@ async def dial_quic_host(hostname, port, quic_config, known_hosts_path):

auth_methods.append(config_auth_methods)

for issuer_config in oidc_config:
if issuer_url == issuer_config.issuer_url:
auth_methods.append(OIDCAuthMethod(args.doPkce,issuer_config))
if oidc_config:
for issuer_config in oidc_config:
if issuer_url == issuer_config.issuer_url:
auth_methods.append(OIDCAuthMethod(args.doPkce,issuer_config))

log.debug(f"Try the following auth methods: {auth_methods}")

identity = None
for method in auth_methods:
if isinstance(method, PasswordAuthMethod):
password = input(f"Password for {parsed_url}: ")
password = input(f"Password: ")
identity = method.into_identity(password)
elif isinstance(method, PrivkeyFileAuthMethod):
try:
Expand Down Expand Up @@ -465,7 +468,7 @@ async def dial_quic_host(hostname, port, quic_config, known_hosts_path):
log.debug("Send CONNECT request to the server")

try:
ret = conv.establish_client_conversation(req, client)
ret, err = await conv.establish_client_conversation(req, client)
if ret == "Unauthorized": # Replace with your specific error class
log.error("Access denied from the server: unauthorized")
exit(-1)
Expand Down
2 changes: 2 additions & 0 deletions py-ssh3/http3/http3_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ async def perform_http_request(
start = time.time()
if data is not None:
data_bytes = data.encode()
logger.info("Sending %d-byte request body" % len(data_bytes))
http_events = await client.post(
url,
data=data_bytes,
Expand All @@ -265,6 +266,7 @@ async def perform_http_request(
)
method = "POST"
else:
logger.info("Sending GET request")
http_events = await client.get(url)
method = "GET"
elapsed = time.time() - start
Expand Down
1 change: 1 addition & 0 deletions py-ssh3/http3/http3_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from aioquic.quic.logger import QuicFileLogger
from aioquic.tls import SessionTicket


try:
import uvloop
except ImportError:
Expand Down
111 changes: 0 additions & 111 deletions py-ssh3/http3/quic_round_trip.py

This file was deleted.

119 changes: 84 additions & 35 deletions py-ssh3/linux_server/auth.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,100 @@
import base64
import logging
from aioquic.asyncio import serve
from aioquic.asyncio.protocol import QuicConnectionProtocol
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.events import HandshakeCompleted
from aioquic.asyncio.server import HttpRequestHandler, HttpServerProtocol, Route
from aioquic.quic.events import ProtocolNegotiated
from typing import Callable
import util.linux_util as linux_util
from util.linux_util.linux_user import *
from http3.http3_server import HttpRequestHandler
from linux_server.handlers import *
from ssh.version import *

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)


def handle_auths(enablePasswordLogin: bool, defaultMaxPacketSize: int) -> Callable:
async def handle_request(handler: HttpRequestHandler, event: ProtocolNegotiated):
request = handler._http_request_received
logger.debug(f"Received request from User-Agent {request.headers.get('user-agent')}")
async def handle_auths(
enable_password_login: bool,
default_max_packet_size: int,
handler_func: callable,
request_handler: HttpRequestHandler
):
"""
Handle different types of authentication for a given HTTP request.
"""
# Set response server header
request_handler.send({
"type": "http.response.start",
"status": 200,
"headers": [(b"server", b"MySSH3Server")] # Replace with your server version
})

# Add your version check and logic here
# Check SSH3 version
user_agent = request_handler.scope["headers"].get(b"user-agent", b"").decode()
major, minor, patch = parse_version(user_agent) # Implement this function
if major != MAJOR or minor != MINOR:
request_handler.send({
"type": "http.response.body",
"body": b"Unsupported version",
"more_body": False,
})
return

if not handler._quic._is_handshake_complete:
handler._quic.send_response(status_code=425) # 425 Too Early
return
# Check if connection is complete
if not isinstance(request_handler.connection._quic, QuicConnection) or not request_handler.connection._quic._handshake_complete:
request_handler.send({
"type": "http.response.start",
"status": 425, # HTTP StatusTooEarly
"headers": []
})
return

# Process the request and perform authentication
authorization = request.headers.get('authorization')
if enablePasswordLogin and authorization.startswith('Basic '):
await handle_basic_auth(handler, request)
elif authorization.startswith('Bearer '):
# Handle bearer authentication
pass
else:
handler._quic.send_response(status_code=401) # 401 Unauthorized
# Create a new conversation
# Implement NewServerConversation based on your protocol's specifics
conv = await NewServerConversation(
request_handler.connection._quic,
default_max_packet_size
)

return handle_request
# Handle authentication
authorization = request_handler.scope["headers"].get(b"authorization", b"").decode()
if enable_password_login and authorization.startswith("Basic "):
await handle_basic_auth(handler_func, conv, request_handler)
elif authorization.startswith("Bearer "):
username = request_handler.scope["headers"].get(b":path").decode().split("?", 1)[0].lstrip("/")
conv_id = base64.b64encode(conv.id).decode()
await HandleBearerAuth(username, conv_id, handler_func, request_handler)
else:
request_handler.send({
"type": "http.response.start",
"status": 401,
"headers": [(b"www-authenticate", b"Basic")]
})

await request_handler.transmit()

def handle_basic_auth(handler: HttpRequestHandler, request):
auth = request.headers.get('authorization')
username, password = base64.b64decode(auth.split(' ')[1]).decode().split(':')
if not linux_util.UserPasswordAuthentication(username, password):
handler._quic.send_response(status_code=401) # 401 Unauthorized
return
async def handle_basic_auth(request, handler_func, conv, request_handler):
# Extract Basic Auth credentials
username, password, ok = extract_basic_auth(request)
if not ok:
return web.Response(status=401)

# Replace this with your own authentication method
ok = await user_password_authentication(username, password)
if not ok:
return web.Response(status=401)

return await handler_func(username, conv, request)

def extract_basic_auth(request):
auth_header = request.headers.get('Authorization')
if not auth_header:
return None, None, False

# Continue with the authenticated request processing
# Basic Auth Parsing
try:
auth_type, auth_info = auth_header.split(' ', 1)
if auth_type.lower() != 'basic':
return None, None, False

def check_credentials(username, password):
# Placeholder for checking username and password
return True # Assuming credentials are valid
username, password = base64.b64decode(auth_info).decode().split(':', 1)
return username, password, True
except Exception as e:
return None, None, False
Loading

0 comments on commit 750e158

Please sign in to comment.