lars
ad3de60dd1
add "self.root_action" to the plugin specification implement non-interactive key certificate creation during startup if necessary (the produced certificate is still broken) run stunnel during startup returned environment warnings are expected to be lists
208 lines
6.1 KiB
Python
208 lines
6.1 KiB
Python
#
|
|
# Copyright 2007 sense.lab e.V.
|
|
#
|
|
# This file is part of the CryptoBox.
|
|
#
|
|
# The CryptoBox is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# The CryptoBox is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with the CryptoBox; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
|
|
"""Create an SSL certificate to encrypt the webinterface connection via stunnel
|
|
|
|
requires:
|
|
- stunnel (>= 4.0)
|
|
- "M2Crypto" python module
|
|
"""
|
|
|
|
__revision__ = "$Id"
|
|
|
|
import cryptobox.plugins.base
|
|
import subprocess
|
|
import os
|
|
import cherrypy
|
|
|
|
CERT_FILENAME = 'cryptobox-ssl-certificate.pem'
|
|
KEY_BITS = 409
|
|
CERT_INFOS = {
|
|
"C": "SomeCountry",
|
|
"ST": "SomeState",
|
|
"L": "SomeLocality",
|
|
"O": "SomeOrganization",
|
|
"OU": "CryptoBox-Server",
|
|
"CN": "*",
|
|
"emailAddress": ""}
|
|
EXPIRE_TIME = 60*60*24*365*20 # 20 years
|
|
SIGN_DIGEST = "sha256"
|
|
PID_FILE = os.path.join("/tmp/cryptobox-stunnel.pid")
|
|
|
|
|
|
class encrypted_webinterface(cryptobox.plugins.base.CryptoBoxPlugin):
|
|
"""Provide an encrypted webinterface connection via stunnel
|
|
"""
|
|
|
|
plugin_capabilities = [ "system" ]
|
|
plugin_visibility = []
|
|
request_auth = True
|
|
rank = 80
|
|
|
|
def do_action(self):
|
|
"""The action handler.
|
|
"""
|
|
return None
|
|
|
|
|
|
def get_status(self):
|
|
"""Retrieve the status of the feature.
|
|
"""
|
|
return "TODO"
|
|
|
|
|
|
def get_warnings(self):
|
|
"""check if the connection is encrypted
|
|
"""
|
|
warnings = []
|
|
## check if m2crypto is available
|
|
try:
|
|
import M2Crypto
|
|
except ImportError:
|
|
warnings.append((45, "Plugins.%s.MissingModuleM2Crypto" % self.get_name()))
|
|
if not os.path.isfile(self.root_action.STUNNEL_BIN):
|
|
warnings.append((44, "Plugins.%s.MissingProgramStunnel" % self.get_name()))
|
|
## perform some checks for encrypted connections
|
|
## check an environment setting - this is quite common behind proxies
|
|
## the arbitrarily chosen header is documented in README.proxy
|
|
if (cherrypy.request.scheme != "https") \
|
|
and (not os.environ.has_key("HTTPS")) \
|
|
and (not (cherrypy.request.headers.has_key("X-SSL-Request") \
|
|
and (cherrypy.request.headers["X-SSL-Request"] == "1"))):
|
|
## plaintext connection -> "heavy security risk" (priority=20..39)
|
|
warnings.append((25, "Plugins.%s.NoSSL" % self.get_name()))
|
|
return warnings
|
|
|
|
|
|
def handle_event(self, event, event_info=None):
|
|
"""Create a certificate during startup (if it does not exist) and run stunnel
|
|
"""
|
|
if event == "bootup":
|
|
cert_abs_name = self.cbox.prefs.get_misc_config_filename(CERT_FILENAME)
|
|
if not os.path.isfile(cert_abs_name):
|
|
try:
|
|
self.__create_certificate(cert_abs_name)
|
|
self.cbox.log.info("Created new SSL certificate: %s" % cert_abs_name)
|
|
except IOError, err_msg:
|
|
## do not run stunnel without a certificate
|
|
self.cbox.log.warn("Failed to create new SSL certificate (%s): %s" % \
|
|
(cert_abs_name, err_msg))
|
|
return
|
|
self.__run_stunnel(cert_abs_name)
|
|
elif event == "shutdown":
|
|
self.__kill_stunnel()
|
|
|
|
|
|
def __kill_stunnel(self):
|
|
"""try to kill a running stunnel daemon
|
|
"""
|
|
if not os.path.isfile(PID_FILE):
|
|
self.cbox.log.warn("Could not find the pid file of a running stunnel daemon: %s" % PID_FILE)
|
|
return
|
|
try:
|
|
pfile = open(PID_FILE, "r")
|
|
try:
|
|
pid = pfile.read().strip()
|
|
except IOError, err_msg:
|
|
self.cbox.log.warn("Failed to read the pid file (%s): %s" % (PID_FILE, err_msg))
|
|
pfile.close()
|
|
return
|
|
pfile.close()
|
|
except IOError, err_msg:
|
|
self.cbox.log.warn("Failed to open the pid file (%s): %s" % (PID_FILE, err_msg))
|
|
return
|
|
if pid.isdigit():
|
|
pid = int(pid)
|
|
else:
|
|
return
|
|
try:
|
|
## SIGTERM = 15
|
|
os.kill(pid, 15)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def __run_stunnel(self, cert_name, dest_port=443):
|
|
## retrieve currently requested port (not necessarily the port served
|
|
## by cherrypy - e.g. in a proxy setup)
|
|
request_port = cherrypy.config.get("server.socket_port", 80)
|
|
proc = subprocess.Popen(
|
|
shell = False,
|
|
stdout = subprocess.PIPE,
|
|
stderr = subprocess.PIPE,
|
|
args = [
|
|
self.cbox.prefs["Programs"]["super"],
|
|
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
|
"plugin", os.path.join(self.plugin_dir, "root_action.py"),
|
|
cert_name,
|
|
str(request_port),
|
|
str(dest_port),
|
|
PID_FILE ])
|
|
(output, error) = proc.communicate()
|
|
if proc.returncode == 0:
|
|
self.cbox.log.info("Successfully started 'stunnel'")
|
|
return True
|
|
else:
|
|
self.cbox.log.warn("Failed to run 'stunnel': %s" % error)
|
|
return False
|
|
|
|
|
|
def __create_certificate(self, filename):
|
|
import M2Crypto
|
|
import time
|
|
string_type = 0x1000 | 1 # see http://www.koders.com/python/..
|
|
# ../fid07A99E089F55187896A06CD4E0B6F21B9B8F5B0B.aspx?s=bavaria
|
|
key_gen_number = 0x10001 # commonly used for key generation: 65537
|
|
rsa_key = M2Crypto.RSA.gen_key(KEY_BITS, key_gen_number, callback=lambda: None)
|
|
pkey = M2Crypto.EVP.PKey(md=SIGN_DIGEST)
|
|
pkey.assign_rsa(rsa_key)
|
|
issuer = M2Crypto.X509.X509_Name()
|
|
for (key, value) in CERT_INFOS.items():
|
|
issuer.add_entry_by_txt(key, string_type, value, 1, 1, 0)
|
|
## time object
|
|
asn_time1 = M2Crypto.ASN1.ASN1_UTCTIME()
|
|
asn_time1.set_time(long(time.time()) - EXPIRE_TIME)
|
|
asn_time2 = M2Crypto.ASN1.ASN1_UTCTIME()
|
|
asn_time2.set_time(long(time.time()) + EXPIRE_TIME)
|
|
cert = M2Crypto.X509.X509()
|
|
cert.set_issuer(issuer)
|
|
cert.set_subject(issuer)
|
|
cert.set_pubkey(pkey)
|
|
cert.set_not_before(asn_time1)
|
|
cert.set_not_after(asn_time2)
|
|
cert.sign(pkey, SIGN_DIGEST)
|
|
result = ""
|
|
result += cert.as_pem()
|
|
result += pkey.as_pem(cipher=None)
|
|
if not os.path.exists(os.path.dirname(filename)):
|
|
os.mkdir(os.path.dirname(filename))
|
|
try:
|
|
certfile = open(filename, "w")
|
|
except IOError:
|
|
raise
|
|
try:
|
|
certfile.write(result)
|
|
except IOError:
|
|
certfile.close()
|
|
raise
|
|
certfile.close()
|
|
os.chmod(filename, 0600)
|
|
|