Cleanup of telnet-ssl, creating public/private/certs in code rather than
calling openssl in a subprocess. Also better handle errors and reporting.
This commit is contained in:
parent
5e08ca3579
commit
948d27cd92
3 changed files with 161 additions and 128 deletions
|
|
@ -10,14 +10,11 @@ by game/evennia.py).
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from builtins import object
|
from builtins import object
|
||||||
|
|
||||||
import time
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from twisted.application import internet, service
|
from twisted.application import internet, service
|
||||||
from twisted.internet import protocol, reactor
|
from twisted.internet import protocol, reactor
|
||||||
from twisted.internet.task import LoopingCall
|
|
||||||
from twisted.web import server
|
|
||||||
import django
|
import django
|
||||||
django.setup()
|
django.setup()
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -228,9 +225,9 @@ if TELNET_ENABLED:
|
||||||
|
|
||||||
if SSL_ENABLED:
|
if SSL_ENABLED:
|
||||||
|
|
||||||
# Start SSL game connection (requires PyOpenSSL).
|
# Start Telnet SSL game connection (requires PyOpenSSL).
|
||||||
|
|
||||||
from evennia.server.portal import ssl
|
from evennia.server.portal import telnet_ssl
|
||||||
|
|
||||||
for interface in SSL_INTERFACES:
|
for interface in SSL_INTERFACES:
|
||||||
ifacestr = ""
|
ifacestr = ""
|
||||||
|
|
@ -241,15 +238,20 @@ if SSL_ENABLED:
|
||||||
factory = protocol.ServerFactory()
|
factory = protocol.ServerFactory()
|
||||||
factory.noisy = False
|
factory.noisy = False
|
||||||
factory.sessionhandler = PORTAL_SESSIONS
|
factory.sessionhandler = PORTAL_SESSIONS
|
||||||
factory.protocol = ssl.SSLProtocol
|
factory.protocol = telnet_ssl.SSLProtocol
|
||||||
ssl_service = internet.SSLServer(port,
|
|
||||||
factory,
|
|
||||||
ssl.getSSLContext(),
|
|
||||||
interface=interface)
|
|
||||||
ssl_service.setName('EvenniaSSL%s' % pstring)
|
|
||||||
PORTAL.services.addService(ssl_service)
|
|
||||||
|
|
||||||
print(" ssl%s: %s" % (ifacestr, port))
|
ssl_context = telnet_ssl.getSSLContext()
|
||||||
|
if ssl_context:
|
||||||
|
ssl_service = internet.SSLServer(port,
|
||||||
|
factory,
|
||||||
|
telnet_ssl.getSSLContext(),
|
||||||
|
interface=interface)
|
||||||
|
ssl_service.setName('EvenniaSSL%s' % pstring)
|
||||||
|
PORTAL.services.addService(ssl_service)
|
||||||
|
|
||||||
|
print(" ssl%s: %s" % (ifacestr, port))
|
||||||
|
else:
|
||||||
|
print(" ssl%s: %s (deactivated - keys/certificate unset)" % (ifacestr, port))
|
||||||
|
|
||||||
|
|
||||||
if SSH_ENABLED:
|
if SSH_ENABLED:
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
"""
|
|
||||||
This is a simple context factory for auto-creating
|
|
||||||
SSL keys and certificates.
|
|
||||||
|
|
||||||
"""
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
try:
|
|
||||||
import OpenSSL
|
|
||||||
from twisted.internet import ssl as twisted_ssl
|
|
||||||
except ImportError as error:
|
|
||||||
errstr = """
|
|
||||||
{err}
|
|
||||||
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
|
|
||||||
|
|
||||||
# messages
|
|
||||||
|
|
||||||
NO_AUTOGEN = """
|
|
||||||
|
|
||||||
{err}
|
|
||||||
Evennia could not auto-generate the SSL private key. If this error
|
|
||||||
persists, create {keyfile} yourself using third-party tools.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NO_AUTOCERT = """
|
|
||||||
|
|
||||||
{err}
|
|
||||||
Evennia's SSL context factory could not automatically, create an SSL
|
|
||||||
certificate {certfile}.
|
|
||||||
|
|
||||||
A private key {keyfile} was already created. Please create {certfile}
|
|
||||||
manually using the commands valid for your operating system, for
|
|
||||||
example (linux, using the openssl program):
|
|
||||||
{exestring}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SSLProtocol(TelnetProtocol):
|
|
||||||
"""
|
|
||||||
Communication is the same as telnet, except data transfer
|
|
||||||
is done with encryption.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SSLProtocol, self).__init__(*args, **kwargs)
|
|
||||||
self.protocol_name = "ssl"
|
|
||||||
|
|
||||||
|
|
||||||
def verify_SSL_key_and_cert(keyfile, certfile):
|
|
||||||
"""
|
|
||||||
This function looks for RSA key and certificate in the current
|
|
||||||
directory. If files ssl.key and ssl.cert does not exist, they
|
|
||||||
are created.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not (os.path.exists(keyfile) and os.path.exists(certfile)):
|
|
||||||
# key/cert does not exist. Create.
|
|
||||||
import subprocess
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from twisted.conch.ssh.keys import Key
|
|
||||||
|
|
||||||
print(" Creating SSL key and certificate ... ", end=' ')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# create the RSA key and store it.
|
|
||||||
KEY_LENGTH = 1024
|
|
||||||
rsaKey = Key(RSA.generate(KEY_LENGTH))
|
|
||||||
keyString = rsaKey.toString(type="OPENSSH")
|
|
||||||
file(keyfile, 'w+b').write(keyString)
|
|
||||||
except Exception as err:
|
|
||||||
print(NO_AUTOGEN.format(err=err, keyfile=keyfile))
|
|
||||||
sys.exit(5)
|
|
||||||
|
|
||||||
# try to create the certificate
|
|
||||||
CERT_EXPIRE = 365 * 20 # twenty years validity
|
|
||||||
# default:
|
|
||||||
# openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
|
|
||||||
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
|
|
||||||
try:
|
|
||||||
subprocess.call(exestring)
|
|
||||||
except OSError as err:
|
|
||||||
raise OSError(NO_AUTOCERT.format(err=err, certfile=certfile, keyfile=keyfile, exestring=exestring))
|
|
||||||
print("done.")
|
|
||||||
|
|
||||||
|
|
||||||
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 or created on the fly.
|
|
||||||
|
|
||||||
"""
|
|
||||||
keyfile = os.path.join(_GAME_DIR, "server", "ssl.key")
|
|
||||||
certfile = os.path.join(_GAME_DIR, "server", "ssl.cert")
|
|
||||||
|
|
||||||
verify_SSL_key_and_cert(keyfile, certfile)
|
|
||||||
return twisted_ssl.DefaultOpenSSLContextFactory(keyfile, certfile)
|
|
||||||
146
evennia/server/portal/telnet_ssl.py
Normal file
146
evennia/server/portal/telnet_ssl.py
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
"""
|
||||||
|
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_name = "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(_PRIVATE_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(_PRIVATE_KEY_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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue