Skip to content

Commit

Permalink
Update SNI branch to Python 3
Browse files Browse the repository at this point in the history
- Replaced expired CA certificate
- Changed certificate sigining algorithm to SHA256
- Added extensions to generated X509 certificates to support
  interaction with latest browsers

Thanks to Nhan Huynh for implementing this feature :)
  • Loading branch information
tinajohnson committed Dec 15, 2023
1 parent 81ef1df commit 22a6f6c
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 137 deletions.
11 changes: 8 additions & 3 deletions fakenet/configs/default.ini
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355
# hostname string, !hostname to insert the actual hostname
# of the system, or !random to generate a random hostname
# between 1 and 15 characters (inclusive).
# * Static_CA - Set FakeNet to use user provided CA certificate to sign generated certificates.
# * CA_Cert - CA certificate in PEM format to be used when Static_CA config is set. Manually
# add this certificate to Windows trust store before executing FakeNet.
# * CA_Key - CA private key in PEM format to be used when Static_CA config is set.


[ProxyTCPListener]
Enabled: True
Expand All @@ -208,8 +213,8 @@ Port: 38926
Listeners: HTTPListener, RawListener, FTPListener, DNSListener, POPListener, SMTPListener, TFTPListener, IRCListener
Hidden: False
Static_CA: No
CA_Cert: listeners/ssl_utils/server.pem
CA_Key: listeners/ssl_utils/privkey.pem
CA_Cert: configs/fakenet_ca.crt
CA_Key: configs/fakenet_ca.key

[ProxyUDPListener]
Enabled: True
Expand Down Expand Up @@ -286,7 +291,7 @@ Hidden: False
# Custom: sample_custom_response.ini

