146 lines
4.8 KiB
Python
146 lines
4.8 KiB
Python
"""
|
|
This allows for running the telnet communication over an encrypted SSL tunnel. To use it, requires a
|
|
client supporting Telnet SSL.
|
|
|
|
The protocol will try to automatically create the private key and certificate on the server side
|
|
when starting and will warn if this was not possible. These will appear as files ssl.key and
|
|
ssl.cert in mygame/server/.
|
|
|
|
"""
|
|
from __future__ import print_function
|
|
|
|
import os
|
|
try:
|
|
from OpenSSL import crypto
|
|
from twisted.internet import ssl as twisted_ssl
|
|
except ImportError as error:
|
|
errstr = """
|
|
{err}
|
|
Telnet-SSL requires the PyOpenSSL library and dependencies:
|
|
|
|
pip install pyopenssl pycrypto enum pyasn1 service_identity
|
|
|
|
Stop and start Evennia again. If no certificate can be generated, you'll
|
|
get a suggestion for a (linux) command to generate this locally.
|
|
|
|
"""
|
|
raise ImportError(errstr.format(err=error))
|
|
|
|
from django.conf import settings
|
|
from evennia.server.portal.telnet import TelnetProtocol
|
|
|
|
_GAME_DIR = settings.GAME_DIR
|
|
|
|
_PRIVATE_KEY_LENGTH = 2048
|
|
_PRIVATE_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssl.key")
|
|
_PUBLIC_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssl-public.key")
|
|
_CERTIFICATE_FILE = os.path.join(_GAME_DIR, "server", "ssl.cert")
|
|
_CERTIFICATE_EXPIRE = 365 * 24 * 60 * 60 * 20 # 20 years
|
|
_CERTIFICATE_ISSUER = {"C": "EV", "ST": "Evennia", "L": "Evennia", "O":
|
|
"Evennia Security", "OU": "Evennia Department", "CN": "evennia"}
|
|
|
|
# messages
|
|
|
|
NO_AUTOGEN = """
|
|
Evennia could not auto-generate the SSL private- and public keys ({{err}}).
|
|
If this error persists, create them manually (using the tools for your OS). The files
|
|
should be placed and named like this:
|
|
{}
|
|
{}
|
|
""".format(_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE)
|
|
|
|
NO_AUTOCERT = """
|
|
Evennia's could not auto-generate the SSL certificate ({{err}}).
|
|
The private key already exists here:
|
|
{}
|
|
If this error persists, create the certificate manually (using the private key and
|
|
the tools for your OS). The file should be placed and named like this:
|
|
{}
|
|
""".format(_PRIVATE_KEY_FILE, _CERTIFICATE_FILE)
|
|
|
|
|
|
class SSLProtocol(TelnetProtocol):
|
|
"""
|
|
Communication is the same as telnet, except data transfer
|
|
is done with encryption set up by the portal at start time.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(SSLProtocol, self).__init__(*args, **kwargs)
|
|
self.protocol_key = "telnet/ssl"
|
|
|
|
|
|
def verify_or_create_SSL_key_and_cert(keyfile, certfile):
|
|
"""
|
|
Verify or create new key/certificate files.
|
|
|
|
Args:
|
|
keyfile (str): Path to ssl.key file.
|
|
certfile (str): Parth to ssl.cert file.
|
|
|
|
Notes:
|
|
If files don't already exist, they are created.
|
|
|
|
"""
|
|
|
|
if not (os.path.exists(keyfile) and os.path.exists(certfile)):
|
|
# key/cert does not exist. Create.
|
|
try:
|
|
# generate the keypair
|
|
keypair = crypto.PKey()
|
|
keypair.generate_key(crypto.TYPE_RSA, _PRIVATE_KEY_LENGTH)
|
|
|
|
with open(_PRIVATE_KEY_FILE, 'wt') as pfile:
|
|
pfile.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, keypair))
|
|
print("Created SSL private key in '{}'.".format(_PRIVATE_KEY_FILE))
|
|
|
|
with open(_PUBLIC_KEY_FILE, 'wt') as pfile:
|
|
pfile.write(crypto.dump_publickey(crypto.FILETYPE_PEM, keypair))
|
|
print("Created SSL public key in '{}'.".format(_PUBLIC_KEY_FILE))
|
|
|
|
except Exception as err:
|
|
print(NO_AUTOGEN.format(err=err))
|
|
return False
|
|
|
|
else:
|
|
|
|
try:
|
|
# create certificate
|
|
cert = crypto.X509()
|
|
subj = cert.get_subject()
|
|
for key, value in _CERTIFICATE_ISSUER.items():
|
|
setattr(subj, key, value)
|
|
cert.set_issuer(subj)
|
|
|
|
cert.set_serial_number(1000)
|
|
cert.gmtime_adj_notBefore(0)
|
|
cert.gmtime_adj_notAfter(_CERTIFICATE_EXPIRE)
|
|
cert.set_pubkey(keypair)
|
|
cert.sign(keypair, 'sha1')
|
|
|
|
with open(_CERTIFICATE_FILE, 'wt') as cfile:
|
|
cfile.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
|
print("Created SSL certificate in '{}'.".format(_CERTIFICATE_FILE))
|
|
|
|
except Exception as err:
|
|
print(NO_AUTOCERT.format(err=err))
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def getSSLContext():
|
|
"""
|
|
This is called by the portal when creating the SSL context
|
|
server-side.
|
|
|
|
Returns:
|
|
ssl_context (tuple): A key and certificate that is either
|
|
existing previously or created on the fly.
|
|
|
|
"""
|
|
|
|
if verify_or_create_SSL_key_and_cert(_PRIVATE_KEY_FILE, _CERTIFICATE_FILE):
|
|
return twisted_ssl.DefaultOpenSSLContextFactory(_PRIVATE_KEY_FILE, _CERTIFICATE_FILE)
|
|
else:
|
|
return None
|