Source code for evennia.server.portal.telnet_ssl
"""
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/.
"""
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
)
[docs]class SSLProtocol(TelnetProtocol):
"""
Communication is the same as telnet, except data transfer
is done with encryption set up by the portal at start time.
"""
[docs] def __init__(self, *args, **kwargs):
super(SSLProtocol, self).__init__(*args, **kwargs)
self.protocol_key = "telnet/ssl"
[docs]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).decode("utf-8"))
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).decode("utf-8"))
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).decode("utf-8"))
print("Created SSL certificate in '{}'.".format(_CERTIFICATE_FILE))
except Exception as err:
print(NO_AUTOCERT.format(err=err))
return False
return True
[docs]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