Implement client/server certificate generation + enable ssl required by default on non localhost hosts

This commit is contained in:
Florian Mounier
2014-03-19 14:40:36 +01:00
parent 515a2c6b46
commit 884eeb169a
5 changed files with 125 additions and 20 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.crt
*.key
*.p12

View File

@@ -20,7 +20,15 @@
import tornado.options
import tornado.ioloop
import tornado.httpserver
import uuid
import ssl
import os
import sys
try:
input = raw_input
except NameError:
pass
tornado.options.define("debug", default=False, help="Debug mode")
tornado.options.define("more", default=False,
@@ -28,13 +36,17 @@ tornado.options.define("more", default=False,
tornado.options.define("host", default='127.0.0.1', help="Server host")
tornado.options.define("port", default=57575, type=int, help="Server port")
tornado.options.define("shell", help="Shell to execute at login")
tornado.options.define("secure", default=False,
help="Choose whether or not to use SSL")
tornado.options.define("reallysecure", default=False,
help="Require certificate authentication.")
tornado.options.define("unsecure", default=False,
help="Don't use ssl not recommended")
tornado.options.define("generate_certs", default=False,
help="Generate butterfly certificates")
tornado.options.define("generate_user_pkcs", default='',
help="Generate user pfx for client authentication")
tornado.options.parse_command_line()
import logging
for logger in ('tornado.access', 'tornado.application',
'tornado.general', 'butterfly'):
@@ -48,28 +60,117 @@ for logger in ('tornado.access', 'tornado.application',
log = logging.getLogger('butterfly')
log.info('Starting server')
ioloop = tornado.ioloop.IOLoop.instance()
ca, ca_key = 'butterfly_ca.crt', 'butterfly_ca.key'
cert, cert_key = 'butterfly.crt', 'butterfly.key'
from butterfly import application
if tornado.options.options.reallysecure:
tornado.options.options.secure = True
reqs = ssl.CERT_REQUIRED
elif tornado.options.options.secure:
reqs = ssl.CERT_OPTIONAL
if tornado.options.options.generate_certs:
from OpenSSL import crypto
ca_pk = crypto.PKey()
ca_pk.generate_key(crypto.TYPE_RSA, 2048)
ca_cert = crypto.X509()
ca_cert.get_subject().CN = 'butterfly ca'
ca_cert.set_serial_number(100)
ca_cert.gmtime_adj_notBefore(0) # From now
ca_cert.gmtime_adj_notAfter(315360000) # to 10y
ca_cert.set_issuer(ca_cert.get_subject()) # Self signed
ca_cert.set_pubkey(ca_pk)
ca_cert.sign(ca_pk, 'sha1')
ssl_opts = None
if tornado.options.options.secure:
ssl_opts = dict(certfile="butterfly.crt", keyfile="butterfly.key",
cert_reqs=reqs, ca_certs="butterflyca.crt")
with open(ca, "wb") as cf:
cf.write(
crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert))
with open(ca_key, "wb") as cf:
cf.write(
crypto.dump_privatekey(crypto.FILETYPE_PEM, ca_pk))
server_pk = crypto.PKey()
server_pk.generate_key(crypto.TYPE_RSA, 2048)
server_cert = crypto.X509()
server_cert.get_subject().CN = tornado.options.options.host
server_cert.set_serial_number(200)
server_cert.gmtime_adj_notBefore(0) # From now
server_cert.gmtime_adj_notAfter(315360000) # to 10y
server_cert.set_issuer(ca_cert.get_subject()) # Signed by ca
server_cert.set_pubkey(server_pk)
server_cert.sign(ca_pk, 'sha1')
with open(cert, "wb") as cf:
cf.write(crypto.dump_certificate(crypto.FILETYPE_PEM, server_cert))
with open(cert_key, "wb") as cf:
cf.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, server_pk))
print('Done')
sys.exit(0)
if tornado.options.options.generate_user_pkcs:
from OpenSSL import crypto
if not all(map(os.path.exists,
[cert, cert_key, ca, ca_key])):
print('Please generate certificates using --generate_certs before')
sys.exit(1)
user = tornado.options.options.generate_user_pkcs
with open(ca, 'rb') as cf:
ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, cf.read())
with open(ca_key, 'rb') as cf:
ca_pk = crypto.load_privatekey(crypto.FILETYPE_PEM, cf.read())
client_pk = crypto.PKey()
client_pk.generate_key(crypto.TYPE_RSA, 2048)
client_cert = crypto.X509()
client_cert.get_subject().CN = user
client_cert.set_serial_number(uuid.uuid4().int)
client_cert.gmtime_adj_notBefore(0) # From now
client_cert.gmtime_adj_notAfter(315360000) # to 10y
client_cert.set_issuer(ca_cert.get_subject()) # Signed by ca
client_cert.set_pubkey(client_pk)
client_cert.sign(client_pk, 'sha1')
client_cert.sign(ca_pk, 'sha1')
pfx = crypto.PKCS12()
pfx.set_certificate(client_cert)
pfx.set_privatekey(client_pk)
pfx.set_ca_certificates([ca_cert])
pfx.set_friendlyname(('%s cert for butterfly' % user).encode('utf-8'))
with open('%s.p12' % user, "wb") as cf:
cf.write(pfx.export(b''))
print('%s.p12 written.' % user)
sys.exit(0)
if (tornado.options.options.unsecure or
tornado.options.options.host == '127.0.0.1'):
ssl_opts = None
else:
if not all(map(os.path.exists,
[cert, cert_key, ca, ca_key])):
print("Unable to find butterfly certificate. "
"Can't run butterfly without certificate. "
"Either generate them or run as --unsecure "
"(NOT RECOMMENDED)")
sys.exit(1)
ssl_opts = {
'certfile': cert,
'keyfile': cert_key,
'ca_certs': ca,
'cert_reqs': ssl.CERT_REQUIRED
}
http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_opts)
http_server.listen(
tornado.options.options.port, address=tornado.options.options.host)
url = "http%s://%s:%d/*" % ("s" if tornado.options.options.secure else "",
tornado.options.options.host,
tornado.options.options.port)
url = "http%s://%s:%d/*" % (
"s" if not tornado.options.options.unsecure else "",
tornado.options.options.host,
tornado.options.options.port)
# This is for debugging purpose
try:

View File

@@ -122,7 +122,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
env["COLORTERM"] = "butterfly"
env["HOME"] = self.callee.dir
env["LOCATION"] = "http%s://%s:%d/" % (
"s" if tornado.options.options.secure else "",
"s" if not tornado.options.options.unsecure else "",
tornado.options.options.host, tornado.options.options.port)
env["PATH"] = '%s:%s' % (os.path.abspath(os.path.join(
os.path.dirname(__file__), '..', 'bin')), env.get("PATH"))
@@ -186,7 +186,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
def open(self, user, path):
if self.request.headers['Origin'] != 'http%s://%s' % (
"s" if tornado.options.options.secure else "",
"s" if not tornado.options.options.unsecure else "",
self.request.headers['Host']):
self.log.warning(
'Unauthorized connection attempt: from : %s to: %s' % (
@@ -201,7 +201,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
self.path = path
self.user = user.decode('utf-8') if user else None
self.caller = self.callee = None
if tornado.options.options.secure:
if not tornado.options.options.unsecure:
cert = self.request.get_ssl_certificate()
if cert is not None:
for field in cert['subject']:

View File

@@ -33,6 +33,7 @@ if location.protocol == 'https:'
ws_url = 'wss://'
else
ws_url = 'ws://'
ws_url += document.location.host + '/ws' + location.pathname
ws = new WebSocket ws_url

View File

@@ -24,7 +24,7 @@ options = dict(
platforms="Any",
scripts=['butterfly.server.py'],
packages=['butterfly'],
install_requires=["tornado>=3.2"],
install_requires=["tornado>=3.2", "pyOpenSSL"],
package_data={
'butterfly': [
'static/fonts/*',