Add SSL cert and key validation

This commit is contained in:
Joseph Schorr 2015-02-05 13:06:56 -05:00
parent 1255cb94ea
commit 400ffa73e6
5 changed files with 67 additions and 0 deletions

View file

@ -41,3 +41,4 @@ git+https://github.com/DevTable/anunidecode.git
git+https://github.com/DevTable/avatar-generator.git
git+https://github.com/DevTable/pygithub.git
gipc
pyOpenSSL

View file

@ -44,6 +44,7 @@ python-dateutil==2.4.0
python-ldap==2.4.19
python-magic==0.4.6
pytz==2014.10
pyOpenSSL==0.14
raven==5.1.1
redis==2.10.3
reportlab==2.7

View file

@ -601,6 +601,10 @@
text-align: left;
}
.co-dialog .modal-footer.working .btn {
float: right;
}
.co-dialog .modal-footer.working .cor-loader-inline {
margin-right: 10px;
}

View file

@ -2,6 +2,7 @@ import os
import yaml
import logging
import json
from StringIO import StringIO
logger = logging.getLogger(__name__)
@ -55,6 +56,10 @@ class BaseProvider(object):
""" Returns whether the file with the given name exists under the config override volume. """
raise NotImplementedError
def get_volume_file(self, filename, mode='r'):
""" Returns a Python file referring to the given name under the config override volumne. """
raise NotImplementedError
def save_volume_file(self, filename, flask_file):
""" Saves the given flask file to the config override volume, with the given
filename.
@ -107,6 +112,9 @@ class FileConfigProvider(BaseProvider):
def volume_file_exists(self, filename):
return os.path.exists(os.path.join(self.config_volume, filename))
def get_volume_file(self, filename, mode='r'):
return open(os.path.join(self.config_volume, filename), mode)
def save_volume_file(self, filename, flask_file):
flask_file.save(os.path.join(self.config_volume, filename))
@ -152,6 +160,9 @@ class TestConfigProvider(BaseProvider):
def save_volume_file(self, filename, flask_file):
self.files[filename] = ''
def get_volume_file(self, filename, mode='r'):
return StringIO(self.files[filename])
def requires_restart(self, app_config):
return False

View file

@ -3,7 +3,10 @@ import os
import json
import ldap
import peewee
import OpenSSL
import logging
from fnmatch import fnmatch
from data.users import LDAPConnection
from flask import Flask
from flask.ext.mail import Mail, Message
@ -13,6 +16,8 @@ from app import app, CONFIG_PROVIDER
from auth.auth_context import get_authenticated_user
from util.oauth import GoogleOAuthConfig, GithubOAuthConfig
logger = logging.getLogger(__name__)
SSL_FILENAMES = ['ssl.cert', 'ssl.key']
def get_storage_provider(config):
@ -35,6 +40,7 @@ def validate_service_for_config(service, config):
'status': True
}
except Exception as ex:
logger.exception('Validation exception')
return {
'status': False,
'reason': str(ex)
@ -150,6 +156,50 @@ def _validate_ssl(config):
if not CONFIG_PROVIDER.volume_file_exists(filename):
raise Exception('Missing required SSL file: %s' % filename)
with CONFIG_PROVIDER.get_volume_file(SSL_FILENAMES[0]) as f:
cert_contents = f.read()
# Validate the certificate.
try:
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_contents)
except:
raise Exception('Could not parse certificate file. Is it a valid PEM certificate?')
if cert.has_expired():
raise Exception('The specified SSL certificate has expired.')
private_key_path = None
with CONFIG_PROVIDER.get_volume_file(SSL_FILENAMES[1]) as f:
private_key_path = f.name
if not private_key_path:
# Only in testing.
return
# Validate the private key with the certificate.
context = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
context.use_certificate(cert)
try:
context.use_privatekey_file(private_key_path)
except:
raise Exception('Could not parse key file. Is it a valid PEM private key?')
try:
context.check_privatekey()
except OpenSSL.SSL.Error as e:
raise Exception('SSL key failed to validate: %s' % str(e))
# Verify the hostname matches the name in the certificate.
common_name = cert.get_subject().commonName
if common_name is None:
raise Exception('Missing CommonName (CN) from SSL certificate')
if not fnmatch(config['SERVER_HOSTNAME'], common_name):
raise Exception('CommonName (CN) "%s" in SSL cert does not match server hostname "%s"' %
(common_name, config['SERVER_HOSTNAME']))
def _validate_ldap(config):
""" Validates the LDAP connection. """