def __init__(self, config=None): if config is None: config = get_config_from_file("config.ini") self.config = config # Seed the trust stores Sync(self.config).seed() self.cert_processor = CertProcessor(config)
def setUp(self): self.USER_GNUPGHOME = tempfile.TemporaryDirectory() self.ADMIN_GNUPGHOME = tempfile.TemporaryDirectory() self.AUTHORITY_FOLDER = tempfile.TemporaryDirectory() self.FQDN = "my.test.server" self.fqdn_patch = patch.dict("os.environ", {"FQDN": self.FQDN}) self.fqdn_patch.start() config = ConfigParser() config.read_string( """ [ca] key = {authority_folder}/RootCA.key cert = {authority_folder}/RootCA.pem issuer = My Company Name alternate_name = *.myname.com [gnupg] user={user_gnupghome} admin={admin_gnupghome} [storage] engine=sqlite3 [storage.sqlite3] db_path=:memory: """.format( user_gnupghome=self.USER_GNUPGHOME.name, admin_gnupghome=self.ADMIN_GNUPGHOME.name, authority_folder=self.AUTHORITY_FOLDER.name, ) ) self.common_name = "user@host" self.key = generate_key() self.engine = storage.SQLiteStorageEngine(config) cur = self.engine.conn.cursor() cur.execute("DROP TABLE IF EXISTS certs") self.engine.conn.commit() self.engine.init_db() self.cert_processor = CertProcessor(config) self.user_gpg = gnupg.GPG(gnupghome=self.USER_GNUPGHOME.name) self.admin_gpg = gnupg.GPG(gnupghome=self.ADMIN_GNUPGHOME.name) self.users = [ User("*****@*****.**", gen_passwd(), generate_key(), gpg=self.user_gpg) ] for user in self.users: self.user_gpg.import_keys(self.user_gpg.export_keys(user.fingerprint))
def setUp(self): self.USER_GNUPGHOME = tempfile.TemporaryDirectory() self.ADMIN_GNUPGHOME = tempfile.TemporaryDirectory() self.NEW_USER_GNUPGHOME = tempfile.TemporaryDirectory() self.NEW_ADMIN_GNUPGHOME = tempfile.TemporaryDirectory() self.SEED_DIR = tempfile.TemporaryDirectory() self.config = ConfigParser() self.config.read_string(""" [mtls] min_lifetime=60 max_lifetime=0 seed_dir={seed_dir} [ca] key = secrets/certs/authority/RootCA.key cert = secrets/certs/authority/RootCA.pem issuer = My Company Name alternate_name = *.myname.com [gnupg] user={user_gnupghome} admin={admin_gnupghome} [storage] engine=sqlite3 [storage.sqlite3] db_path=:memory: """.format( user_gnupghome=self.USER_GNUPGHOME.name, admin_gnupghome=self.ADMIN_GNUPGHOME.name, seed_dir=self.SEED_DIR.name, )) self.common_name = "user@host" self.key = generate_key() self.engine = storage.SQLiteStorageEngine(self.config) cur = self.engine.conn.cursor() cur.execute("DROP TABLE IF EXISTS certs") self.engine.conn.commit() self.engine.init_db() self.cert_processor = CertProcessor(self.config) self.user_gpg = gnupg.GPG(gnupghome=self.USER_GNUPGHOME.name) self.admin_gpg = gnupg.GPG(gnupghome=self.ADMIN_GNUPGHOME.name) self.new_user_gpg = gnupg.GPG(gnupghome=self.NEW_USER_GNUPGHOME.name) self.new_admin_gpg = gnupg.GPG(gnupghome=self.NEW_ADMIN_GNUPGHOME.name) self.new_users = [ User("user@host", gen_passwd(), generate_key(), gpg=self.new_user_gpg) ] self.new_admins = [ User("admin@host", gen_passwd(), generate_key(), gpg=self.new_admin_gpg) ]
def setUp(self): self.USER_GNUPGHOME = tempfile.TemporaryDirectory() self.ADMIN_GNUPGHOME = tempfile.TemporaryDirectory() config = ConfigParser() config.read_string( """ [ca] key = secrets/certs/authority/RootCA.key cert = secrets/certs/authority/RootCA.pem issuer = My Company Name alternate_name = *.myname.com [gnupg] user={user_gnupghome} admin={admin_gnupghome} [storage] engine=postgres [storage.postgres] database = mtls user = postgres password = postgres host = localhost """.format( user_gnupghome=self.USER_GNUPGHOME.name, admin_gnupghome=self.ADMIN_GNUPGHOME.name, ) ) self.common_name = "user@host" self.key = generate_key() self.engine = storage.PostgresqlStorageEngine(config) cur = self.engine.conn.cursor() cur.execute("DROP TABLE IF EXISTS certs") self.engine.conn.commit() self.engine.init_db() self.cert_processor = CertProcessor(config) self.user_gpg = gnupg.GPG(gnupghome=self.USER_GNUPGHOME.name) self.admin_gpg = gnupg.GPG(gnupghome=self.ADMIN_GNUPGHOME.name) self.users = [ User("user@host", gen_passwd(), generate_key(), gpg=self.user_gpg), User("user2@host", gen_passwd(), generate_key(), gpg=self.user_gpg), User("user3@host", gen_passwd(), generate_key(), gpg=self.user_gpg), ] self.invalid_users = [ User("user4@host", gen_passwd(), generate_key(), gpg=self.user_gpg) ] self.admin_users = [ User("admin@host", gen_passwd(), generate_key(), gpg=self.admin_gpg) ] for user in self.users: self.user_gpg.import_keys(self.user_gpg.export_keys(user.fingerprint)) for user in self.admin_users: self.admin_gpg.import_keys(self.admin_gpg.export_keys(user.fingerprint))
def setUp(self): dir_path = os.path.dirname(os.path.realpath(__file__)) self.USER_GNUPGHOME = tempfile.TemporaryDirectory(prefix=dir_path + "/secrets/") self.ADMIN_GNUPGHOME = tempfile.TemporaryDirectory(prefix=dir_path + "/secrets/") relative_user = "******" + self.USER_GNUPGHOME.name.split(dir_path)[1] relative_admin = "." + self.ADMIN_GNUPGHOME.name.split(dir_path)[1] config = ConfigParser() config.read_string( """ [ca] key = secrets/certs/authority/RootCA.key cert = secrets/certs/authority/RootCA.pem issuer = My Company Name alternate_name = *.myname.com [gnupg] user={user_gnupghome} admin={admin_gnupghome} [storage] engine=sqlite3 [storage.sqlite3] db_path=:memory: """.format( user_gnupghome=relative_user, admin_gnupghome=relative_admin ) ) self.common_name = "user@host" self.key = generate_key() self.engine = storage.SQLiteStorageEngine(config) cur = self.engine.conn.cursor() cur.execute("DROP TABLE IF EXISTS certs") self.engine.conn.commit() self.engine.init_db() self.cert_processor = CertProcessor(config) self.user_gpg = gnupg.GPG(gnupghome=self.USER_GNUPGHOME.name) self.admin_gpg = gnupg.GPG(gnupghome=self.ADMIN_GNUPGHOME.name) self.users = [ User("user@host", gen_passwd(), generate_key(), gpg=self.user_gpg), User("user2@host", gen_passwd(), generate_key(), gpg=self.user_gpg), User("user3@host", gen_passwd(), generate_key(), gpg=self.user_gpg), ] self.invalid_users = [ User("user4@host", gen_passwd(), generate_key(), gpg=self.user_gpg) ] self.admin_users = [ User("admin@host", gen_passwd(), generate_key(), gpg=self.admin_gpg) ] for user in self.users: self.user_gpg.import_keys(self.user_gpg.export_keys(user.fingerprint)) for user in self.admin_users: self.admin_gpg.import_keys(self.admin_gpg.export_keys(user.fingerprint))
from cert_processor import CertProcessorKeyNotFoundError from cert_processor import CertProcessorInvalidSignatureError __author__ = 'Danny Grove <*****@*****.**>' VERSION = 'version 0.1' app = Flask(__name__) config = ConfigParser() config_path = os.path.abspath( os.path.join( os.path.dirname(__file__), 'config.ini' ) ) config.read(config_path) cert_processor = CertProcessor(config) def error_response(msg, status_code=501): return json.dumps({ 'error': True, 'msg': msg }), status_code @app.route('/', methods=['POST']) def create_cert(): body = request.get_json() lifetime = int(body['lifetime']) if lifetime < 1: error_response('lifetime must be greater than 1 hour')
def __init__(self, config=None): if config is None: config = get_config_from_file("config.ini") self.config = config self.cert_processor = CertProcessor(config) self.seed()
class Handler: def __init__(self, config=None): if config is None: config = get_config_from_file("config.ini") self.config = config self.cert_processor = CertProcessor(config) self.seed() def seed(self): """Seeds the User and Admin trust databases.""" logger.info("Seeding PGP Trust Databases") seed_base_dir = self.config.get("mtls", "seed_dir", fallback="/tmp/seeds") if os.path.isdir(seed_base_dir): for trust in ["user", "admin"]: seed_dir = os.path.join(seed_base_dir, trust) if os.path.isdir(seed_dir): logger.info("Seeding {} Trust Store".format(trust)) for f in os.listdir(seed_dir): f_path = os.path.join(seed_dir, f) if os.path.isfile(f_path): fingerprint = f.split(".")[0] with open(f_path, "r") as gpg_data: gpg_data = str(gpg_data.read()) if trust == "admin": self.import_and_trust( gpg_data, self.cert_processor.admin_gpg) # If we add an admin, they're also a user, # so we can just pull the fingerprint once # and use that for logging. It will only show # it's being 'added' to the admin store, but # that's fine since that assumption is already # made fingerprint = self.import_and_trust( gpg_data, self.cert_processor.user_gpg) logger.info("Added {fp} to {t} Store".format( fp=fingerprint, t=trust)) def import_and_trust(self, key_data, gpg): """Imports a key into a given keyring and trust database as well as properly trusting it for use. Args: key_data (str): The key data in ACSII or binary format. gpg (gnupg.GPG): The gpg instance. Returns: str: The fingerprint of the newly imported and trusted key. """ import_data = gpg.import_keys(key_data) fingerprint = import_data.fingerprints[0] gpg.trust_keys([fingerprint], "TRUST_ULTIMATE") return fingerprint def create_cert(self, body): """Create a certificate.""" lifetime = int(body["lifetime"]) min_lifetime = int(self.config.get("mtls", "min_lifetime", fallback=60)) max_lifetime = int(self.config.get("mtls", "max_lifetime", fallback=0)) if lifetime < min_lifetime: logger.info( "User requested lifetime less than minimum. {} < {}".format( lifetime, min_lifetime)) error_response("lifetime must be greater than {} seconds".format( min_lifetime)) if max_lifetime != 0: if lifetime > max_lifetime: logger.info( "User requested lifetime greater than maximum. {} < {}". format(lifetime, max_lifetime)) error_response("lifetime must be less than {} seconds".format( max_lifetime)) csr_str = body["csr"] csr = self.cert_processor.get_csr(csr_str) if csr is None: return error_response("Could not load CSR") try: csr_public_bytes = csr.public_bytes(serialization.Encoding.PEM) sig_path = write_sig_to_file(body["signature"]) fingerprint = self.cert_processor.verify(csr_public_bytes, sig_path) os.remove(sig_path) except CertProcessorUntrustedSignatureError as e: logger.info("Unauthorized: {}".format(e)) return error_response("Unauthorized", 403) except CertProcessorInvalidSignatureError: logger.info("Invalid signature in CSR.") return error_response("Invalid signature", 401) if csr is None: logger.info("Invalid CSR.") return error_response("Invalid CSR") cert = None try: cert = self.cert_processor.generate_cert(csr, lifetime, fingerprint) return json.dumps({"cert": cert.decode("UTF-8")}), 200 except CertProcessorKeyNotFoundError: logger.critical("Key missing. Service not properly initialized") return error_response("Internal Error") except CertProcessorMismatchedPublicKeyError: logger.error("CSR Public Key does not match found certificate.") return error_response("Internal Error") except CertProcessorNotAdminUserError: logger.error( "User {} is not an admin and attempted ".format(fingerprint) + "to generate a certificate they are not allowed to generate.") return error_response("Invalid Request", 403) except CertProcessorNoPGPKeyFoundError: logger.info("PGP Key not found.") return error_response("Unauthorized", 401) def revoke_cert(self, body): """ A user should be able to revoke their own certificate. An admin should be able to revoke the certificate of any user. Args: body: A dictionary from the JSON input. Returns: (json, int): a tuple of the json response and http status code. """ is_admin = False fingerprint = None sig_path = write_sig_to_file(body["signature"]) try: fingerprint = self.cert_processor.admin_verify( json.dumps(body["query"]).encode("UTF-8"), sig_path) is_admin = True logger.info( "Admin {adminfp} revoking certificate with query {query}". format(adminfp=fingerprint, query=json.dumps(body["query"]))) os.remove(sig_path) except (CertProcessorInvalidSignatureError, CertProcessorUntrustedSignatureError): try: fingerprint = self.cert_processor.verify( json.dumps(body["query"]).encode("UTF-8"), sig_path) logger.info( "User {userfp} revoking certificate with query {query}". format(userfp=fingerprint, query=json.dumps(body["query"]))) os.remove(sig_path) except ( CertProcessorInvalidSignatureError, CertProcessorUntrustedSignatureError, ): os.remove(sig_path) return error_response("Unauthorized", 403) certs = self.cert_processor.storage.get_cert(**body["query"]) if certs is None: return error_response("No Cert to revoke") for cert in certs: cert = x509.load_pem_x509_certificate(str(cert).encode("UTF-8"), backend=default_backend()) self.cert_processor.revoke_cert(cert.serial_number) return json.dumps({"msg": "success"}), 200 def add_user(self, body, is_admin=False): """Add a user or admin.""" fingerprint = None sig_path = write_sig_to_file(body["signature"]) try: fingerprint = self.cert_processor.admin_verify( body["fingerprint"].encode("UTF-8"), sig_path) except (CertProcessorInvalidSignatureError, CertProcessorUntrustedSignatureError): os.remove(sig_path) logger.error( "Invalid signature on adding fingerprint: {fp}".format( fp=body["fingerprint"])) return error_response("Unauthorized", 403) # Remove signature file os.remove(sig_path) fingerprint = body["fingerprint"] try: if is_admin: has_user = self.has_user(self.cert_processor.admin_gpg, fingerprint) if not has_user: logger.info( "Admin {adminfp} adding admin user {userfp}".format( adminfp=fingerprint, userfp=body["fingerprint"])) # Add a user to the admin trust store self.add_and_trust_user(self.cert_processor.admin_gpg, fingerprint) has_user = self.has_user(self.cert_processor.user_gpg, fingerprint) if not has_user: # Add the user to the user trust store logger.info( "Admin {adminfp} adding admin user {userfp}".format( adminfp=fingerprint, userfp=body["fingerprint"])) self.add_and_trust_user(self.cert_processor.user_gpg, fingerprint) return json.dumps({"msg": "success"}), 201 except GPGKeyNotFoundException: return ( json.dumps( {"msg": "Key not found on keyserver. Could not import"}), 422, ) def has_user(self, gpg, fingerprint): keys = gpg.list_keys(keys=fingerprint) if len(keys) is 0: return False return True def add_and_trust_user(self, gpg, fingerprint): result = self.cert_processor.user_gpg.recv_keys( self.config.get("gnupg", "keyserver", fallback="keyserver.ubuntu.com"), fingerprint, ) if result.count is None or result.count == 0: raise GPGKeyNotFoundException() self.cert_processor.user_gpg.trust_keys([fingerprint], "TRUST_ULTIMATE") def remove_user(self, body, is_admin=False): """Remove a user or admin.""" fingerprint = None sig_path = write_sig_to_file(body["signature"]) try: fingerprint = self.cert_processor.admin_verify( body["fingerprint"].encode("UTF-8"), sig_path) logger.info("Admin {adminfp} adding user {userfp}".format( adminfp=fingerprint, userfp=body["fingerprint"])) except (CertProcessorInvalidSignatureError, CertProcessorUntrustedSignatureError): os.remove(sig_path) logger.error( "Invalid signature on adding fingerprint: {fp}".format( fp=body["fingerprint"])) return error_response("Unauthorized", 403) # Remove signature file os.remove(sig_path) if is_admin: # Add a user to the admin trust store self.cert_processor.admin_gpg.delete_keys(body["fingerprint"]) # Add the user to the user trust store self.cert_processor.user_gpg.delete_keys(body["fingerprint"]) return json.dumps({"msg": "success"}), 201
class TestCertProcessorCRLDistributionPath(TestCertProcessorBase): def setUp(self): self.USER_GNUPGHOME = tempfile.TemporaryDirectory() self.ADMIN_GNUPGHOME = tempfile.TemporaryDirectory() self.AUTHORITY_FOLDER = tempfile.TemporaryDirectory() self.FQDN = "my.test.server" self.fqdn_patch = patch.dict("os.environ", {"FQDN": self.FQDN}) self.fqdn_patch.start() config = ConfigParser() config.read_string( """ [ca] key = {authority_folder}/RootCA.key cert = {authority_folder}/RootCA.pem issuer = My Company Name alternate_name = *.myname.com [gnupg] user={user_gnupghome} admin={admin_gnupghome} [storage] engine=sqlite3 [storage.sqlite3] db_path=:memory: """.format( user_gnupghome=self.USER_GNUPGHOME.name, admin_gnupghome=self.ADMIN_GNUPGHOME.name, authority_folder=self.AUTHORITY_FOLDER.name, ) ) self.common_name = "user@host" self.key = generate_key() self.engine = storage.SQLiteStorageEngine(config) cur = self.engine.conn.cursor() cur.execute("DROP TABLE IF EXISTS certs") self.engine.conn.commit() self.engine.init_db() self.cert_processor = CertProcessor(config) self.user_gpg = gnupg.GPG(gnupghome=self.USER_GNUPGHOME.name) self.admin_gpg = gnupg.GPG(gnupghome=self.ADMIN_GNUPGHOME.name) self.users = [ User("*****@*****.**", gen_passwd(), generate_key(), gpg=self.user_gpg) ] for user in self.users: self.user_gpg.import_keys(self.user_gpg.export_keys(user.fingerprint)) def tearDown(self): self.USER_GNUPGHOME.cleanup() self.ADMIN_GNUPGHOME.cleanup() self.AUTHORITY_FOLDER.cleanup() self.fqdn_patch.stop() def test_crl_distribution_path(self): user = self.users[0] csr = user.gen_csr(email=user.email) sig = self.user_gpg.sign( csr.public_bytes(serialization.Encoding.PEM), keyid=user.fingerprint, detach=True, clearsign=True, passphrase=user.password, ) bcert = self.cert_processor.generate_cert(csr, 60, user.fingerprint) cert = x509.load_pem_x509_certificate(bcert, backend=default_backend()) self.assertIsInstance(cert, openssl.x509._Certificate) has_crl_extension = False for extension in cert.extensions: if isinstance(extension.value, x509.CRLDistributionPoints): has_crl_extension = True for distributionPoint in extension.value: uris = distributionPoint.full_name for uri in uris: crl_path = "http://{FQDN}/crl".format(FQDN=self.FQDN) self.assertEqual(uri.value, crl_path) self.assertTrue(has_crl_extension)
def test_missing_storage(self): with self.assertRaises(storage.StorageEngineMissing): self.cert_processor = CertProcessor(self.config)
class Handler: def __init__(self, config=None): if config is None: config = get_config_from_file("config.ini") self.config = config # Seed the trust stores Sync(self.config).seed() self.cert_processor = CertProcessor(config) def create_cert(self, body): """Create a certificate.""" lifetime = int(body["lifetime"]) min_lifetime = int(self.config.get("mtls", "min_lifetime", fallback=60)) max_lifetime = int(self.config.get("mtls", "max_lifetime", fallback=0)) if lifetime < min_lifetime: logger.info( "User requested lifetime less than minimum. {} < {}".format( lifetime, min_lifetime)) return error_response( "lifetime must be greater than {} seconds".format( min_lifetime)) if max_lifetime != 0: if lifetime > max_lifetime: logger.info( "User requested lifetime greater than maximum. {} < {}". format(lifetime, max_lifetime)) return error_response( "lifetime must be less than {} seconds".format( max_lifetime)) csr_str = body["csr"] csr = self.cert_processor.get_csr(csr_str) if csr is None: return error_response("Could not load CSR") try: logger.info("create_cert: get csr_public_bytes") csr_public_bytes = csr.public_bytes(serialization.Encoding.PEM) logger.info("create_cert: write to temp sig file") sig_path = write_sig_to_file(body["signature"]) logger.info("create_cert: get fingerprint") fingerprint = self.cert_processor.verify(csr_public_bytes, sig_path) logger.info("create_cert: remove sig file") os.remove(sig_path) except CertProcessorUntrustedSignatureError as e: logger.info("Unauthorized: {}".format(e)) return error_response("Unauthorized", 403) except CertProcessorInvalidSignatureError: logger.info("Invalid signature in CSR.") return error_response("Invalid signature", 401) except Exception as e: logger.critical("Unknown Error: {}".format(e)) return error_response("Internal Server Error", 500) if csr is None: logger.info("Invalid CSR.") return error_response("Invalid CSR") cert = None try: logger.info( f"create_cert: generating certificate for: {fingerprint}") cert = self.cert_processor.generate_cert(csr, lifetime, fingerprint) logger.info( f"create_cert: sending certificate to client for: {fingerprint}" ) return json.dumps({"cert": cert.decode("UTF-8")}), 200 except CertProcessorKeyNotFoundError: logger.critical("Key missing. Service not properly initialized") return error_response("Internal Error") except CertProcessorMismatchedPublicKeyError: logger.error("CSR Public Key does not match found certificate.") return error_response("Internal Error") except CertProcessorNotAdminUserError: logger.error( "User {} is not an admin and attempted ".format(fingerprint) + "to generate a certificate they are not allowed to generate.") return error_response("Invalid Request", 403) except CertProcessorNoPGPKeyFoundError: logger.info("PGP Key not found.") return error_response("Unauthorized", 401) except Exception as e: logger.critical(f"Unhandled Exception: {e}") return error_response("Internal Server Error", 500) def revoke_cert(self, body): """ A user should be able to revoke their own certificate. An admin should be able to revoke the certificate of any user. Args: body: A dictionary from the JSON input. Returns: (json, int): a tuple of the json response and http status code. """ is_admin = False fingerprint = None sig_path = write_sig_to_file(body["signature"]) try: fingerprint = self.cert_processor.admin_verify( json.dumps(body["query"]).encode("UTF-8"), sig_path) is_admin = True logger.info( "Admin {adminfp} revoking certificate with query {query}". format(adminfp=fingerprint, query=json.dumps(body["query"]))) os.remove(sig_path) except (CertProcessorInvalidSignatureError, CertProcessorUntrustedSignatureError): try: fingerprint = self.cert_processor.verify( json.dumps(body["query"]).encode("UTF-8"), sig_path) logger.info( "User {userfp} revoking certificate with query {query}". format(userfp=fingerprint, query=json.dumps(body["query"]))) os.remove(sig_path) except ( CertProcessorInvalidSignatureError, CertProcessorUntrustedSignatureError, ): os.remove(sig_path) return error_response("Unauthorized", 403) certs = self.cert_processor.storage.get_cert(**body["query"]) if certs is None: return error_response("No Cert to revoke") for cert in certs: cert = x509.load_pem_x509_certificate(str(cert).encode("UTF-8"), backend=default_backend()) self.cert_processor.revoke_cert(cert.serial_number) return json.dumps({"msg": "success"}), 200 def add_user(self, body, is_admin=False): """Add a user or admin.""" fingerprint = None sig_path = write_sig_to_file(body["signature"]) try: fingerprint = self.cert_processor.admin_verify( body["fingerprint"].encode("UTF-8"), sig_path) except (CertProcessorInvalidSignatureError, CertProcessorUntrustedSignatureError): os.remove(sig_path) logger.error( "Invalid signature on adding fingerprint: {fp}".format( fp=body["fingerprint"])) return error_response("Unauthorized", 403) # Remove signature file os.remove(sig_path) fingerprint = body["fingerprint"] try: if is_admin: has_user = self.has_user(self.cert_processor.admin_gpg, fingerprint) if not has_user: logger.info( "Admin {adminfp} adding admin user {userfp}".format( adminfp=fingerprint, userfp=body["fingerprint"])) # Add a user to the admin trust store self.add_and_trust_user(self.cert_processor.admin_gpg, fingerprint) has_user = self.has_user(self.cert_processor.user_gpg, fingerprint) if not has_user: # Add the user to the user trust store logger.info( "Admin {adminfp} adding admin user {userfp}".format( adminfp=fingerprint, userfp=body["fingerprint"])) self.add_and_trust_user(self.cert_processor.user_gpg, fingerprint) return json.dumps({"msg": "success"}), 201 except GPGKeyNotFoundException: return ( json.dumps( {"msg": "Key not found on keyserver. Could not import"}), 422, ) def has_user(self, gpg, fingerprint): keys = gpg.list_keys(keys=fingerprint) if len(keys) == 0: return False return True def add_and_trust_user(self, gpg, fingerprint): result = self.cert_processor.user_gpg.recv_keys( self.config.get("gnupg", "keyserver", fallback="keyserver.ubuntu.com"), fingerprint, ) if result.count is None or result.count == 0: raise GPGKeyNotFoundException() self.cert_processor.user_gpg.trust_keys([fingerprint], "TRUST_ULTIMATE") def remove_user(self, body, is_admin=False): """Remove a user or admin.""" fingerprint = None sig_path = write_sig_to_file(body["signature"]) try: fingerprint = self.cert_processor.admin_verify( body["fingerprint"].encode("UTF-8"), sig_path) logger.info("Admin {adminfp} adding user {userfp}".format( adminfp=fingerprint, userfp=body["fingerprint"])) except (CertProcessorInvalidSignatureError, CertProcessorUntrustedSignatureError): os.remove(sig_path) logger.error( "Invalid signature on adding fingerprint: {fp}".format( fp=body["fingerprint"])) return error_response("Unauthorized", 403) # Remove signature file os.remove(sig_path) if is_admin: # Add a user to the admin trust store self.cert_processor.admin_gpg.delete_keys(body["fingerprint"]) # Add the user to the user trust store self.cert_processor.user_gpg.delete_keys(body["fingerprint"]) return json.dumps({"msg": "success"}), 201
def setUp(self): self.USER_GNUPGHOME = tempfile.TemporaryDirectory() self.ADMIN_GNUPGHOME = tempfile.TemporaryDirectory() self.INVALID_GNUPGHOME = tempfile.TemporaryDirectory() self.NEW_USER_GNUPGHOME = tempfile.TemporaryDirectory() self.config = ConfigParser() self.config.read_string(""" [mtls] min_lifetime=60 max_lifetime=0 [ca] key = secrets/certs/authority/RootCA.key cert = secrets/certs/authority/RootCA.pem issuer = My Company Name alternate_name = *.myname.com [gnupg] user={user_gnupghome} admin={admin_gnupghome} [storage] engine=sqlite3 [storage.sqlite3] db_path=:memory: """.format( user_gnupghome=self.USER_GNUPGHOME.name, admin_gnupghome=self.ADMIN_GNUPGHOME.name, )) self.common_name = "user@host" self.key = generate_key() self.engine = storage.SQLiteStorageEngine(self.config) cur = self.engine.conn.cursor() cur.execute("DROP TABLE IF EXISTS certs") self.engine.conn.commit() self.engine.init_db() self.cert_processor = CertProcessor(self.config) self.handler = Handler(self.config) self.user_gpg = gnupg.GPG(gnupghome=self.USER_GNUPGHOME.name) self.admin_gpg = gnupg.GPG(gnupghome=self.ADMIN_GNUPGHOME.name) self.invalid_gpg = gnupg.GPG(gnupghome=self.INVALID_GNUPGHOME.name) self.new_user_gpg = gnupg.GPG(gnupghome=self.NEW_USER_GNUPGHOME.name) self.users = [ User("user@host", gen_passwd(), generate_key(), gpg=self.user_gpg), User("user2@host", gen_passwd(), generate_key(), gpg=self.user_gpg), User("user3@host", gen_passwd(), generate_key(), gpg=self.user_gpg), ] self.invalid_users = [ User("user4@host", gen_passwd(), generate_key(), gpg=self.invalid_gpg) ] self.admin_users = [ User("admin@host", gen_passwd(), generate_key(), gpg=self.admin_gpg) ] self.new_users = [ User("newuser@host", gen_passwd(), generate_key(), gpg=self.new_user_gpg), User("newuser2@host", gen_passwd(), generate_key(), gpg=self.new_user_gpg), ] for user in self.users: self.user_gpg.import_keys( self.user_gpg.export_keys(user.fingerprint)) self.user_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") for user in self.admin_users: # Import to admin keychain self.admin_gpg.import_keys( self.admin_gpg.export_keys(user.fingerprint)) self.admin_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") # Import to user keychain self.user_gpg.import_keys( self.admin_gpg.export_keys(user.fingerprint)) self.user_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") for user in self.invalid_users: self.invalid_gpg.import_keys( self.invalid_gpg.export_keys(user.fingerprint)) self.invalid_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") for user in self.new_users: self.new_user_gpg.import_keys( self.new_user_gpg.export_keys(user.fingerprint)) self.new_user_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE")
class TestHandler(unittest.TestCase): def setUp(self): self.USER_GNUPGHOME = tempfile.TemporaryDirectory() self.ADMIN_GNUPGHOME = tempfile.TemporaryDirectory() self.INVALID_GNUPGHOME = tempfile.TemporaryDirectory() self.NEW_USER_GNUPGHOME = tempfile.TemporaryDirectory() self.config = ConfigParser() self.config.read_string(""" [mtls] min_lifetime=60 max_lifetime=0 [ca] key = secrets/certs/authority/RootCA.key cert = secrets/certs/authority/RootCA.pem issuer = My Company Name alternate_name = *.myname.com [gnupg] user={user_gnupghome} admin={admin_gnupghome} [storage] engine=sqlite3 [storage.sqlite3] db_path=:memory: """.format( user_gnupghome=self.USER_GNUPGHOME.name, admin_gnupghome=self.ADMIN_GNUPGHOME.name, )) self.common_name = "user@host" self.key = generate_key() self.engine = storage.SQLiteStorageEngine(self.config) cur = self.engine.conn.cursor() cur.execute("DROP TABLE IF EXISTS certs") self.engine.conn.commit() self.engine.init_db() self.cert_processor = CertProcessor(self.config) self.handler = Handler(self.config) self.user_gpg = gnupg.GPG(gnupghome=self.USER_GNUPGHOME.name) self.admin_gpg = gnupg.GPG(gnupghome=self.ADMIN_GNUPGHOME.name) self.invalid_gpg = gnupg.GPG(gnupghome=self.INVALID_GNUPGHOME.name) self.new_user_gpg = gnupg.GPG(gnupghome=self.NEW_USER_GNUPGHOME.name) self.users = [ User("user@host", gen_passwd(), generate_key(), gpg=self.user_gpg), User("user2@host", gen_passwd(), generate_key(), gpg=self.user_gpg), User("user3@host", gen_passwd(), generate_key(), gpg=self.user_gpg), ] self.invalid_users = [ User("user4@host", gen_passwd(), generate_key(), gpg=self.invalid_gpg) ] self.admin_users = [ User("admin@host", gen_passwd(), generate_key(), gpg=self.admin_gpg) ] self.new_users = [ User("newuser@host", gen_passwd(), generate_key(), gpg=self.new_user_gpg), User("newuser2@host", gen_passwd(), generate_key(), gpg=self.new_user_gpg), ] for user in self.users: self.user_gpg.import_keys( self.user_gpg.export_keys(user.fingerprint)) self.user_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") for user in self.admin_users: # Import to admin keychain self.admin_gpg.import_keys( self.admin_gpg.export_keys(user.fingerprint)) self.admin_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") # Import to user keychain self.user_gpg.import_keys( self.admin_gpg.export_keys(user.fingerprint)) self.user_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") for user in self.invalid_users: self.invalid_gpg.import_keys( self.invalid_gpg.export_keys(user.fingerprint)) self.invalid_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") for user in self.new_users: self.new_user_gpg.import_keys( self.new_user_gpg.export_keys(user.fingerprint)) self.new_user_gpg.trust_keys([user.fingerprint], "TRUST_ULTIMATE") def tearDown(self): self.USER_GNUPGHOME.cleanup() self.ADMIN_GNUPGHOME.cleanup() self.INVALID_GNUPGHOME.cleanup() self.NEW_USER_GNUPGHOME.cleanup() def test_user_revoke_cert_serial_number(self): user = self.users[0] csr = user.gen_csr() bcert = self.cert_processor.generate_cert(csr, 60, user.fingerprint) cert = x509.load_pem_x509_certificate(bcert, backend=default_backend()) body = {"query": {"serial_number": str(cert.serial_number)}} data = json.dumps(body["query"]).encode("UTF-8") sig = self.user_gpg.sign( data, keyid=user.fingerprint, detach=True, clearsign=True, passphrase=user.password, ) body["signature"] = str(sig) response = json.loads(self.handler.revoke_cert(body)[0]) self.assertTrue(response["msg"] == "success") def test_admin_revoke_cert_serial_number(self): admin = self.admin_users[0] user = self.users[0] user_csr = user.gen_csr() user_bcert = self.cert_processor.generate_cert(user_csr, 60, user.fingerprint) user_cert = x509.load_pem_x509_certificate(user_bcert, backend=default_backend()) body = {"query": {"serial_number": str(user_cert.serial_number)}} data = json.dumps(body["query"]).encode("UTF-8") sig = self.admin_gpg.sign( data, keyid=admin.fingerprint, detach=True, clearsign=True, passphrase=admin.password, ) body["signature"] = str(sig) response = json.loads(self.handler.revoke_cert(body)[0]) self.assertTrue(response["msg"] == "success") def test_invalid_revoke_cert_serial_number(self): valid_user = self.users[0] user = self.invalid_users[0] csr = valid_user.gen_csr() bcert = self.cert_processor.generate_cert(csr, 60, valid_user.fingerprint) cert = x509.load_pem_x509_certificate(bcert, backend=default_backend()) body = {"query": {"serial_number": str(cert.serial_number)}} data = json.dumps(body["query"]).encode("UTF-8") sig = self.invalid_gpg.sign( data, keyid=user.fingerprint, detach=True, clearsign=True, passphrase=user.password, ) body["signature"] = str(sig) response = json.loads(self.handler.revoke_cert(body)[0]) self.assertEqual(response["error"], True, msg=response) def test_create_cert(self): for user in self.users: csr = user.gen_csr() sig = self.user_gpg.sign( csr.public_bytes(serialization.Encoding.PEM), keyid=user.fingerprint, detach=True, clearsign=True, passphrase=user.password, ) payload = { "csr": csr.public_bytes(serialization.Encoding.PEM).decode("utf-8"), "signature": str(sig), "lifetime": 60, "type": "CERTIFICATE", } response = json.loads(self.handler.create_cert(payload)[0]) self.assertIn("-----BEGIN CERTIFICATE-----", response["cert"]) cert = x509.load_pem_x509_certificate( response["cert"].encode("UTF-8"), backend=default_backend()) self.assertIsInstance(cert, openssl.x509._Certificate) def test_create_cert_for_other_user_as_user(self): user = self.users[0] csr = user.gen_csr("Some other random user", "*****@*****.**") sig = self.user_gpg.sign( csr.public_bytes(serialization.Encoding.PEM), keyid=user.fingerprint, detach=True, clearsign=True, passphrase=user.password, ) payload = { "csr": csr.public_bytes(serialization.Encoding.PEM).decode("utf-8"), "signature": str(sig), "lifetime": 60, "type": "CERTIFICATE", } response = json.loads(self.handler.create_cert(payload)[0]) self.assertEqual(response["error"], True, msg=response) def test_create_cert_for_other_user_as_admin(self): user = self.admin_users[0] csr = user.gen_csr("Some other random user", "*****@*****.**") sig = self.admin_gpg.sign( csr.public_bytes(serialization.Encoding.PEM), keyid=user.fingerprint, detach=True, clearsign=True, passphrase=user.password, ) payload = { "csr": csr.public_bytes(serialization.Encoding.PEM).decode("utf-8"), "signature": str(sig), "lifetime": 60, "type": "CERTIFICATE", } response = json.loads(self.handler.create_cert(payload)[0]) self.assertIn("-----BEGIN CERTIFICATE-----", response.get("cert", ""), msg=response) cert = x509.load_pem_x509_certificate(response["cert"].encode("UTF-8"), backend=default_backend()) email = cert.subject.get_attributes_for_oid(NameOID.EMAIL_ADDRESS)[0] email = email.value self.assertEqual(email, "*****@*****.**", msg=response) def test_invalid_user_create_cert(self): user = self.invalid_users[0] csr = user.gen_csr() sig = self.invalid_gpg.sign( csr.public_bytes(serialization.Encoding.PEM), keyid=user.fingerprint, detach=True, clearsign=True, passphrase=user.password, ) payload = { "csr": csr.public_bytes(serialization.Encoding.PEM).decode("utf-8"), "signature": str(sig), "lifetime": 60, "type": "CERTIFICATE", } response = json.loads(self.handler.create_cert(payload)[0]) self.assertEqual(response["error"], True) def test_add_user_valid_admin(self): admin = self.admin_users[0] new_user = self.new_users[0] sig = self.admin_gpg.sign( new_user.fingerprint.encode("UTF-8"), keyid=admin.fingerprint, clearsign=True, detach=True, passphrase=admin.password, ) payload = { "fingerprint": new_user.fingerprint, "signature": str(sig), "type": "USER", } response = json.loads(self.handler.add_user(payload)[0]) self.assertEqual(response["msg"], "success") def test_add_user_invalid_admin(self): user = self.users[0] new_user = self.new_users[0] sig = self.user_gpg.sign( new_user.fingerprint.encode("UTF-8"), keyid=user.fingerprint, clearsign=True, detach=True, passphrase=user.password, ) payload = { "fingerprint": new_user.fingerprint, "signature": str(sig), "type": "USER", } response = json.loads(self.handler.add_user(payload)[0]) self.assertEqual(response["error"], True) def test_add_admin_valid_admin(self): admin = self.admin_users[0] new_user = self.new_users[0] sig = self.admin_gpg.sign( "B10116B8193F2DBD", keyid=admin.fingerprint, clearsign=True, detach=True, passphrase=admin.password, ) payload = { "fingerprint": "B10116B8193F2DBD", "signature": str(sig), "type": "USER", } response = self.handler.add_user(payload, is_admin=True) response_json = json.loads(response[0]) self.assertEqual(response_json["msg"], "success") def test_add_admin_twice_valid_admin(self): admin = self.admin_users[0] new_user = self.new_users[0] sig = self.admin_gpg.sign( "B10116B8193F2DBD", keyid=admin.fingerprint, clearsign=True, detach=True, passphrase=admin.password, ) payload = { "fingerprint": "B10116B8193F2DBD", "signature": str(sig), "type": "USER", } response = self.handler.add_user(payload, is_admin=True) response_json = json.loads(response[0]) self.assertEqual(response_json["msg"], "success") response = self.handler.add_user(payload, is_admin=True) response_json = json.loads(response[0]) self.assertEqual(response_json["msg"], "success") def test_add_admin_add_key_not_on_keyserver(self): admin = self.admin_users[0] new_user = self.invalid_users[0] sig = self.admin_gpg.sign( new_user.fingerprint.encode("UTF-8"), keyid=admin.fingerprint, clearsign=True, detach=True, passphrase=admin.password, ) payload = { "fingerprint": new_user.fingerprint, "signature": str(sig), "type": "USER", } response = self.handler.add_user(payload, is_admin=True) self.assertEqual(response[1], 422) def test_add_admin_invalid_admin(self): admin = self.users[0] new_user = self.new_users[0] sig = self.admin_gpg.sign( new_user.fingerprint.encode("UTF-8"), keyid=admin.fingerprint, clearsign=True, detach=True, passphrase=admin.password, ) payload = { "fingerprint": new_user.fingerprint, "signature": str(sig), "type": "USER", } response = json.loads(self.handler.add_user(payload, is_admin=True)[0]) self.assertEqual(response["error"], True) def test_remove_user_valid_admin(self): admin = self.admin_users[0] new_user = self.new_users[0] sig = self.admin_gpg.sign( new_user.fingerprint.encode("UTF-8"), keyid=admin.fingerprint, clearsign=True, detach=True, passphrase=admin.password, ) payload = { "fingerprint": new_user.fingerprint, "signature": str(sig), "type": "USER", } response = json.loads(self.handler.add_user(payload)[0]) self.assertEqual(response["msg"], "success") response = json.loads(self.handler.remove_user(payload)[0]) self.assertEqual(response["msg"], "success") def test_remove_user_invalid_admin(self): admin = self.users[0] new_user = self.new_users[0] sig = self.admin_gpg.sign( new_user.fingerprint.encode("UTF-8"), keyid=admin.fingerprint, clearsign=True, detach=True, passphrase=admin.password, ) payload = { "fingerprint": new_user.fingerprint, "signature": str(sig), "type": "USER", } response = json.loads(self.handler.add_user(payload, is_admin=True)[0]) self.assertEqual(response["error"], True) def test_remove_admin_valid_admin(self): admin = self.admin_users[0] sig = self.admin_gpg.sign( "C92FE5A3FBD58DD3EC5AA26BB10116B8193F2DBD".encode("UTF-8"), keyid=admin.fingerprint, clearsign=True, detach=True, passphrase=admin.password, ) payload = { "fingerprint": "C92FE5A3FBD58DD3EC5AA26BB10116B8193F2DBD", "signature": str(sig), "type": "ADMIN", } response = json.loads(self.handler.add_user(payload)[0]) self.assertEqual(response["msg"], "success") response = json.loads(self.handler.remove_user(payload)[0]) self.assertEqual(response["msg"], "success") def test_remove_admin_invalid_admin(self): user = self.users[0] new_user = self.new_users[0] sig = self.user_gpg.sign( new_user.fingerprint.encode("UTF-8"), keyid=user.fingerprint, clearsign=True, detach=True, passphrase=user.password, ) payload = { "fingerprint": new_user.fingerprint, "signature": str(sig), "type": "ADMIN", } response = json.loads(self.handler.add_user(payload, is_admin=True)[0]) self.assertEqual(response["error"], True)