[HTTPListener443]
Enabled: False
Enabled: True
Port: 443
Protocol: TCP
Listener: HTTPListener
Expand Down
19 changes: 19 additions & 0 deletions fakenet/configs/fakenet_ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDHzCCAgegAwIBAgIUdwSkHdOM2mMDw094Kha+9Z9/w60wDQYJKoZIhvcNAQEL
BQAwHzELMAkGA1UEBhMCVVMxEDAOBgNVBAMMB2Zha2VuZXQwHhcNMjMxMjE0MDAx
NDUxWhcNMjQwMTEzMDAxNDUxWjAfMQswCQYDVQQGEwJVUzEQMA4GA1UEAwwHZmFr
ZW5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7pGzS8bX8M3SSQ
mk79puvqGBHwWVpDK82T44N/8mXHJ1R/7jMDq2wpkSjliiAE0mxPpkzMbr9mkeP/
/31GKAszbJYnurrxxYbLyOdRst2VqoXkWTia61lrsRIGcjwzKe2zyMCcuiyRTLcP
BmYd/ie5AzyHxitlS49cub+QkIODUAKTiZT3mPu6Yw2XvYkg+up69NzC0a/XexUv
PvgbBizquKj/YzMSp5X7ieYGv0xHf8Dhf3mqh9oLk35X/qV3LqdnVPjweCR8X9ze
yhfBbDr1VoBnOe2Nb5hlU9MB//A0hgDYj5TrHa4JrbNkv3lYMd4uv/CBW6o18Ba+
/zjEvyECAwEAAaNTMFEwHQYDVR0OBBYEFD3wRGMPQdWtBCKRy5c9N5YWnki2MB8G
A1UdIwQYMBaAFD3wRGMPQdWtBCKRy5c9N5YWnki2MA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQELBQADggEBAGT5rmafrlv8VIXAgc0iazNd7A6rT7xNLkuF2JGK
7NV3yvsWM6SA+DlG7y70om+eKjd+qxzinxnSt6uFJhcdCqot1LU1u3OZDifTTJmk
31yEYp/+A93qjwe1Ag2rsVcztRl88KtsKrKNohv24iiWfIVDnHo0joerXoaGQwo6
zXl4GVJBEEAhf2GQRgyXcoWkSrsq8UKtVV9dI5QgIS6vZ65oNQEeoAXH56ihFUBX
hS+4Ko2FfUtxbfbw7tpDaNtqhAzJ+LE4RoDUepyCDXPha0Wb4giGOd5EEubYrFKi
DOdAMiiQT1WLK3/UnMlCOV4lne+g9JwBCXL8C0F0W1fFZY8=
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions fakenet/configs/fakenet_ca.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDO6Rs0vG1/DN0k
kJpO/abr6hgR8FlaQyvNk+ODf/JlxydUf+4zA6tsKZEo5YogBNJsT6ZMzG6/ZpHj
//99RigLM2yWJ7q68cWGy8jnUbLdlaqF5Fk4mutZa7ESBnI8Mynts8jAnLoskUy3
DwZmHf4nuQM8h8YrZUuPXLm/kJCDg1ACk4mU95j7umMNl72JIPrqevTcwtGv13sV
Lz74GwYs6rio/2MzEqeV+4nmBr9MR3/A4X95qofaC5N+V/6ldy6nZ1T48HgkfF/c
3soXwWw69VaAZzntjW+YZVPTAf/wNIYA2I+U6x2uCa2zZL95WDHeLr/wgVuqNfAW
vv84xL8hAgMBAAECggEAAyp6KDPcHCjH5XU4hXGPeGYvkhldQtxqsw2Rr7xpVWrl
dw1q8/dR2kVGVFSlBWe7tIPk0Ew8fD14+xtXG4xhmECAoElTHY+b7VgxkVJPem9h
RD4XOLsP4ba4rlNes+DiUCHKbyHTOox1RXytQZxNbgbVr7tOVNU1nf0rVmzt9Zbw
uPltpCcL+yvnaWmBUgdCLIhIT/HDrp4+uZP+zW7pVm/KMQL0I9OMnm6dCimP88pU
meepDRdxHkHVb347jx0+nWSH2V63wxH6WR8lAb4oAXeQXUgS8Q0lXh70EW/x0Ut3
neB/mfuaXLVrBcBTbhPTU7PKd/Sc+dkkqhEpNRR3dQKBgQDyLNPyWEJVXwQ6iOom
xxyqm61DZw8IpBYVmfYrOisTUWVC+oNlpsNPzWsgwmWzpcJ8s6ykupWFcWZeJv2h
XPlq+Ai6Ky0v+yESiUtNb+lKuXGvHqTpTxvPgL/20ZMfRIkWs9YBLA+aTh5RC8Vs
i12fRW9JTZTKgyf7PEkj+DjCCwKBgQDauOszqtFs6d36AMVJJ7OlmZnEWaELv9bW
ns7kdeBgr35gzGbDOkyQGBII5SCwpgWbXyxT39T9xuNoWCWXSZdHDco/UlAG47qI
Pq/KmtbrKxvg6yKsPIDV+cexuBrLIvzsaz8qPbfE/W7nxf+FtvDhvnGyqoiYd3DS
XSD5HcwLAwKBgQDdQpG+mF66qx4s8Myl80NAqQ1LSMyWg3xd7hXYdsPGWZaf9Eu6
wvstXSvkeVf8I5Um4+33bzWO/wWdPhh6pnyG++jVVv9pGBOmYOP48yd9iyLP8bqQ
IyPwmNxKgD3f0nlB0brTxVLYE0llmNCelFJMY18C5SvtPpl31COrBm2s8wKBgG8D
zN28pe+SBIkQOxKWhChZfiKbG5LLHFBy6rAq5GguqwaWuNH+lT3N+dlp8t22ZsIl
3Gn2AjWM7X/Yvbu8LnxyE2Vwcg4NKHBe4PsE/HEAwHW44zBoxTvWO/WIbJEOgTG+
faEDEnN57wDVDozf/gOWlj8JL6uzdCBSBJps9VPhAoGAWQUkdpxRxEARhL7wYR7g
EidDWKnULiULjlrP7VPMJ5hrZf5PmWyZLWW3SkEUhcf25CnJoUoq1OOB1GjL9lBL
0+tXalLnA/mRxO5ILzwJivHyJKnljuPKyXmpKt8H4KRUXV3Uk2may58Jwn8InuRE
aW956i0O1tgrDOj3tSAT8KI=
-----END PRIVATE KEY-----
2 changes: 1 addition & 1 deletion fakenet/listeners/DNSListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DNSListener(object):

def taste(self, data, dport):

confidence = 1 if dport is 53 else 0
confidence = 1 if dport == 53 else 0

try:
d = DNSRecord.parse(data)
Expand Down
13 changes: 5 additions & 8 deletions fakenet/listeners/HTTPListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@
import mimetypes

import time
import shutil
import traceback
import subprocess
from OpenSSL import crypto
from ssl_utils import SSLWrapper

from .ssl_utils import SSLWrapper
from . import *

MIME_FILE_RESPONSE = {
Expand Down Expand Up @@ -208,14 +205,14 @@ def start(self):
if self.config.get('usessl') == 'Yes':
self.logger.debug("HTTP Listener starting with SSL")
config = {
'cert_dir': self.config.get('cert_dir', 'temp_certs'),
'cert_dir': self.config.get('cert_dir', 'configs/temp_certs'),
'networkmode': self.config.get('networkmode', None),
'static_ca': self.config.get('static_ca', False),
'static_ca': self.config.get('static_ca', "No"),
'ca_cert': self.config.get('ca_cert'),
'ca_key': self.config.get('ca_key')
}
self.sslwrapper = SSLWrapper(config)
self.server.sslwrapper = sslwrapper
self.server.sslwrapper = self.sslwrapper
self.server.socket = self.server.sslwrapper.wrap_socket(
self.server.socket)

Expand Down
4 changes: 2 additions & 2 deletions fakenet/listeners/ProxyListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import logging
import ssl
from OpenSSL import SSL
from .ssl_utils import ssl_detector
from .ssl_utils import ssl_detector, SSLWrapper
from . import *
import os
import traceback
Expand Down Expand Up @@ -54,7 +54,7 @@ def start(self):

self.logger.debug('Starting TCP ...')
config = {
'cert_dir': self.config.get('cert_dir', 'temp_certs'),
'cert_dir': self.config.get('cert_dir', 'configs/temp_certs'),
'networkmode': self.config.get('networkmode', None),
'static_ca': self.config.get('static_ca', False),
'ca_cert': self.config.get('ca_cert'),
Expand Down
100 changes: 54 additions & 46 deletions fakenet/listeners/ssl_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import sys
import ssl
import random
from listeners import ListenerBase
from pathlib import Path
from OpenSSL import crypto

g_ssl_fellback = False # Notify only once of SSL static certificate fallback

from fakenet import listeners
from fakenet.listeners import ListenerBase

class SSLWrapper(object):
NOT_AFTER_DELTA_SECONDS = 300 * 24 * 60 * 60
Expand All @@ -22,18 +22,20 @@ def __init__(self, config):
self.config = config
self.ca_cert = None
self.ca_key = None
self.ca_cn = self.CN

cert_dir = self.config.get('cert_dir', None)
cert_dir = self.abs_config_path(self.config.get('cert_dir', None))
if cert_dir is None:
raise RuntimeError("certdir key is not specified in config")
raise RuntimeError("cert_dir key is not specified in config")

if not os.path.isdir(cert_dir):
os.makedirs(cert_dir)

# generate and add root CA, which is used to sign for other certs:
if self.config.get('static_ca') == 'Yes':
self.ca_cert = self.config.get('ca_cert', None)
self.ca_key = self.config.get('ca_key', None)
self.ca_cert = self.abs_config_path(self.config.get('ca_cert', None))
self.ca_key = self.abs_config_path(self.config.get('ca_key', None))
self.ca_cn = self._load_cert(self.ca_cert).get_subject().CN
else:
self.ca_cert, self.ca_key = self.create_cert(self.CN)
if ( not self.config.get('networkmode', None) == 'multihost' and
Expand All @@ -42,36 +44,16 @@ def __init__(self, config):
self._add_root_ca(self.ca_cert)

def wrap_socket(self, s):
global g_ssl_fellback
try:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
except AttributeError as e:
if not g_ssl_fellback:
g_ssl_fellback = True
self.logger.error('Exception calling ssl.SSLContext: %s' %
(e.message))
self.logger.error('Falling back on static certificate')
return self.wrap_socket_fallback(s)
self.logger.error('Exception calling ssl.SSLContext: %s' %
(e.message))
else:
ctx.set_servername_callback(self.sni_callback)
ctx.sni_callback = self.sni_callback
ctx.load_cert_chain(certfile=self.ca_cert, keyfile=self.ca_key)
return ctx.wrap_socket(s, server_side=True)

Check failure

Code scanning / CodeQL

Use of insecure SSL/TLS version High

Insecure SSL/TLS protocol version TLSv1 allowed by
call to ssl.SSLContext
.
Insecure SSL/TLS protocol version TLSv1_1 allowed by
call to ssl.SSLContext
.

def wrap_socket_fallback(self, s):
keyfile_path = 'listeners/ssl_utils/privkey.pem'
keyfile_path = ListenerBase.abs_config_path(keyfile_path)
if keyfile_path is None:
raise RuntimeError('Could not locate %s', (key_file,))

certfile_path = 'listeners/ssl_utils/server.pem'
certfile_path = ListenerBase.abs_config_path(certfile_path)
if certfile_path is None:
raise RuntimeError('Cound not locate %s' % (certfile_path,))

return ssl.wrap_socket(s, keyfile=keyfile_path, certfile=certfile_path,
server_side=True, ciphers='RSA')


def create_cert(self, cn, ca_cert=None, ca_key=None, cert_dir=None):
"""
Create a cert given the common name, a signing CA, CA private key and
Expand All @@ -83,10 +65,10 @@ def create_cert(self, cn, ca_cert=None, ca_key=None, cert_dir=None):

f_selfsign = ca_cert is None or ca_key is None
if not cert_dir:
cert_dir = os.path.abspath(self.config.get('cert_dir'))
cert_dir = self.abs_config_path(self.config.get('cert_dir'))
else:
cert_dir = os.path.abspath(cert_dir)

cert_file = os.path.join(cert_dir, "%s.crt" % (cn))
key_file = os.path.join(cert_dir, "%s.key" % (cn))
if os.path.exists(cert_file) and os.path.exists(key_file):
Expand All @@ -108,6 +90,10 @@ def create_cert(self, cn, ca_cert=None, ca_key=None, cert_dir=None):
# Create a cert

cert = crypto.X509()

# Setting certificate version to 3. This is required to use certificate
# extensions which have proven necessary when working with browsers
cert.set_version(2)
cert.get_subject().C = "US"
cert.get_subject().CN = cn
cert.set_serial_number(random.randint(1, 0x31337))
Expand All @@ -117,11 +103,21 @@ def create_cert(self, cn, ca_cert=None, ca_key=None, cert_dir=None):
cert.gmtime_adj_notAfter(na)
cert.set_pubkey(key)
if f_selfsign:
extensions = [
crypto.X509Extension(b'basicConstraints', True, b'CA:TRUE'),
]
cert.set_issuer(cert.get_subject())
cert.sign(key, "sha1")
cert.add_extensions(extensions)
cert.sign(key, "sha256")
else:
alt_name = b'DNS:' + cn.encode()
extensions = [
crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
crypto.X509Extension(b'subjectAltName', False, alt_name)
]
cert.set_issuer(ca_cert_data.get_subject())
cert.sign(ca_key_data, "sha1")
cert.add_extensions(extensions)
cert.sign(ca_key_data, "sha256")

try:
with open(cert_file, "wb") as cert_file_input:
Expand Down Expand Up @@ -156,8 +152,8 @@ def _load_cert(self, certpath):
with open(certpath, 'rb') as cert_file_input:
data = cert_file_input.read()
ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, data)
except:
self.logger.error("Failed to load certficate")
except crypto.Error as e:
self.logger.error("Failed to load certficate: %s" % e.message)
return ca_cert

def _load_private_key(self, keypath):
Expand Down Expand Up @@ -189,17 +185,29 @@ def _remove_root_ca(self, cn):
argv = ['certutil', '-delstore', 'Root', cn]
return self._run_win_certutil(argv)


def __del__(self):
cert = None
if self.ca_cert:
cert = self._load_cert(self.ca_cert)

if (cert is not None and
not self.config.get('networkmode', None) == 'multihost' and
not self.config.get('static_ca') == 'Yes'):
self._remove_root_ca(cert.get_subject().CN)
shutil.rmtree(self.config.get('cert_dir'), ignore_errors=True)
if (not self.config.get('networkmode', None) == 'multihost' and
not self.config.get('static_ca') == 'Yes'):
self._remove_root_ca(self.ca_cn)
shutil.rmtree(self.abs_config_path(self.config.get('cert_dir', None)), ignore_errors=True)
return

def abs_config_path(self, path):
"""
Attempts to return the absolute path of a path from a configuration
setting.
"""

# Try absolute path first
abspath = os.path.abspath(path)
if os.path.exists(abspath):
return abspath

if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
abspath = os.path.join(os.getcwd(), path)
else:
abspath = os.path.join(os.fspath(Path(__file__).parents[2]), path)

return abspath


47 changes: 0 additions & 47 deletions fakenet/listeners/ssl_utils/privkey.pem

This file was deleted.

18 changes: 0 additions & 18 deletions fakenet/listeners/ssl_utils/server.pem

This file was deleted.

4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
],
package_dir={'fakenet': 'fakenet'},
package_data={'fakenet': ['*.pem','diverters/*.py', 'listeners/*.py',
'listeners/ssl_utils/*.py', 'listeners/ssl_utils/*.pem', 'configs/*.ini', 'defaultFiles/*',
'lib/64/*', 'lib/32/*']},
'listeners/ssl_utils/*.py', 'configs/*.crt', 'configs/*.key',
'configs/*.ini', 'defaultFiles/*', 'lib/64/*', 'lib/32/*']},
entry_points={
"console_scripts": [
"fakenet=fakenet.fakenet:main",
Expand Down
Loading

0 comments on commit 22a6f6c

Please sign in to comment.