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 builtins import object
|
||||
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
from twisted.application import internet, service
|
||||
from twisted.internet import protocol, reactor
|
||||
from twisted.internet.task import LoopingCall
|
||||
from twisted.web import server
|
||||
import django
|
||||
django.setup()
|
||||
from django.conf import settings
|
||||
|
|
@ -228,9 +225,9 @@ if TELNET_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:
|
||||
ifacestr = ""
|
||||
|
|
@ -241,15 +238,20 @@ if SSL_ENABLED:
|
|||
factory = protocol.ServerFactory()
|
||||
factory.noisy = False
|
||||
factory.sessionhandler = PORTAL_SESSIONS
|
||||
factory.protocol = ssl.SSLProtocol
|
||||
ssl_service = internet.SSLServer(port,
|
||||
factory,
|
||||
ssl.getSSLContext(),
|
||||
interface=interface)
|
||||
ssl_service.setName('EvenniaSSL%s' % pstring)
|
||||
PORTAL.services.addService(ssl_service)
|
||||
factory.protocol = telnet_ssl.SSLProtocol
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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