Skip to content

Commit

Permalink
Impl crypto binding (#37)
Browse files Browse the repository at this point in the history
This fixes #1
* Return early from sstp_call_connected_received
* Log server cert sha fingerprints as info
* Improve input validation in call_connected
* Ensure Crypto Binding Attribute is present in Call Connected
* Ensure Crypto Binding Attribute is of correct size (104 bytes)
* Ensure Crypto Binding Hash is either SHA1 or SHA256
* Implement the PPP SSTP API protocol
* Integrate the PPP SSTP API into sstp-server
* Verify crypto binding set by client using HLAK
* Update README.rst in regard to crypto binding support

Signed-off-by: Tijs Van Buggenhout <tijs.van.buggenhout@axsguard.com>
  • Loading branch information
tisj committed Jan 18, 2021
1 parent 2712ac7 commit e60442d
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 17 deletions.
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Requirements

For Python 2.7, use v0.4.x

**Crypto Binding** is supported using *SSTP ppp API* plug-in sstp-pppd-plugin.so from `sstp-client <http://sstp-client.sourceforge.net/>`_.

Install
-------

Expand Down Expand Up @@ -65,7 +67,6 @@ Or:
Known Issues
------------

- Not implemented *Crypto Binding* yet. Potential MITM attack risk exists.
- High CPU usage, may not suitable for high thougthput applications.

License
Expand Down
4 changes: 2 additions & 2 deletions sstpd/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ def main():
ssl_ctx.set_ciphers(args.ciphers)
if args.pem_cert:
cert_hash = certtool.get_fingerprint(args.pem_cert)
logging.debug('Cert SHA-1: %s', hexlify(cert_hash.sha1).decode())
logging.debug('Cert SHA-256: %s', hexlify(cert_hash.sha256).decode())
logging.info('Cert SHA-1: %s', hexlify(cert_hash.sha1).decode())
logging.info('Cert SHA-256: %s', hexlify(cert_hash.sha256).decode())
else:
cert_hash = None
logging.warning('--pem_cert not given, hash checking disabled')
Expand Down
122 changes: 122 additions & 0 deletions sstpd/ppp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
from struct import pack
import asyncio
from binascii import hexlify

from .constants import VERBOSE
from .codec import escape, PppDecoder
Expand Down Expand Up @@ -108,3 +109,124 @@ def __call__(self):
proto.sstp = self.sstp
proto.remote = self.remote
return proto


class PPPDSSTPAPIProtocol(asyncio.Protocol):
SSTP_API_MSG_UNKNOWN = 0
SSTP_API_MSG_AUTH = 1
SSTP_API_MSG_ADDR = 2
SSTP_API_MSG_ACK = 3

message_str = {
SSTP_API_MSG_UNKNOWN: 'SSTP_API_MSG_UNKNOWN',
SSTP_API_MSG_AUTH: 'SSTP_API_MSG_AUTH',
SSTP_API_MSG_ADDR: 'SSTP_API_MSG_ADDR',
SSTP_API_MSG_ACK: 'SSTP_API_MSG_ACK', }

SSTP_API_ATTR_UNKNOWN = 0
SSTP_API_ATTR_MPPE_SEND = 1
SSTP_API_ATTR_MPPE_RECV = 2
SSTP_API_ATTR_GATEWAY = 3
SSTP_API_ATTR_ADDR = 4

attribute_str = {
SSTP_API_ATTR_UNKNOWN: 'SSTP_API_ATTR_UNKNOWN',
SSTP_API_ATTR_MPPE_SEND: 'SSTP_API_ATTR_MPPE_SEND',
SSTP_API_ATTR_MPPE_RECV: 'SSTP_API_ATTR_MPPE_RECV',
SSTP_API_ATTR_GATEWAY: 'SSTP_API_ATTR_GATEWAY',
SSTP_API_ATTR_ADDR: 'SSTP_API_ATTR_ADDR', }

def __init__(self):
self.sstp = None
self.master_send_key = None
self.master_recv_key = None

def connection_made(self, transport):
sockname = transport.get_extra_info('sockname')
logging.info('Initiate PPP SSTP API protocol on %s.', sockname)
self.transport = transport

def message_type(self, mtype):
return self.message_str.get(mtype,
self.message_str[self.SSTP_API_MSG_UNKNOWN])

def is_auth_message(self, mtype):
return mtype is self.SSTP_API_MSG_AUTH

def attribute_type(self, atype):
return self.attribute_str.get(atype,
self.attribute_str[self.SSTP_API_ATTR_UNKNOWN])

def is_mppe_send_attribute(self, atype):
return atype is self.SSTP_API_ATTR_MPPE_SEND

def is_mppe_recv_attribute(self, atype):
return atype is self.SSTP_API_ATTR_MPPE_RECV

def handle_attribute(self, atype, adata):
if self.is_mppe_send_attribute(atype):
self.master_send_key = adata
if __debug__:
logging.debug("PPP master send key %s",
hexlify(self.master_send_key))
elif self.is_mppe_recv_attribute(atype):
self.master_recv_key = adata
if __debug__:
logging.debug("PPP master receive key %s",
hexlify(self.master_recv_key))

def message_parse(self, message):
idx = 0
while idx < len(message):
if (idx + 4 > len(message)):
break
atype = (message[idx+1] << 8) | message[idx]
alen = (message[idx+3] << 8) | message[idx+2]
logging.debug("SSTP API message - attribute %s (len: %d)",
self.attribute_type(atype), alen)
idx += 4
self.handle_attribute(atype, message[idx:idx + alen])
idx += alen

return (idx == len(message))

def data_received(self, message):
# magic 'sstp' as 32-bits integer in network order
magic = b'\x70\x74\x73\x73'
# ack whatever received and close connection
ack = magic + b'\x00\x00' + b'\x03\x00'
self.transport.write(ack)
self.close()
if message[0:4] != magic:
logging.error("SSTP API message - invalid magic %a.", message[0:4])
return
length = (message[5] << 8) | message[4]
if length + 8 != len(message):
logging.error("SSTP API message - incorrect length.")
return
if not self.message_parse(message[8:]) :
logging.error("SSTP API message - failed parsing attributes.")
return
mtype = (message[7] << 8) | message[6]
if self.is_auth_message(mtype):
if (self.master_send_key is None or
self.master_recv_key is None):
logging.error("SSTP API message - missing master "
"send and/or receive key.")
return
self.sstp.higher_layer_authentication_key(
self.master_send_key, self.master_recv_key)

def close(self):
logging.info('Finished PPP SSTP API protocol.')
self.transport.close()


class PPPDSSTPPluginFactory:
def __init__(self, callback):
self.sstp = callback

def __call__(self):
proto = PPPDSSTPAPIProtocol()
proto.sstp = self.sstp
return proto
Loading

0 comments on commit e60442d

Please sign in to comment.