From 48017a9d4c66344860bbf007bccc435cc02f9fd5 Mon Sep 17 00:00:00 2001 From: Non Sequitur Date: Wed, 17 Oct 2018 11:42:09 -0400 Subject: [PATCH 01/23] Added get_by_attributes to the certificates service, for fetching certs based on arbitrary attributes. Also associated test and extra tests for other service methods --- lemur/certificates/service.py | 18 ++++++- lemur/sources/service.py | 7 ++- lemur/tests/test_certificates.py | 83 ++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 0bd50694..c8a5365b 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -54,7 +54,7 @@ def get_by_name(name): def get_by_serial(serial): """ - Retrieves certificate by it's Serial. + Retrieves certificate(s) by serial number. :param serial: :return: """ @@ -64,6 +64,22 @@ def get_by_serial(serial): return Certificate.query.filter(Certificate.serial == serial).all() +def get_by_attributes(conditions): + """ + Retrieves certificate(s) by conditions given in a hash of given key=>value pairs. + :param serial: + :return: + """ + # Ensure that each of the given conditions corresponds to actual columns + # if not, silently remove it + for attr in conditions.keys(): + if attr not in Certificate.__table__.columns: + conditions.pop(attr) + + query = database.session_query(Certificate) + return database.find_all(query, Certificate, conditions).all() + + def delete(cert_id): """ Delete's a certificate. diff --git a/lemur/sources/service.py b/lemur/sources/service.py index 227f1bce..5002041c 100644 --- a/lemur/sources/service.py +++ b/lemur/sources/service.py @@ -116,7 +116,12 @@ def sync_certificates(source, user): for certificate in certificates: exists = False - if certificate.get('name'): + + if certificate.get('search', None): + conditions = certificate.pop('search') + exists = certificate_service.get_by_attributes(conditions) + + if not exists and certificate.get('name'): result = certificate_service.get_by_name(certificate['name']) if result: exists = [result] diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index 1a4d644b..0f46e4a5 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -41,6 +41,89 @@ def test_get_or_increase_name(session, certificate): assert get_or_increase_name('certificate1', int(serial, 16)) == 'certificate1-{}-1'.format(serial) +def test_get_all_certs(session, certificate): + from lemur.certificates.service import get_all_certs + assert len(get_all_certs()) > 1 + + +def test_get_by_name(session, certificate): + from lemur.certificates.service import get_by_name + + found = get_by_name(certificate.name) + + assert found + + +def test_get_by_serial(session, certificate): + from lemur.certificates.service import get_by_serial + + found = get_by_serial(certificate.serial) + + assert found + + +def test_delete_cert(session): + from lemur.certificates.service import delete, get + from lemur.tests.factories import CertificateFactory + + delete_this = CertificateFactory(name='DELETEME') + session.commit() + + cert_exists = get(delete_this.id) + + # it needs to exist first + assert cert_exists + + delete(delete_this.id) + cert_exists = get(delete_this.id) + + # then not exist after delete + assert not cert_exists + + +def test_get_by_attributes(session, certificate): + from lemur.certificates.service import get_by_attributes + + # Should get one cert + certificate1 = get_by_attributes({ + 'name': 'SAN-san.example.org-LemurTrustUnittestsClass1CA2018-20171231-20471231' + }) + + # Should get one cert using multiple attrs + certificate2 = get_by_attributes({ + 'name': 'test-cert-11111111-1', + 'cn': 'san.example.org' + }) + + # Should get multiple certs + multiple = get_by_attributes({ + 'cn': 'LemurTrust Unittests Class 1 CA 2018', + 'issuer': 'LemurTrustUnittestsRootCA2018' + }) + + assert len(certificate1) == 1 + assert len(certificate2) == 1 + assert len(multiple) > 1 + + +def test_find_duplicates(session): + from lemur.certificates.service import find_duplicates + + cert = { + 'body': SAN_CERT_STR, + 'chain': INTERMEDIATE_CERT_STR + } + + dups1 = find_duplicates(cert) + + cert['chain'] = '' + + dups2 = find_duplicates(cert) + + assert len(dups1) > 0 + assert len(dups2) > 0 + + def test_get_certificate_primitives(certificate): from lemur.certificates.service import get_certificate_primitives From f02178c154922ea67c1b5b6cba64dafca7da6c39 Mon Sep 17 00:00:00 2001 From: sirferl Date: Thu, 20 Dec 2018 11:54:47 +0100 Subject: [PATCH 02/23] added ADCS issuer and source plugin --- lemur/plugins/lemur_adcs/__init__.py | 6 ++ lemur/plugins/lemur_adcs/plugin.py | 120 +++++++++++++++++++++++++++ requirements-dev.txt | 1 + requirements-docs.txt | 18 ++-- requirements-tests.txt | 4 +- requirements.in | 3 +- requirements.txt | 7 +- setup.py | 4 +- 8 files changed, 147 insertions(+), 16 deletions(-) create mode 100644 lemur/plugins/lemur_adcs/__init__.py create mode 100644 lemur/plugins/lemur_adcs/plugin.py diff --git a/lemur/plugins/lemur_adcs/__init__.py b/lemur/plugins/lemur_adcs/__init__.py new file mode 100644 index 00000000..6b61e936 --- /dev/null +++ b/lemur/plugins/lemur_adcs/__init__.py @@ -0,0 +1,6 @@ +"""Set the version information.""" +try: + VERSION = __import__('pkg_resources') \ + .get_distribution(__name__).version +except Exception as e: + VERSION = 'unknown' diff --git a/lemur/plugins/lemur_adcs/plugin.py b/lemur/plugins/lemur_adcs/plugin.py new file mode 100644 index 00000000..48a3e85b --- /dev/null +++ b/lemur/plugins/lemur_adcs/plugin.py @@ -0,0 +1,120 @@ +from lemur.plugins.bases import IssuerPlugin, SourcePlugin +import requests +import datetime +import lemur_adcs as ADCS +from certsrv import Certsrv +import ssl +from OpenSSL import crypto +from flask import current_app + +class ADCSIssuerPlugin(IssuerPlugin): + title = 'ADCS' + slug = 'adcs-issuer' + description = 'Enables the creation of certificates by ADCS (Active Direcory Certificate Services)' + version = ADCS.VERSION + + author = 'sirferl' + author_url = 'https://github.com/sirferl/lemur' + + def __init__(self, *args, **kwargs): + """Initialize the issuer with the appropriate details.""" + self.session = requests.Session() + super(ADCSIssuerPlugin, self).__init__(*args, **kwargs) + + @staticmethod + def create_authority(options): + """Create an authority. + Creates an authority, this authority is then used by Lemur to + allow a user to specify which Certificate Authority they want + to sign their certificate. + + :param options: + :return: + """ + role = {'username': '', 'password': '', 'name': 'adcs'} + return constants.ADCS_ROOT, constants.ADCS_ISSUING, [role] + + def create_certificate(self, csr, issuer_options): + adcs_server = current_app.config.get('ADCS_SERVER') + adcs_user = current_app.config.get('ADCS_USER') + adcs_pwd = current_app.config.get('ADCS_PWD') + adcs_auth_method = current_app.config.get('ADCS_AUTH_METHOD') + ca_server = Certsrv(adcs_server, adcs_user, adcs_pwd, auth_method = adcs_auth_method) + current_app.logger.info("Requesting CSR: {0}".format(csr)) + current_app.logger.info("Issuer options: {0}".format(issuer_options)) + cert, req_id = ca_server.get_cert(csr, ADCS_TEMPLATE, encoding='b64').decode('utf-8').replace('\r\n', '\n') + chain = ca_server.get_ca_cert(encoding='b64').decode('utf-8').replace('\r\n', '\n') + return cert, chain, req_id + + def revoke_certificate(self, certificate, comments): + # requests.put('a third party') + raise NotImplementedError('Not implemented\n', self,certificate, comments) + + def get_ordered_certificate(self, order_id): + # requests.get('already existing certificate') + raise NotImplementedError('Not implemented\n',self, order_id) + + def canceled_ordered_certificate(self, pending_cert, **kwargs): + # requests.put('cancel an order that has yet to be issued') + raise NotImplementedError('Not implemented\n',self, pending_cert, **kwargs) + +class ADCSSourcePlugin(SourcePlugin): + title = 'ADCS' + slug = 'adcs-source' + description = 'Enables the collecion of certificates' + version = ADCS.VERSION + + author = 'sirferl' + author_url = 'https://github.com/sirferl/lemur' + options = [ + { + 'name': 'dummy', + 'type': 'str', + 'required': False, + 'validation': '/^[0-9]{12,12}$/', + 'helpMessage': 'Just to prevent error' + } + + ] + + def get_certificates(self,options, **kwargs): + adcs_server = current_app.config.get('ADCS_SERVER') + adcs_user = current_app.config.get('ADCS_USER') + adcs_pwd = current_app.config.get('ADCS_PWD') + adcs_auth_method = current_app.config.get('ADCS_AUTH_METHOD') + adcs_start = current_app.config.get('ADCS_START') + adcs_stop = current_app.config.get('ADCS_STOP') + ca_server = Certsrv(adcs_server, adcs_user, adcs_pwd, auth_method = adcs_auth_method) + out_certlist = [] + for id in range(adcs_start,adcs_stop): + try: + cert = ca_server.get_existing_cert(id, encoding='b64').decode('utf-8').replace('\r\n', '\n') + except Exception as err: + if '{0}'.format(err).find("CERTSRV_E_PROPERTY_EMPTY"): + #this error indicates end of certificate list(?), so we stop + break + else: + # We do nothing in case there is no certificate returned with the current id for other reasons + current_app.logger.info("Error with id {0}: {1}".format(id, err)) + else: + #we have a certificate + pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + #loop through extensions to see if we find "TLS Web Server Authentication" + for e_id in range(0,pubkey.get_extension_count()-1): + try: + extension = '{0}'.format(pubkey.get_extension(e_id)) + except: + extensionn = '' + if extension.find("TLS Web Server Authentication") != -1: + out_certlist.append ( { + 'name': format(pubkey.get_subject().CN), + 'body' : cert}) + break + + return out_certlist + + + def get_endpoints(self, options, **kwargs): + # There are no endpoints in the ADCS + raise NotImplementedError('Not implemented\n',self, options, **kwargs) + diff --git a/requirements-dev.txt b/requirements-dev.txt index 7b427b20..d8c24e4d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,6 +15,7 @@ flake8==3.5.0 identify==1.1.7 # via pre-commit idna==2.8 # via requests importlib-metadata==0.7 # via pre-commit +importlib-resources==1.0.2 # via pre-commit invoke==1.2.0 mccabe==0.6.1 # via flake8 nodeenv==1.3.3 diff --git a/requirements-docs.txt b/requirements-docs.txt index 3f036915..80f38e5f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -14,11 +14,11 @@ arrow==0.12.1 asn1crypto==0.24.0 asyncpool==1.0 babel==2.6.0 # via sphinx -bcrypt==3.1.4 +bcrypt==3.1.5 billiard==3.5.0.5 blinker==1.4 -boto3==1.9.60 -botocore==1.12.60 +boto3==1.9.67 +botocore==1.12.67 celery[redis]==4.2.1 certifi==2018.11.29 cffi==1.11.5 @@ -35,13 +35,13 @@ flask-cors==3.0.7 flask-mail==0.9.1 flask-migrate==2.3.1 flask-principal==0.4.0 -flask-restful==0.3.6 +flask-restful==0.3.7 flask-script==2.0.6 flask-sqlalchemy==2.3.2 flask==1.0.2 future==0.17.1 gunicorn==19.9.0 -idna==2.7 +idna==2.8 imagesize==1.1.0 # via sphinx inflection==0.3.1 itsdangerous==1.1.0 @@ -66,7 +66,7 @@ pyasn1-modules==0.2.2 pyasn1==0.4.4 pycparser==2.19 pygments==2.3.1 # via sphinx -pyjwt==1.7.0 +pyjwt==1.7.1 pynacl==1.3.0 pyopenssl==18.0.0 pyparsing==2.3.0 # via packaging @@ -78,17 +78,17 @@ pyyaml==3.13 raven[flask]==6.9.0 redis==2.10.6 requests-toolbelt==0.8.0 -requests[security]==2.20.1 +requests[security]==2.21.0 retrying==1.3.3 s3transfer==0.1.13 -six==1.11.0 +six==1.12.0 snowballstemmer==1.2.1 # via sphinx sphinx-rtd-theme==0.4.2 sphinx==1.8.2 sphinxcontrib-httpdomain==1.7.0 sphinxcontrib-websupport==1.1.0 # via sphinx sqlalchemy-utils==0.33.9 -sqlalchemy==1.2.14 +sqlalchemy==1.2.15 tabulate==0.8.2 urllib3==1.24.1 vine==1.1.4 diff --git a/requirements-tests.txt b/requirements-tests.txt index 59c626f7..47b83988 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -8,9 +8,9 @@ asn1crypto==0.24.0 # via cryptography atomicwrites==1.2.1 # via pytest attrs==18.2.0 # via pytest aws-xray-sdk==0.95 # via moto -boto3==1.9.67 # via moto +boto3==1.9.69 # via moto boto==2.49.0 # via moto -botocore==1.12.67 # via boto3, moto, s3transfer +botocore==1.12.69 # via boto3, moto, s3transfer certifi==2018.11.29 # via requests cffi==1.11.5 # via cryptography chardet==3.0.4 # via requests diff --git a/requirements.in b/requirements.in index 9824650b..0aea4591 100644 --- a/requirements.in +++ b/requirements.in @@ -8,6 +8,7 @@ boto3 botocore celery[redis] certifi +certsrv CloudFlare cryptography dnspython3 @@ -42,4 +43,4 @@ retrying six SQLAlchemy-Utils tabulate -xmltodict \ No newline at end of file +xmltodict diff --git a/requirements.txt b/requirements.txt index 7ee9a167..e88bcb90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,10 +15,11 @@ asyncpool==1.0 bcrypt==3.1.5 # via flask-bcrypt, paramiko billiard==3.5.0.5 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.9.67 -botocore==1.12.67 +boto3==1.9.69 +botocore==1.12.69 celery[redis]==4.2.1 certifi==2018.11.29 +certsrv==2.1.0 cffi==1.11.5 # via bcrypt, cryptography, pynacl chardet==3.0.4 # via requests click==7.0 # via flask @@ -70,7 +71,7 @@ python-editor==1.0.3 # via alembic python-ldap==3.1.0 pytz==2018.7 # via acme, celery, flask-restful, pyrfc3339 pyyaml==3.13 # via cloudflare -raven[flask]==6.9.0 +raven[flask]==6.10.0 redis==2.10.6 requests-toolbelt==0.8.0 # via acme requests[security]==2.21.0 diff --git a/setup.py b/setup.py index 1511b013..882edb02 100644 --- a/setup.py +++ b/setup.py @@ -154,7 +154,9 @@ setup( 'digicert_cis_issuer = lemur.plugins.lemur_digicert.plugin:DigiCertCISIssuerPlugin', 'digicert_cis_source = lemur.plugins.lemur_digicert.plugin:DigiCertCISSourcePlugin', 'csr_export = lemur.plugins.lemur_csr.plugin:CSRExportPlugin', - 'sftp_destination = lemur.plugins.lemur_sftp.plugin:SFTPDestinationPlugin' + 'sftp_destination = lemur.plugins.lemur_sftp.plugin:SFTPDestinationPlugin', + 'adcs_issuer = lemur.plugins.lemur_adcs.plugin:ADCSIssuerPlugin', + 'adcs_source = lemur.plugins.lemur_adcs.plugin:ADCSSourcePlugin' ], }, classifiers=[ From c62bcd1456bc35198a5895588e6ab042d0213fe5 Mon Sep 17 00:00:00 2001 From: sirferl Date: Mon, 7 Jan 2019 10:02:37 +0100 Subject: [PATCH 03/23] repaired several lint errors --- lemur/plugins/lemur_adcs/plugin.py | 68 ++++++++++++++---------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/lemur/plugins/lemur_adcs/plugin.py b/lemur/plugins/lemur_adcs/plugin.py index 48a3e85b..31dba7b2 100644 --- a/lemur/plugins/lemur_adcs/plugin.py +++ b/lemur/plugins/lemur_adcs/plugin.py @@ -1,12 +1,11 @@ from lemur.plugins.bases import IssuerPlugin, SourcePlugin import requests -import datetime import lemur_adcs as ADCS from certsrv import Certsrv -import ssl from OpenSSL import crypto from flask import current_app + class ADCSIssuerPlugin(IssuerPlugin): title = 'ADCS' slug = 'adcs-issuer' @@ -27,36 +26,37 @@ class ADCSIssuerPlugin(IssuerPlugin): Creates an authority, this authority is then used by Lemur to allow a user to specify which Certificate Authority they want to sign their certificate. - + :param options: :return: """ + adcs_root = current_app.config.get('ADCS_ROOT') + adcs_issuing = current_app.config.get('ADCS_ISSUING') role = {'username': '', 'password': '', 'name': 'adcs'} - return constants.ADCS_ROOT, constants.ADCS_ISSUING, [role] + return adcs_root, adcs_issuing, [role] def create_certificate(self, csr, issuer_options): adcs_server = current_app.config.get('ADCS_SERVER') adcs_user = current_app.config.get('ADCS_USER') adcs_pwd = current_app.config.get('ADCS_PWD') adcs_auth_method = current_app.config.get('ADCS_AUTH_METHOD') - ca_server = Certsrv(adcs_server, adcs_user, adcs_pwd, auth_method = adcs_auth_method) + adcs_template = current_app.config.get('ADCS_TEMPLATE') + ca_server = Certsrv(adcs_server, adcs_user, adcs_pwd, auth_method=adcs_auth_method) current_app.logger.info("Requesting CSR: {0}".format(csr)) current_app.logger.info("Issuer options: {0}".format(issuer_options)) - cert, req_id = ca_server.get_cert(csr, ADCS_TEMPLATE, encoding='b64').decode('utf-8').replace('\r\n', '\n') + cert, req_id = ca_server.get_cert(csr, adcs_template, encoding='b64').decode('utf-8').replace('\r\n', '\n') chain = ca_server.get_ca_cert(encoding='b64').decode('utf-8').replace('\r\n', '\n') return cert, chain, req_id - + def revoke_certificate(self, certificate, comments): - # requests.put('a third party') - raise NotImplementedError('Not implemented\n', self,certificate, comments) - + raise NotImplementedError('Not implemented\n', self, certificate, comments) + def get_ordered_certificate(self, order_id): - # requests.get('already existing certificate') - raise NotImplementedError('Not implemented\n',self, order_id) - + raise NotImplementedError('Not implemented\n', self, order_id) + def canceled_ordered_certificate(self, pending_cert, **kwargs): - # requests.put('cancel an order that has yet to be issued') - raise NotImplementedError('Not implemented\n',self, pending_cert, **kwargs) + raise NotImplementedError('Not implemented\n', self, pending_cert, **kwargs) + class ADCSSourcePlugin(SourcePlugin): title = 'ADCS' @@ -67,54 +67,50 @@ class ADCSSourcePlugin(SourcePlugin): author = 'sirferl' author_url = 'https://github.com/sirferl/lemur' options = [ - { + { 'name': 'dummy', 'type': 'str', 'required': False, 'validation': '/^[0-9]{12,12}$/', 'helpMessage': 'Just to prevent error' } - ] - - def get_certificates(self,options, **kwargs): + + def get_certificates(self, options, **kwargs): adcs_server = current_app.config.get('ADCS_SERVER') adcs_user = current_app.config.get('ADCS_USER') adcs_pwd = current_app.config.get('ADCS_PWD') adcs_auth_method = current_app.config.get('ADCS_AUTH_METHOD') adcs_start = current_app.config.get('ADCS_START') adcs_stop = current_app.config.get('ADCS_STOP') - ca_server = Certsrv(adcs_server, adcs_user, adcs_pwd, auth_method = adcs_auth_method) + ca_server = Certsrv(adcs_server, adcs_user, adcs_pwd, auth_method=adcs_auth_method) out_certlist = [] - for id in range(adcs_start,adcs_stop): - try: + for id in range(adcs_start, adcs_stop): + try: cert = ca_server.get_existing_cert(id, encoding='b64').decode('utf-8').replace('\r\n', '\n') except Exception as err: if '{0}'.format(err).find("CERTSRV_E_PROPERTY_EMPTY"): - #this error indicates end of certificate list(?), so we stop + # this error indicates end of certificate list(?), so we stop break else: # We do nothing in case there is no certificate returned with the current id for other reasons current_app.logger.info("Error with id {0}: {1}".format(id, err)) - else: - #we have a certificate + else: + # we have a certificate pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, cert) - #loop through extensions to see if we find "TLS Web Server Authentication" - for e_id in range(0,pubkey.get_extension_count()-1): + # loop through extensions to see if we find "TLS Web Server Authentication" + for e_id in range(0, pubkey.get_extension_count() - 1): try: extension = '{0}'.format(pubkey.get_extension(e_id)) - except: + except Exception: extensionn = '' - if extension.find("TLS Web Server Authentication") != -1: - out_certlist.append ( { + if extension.find("TLS Web Server Authentication") != -1: + out_certlist.append({ 'name': format(pubkey.get_subject().CN), - 'body' : cert}) + 'body': cert}) break - return out_certlist - - def get_endpoints(self, options, **kwargs): + def get_endpoints(self, options, **kwargs): # There are no endpoints in the ADCS - raise NotImplementedError('Not implemented\n',self, options, **kwargs) - + raise NotImplementedError('Not implemented\n', self, options, **kwargs) From a43476bc8702f56ac4261beff09fa8ca59a1f42d Mon Sep 17 00:00:00 2001 From: sirferl Date: Mon, 7 Jan 2019 11:04:27 +0100 Subject: [PATCH 04/23] minor errors after lint fix --- lemur/plugins/lemur_adcs/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lemur/plugins/lemur_adcs/plugin.py b/lemur/plugins/lemur_adcs/plugin.py index 31dba7b2..db068eb3 100644 --- a/lemur/plugins/lemur_adcs/plugin.py +++ b/lemur/plugins/lemur_adcs/plugin.py @@ -1,6 +1,6 @@ from lemur.plugins.bases import IssuerPlugin, SourcePlugin import requests -import lemur_adcs as ADCS +from lemur.plugins import lemur_adcs as ADCS from certsrv import Certsrv from OpenSSL import crypto from flask import current_app @@ -9,7 +9,7 @@ from flask import current_app class ADCSIssuerPlugin(IssuerPlugin): title = 'ADCS' slug = 'adcs-issuer' - description = 'Enables the creation of certificates by ADCS (Active Direcory Certificate Services)' + description = 'Enables the creation of certificates by ADCS (Active Directory Certificate Services)' version = ADCS.VERSION author = 'sirferl' From af88ad0f0da9d2dcee00f4455faf2b345594d905 Mon Sep 17 00:00:00 2001 From: sirferl Date: Mon, 7 Jan 2019 11:35:56 +0100 Subject: [PATCH 05/23] changed broken kombu ref. from 4.2.2 to 4.2.1 because travis build fails --- requirements-docs.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 80f38e5f..9c3cef5e 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -49,7 +49,7 @@ jinja2==2.10 jmespath==0.9.3 josepy==1.1.0 jsonlines==1.2.0 -kombu==4.2.2 +kombu==4.2.1 # 4.2.2 was broken sirferl lockfile==0.12.2 mako==1.0.7 markupsafe==1.1.0 diff --git a/requirements.txt b/requirements.txt index e88bcb90..c1658efc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ jinja2==2.10 jmespath==0.9.3 # via boto3, botocore josepy==1.1.0 # via acme jsonlines==1.2.0 # via cloudflare -kombu==4.2.2 # via celery +kombu==4.2.1 # via celery - 4.2.2. was removed sirferl lockfile==0.12.2 mako==1.0.7 # via alembic markupsafe==1.1.0 # via jinja2, mako From a1ca61d81365b6bfb6a26973ff7fe73337cea32c Mon Sep 17 00:00:00 2001 From: sirferl Date: Wed, 9 Jan 2019 09:50:26 +0100 Subject: [PATCH 06/23] changed a too long comment --- lemur/plugins/lemur_adcs/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_adcs/plugin.py b/lemur/plugins/lemur_adcs/plugin.py index db068eb3..b7698474 100644 --- a/lemur/plugins/lemur_adcs/plugin.py +++ b/lemur/plugins/lemur_adcs/plugin.py @@ -93,7 +93,7 @@ class ADCSSourcePlugin(SourcePlugin): # this error indicates end of certificate list(?), so we stop break else: - # We do nothing in case there is no certificate returned with the current id for other reasons + # We do nothing in case there is no certificate returned for other reasons current_app.logger.info("Error with id {0}: {1}".format(id, err)) else: # we have a certificate From cb35f19d6ca4b0d84ed2f96e1e28ca482556411d Mon Sep 17 00:00:00 2001 From: Ronald Moesbergen Date: Mon, 21 Jan 2019 10:22:03 +0100 Subject: [PATCH 07/23] Add 'delete_cert' to enum log_type in logs table --- lemur/logs/models.py | 2 +- lemur/migrations/versions/9f79024fe67b_.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 lemur/migrations/versions/9f79024fe67b_.py diff --git a/lemur/logs/models.py b/lemur/logs/models.py index d4239e59..9f982c24 100644 --- a/lemur/logs/models.py +++ b/lemur/logs/models.py @@ -18,6 +18,6 @@ class Log(db.Model): __tablename__ = 'logs' id = Column(Integer, primary_key=True) certificate_id = Column(Integer, ForeignKey('certificates.id')) - log_type = Column(Enum('key_view', 'create_cert', 'update_cert', 'revoke_cert', name='log_type'), nullable=False) + log_type = Column(Enum('key_view', 'create_cert', 'update_cert', 'revoke_cert', 'delete_cert', name='log_type'), nullable=False) logged_at = Column(ArrowType(), PassiveDefault(func.now()), nullable=False) user_id = Column(Integer, ForeignKey('users.id'), nullable=False) diff --git a/lemur/migrations/versions/9f79024fe67b_.py b/lemur/migrations/versions/9f79024fe67b_.py new file mode 100644 index 00000000..ad22d5f3 --- /dev/null +++ b/lemur/migrations/versions/9f79024fe67b_.py @@ -0,0 +1,22 @@ +""" Add delete_cert to log_type enum + +Revision ID: 9f79024fe67b +Revises: ee827d1e1974 +Create Date: 2019-01-03 15:36:59.181911 + +""" + +# revision identifiers, used by Alembic. +revision = '9f79024fe67b' +down_revision = 'ee827d1e1974' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.sync_enum_values('public', 'log_type', ['create_cert', 'key_view', 'revoke_cert', 'update_cert'], ['create_cert', 'delete_cert', 'key_view', 'revoke_cert', 'update_cert']) + + +def downgrade(): + op.sync_enum_values('public', 'log_type', ['create_cert', 'delete_cert', 'key_view', 'revoke_cert', 'update_cert'], ['create_cert', 'key_view', 'revoke_cert', 'update_cert']) From 4c4fbf3e48d3644ac2869d7d5e1688248fb6f597 Mon Sep 17 00:00:00 2001 From: Ronald Moesbergen Date: Mon, 21 Jan 2019 10:25:28 +0100 Subject: [PATCH 08/23] Implement certificates delete API call by marking a cert as 'deleted' in the database. Only certificates that have expired can be deleted. --- lemur/certificates/views.py | 46 ++++++++++++++++++++++++++++++++ lemur/tests/conftest.py | 12 ++++++++- lemur/tests/factories.py | 7 ++++- lemur/tests/test_certificates.py | 19 ++++++++++--- 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index 54c60924..948c44d6 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -6,6 +6,7 @@ .. moduleauthor:: Kevin Glisson """ import base64 +import arrow from builtins import str from flask import Blueprint, make_response, jsonify, g @@ -660,6 +661,51 @@ class Certificates(AuthenticatedResource): log_service.create(g.current_user, 'update_cert', certificate=cert) return cert + def delete(self, certificate_id, data=None): + """ + .. http:delete:: /certificates/1 + + Delete a certificate + + **Example request**: + + .. sourcecode:: http + + DELETE /certificates/1 HTTP/1.1 + Host: example.com + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :reqheader Authorization: OAuth token to authenticate + :statuscode 204: no error + :statuscode 403: unauthenticated + :statusoode 404: certificate not found + + """ + cert = service.get(certificate_id) + + if not cert: + return dict(message="Cannot find specified certificate"), 404 + + # allow creators + if g.current_user != cert.user: + owner_role = role_service.get_by_name(cert.owner) + permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) + + if not permission.can(): + return dict(message='You are not authorized to delete this certificate'), 403 + + if arrow.get(cert.not_after) > arrow.utcnow(): + return dict(message='Certificate is still valid, only expired certificates can be deleted'), 412 + + service.update(certificate_id, deleted=True) + log_service.create(g.current_user, 'delete_cert', certificate=cert) + return '', 204 + class NotificationCertificatesList(AuthenticatedResource): """ Defines the 'certificates' endpoint """ diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index 9a48eb94..3f5fa2d8 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -15,7 +15,8 @@ from lemur.tests.vectors import SAN_CERT_KEY, INTERMEDIATE_KEY from .factories import ApiKeyFactory, AuthorityFactory, NotificationFactory, DestinationFactory, \ CertificateFactory, UserFactory, RoleFactory, SourceFactory, EndpointFactory, \ - RotationPolicyFactory, PendingCertificateFactory, AsyncAuthorityFactory, CryptoAuthorityFactory + RotationPolicyFactory, PendingCertificateFactory, AsyncAuthorityFactory, InvalidCertificateFactory, \ + CryptoAuthorityFactory def pytest_runtest_setup(item): @@ -168,6 +169,15 @@ def pending_certificate(session): return p +@pytest.fixture +def invalid_certificate(session): + u = UserFactory() + a = AsyncAuthorityFactory() + i = InvalidCertificateFactory(user=u, authority=a) + session.commit() + return i + + @pytest.fixture def admin_user(session): u = UserFactory() diff --git a/lemur/tests/factories.py b/lemur/tests/factories.py index 3717c64d..a4af3d43 100644 --- a/lemur/tests/factories.py +++ b/lemur/tests/factories.py @@ -20,7 +20,7 @@ from lemur.policies.models import RotationPolicy from lemur.api_keys.models import ApiKey from .vectors import SAN_CERT_STR, SAN_CERT_KEY, CSR_STR, INTERMEDIATE_CERT_STR, ROOTCA_CERT_STR, INTERMEDIATE_KEY, \ - WILDCARD_CERT_KEY + WILDCARD_CERT_KEY, INVALID_CERT_STR class BaseFactory(SQLAlchemyModelFactory): @@ -137,6 +137,11 @@ class CACertificateFactory(CertificateFactory): private_key = INTERMEDIATE_KEY +class InvalidCertificateFactory(CertificateFactory): + body = INVALID_CERT_STR + private_key = '' + + class AuthorityFactory(BaseFactory): """Authority factory.""" name = Sequence(lambda n: 'authority{0}'.format(n)) diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index a1df1c0d..4d412563 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -647,15 +647,26 @@ def test_certificate_put_with_data(client, certificate, issuer_plugin): @pytest.mark.parametrize("token,status", [ - (VALID_USER_HEADER_TOKEN, 405), - (VALID_ADMIN_HEADER_TOKEN, 405), - (VALID_ADMIN_API_TOKEN, 405), - ('', 405) + (VALID_USER_HEADER_TOKEN, 403), + (VALID_ADMIN_HEADER_TOKEN, 412), + (VALID_ADMIN_API_TOKEN, 412), + ('', 401) ]) def test_certificate_delete(client, token, status): assert client.delete(api.url_for(Certificates, certificate_id=1), headers=token).status_code == status +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 403), + (VALID_ADMIN_HEADER_TOKEN, 204), + (VALID_ADMIN_API_TOKEN, 204), + ('', 401) +]) +def test_invalid_certificate_delete(client, invalid_certificate, token, status): + assert client.delete( + api.url_for(Certificates, certificate_id=invalid_certificate.id), headers=token).status_code == status + + @pytest.mark.parametrize("token,status", [ (VALID_USER_HEADER_TOKEN, 405), (VALID_ADMIN_HEADER_TOKEN, 405), From e24a94d798bd69a0110b1e5ddf532192621ca754 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Wed, 26 Dec 2018 19:49:56 +0200 Subject: [PATCH 09/23] Enforce that PEM strings (certs, keys, CSR) are internally passed as str, not bytes This was already true in most places but not 100%, leading to lots of redundant checks and conversions. --- lemur/certificates/service.py | 10 +--------- lemur/common/utils.py | 17 +++++++---------- lemur/plugins/lemur_aws/iam.py | 3 +-- lemur/plugins/lemur_cryptography/plugin.py | 11 ++++------- lemur/plugins/lemur_csr/plugin.py | 11 +++-------- lemur/plugins/lemur_java/plugin.py | 18 +++++------------- lemur/plugins/lemur_openssl/plugin.py | 11 +++-------- lemur/tests/conftest.py | 7 +++---- 8 files changed, 27 insertions(+), 61 deletions(-) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 1b203260..0f37d70e 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -221,11 +221,6 @@ def upload(**kwargs): else: kwargs['roles'] = roles - if kwargs.get('private_key'): - private_key = kwargs['private_key'] - if not isinstance(private_key, bytes): - kwargs['private_key'] = private_key.encode('utf-8') - cert = Certificate(**kwargs) cert.authority = kwargs.get('authority') cert = database.create(cert) @@ -432,10 +427,7 @@ def create_csr(**csr_config): encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, # would like to use PKCS8 but AWS ELBs don't like it encryption_algorithm=serialization.NoEncryption() - ) - - if isinstance(private_key, bytes): - private_key = private_key.decode('utf-8') + ).decode('utf-8') csr = request.public_bytes( encoding=serialization.Encoding.PEM diff --git a/lemur/common/utils.py b/lemur/common/utils.py index 0504c958..32271e89 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -48,24 +48,22 @@ def parse_certificate(body): :param body: :return: """ - if isinstance(body, str): - body = body.encode('utf-8') + assert isinstance(body, str) - return x509.load_pem_x509_certificate(body, default_backend()) + return x509.load_pem_x509_certificate(body.encode('utf-8'), default_backend()) def parse_private_key(private_key): """ Parses a PEM-format private key (RSA, DSA, ECDSA or any other supported algorithm). - Raises ValueError for an invalid string. + Raises ValueError for an invalid string. Raises AssertionError when passed value is not str-type. :param private_key: String containing PEM private key """ - if isinstance(private_key, str): - private_key = private_key.encode('utf8') + assert isinstance(private_key, str) - return load_pem_private_key(private_key, password=None, backend=default_backend()) + return load_pem_private_key(private_key.encode('utf8'), password=None, backend=default_backend()) def parse_csr(csr): @@ -75,10 +73,9 @@ def parse_csr(csr): :param csr: :return: """ - if isinstance(csr, str): - csr = csr.encode('utf-8') + assert isinstance(csr, str) - return x509.load_pem_x509_csr(csr, default_backend()) + return x509.load_pem_x509_csr(csr.encode('utf-8'), default_backend()) def get_authority_key(body): diff --git a/lemur/plugins/lemur_aws/iam.py b/lemur/plugins/lemur_aws/iam.py index 7010c909..49816c2b 100644 --- a/lemur/plugins/lemur_aws/iam.py +++ b/lemur/plugins/lemur_aws/iam.py @@ -64,6 +64,7 @@ def upload_cert(name, body, private_key, path, cert_chain=None, **kwargs): :param path: :return: """ + assert isinstance(private_key, str) client = kwargs.pop('client') if not path or path == '/': @@ -72,8 +73,6 @@ def upload_cert(name, body, private_key, path, cert_chain=None, **kwargs): name = name + '-' + path.strip('/') try: - if isinstance(private_key, bytes): - private_key = private_key.decode("utf-8") if cert_chain: return client.upload_server_certificate( Path=path, diff --git a/lemur/plugins/lemur_cryptography/plugin.py b/lemur/plugins/lemur_cryptography/plugin.py index fe9d7bb3..97060391 100644 --- a/lemur/plugins/lemur_cryptography/plugin.py +++ b/lemur/plugins/lemur_cryptography/plugin.py @@ -14,6 +14,7 @@ from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization +from lemur.common.utils import parse_private_key from lemur.plugins.bases import IssuerPlugin from lemur.plugins import lemur_cryptography as cryptography_issuer @@ -40,7 +41,8 @@ def issue_certificate(csr, options, private_key=None): if options.get("authority"): # Issue certificate signed by an existing lemur_certificates authority issuer_subject = options['authority'].authority_certificate.subject - issuer_private_key = options['authority'].authority_certificate.private_key + assert private_key is None, "Private would be ignored, authority key used instead" + private_key = options['authority'].authority_certificate.private_key chain_cert_pem = options['authority'].authority_certificate.body authority_key_identifier_public = options['authority'].authority_certificate.public_key authority_key_identifier_subject = x509.SubjectKeyIdentifier.from_public_key(authority_key_identifier_public) @@ -52,7 +54,6 @@ def issue_certificate(csr, options, private_key=None): else: # Issue certificate that is self-signed (new lemur_certificates root authority) issuer_subject = csr.subject - issuer_private_key = private_key chain_cert_pem = "" authority_key_identifier_public = csr.public_key() authority_key_identifier_subject = None @@ -112,11 +113,7 @@ def issue_certificate(csr, options, private_key=None): # FIXME: Not implemented in lemur/schemas.py yet https://github.com/Netflix/lemur/issues/662 pass - private_key = serialization.load_pem_private_key( - bytes(str(issuer_private_key).encode('utf-8')), - password=None, - backend=default_backend() - ) + private_key = parse_private_key(private_key) cert = builder.sign(private_key, hashes.SHA256(), default_backend()) cert_pem = cert.public_bytes( diff --git a/lemur/plugins/lemur_csr/plugin.py b/lemur/plugins/lemur_csr/plugin.py index e06035d1..13f42084 100644 --- a/lemur/plugins/lemur_csr/plugin.py +++ b/lemur/plugins/lemur_csr/plugin.py @@ -38,14 +38,9 @@ def create_csr(cert, chain, csr_tmp, key): :param csr_tmp: :param key: """ - if isinstance(cert, bytes): - cert = cert.decode('utf-8') - - if isinstance(chain, bytes): - chain = chain.decode('utf-8') - - if isinstance(key, bytes): - key = key.decode('utf-8') + assert isinstance(cert, str) + assert isinstance(chain, str) + assert isinstance(key, str) with mktempfile() as key_tmp: with open(key_tmp, 'w') as f: diff --git a/lemur/plugins/lemur_java/plugin.py b/lemur/plugins/lemur_java/plugin.py index 151794da..5aab5342 100644 --- a/lemur/plugins/lemur_java/plugin.py +++ b/lemur/plugins/lemur_java/plugin.py @@ -59,11 +59,8 @@ def split_chain(chain): def create_truststore(cert, chain, jks_tmp, alias, passphrase): - if isinstance(cert, bytes): - cert = cert.decode('utf-8') - - if isinstance(chain, bytes): - chain = chain.decode('utf-8') + assert isinstance(cert, str) + assert isinstance(chain, str) with mktempfile() as cert_tmp: with open(cert_tmp, 'w') as f: @@ -98,14 +95,9 @@ def create_truststore(cert, chain, jks_tmp, alias, passphrase): def create_keystore(cert, chain, jks_tmp, key, alias, passphrase): - if isinstance(cert, bytes): - cert = cert.decode('utf-8') - - if isinstance(chain, bytes): - chain = chain.decode('utf-8') - - if isinstance(key, bytes): - key = key.decode('utf-8') + assert isinstance(cert, str) + assert isinstance(chain, str) + assert isinstance(key, str) # Create PKCS12 keystore from private key and public certificate with mktempfile() as cert_tmp: diff --git a/lemur/plugins/lemur_openssl/plugin.py b/lemur/plugins/lemur_openssl/plugin.py index d50b4e43..9ddce925 100644 --- a/lemur/plugins/lemur_openssl/plugin.py +++ b/lemur/plugins/lemur_openssl/plugin.py @@ -44,14 +44,9 @@ def create_pkcs12(cert, chain, p12_tmp, key, alias, passphrase): :param alias: :param passphrase: """ - if isinstance(cert, bytes): - cert = cert.decode('utf-8') - - if isinstance(chain, bytes): - chain = chain.decode('utf-8') - - if isinstance(key, bytes): - key = key.decode('utf-8') + assert isinstance(cert, str) + assert isinstance(chain, str) + assert isinstance(key, str) with mktempfile() as key_tmp: with open(key_tmp, 'w') as f: diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index 9a48eb94..3790358e 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -3,12 +3,11 @@ import os import datetime import pytest from cryptography import x509 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.serialization import load_pem_private_key from flask import current_app from flask_principal import identity_changed, Identity from lemur import create_app +from lemur.common.utils import parse_private_key from lemur.database import db as _db from lemur.auth.service import create_token from lemur.tests.vectors import SAN_CERT_KEY, INTERMEDIATE_KEY @@ -235,12 +234,12 @@ def logged_in_admin(session, app): @pytest.fixture def private_key(): - return load_pem_private_key(SAN_CERT_KEY.encode(), password=None, backend=default_backend()) + return parse_private_key(SAN_CERT_KEY) @pytest.fixture def issuer_private_key(): - return load_pem_private_key(INTERMEDIATE_KEY.encode(), password=None, backend=default_backend()) + return parse_private_key(INTERMEDIATE_KEY) @pytest.fixture From 51248c193803727c10e9d4c67de8e465210ee3f2 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Thu, 20 Dec 2018 18:13:59 +0200 Subject: [PATCH 10/23] Use special issuer values and in special cases This way it's easy to find/distinguish selfsigned certificates stored in Lemur. --- lemur/common/defaults.py | 13 +++++++++++-- lemur/common/utils.py | 37 +++++++++++++++++++++++++++++++++++- lemur/tests/conftest.py | 8 ++++++++ lemur/tests/test_defaults.py | 14 +++++++++++++- lemur/tests/test_utils.py | 12 ++++++++++++ lemur/tests/vectors.py | 1 + 6 files changed, 81 insertions(+), 4 deletions(-) diff --git a/lemur/common/defaults.py b/lemur/common/defaults.py index 72e863c1..6b259f6b 100644 --- a/lemur/common/defaults.py +++ b/lemur/common/defaults.py @@ -3,6 +3,8 @@ import unicodedata from cryptography import x509 from flask import current_app + +from lemur.common.utils import is_selfsigned from lemur.extensions import sentry from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE @@ -229,15 +231,22 @@ def issuer(cert): """ Gets a sane issuer slug from a given certificate, stripping non-alphanumeric characters. - :param cert: + For self-signed certificates, the special value '' is returned. + If issuer cannot be determined, '' is returned. + + :param cert: Parsed certificate object :return: Issuer slug """ + # If certificate is self-signed, we return a special value -- there really is no distinct "issuer" for it + if is_selfsigned(cert): + return '' + # Try Common Name or fall back to Organization name attrs = (cert.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) or cert.issuer.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME)) if not attrs: current_app.logger.error("Unable to get issuer! Cert serial {:x}".format(cert.serial_number)) - return "Unknown" + return '' return text_to_slug(attrs[0].value, '') diff --git a/lemur/common/utils.py b/lemur/common/utils.py index 32271e89..f3ac5fe7 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -11,9 +11,10 @@ import string import sqlalchemy from cryptography import x509 +from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import rsa, ec +from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding from cryptography.hazmat.primitives.serialization import load_pem_private_key from flask_restful.reqparse import RequestParser from sqlalchemy import and_, func @@ -143,6 +144,40 @@ def generate_private_key(key_type): ) +def check_cert_signature(cert, issuer_public_key): + """ + Check a certificate's signature against an issuer public key. + On success, returns None; on failure, raises UnsupportedAlgorithm or InvalidSignature. + """ + if isinstance(issuer_public_key, rsa.RSAPublicKey): + # RSA requires padding, just to make life difficult for us poor developers :( + if cert.signature_algorithm_oid == x509.SignatureAlgorithmOID.RSASSA_PSS: + # In 2005, IETF devised a more secure padding scheme to replace PKCS #1 v1.5. To make sure that + # nobody can easily support or use it, they mandated lots of complicated parameters, unlike any + # other X.509 signature scheme. + # https://tools.ietf.org/html/rfc4056 + raise UnsupportedAlgorithm("RSASSA-PSS not supported") + else: + padder = padding.PKCS1v15() + issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, padder, cert.signature_hash_algorithm) + else: + # EllipticCurvePublicKey or DSAPublicKey + issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm) + + +def is_selfsigned(cert): + """ + Returns True if the certificate is self-signed. + Returns False for failed verification or unsupported signing algorithm. + """ + try: + check_cert_signature(cert, cert.public_key()) + # If verification was successful, it's self-signed. + return True + except InvalidSignature: + return False + + def is_weekend(date): """ Determines if a given date is on a weekend. diff --git a/lemur/tests/conftest.py b/lemur/tests/conftest.py index 32733e51..b3dad8b2 100644 --- a/lemur/tests/conftest.py +++ b/lemur/tests/conftest.py @@ -3,6 +3,8 @@ import os import datetime import pytest from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes from flask import current_app from flask_principal import identity_changed, Identity @@ -263,6 +265,12 @@ def cert_builder(private_key): .not_valid_after(datetime.datetime(2040, 1, 1))) +@pytest.fixture +def selfsigned_cert(cert_builder, private_key): + # cert_builder uses the same cert public key as 'private_key' + return cert_builder.sign(private_key, hashes.SHA256(), default_backend()) + + @pytest.fixture(scope='function') def aws_credentials(): os.environ['AWS_ACCESS_KEY_ID'] = 'testing' diff --git a/lemur/tests/test_defaults.py b/lemur/tests/test_defaults.py index ffa19727..da9d6c79 100644 --- a/lemur/tests/test_defaults.py +++ b/lemur/tests/test_defaults.py @@ -81,6 +81,13 @@ def test_create_name(client): datetime(2015, 5, 12, 0, 0, 0), False ) == 'xn--mnchen-3ya.de-VertrauenswurdigAutoritat-20150507-20150512' + assert certificate_name( + 'selfie.example.org', + '', + datetime(2015, 5, 7, 0, 0, 0), + datetime(2025, 5, 12, 13, 37, 0), + False + ) == 'selfie.example.org-selfsigned-20150507-20250512' def test_issuer(client, cert_builder, issuer_private_key): @@ -106,4 +113,9 @@ def test_issuer(client, cert_builder, issuer_private_key): cert = (cert_builder .issuer_name(x509.Name([])) .sign(issuer_private_key, hashes.SHA256(), default_backend())) - assert issuer(cert) == 'Unknown' + assert issuer(cert) == '' + + +def test_issuer_selfsigned(selfsigned_cert): + from lemur.common.defaults import issuer + assert issuer(selfsigned_cert) == '' diff --git a/lemur/tests/test_utils.py b/lemur/tests/test_utils.py index 62d021a4..3e226f0f 100644 --- a/lemur/tests/test_utils.py +++ b/lemur/tests/test_utils.py @@ -1,5 +1,7 @@ import pytest +from lemur.tests.vectors import SAN_CERT, INTERMEDIATE_CERT, ROOTCA_CERT + def test_generate_private_key(): from lemur.common.utils import generate_private_key @@ -71,3 +73,13 @@ KFfxwrO1 -----END CERTIFICATE-----''' authority_key = get_authority_key(test_cert) assert authority_key == 'feacb541be81771293affa412d8dc9f66a3ebb80' + + +def test_is_selfsigned(selfsigned_cert): + from lemur.common.utils import is_selfsigned + + assert is_selfsigned(selfsigned_cert) is True + assert is_selfsigned(SAN_CERT) is False + assert is_selfsigned(INTERMEDIATE_CERT) is False + # Root CA certificates are also technically self-signed + assert is_selfsigned(ROOTCA_CERT) is True diff --git a/lemur/tests/vectors.py b/lemur/tests/vectors.py index 6a836b30..5da37c61 100644 --- a/lemur/tests/vectors.py +++ b/lemur/tests/vectors.py @@ -45,6 +45,7 @@ ssvobJ6Xe2D4cCVjUmsqtFEztMgdqgmlcWyGdUKeXdi7CMoeTb4uO+9qRQq46wYW n7K1z+W0Kp5yhnnPAoOioAP4vjASDx3z3RnLaZvMmcO7YdCIwhE5oGV0 -----END CERTIFICATE----- """ +ROOTCA_CERT = parse_certificate(ROOTCA_CERT_STR) ROOTCA_KEY = """\ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAvyVpe0tfIzri3l3PYH2r7hW86wKF58GLY+Ua52rEO5E3eXQq From 176f9bfea6f703467676797a9ecaa4da0189e082 Mon Sep 17 00:00:00 2001 From: Curtis Castrapel Date: Tue, 5 Feb 2019 09:37:04 -0800 Subject: [PATCH 11/23] Updating requirements --- requirements-dev.txt | 4 ++-- requirements-docs.txt | 18 +++++++++--------- requirements-tests.txt | 14 +++++++------- requirements.txt | 16 ++++++++-------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ac35f3e9..29f39314 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,13 +19,13 @@ invoke==1.2.0 mccabe==0.6.1 # via flake8 nodeenv==1.3.3 pkginfo==1.5.0.1 # via twine -pre-commit==1.14.2 +pre-commit==1.14.3 pycodestyle==2.3.1 # via flake8 pyflakes==1.6.0 # via flake8 pygments==2.3.1 # via readme-renderer pyyaml==3.13 # via aspy.yaml, pre-commit readme-renderer==24.0 # via twine -requests-toolbelt==0.9.0 # via twine +requests-toolbelt==0.9.1 # via twine requests==2.21.0 # via requests-toolbelt, twine six==1.12.0 # via bleach, cfgv, pre-commit, readme-renderer toml==0.10.0 # via pre-commit diff --git a/requirements-docs.txt b/requirements-docs.txt index 15085766..21dc110c 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -8,7 +8,7 @@ acme==0.30.2 alabaster==0.7.12 # via sphinx alembic-autogenerate-enums==0.0.2 alembic==1.0.7 -amqp==2.4.0 +amqp==2.4.1 aniso8601==4.1.0 arrow==0.13.0 asn1crypto==0.24.0 @@ -17,8 +17,8 @@ babel==2.6.0 # via sphinx bcrypt==3.1.6 billiard==3.5.0.5 blinker==1.4 -boto3==1.9.86 -botocore==1.12.86 +boto3==1.9.87 +botocore==1.12.87 celery[redis]==4.2.1 certifi==2018.11.29 cffi==1.11.5 @@ -53,13 +53,13 @@ kombu==4.2.2.post1 lockfile==0.12.2 mako==1.0.7 markupsafe==1.1.0 -marshmallow-sqlalchemy==0.15.0 +marshmallow-sqlalchemy==0.16.0 marshmallow==2.18.0 mock==2.0.0 ndg-httpsclient==0.5.1 packaging==19.0 # via sphinx paramiko==2.4.2 -pbr==5.1.1 +pbr==5.1.2 pem==18.2.0 psycopg2==2.7.7 pyasn1-modules==0.2.4 @@ -71,20 +71,20 @@ pynacl==1.3.0 pyopenssl==19.0.0 pyparsing==2.3.1 # via packaging pyrfc3339==1.1 -python-dateutil==2.7.5 -python-editor==1.0.3 +python-dateutil==2.8.0 +python-editor==1.0.4 pytz==2018.9 pyyaml==3.13 raven[flask]==6.10.0 redis==2.10.6 -requests-toolbelt==0.9.0 +requests-toolbelt==0.9.1 requests[security]==2.21.0 retrying==1.3.3 s3transfer==0.1.13 six==1.12.0 snowballstemmer==1.2.1 # via sphinx sphinx-rtd-theme==0.4.2 -sphinx==1.8.3 +sphinx==1.8.4 sphinxcontrib-httpdomain==1.7.0 sphinxcontrib-websupport==1.1.0 # via sphinx sqlalchemy-utils==0.33.11 diff --git a/requirements-tests.txt b/requirements-tests.txt index c326e951..354f4f1a 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -5,12 +5,12 @@ # pip-compile --no-index --output-file requirements-tests.txt requirements-tests.in # asn1crypto==0.24.0 # via cryptography -atomicwrites==1.2.1 # via pytest +atomicwrites==1.3.0 # via pytest attrs==18.2.0 # via pytest aws-xray-sdk==0.95 # via moto -boto3==1.9.86 # via moto +boto3==1.9.87 # via moto boto==2.49.0 # via moto -botocore==1.12.86 # via boto3, moto, s3transfer +botocore==1.12.87 # via boto3, moto, s3transfer certifi==2018.11.29 # via requests cffi==1.11.5 # via cryptography chardet==3.0.4 # via requests @@ -37,7 +37,7 @@ mock==2.0.0 # via moto more-itertools==5.0.0 # via pytest moto==1.3.7 nose==1.3.7 -pbr==5.1.1 # via mock +pbr==5.1.2 # via mock pluggy==0.8.1 # via pytest py==1.7.0 # via pytest pyaml==18.11.0 # via moto @@ -45,9 +45,9 @@ pycparser==2.19 # via cffi pycryptodome==3.7.3 # via python-jose pyflakes==2.1.0 pytest-flask==0.14.0 -pytest-mock==1.10.0 -pytest==4.1.1 -python-dateutil==2.7.5 # via botocore, faker, freezegun, moto +pytest-mock==1.10.1 +pytest==4.2.0 +python-dateutil==2.8.0 # via botocore, faker, freezegun, moto python-jose==2.0.2 # via moto pytz==2018.9 # via moto pyyaml==3.13 # via pyaml diff --git a/requirements.txt b/requirements.txt index c595e509..cb08b22d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ acme==0.30.2 alembic-autogenerate-enums==0.0.2 alembic==1.0.7 # via flask-migrate -amqp==2.4.0 # via kombu +amqp==2.4.1 # via kombu aniso8601==4.1.0 # via flask-restful arrow==0.13.0 asn1crypto==0.24.0 # via cryptography @@ -15,8 +15,8 @@ asyncpool==1.0 bcrypt==3.1.6 # via flask-bcrypt, paramiko billiard==3.5.0.5 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.9.86 -botocore==1.12.86 +boto3==1.9.87 +botocore==1.12.87 celery[redis]==4.2.1 certifi==2018.11.29 cffi==1.11.5 # via bcrypt, cryptography, pynacl @@ -50,12 +50,12 @@ kombu==4.2.2.post1 # via celery lockfile==0.12.2 mako==1.0.7 # via alembic markupsafe==1.1.0 # via jinja2, mako -marshmallow-sqlalchemy==0.15.0 +marshmallow-sqlalchemy==0.16.0 marshmallow==2.18.0 mock==2.0.0 # via acme ndg-httpsclient==0.5.1 paramiko==2.4.2 -pbr==5.1.1 # via mock +pbr==5.1.2 # via mock pem==18.2.0 psycopg2==2.7.7 pyasn1-modules==0.2.4 # via python-ldap @@ -65,14 +65,14 @@ pyjwt==1.7.1 pynacl==1.3.0 # via paramiko pyopenssl==19.0.0 pyrfc3339==1.1 # via acme -python-dateutil==2.7.5 # via alembic, arrow, botocore -python-editor==1.0.3 # via alembic +python-dateutil==2.8.0 # via alembic, arrow, botocore +python-editor==1.0.4 # via alembic python-ldap==3.1.0 pytz==2018.9 # via acme, celery, flask-restful, pyrfc3339 pyyaml==3.13 # via cloudflare raven[flask]==6.10.0 redis==2.10.6 -requests-toolbelt==0.9.0 # via acme +requests-toolbelt==0.9.1 # via acme requests[security]==2.21.0 retrying==1.3.3 s3transfer==0.1.13 # via boto3 From 70a70663a2f766523bed0a688456aac99d265919 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Thu, 7 Feb 2019 09:51:34 -0800 Subject: [PATCH 12/23] updating requirements --- requirements-dev.txt | 5 ++--- requirements-docs.txt | 22 +++++++++++----------- requirements-tests.txt | 16 ++++++++-------- requirements.txt | 20 ++++++++++---------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ac35f3e9..440f932b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,18 +14,17 @@ flake8==3.5.0 identify==1.2.1 # via pre-commit idna==2.8 # via requests importlib-metadata==0.8 # via pre-commit -importlib-resources==1.0.2 # via pre-commit invoke==1.2.0 mccabe==0.6.1 # via flake8 nodeenv==1.3.3 pkginfo==1.5.0.1 # via twine -pre-commit==1.14.2 +pre-commit==1.14.3 pycodestyle==2.3.1 # via flake8 pyflakes==1.6.0 # via flake8 pygments==2.3.1 # via readme-renderer pyyaml==3.13 # via aspy.yaml, pre-commit readme-renderer==24.0 # via twine -requests-toolbelt==0.9.0 # via twine +requests-toolbelt==0.9.1 # via twine requests==2.21.0 # via requests-toolbelt, twine six==1.12.0 # via bleach, cfgv, pre-commit, readme-renderer toml==0.10.0 # via pre-commit diff --git a/requirements-docs.txt b/requirements-docs.txt index 15085766..194708ed 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -8,7 +8,7 @@ acme==0.30.2 alabaster==0.7.12 # via sphinx alembic-autogenerate-enums==0.0.2 alembic==1.0.7 -amqp==2.4.0 +amqp==2.4.1 aniso8601==4.1.0 arrow==0.13.0 asn1crypto==0.24.0 @@ -17,8 +17,8 @@ babel==2.6.0 # via sphinx bcrypt==3.1.6 billiard==3.5.0.5 blinker==1.4 -boto3==1.9.86 -botocore==1.12.86 +boto3==1.9.89 +botocore==1.12.89 celery[redis]==4.2.1 certifi==2018.11.29 cffi==1.11.5 @@ -49,17 +49,17 @@ jinja2==2.10 jmespath==0.9.3 josepy==1.1.0 jsonlines==1.2.0 -kombu==4.2.2.post1 +kombu==4.3.0 lockfile==0.12.2 mako==1.0.7 markupsafe==1.1.0 -marshmallow-sqlalchemy==0.15.0 +marshmallow-sqlalchemy==0.16.0 marshmallow==2.18.0 mock==2.0.0 ndg-httpsclient==0.5.1 packaging==19.0 # via sphinx paramiko==2.4.2 -pbr==5.1.1 +pbr==5.1.2 pem==18.2.0 psycopg2==2.7.7 pyasn1-modules==0.2.4 @@ -71,20 +71,20 @@ pynacl==1.3.0 pyopenssl==19.0.0 pyparsing==2.3.1 # via packaging pyrfc3339==1.1 -python-dateutil==2.7.5 -python-editor==1.0.3 +python-dateutil==2.8.0 +python-editor==1.0.4 pytz==2018.9 pyyaml==3.13 raven[flask]==6.10.0 redis==2.10.6 -requests-toolbelt==0.9.0 +requests-toolbelt==0.9.1 requests[security]==2.21.0 retrying==1.3.3 -s3transfer==0.1.13 +s3transfer==0.2.0 six==1.12.0 snowballstemmer==1.2.1 # via sphinx sphinx-rtd-theme==0.4.2 -sphinx==1.8.3 +sphinx==1.8.4 sphinxcontrib-httpdomain==1.7.0 sphinxcontrib-websupport==1.1.0 # via sphinx sqlalchemy-utils==0.33.11 diff --git a/requirements-tests.txt b/requirements-tests.txt index c326e951..174e60ff 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -5,12 +5,12 @@ # pip-compile --no-index --output-file requirements-tests.txt requirements-tests.in # asn1crypto==0.24.0 # via cryptography -atomicwrites==1.2.1 # via pytest +atomicwrites==1.3.0 # via pytest attrs==18.2.0 # via pytest aws-xray-sdk==0.95 # via moto -boto3==1.9.86 # via moto +boto3==1.9.89 # via moto boto==2.49.0 # via moto -botocore==1.12.86 # via boto3, moto, s3transfer +botocore==1.12.89 # via boto3, moto, s3transfer certifi==2018.11.29 # via requests cffi==1.11.5 # via cryptography chardet==3.0.4 # via requests @@ -37,7 +37,7 @@ mock==2.0.0 # via moto more-itertools==5.0.0 # via pytest moto==1.3.7 nose==1.3.7 -pbr==5.1.1 # via mock +pbr==5.1.2 # via mock pluggy==0.8.1 # via pytest py==1.7.0 # via pytest pyaml==18.11.0 # via moto @@ -45,16 +45,16 @@ pycparser==2.19 # via cffi pycryptodome==3.7.3 # via python-jose pyflakes==2.1.0 pytest-flask==0.14.0 -pytest-mock==1.10.0 -pytest==4.1.1 -python-dateutil==2.7.5 # via botocore, faker, freezegun, moto +pytest-mock==1.10.1 +pytest==4.2.0 +python-dateutil==2.8.0 # via botocore, faker, freezegun, moto python-jose==2.0.2 # via moto pytz==2018.9 # via moto pyyaml==3.13 # via pyaml requests-mock==1.5.2 requests==2.21.0 # via aws-xray-sdk, docker, moto, requests-mock, responses responses==0.10.5 # via moto -s3transfer==0.1.13 # via boto3 +s3transfer==0.2.0 # via boto3 six==1.12.0 # via cryptography, docker, docker-pycreds, faker, freezegun, mock, more-itertools, moto, pytest, python-dateutil, python-jose, requests-mock, responses, websocket-client text-unidecode==1.2 # via faker urllib3==1.24.1 # via botocore, requests diff --git a/requirements.txt b/requirements.txt index c595e509..db661030 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ acme==0.30.2 alembic-autogenerate-enums==0.0.2 alembic==1.0.7 # via flask-migrate -amqp==2.4.0 # via kombu +amqp==2.4.1 # via kombu aniso8601==4.1.0 # via flask-restful arrow==0.13.0 asn1crypto==0.24.0 # via cryptography @@ -15,8 +15,8 @@ asyncpool==1.0 bcrypt==3.1.6 # via flask-bcrypt, paramiko billiard==3.5.0.5 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.9.86 -botocore==1.12.86 +boto3==1.9.89 +botocore==1.12.89 celery[redis]==4.2.1 certifi==2018.11.29 cffi==1.11.5 # via bcrypt, cryptography, pynacl @@ -46,16 +46,16 @@ jinja2==2.10 jmespath==0.9.3 # via boto3, botocore josepy==1.1.0 # via acme jsonlines==1.2.0 # via cloudflare -kombu==4.2.2.post1 # via celery +kombu==4.3.0 # via celery lockfile==0.12.2 mako==1.0.7 # via alembic markupsafe==1.1.0 # via jinja2, mako -marshmallow-sqlalchemy==0.15.0 +marshmallow-sqlalchemy==0.16.0 marshmallow==2.18.0 mock==2.0.0 # via acme ndg-httpsclient==0.5.1 paramiko==2.4.2 -pbr==5.1.1 # via mock +pbr==5.1.2 # via mock pem==18.2.0 psycopg2==2.7.7 pyasn1-modules==0.2.4 # via python-ldap @@ -65,17 +65,17 @@ pyjwt==1.7.1 pynacl==1.3.0 # via paramiko pyopenssl==19.0.0 pyrfc3339==1.1 # via acme -python-dateutil==2.7.5 # via alembic, arrow, botocore -python-editor==1.0.3 # via alembic +python-dateutil==2.8.0 # via alembic, arrow, botocore +python-editor==1.0.4 # via alembic python-ldap==3.1.0 pytz==2018.9 # via acme, celery, flask-restful, pyrfc3339 pyyaml==3.13 # via cloudflare raven[flask]==6.10.0 redis==2.10.6 -requests-toolbelt==0.9.0 # via acme +requests-toolbelt==0.9.1 # via acme requests[security]==2.21.0 retrying==1.3.3 -s3transfer==0.1.13 # via boto3 +s3transfer==0.2.0 # via boto3 six==1.12.0 sqlalchemy-utils==0.33.11 sqlalchemy==1.2.17 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils From a43c6cf954bcaff127f9703be3c91bc594968ca2 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Thu, 7 Feb 2019 09:57:42 -0800 Subject: [PATCH 13/23] Update requirements-docs.txt --- requirements-docs.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index e68bfc5e..194708ed 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -17,7 +17,6 @@ babel==2.6.0 # via sphinx bcrypt==3.1.6 billiard==3.5.0.5 blinker==1.4 - boto3==1.9.89 botocore==1.12.89 celery[redis]==4.2.1 From fd60b163423167aaf51b6a770f058d1002c006fb Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Thu, 7 Feb 2019 17:12:37 -0800 Subject: [PATCH 14/23] updating requirements, pinning pyyaml to patched version. --- requirements-docs.txt | 4 ++-- requirements.in | 2 ++ requirements.txt | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 194708ed..4ebea0a0 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -4,7 +4,7 @@ # # pip-compile --no-index --output-file requirements-docs.txt requirements-docs.in # -acme==0.30.2 +acme==0.31.0 alabaster==0.7.12 # via sphinx alembic-autogenerate-enums==0.0.2 alembic==1.0.7 @@ -74,7 +74,7 @@ pyrfc3339==1.1 python-dateutil==2.8.0 python-editor==1.0.4 pytz==2018.9 -pyyaml==3.13 +pyyaml==4.2b4 raven[flask]==6.10.0 redis==2.10.6 requests-toolbelt==0.9.1 diff --git a/requirements.in b/requirements.in index 0aea4591..b085f5c7 100644 --- a/requirements.in +++ b/requirements.in @@ -44,3 +44,5 @@ six SQLAlchemy-Utils tabulate xmltodict +pyyaml>=4.2b1 #high severity alert + diff --git a/requirements.txt b/requirements.txt index f391d016..fd164c3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ # # pip-compile --no-index --output-file requirements.txt requirements.in # -acme==0.30.2 +acme==0.31.0 alembic-autogenerate-enums==0.0.2 alembic==1.0.7 # via flask-migrate amqp==2.4.1 # via kombu @@ -70,7 +70,7 @@ python-dateutil==2.8.0 # via alembic, arrow, botocore python-editor==1.0.4 # via alembic python-ldap==3.1.0 pytz==2018.9 # via acme, celery, flask-restful, pyrfc3339 -pyyaml==3.13 # via cloudflare +pyyaml==4.2b4 raven[flask]==6.10.0 redis==2.10.6 requests-toolbelt==0.9.1 # via acme From 73a474bd352b80a21751738506c30f3706ffc59c Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 8 Feb 2019 08:23:42 -0800 Subject: [PATCH 15/23] pinning pyyaml to ensure only using the patched version --- requirements-dev.in | 3 ++- requirements-dev.txt | 2 +- requirements-docs.txt | 5 +++-- requirements-tests.in | 1 + requirements-tests.txt | 6 +++--- requirements.txt | 6 +++--- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/requirements-dev.in b/requirements-dev.in index 84104679..2ffc5488 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -4,4 +4,5 @@ flake8==3.5.0 # flake8 3.6.0 is giving erroneous "W605 invalid escape sequence" pre-commit invoke twine -nodeenv \ No newline at end of file +nodeenv +pyyaml>=4.2b1 \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 440f932b..fd491663 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ pre-commit==1.14.3 pycodestyle==2.3.1 # via flake8 pyflakes==1.6.0 # via flake8 pygments==2.3.1 # via readme-renderer -pyyaml==3.13 # via aspy.yaml, pre-commit +pyyaml==4.2b4 readme-renderer==24.0 # via twine requests-toolbelt==0.9.1 # via twine requests==2.21.0 # via requests-toolbelt, twine diff --git a/requirements-docs.txt b/requirements-docs.txt index 4ebea0a0..a6c05582 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -17,10 +17,11 @@ babel==2.6.0 # via sphinx bcrypt==3.1.6 billiard==3.5.0.5 blinker==1.4 -boto3==1.9.89 -botocore==1.12.89 +boto3==1.9.90 +botocore==1.12.90 celery[redis]==4.2.1 certifi==2018.11.29 +certsrv==2.1.1 cffi==1.11.5 chardet==3.0.4 click==7.0 diff --git a/requirements-tests.in b/requirements-tests.in index 02a2b0ae..dcd3d0c7 100644 --- a/requirements-tests.in +++ b/requirements-tests.in @@ -11,3 +11,4 @@ pytest pytest-flask pytest-mock requests-mock +pyyaml>=4.2b1 \ No newline at end of file diff --git a/requirements-tests.txt b/requirements-tests.txt index 174e60ff..e4a34412 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -8,9 +8,9 @@ asn1crypto==0.24.0 # via cryptography atomicwrites==1.3.0 # via pytest attrs==18.2.0 # via pytest aws-xray-sdk==0.95 # via moto -boto3==1.9.89 # via moto +boto3==1.9.90 # via moto boto==2.49.0 # via moto -botocore==1.12.89 # via boto3, moto, s3transfer +botocore==1.12.90 # via boto3, moto, s3transfer certifi==2018.11.29 # via requests cffi==1.11.5 # via cryptography chardet==3.0.4 # via requests @@ -50,7 +50,7 @@ pytest==4.2.0 python-dateutil==2.8.0 # via botocore, faker, freezegun, moto python-jose==2.0.2 # via moto pytz==2018.9 # via moto -pyyaml==3.13 # via pyaml +pyyaml==4.2b4 requests-mock==1.5.2 requests==2.21.0 # via aws-xray-sdk, docker, moto, requests-mock, responses responses==0.10.5 # via moto diff --git a/requirements.txt b/requirements.txt index fd164c3d..f24d274e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,11 +15,11 @@ asyncpool==1.0 bcrypt==3.1.6 # via flask-bcrypt, paramiko billiard==3.5.0.5 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.9.89 -botocore==1.12.89 +boto3==1.9.90 +botocore==1.12.90 celery[redis]==4.2.1 certifi==2018.11.29 -certsrv==2.1.0 +certsrv==2.1.1 cffi==1.11.5 # via bcrypt, cryptography, pynacl chardet==3.0.4 # via requests click==7.0 # via flask From 42af082d3a5a4095300f961533665991947fbdae Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Mon, 11 Feb 2019 10:22:54 -0800 Subject: [PATCH 16/23] updating requirements --- requirements-dev.txt | 4 ++-- requirements-docs.txt | 6 +++--- requirements-tests.txt | 6 +++--- requirements.txt | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index fd491663..f5d6be3c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,9 +28,9 @@ requests-toolbelt==0.9.1 # via twine requests==2.21.0 # via requests-toolbelt, twine six==1.12.0 # via bleach, cfgv, pre-commit, readme-renderer toml==0.10.0 # via pre-commit -tqdm==4.30.0 # via twine +tqdm==4.31.1 # via twine twine==1.12.1 urllib3==1.24.1 # via requests -virtualenv==16.3.0 # via pre-commit +virtualenv==16.4.0 # via pre-commit webencodings==0.5.1 # via bleach zipp==0.3.3 # via importlib-metadata diff --git a/requirements-docs.txt b/requirements-docs.txt index a6c05582..80822929 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -17,8 +17,8 @@ babel==2.6.0 # via sphinx bcrypt==3.1.6 billiard==3.5.0.5 blinker==1.4 -boto3==1.9.90 -botocore==1.12.90 +boto3==1.9.91 +botocore==1.12.91 celery[redis]==4.2.1 certifi==2018.11.29 certsrv==2.1.1 @@ -94,4 +94,4 @@ tabulate==0.8.3 urllib3==1.24.1 vine==1.2.0 werkzeug==0.14.1 -xmltodict==0.11.0 +xmltodict==0.12.0 diff --git a/requirements-tests.txt b/requirements-tests.txt index e4a34412..60cda2d7 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -8,9 +8,9 @@ asn1crypto==0.24.0 # via cryptography atomicwrites==1.3.0 # via pytest attrs==18.2.0 # via pytest aws-xray-sdk==0.95 # via moto -boto3==1.9.90 # via moto +boto3==1.9.91 # via moto boto==2.49.0 # via moto -botocore==1.12.90 # via boto3, moto, s3transfer +botocore==1.12.91 # via boto3, moto, s3transfer certifi==2018.11.29 # via requests cffi==1.11.5 # via cryptography chardet==3.0.4 # via requests @@ -61,4 +61,4 @@ urllib3==1.24.1 # via botocore, requests websocket-client==0.54.0 # via docker werkzeug==0.14.1 # via flask, moto, pytest-flask wrapt==1.11.1 # via aws-xray-sdk -xmltodict==0.11.0 # via moto +xmltodict==0.12.0 # via moto diff --git a/requirements.txt b/requirements.txt index f24d274e..8bc96ac2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,8 +15,8 @@ asyncpool==1.0 bcrypt==3.1.6 # via flask-bcrypt, paramiko billiard==3.5.0.5 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.9.90 -botocore==1.12.90 +boto3==1.9.91 +botocore==1.12.91 celery[redis]==4.2.1 certifi==2018.11.29 certsrv==2.1.1 @@ -84,4 +84,4 @@ tabulate==0.8.3 urllib3==1.24.1 # via botocore, requests vine==1.2.0 # via amqp werkzeug==0.14.1 # via flask -xmltodict==0.11.0 +xmltodict==0.12.0 From ef0c08dfd9927e5d9db149a64685c9b8ba9fb350 Mon Sep 17 00:00:00 2001 From: Ronald Moesbergen Date: Thu, 21 Feb 2019 16:33:43 +0100 Subject: [PATCH 17/23] Fix: when no alias is entered when exporting a certificate, the alias is set to 'blah'. This fix sets it to the common name instead. --- lemur/plugins/lemur_java/plugin.py | 4 +++- lemur/plugins/lemur_openssl/plugin.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lemur/plugins/lemur_java/plugin.py b/lemur/plugins/lemur_java/plugin.py index 5aab5342..7eb33b90 100644 --- a/lemur/plugins/lemur_java/plugin.py +++ b/lemur/plugins/lemur_java/plugin.py @@ -15,6 +15,8 @@ from cryptography.fernet import Fernet from lemur.utils import mktempfile, mktemppath from lemur.plugins.bases import ExportPlugin from lemur.plugins import lemur_java as java +from lemur.common.utils import parse_certificate +from lemur.common.defaults import common_name def run_process(command): @@ -233,7 +235,7 @@ class JavaKeystoreExportPlugin(ExportPlugin): if self.get_option('alias', options): alias = self.get_option('alias', options) else: - alias = "blah" + alias = common_name(parse_certificate(body)) with mktemppath() as jks_tmp: create_keystore(body, chain, jks_tmp, key, alias, passphrase) diff --git a/lemur/plugins/lemur_openssl/plugin.py b/lemur/plugins/lemur_openssl/plugin.py index 9ddce925..6d6f89aa 100644 --- a/lemur/plugins/lemur_openssl/plugin.py +++ b/lemur/plugins/lemur_openssl/plugin.py @@ -14,7 +14,8 @@ from flask import current_app from lemur.utils import mktempfile, mktemppath from lemur.plugins.bases import ExportPlugin from lemur.plugins import lemur_openssl as openssl -from lemur.common.utils import get_psuedo_random_string +from lemur.common.utils import get_psuedo_random_string, parse_certificate +from lemur.common.defaults import common_name def run_process(command): @@ -122,7 +123,7 @@ class OpenSSLExportPlugin(ExportPlugin): if self.get_option('alias', options): alias = self.get_option('alias', options) else: - alias = "blah" + alias = common_name(parse_certificate(body)) type = self.get_option('type', options) From 14d8596b8a175cca79d41eaba19fc53a3dd249fd Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Thu, 21 Feb 2019 20:19:14 -0800 Subject: [PATCH 18/23] updating requirements --- requirements-dev.txt | 8 ++++---- requirements-docs.txt | 18 +++++++++--------- requirements-tests.txt | 16 ++++++++-------- requirements.txt | 16 ++++++++-------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f5d6be3c..c7d7986c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile --no-index --output-file requirements-dev.txt requirements-dev.in +# pip-compile --output-file requirements-dev.txt requirements-dev.in -U --no-index # aspy.yaml==1.1.2 # via pre-commit bleach==3.1.0 # via readme-renderer @@ -11,14 +11,14 @@ cfgv==1.4.0 # via pre-commit chardet==3.0.4 # via requests docutils==0.14 # via readme-renderer flake8==3.5.0 -identify==1.2.1 # via pre-commit +identify==1.2.2 # via pre-commit idna==2.8 # via requests importlib-metadata==0.8 # via pre-commit invoke==1.2.0 mccabe==0.6.1 # via flake8 nodeenv==1.3.3 pkginfo==1.5.0.1 # via twine -pre-commit==1.14.3 +pre-commit==1.14.4 pycodestyle==2.3.1 # via flake8 pyflakes==1.6.0 # via flake8 pygments==2.3.1 # via readme-renderer @@ -29,7 +29,7 @@ requests==2.21.0 # via requests-toolbelt, twine six==1.12.0 # via bleach, cfgv, pre-commit, readme-renderer toml==0.10.0 # via pre-commit tqdm==4.31.1 # via twine -twine==1.12.1 +twine==1.13.0 urllib3==1.24.1 # via requests virtualenv==16.4.0 # via pre-commit webencodings==0.5.1 # via bleach diff --git a/requirements-docs.txt b/requirements-docs.txt index 80822929..c3848b44 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile --no-index --output-file requirements-docs.txt requirements-docs.in +# pip-compile --output-file requirements-docs.txt requirements-docs.in -U --no-index # acme==0.31.0 alabaster==0.7.12 # via sphinx @@ -10,19 +10,19 @@ alembic-autogenerate-enums==0.0.2 alembic==1.0.7 amqp==2.4.1 aniso8601==4.1.0 -arrow==0.13.0 +arrow==0.13.1 asn1crypto==0.24.0 asyncpool==1.0 babel==2.6.0 # via sphinx bcrypt==3.1.6 billiard==3.5.0.5 blinker==1.4 -boto3==1.9.91 -botocore==1.12.91 +boto3==1.9.100 +botocore==1.12.100 celery[redis]==4.2.1 certifi==2018.11.29 certsrv==2.1.1 -cffi==1.11.5 +cffi==1.12.1 chardet==3.0.4 click==7.0 cloudflare==2.1.0 @@ -34,7 +34,7 @@ dyn==1.8.1 flask-bcrypt==0.7.1 flask-cors==3.0.7 flask-mail==0.9.1 -flask-migrate==2.3.1 +flask-migrate==2.4.0 flask-principal==0.4.0 flask-restful==0.3.7 flask-script==2.0.6 @@ -55,7 +55,7 @@ lockfile==0.12.2 mako==1.0.7 markupsafe==1.1.0 marshmallow-sqlalchemy==0.16.0 -marshmallow==2.18.0 +marshmallow==2.18.1 mock==2.0.0 ndg-httpsclient==0.5.1 packaging==19.0 # via sphinx @@ -84,12 +84,12 @@ retrying==1.3.3 s3transfer==0.2.0 six==1.12.0 snowballstemmer==1.2.1 # via sphinx -sphinx-rtd-theme==0.4.2 +sphinx-rtd-theme==0.4.3 sphinx==1.8.4 sphinxcontrib-httpdomain==1.7.0 sphinxcontrib-websupport==1.1.0 # via sphinx sqlalchemy-utils==0.33.11 -sqlalchemy==1.2.17 +sqlalchemy==1.2.18 tabulate==0.8.3 urllib3==1.24.1 vine==1.2.0 diff --git a/requirements-tests.txt b/requirements-tests.txt index 60cda2d7..ad97675e 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -2,17 +2,17 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile --no-index --output-file requirements-tests.txt requirements-tests.in +# pip-compile --output-file requirements-tests.txt requirements-tests.in -U --no-index # asn1crypto==0.24.0 # via cryptography atomicwrites==1.3.0 # via pytest attrs==18.2.0 # via pytest aws-xray-sdk==0.95 # via moto -boto3==1.9.91 # via moto +boto3==1.9.100 # via moto boto==2.49.0 # via moto -botocore==1.12.91 # via boto3, moto, s3transfer +botocore==1.12.100 # via boto3, moto, s3transfer certifi==2018.11.29 # via requests -cffi==1.11.5 # via cryptography +cffi==1.12.1 # via cryptography chardet==3.0.4 # via requests click==7.0 # via flask coverage==4.5.2 @@ -34,19 +34,19 @@ jsondiff==1.1.1 # via moto jsonpickle==1.1 # via aws-xray-sdk markupsafe==1.1.0 # via jinja2 mock==2.0.0 # via moto -more-itertools==5.0.0 # via pytest +more-itertools==6.0.0 # via pytest moto==1.3.7 nose==1.3.7 pbr==5.1.2 # via mock pluggy==0.8.1 # via pytest -py==1.7.0 # via pytest +py==1.8.0 # via pytest pyaml==18.11.0 # via moto pycparser==2.19 # via cffi pycryptodome==3.7.3 # via python-jose pyflakes==2.1.0 pytest-flask==0.14.0 pytest-mock==1.10.1 -pytest==4.2.0 +pytest==4.3.0 python-dateutil==2.8.0 # via botocore, faker, freezegun, moto python-jose==2.0.2 # via moto pytz==2018.9 # via moto @@ -55,7 +55,7 @@ requests-mock==1.5.2 requests==2.21.0 # via aws-xray-sdk, docker, moto, requests-mock, responses responses==0.10.5 # via moto s3transfer==0.2.0 # via boto3 -six==1.12.0 # via cryptography, docker, docker-pycreds, faker, freezegun, mock, more-itertools, moto, pytest, python-dateutil, python-jose, requests-mock, responses, websocket-client +six==1.12.0 # via cryptography, docker, docker-pycreds, faker, freezegun, mock, moto, pytest, python-dateutil, python-jose, requests-mock, responses, websocket-client text-unidecode==1.2 # via faker urllib3==1.24.1 # via botocore, requests websocket-client==0.54.0 # via docker diff --git a/requirements.txt b/requirements.txt index 8bc96ac2..0319fa3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile --no-index --output-file requirements.txt requirements.in +# pip-compile --output-file requirements.txt requirements.in -U --no-index # acme==0.31.0 alembic-autogenerate-enums==0.0.2 alembic==1.0.7 # via flask-migrate amqp==2.4.1 # via kombu aniso8601==4.1.0 # via flask-restful -arrow==0.13.0 +arrow==0.13.1 asn1crypto==0.24.0 # via cryptography asyncpool==1.0 bcrypt==3.1.6 # via flask-bcrypt, paramiko billiard==3.5.0.5 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.9.91 -botocore==1.12.91 +boto3==1.9.100 +botocore==1.12.100 celery[redis]==4.2.1 certifi==2018.11.29 certsrv==2.1.1 -cffi==1.11.5 # via bcrypt, cryptography, pynacl +cffi==1.12.1 # via bcrypt, cryptography, pynacl chardet==3.0.4 # via requests click==7.0 # via flask cloudflare==2.1.0 @@ -32,7 +32,7 @@ dyn==1.8.1 flask-bcrypt==0.7.1 flask-cors==3.0.7 flask-mail==0.9.1 -flask-migrate==2.3.1 +flask-migrate==2.4.0 flask-principal==0.4.0 flask-restful==0.3.7 flask-script==2.0.6 @@ -52,7 +52,7 @@ lockfile==0.12.2 mako==1.0.7 # via alembic markupsafe==1.1.0 # via jinja2, mako marshmallow-sqlalchemy==0.16.0 -marshmallow==2.18.0 +marshmallow==2.18.1 mock==2.0.0 # via acme ndg-httpsclient==0.5.1 paramiko==2.4.2 @@ -79,7 +79,7 @@ retrying==1.3.3 s3transfer==0.2.0 # via boto3 six==1.12.0 sqlalchemy-utils==0.33.11 -sqlalchemy==1.2.17 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils +sqlalchemy==1.2.18 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils tabulate==0.8.3 urllib3==1.24.1 # via botocore, requests vine==1.2.0 # via amqp From 40fac02d8b0dc9d411331eb25a4f16fddb774ab0 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Mon, 25 Feb 2019 19:05:54 -0800 Subject: [PATCH 19/23] the check_cert_signature() method was attempting to compare RSA and ECC signatures. If a ec public-key certificate is signed with an RSA key, then it can't be a self-signed certificate, in which case we just raise InvalidSignature. --- lemur/common/utils.py | 9 +++++++-- lemur/tests/test_utils.py | 3 ++- lemur/tests/vectors.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lemur/common/utils.py b/lemur/common/utils.py index f3ac5fe7..7c9269cf 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -147,6 +147,8 @@ def generate_private_key(key_type): def check_cert_signature(cert, issuer_public_key): """ Check a certificate's signature against an issuer public key. + Before EC validation, make sure public key and signature are of the same type, + otherwise verification not possible (raise InvalidSignature) On success, returns None; on failure, raises UnsupportedAlgorithm or InvalidSignature. """ if isinstance(issuer_public_key, rsa.RSAPublicKey): @@ -160,9 +162,10 @@ def check_cert_signature(cert, issuer_public_key): else: padder = padding.PKCS1v15() issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, padder, cert.signature_hash_algorithm) + elif isinstance(issuer_public_key, ec.EllipticCurvePublicKey) and isinstance(cert.signature_hash_algorithm, ec.ECDSA): + issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm) else: - # EllipticCurvePublicKey or DSAPublicKey - issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm) + raise InvalidSignature def is_selfsigned(cert): @@ -176,6 +179,8 @@ def is_selfsigned(cert): return True except InvalidSignature: return False + except UnsupportedAlgorithm as e: + raise Exception(e) def is_weekend(date): diff --git a/lemur/tests/test_utils.py b/lemur/tests/test_utils.py index 3e226f0f..c44f7b9c 100644 --- a/lemur/tests/test_utils.py +++ b/lemur/tests/test_utils.py @@ -1,6 +1,6 @@ import pytest -from lemur.tests.vectors import SAN_CERT, INTERMEDIATE_CERT, ROOTCA_CERT +from lemur.tests.vectors import SAN_CERT, INTERMEDIATE_CERT, ROOTCA_CERT, EC_CERT_EXAMPLE def test_generate_private_key(): @@ -83,3 +83,4 @@ def test_is_selfsigned(selfsigned_cert): assert is_selfsigned(INTERMEDIATE_CERT) is False # Root CA certificates are also technically self-signed assert is_selfsigned(ROOTCA_CERT) is True + assert is_selfsigned(EC_CERT_EXAMPLE) is False diff --git a/lemur/tests/vectors.py b/lemur/tests/vectors.py index 5da37c61..9af77bf6 100644 --- a/lemur/tests/vectors.py +++ b/lemur/tests/vectors.py @@ -394,3 +394,31 @@ zm3Cn4Ul8DO26w9QS4fmZjmnPOZFXYMWoOR6osHzb62PWQ8FBMqXcdToBV2Q9Iw4 PiFAxlc0tVjlLqQ= -----END CERTIFICATE REQUEST----- """ + + +EC_CERT_STR = """ +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIIHsJeci1JWAkwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UE +BhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczElMCMGA1UEAxMc +R29vZ2xlIEludGVybmV0IEF1dGhvcml0eSBHMzAeFw0xOTAyMTMxNTM1NTdaFw0x +OTA1MDgxNTM1MDBaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKDApHb29nbGUgTExDMRcw +FQYDVQQDDA53d3cuZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA +BKwMlIbd4rAwf6eWoa6RrR2w0s5k1M40XOORPf96PByPmld+qhjRMLvA/xcAxdCR +XdcMfaX6EUr0Zw8CepitMB2jggFSMIIBTjATBgNVHSUEDDAKBggrBgEFBQcDATAO +BgNVHQ8BAf8EBAMCB4AwGQYDVR0RBBIwEIIOd3d3Lmdvb2dsZS5jb20waAYIKwYB +BQUHAQEEXDBaMC0GCCsGAQUFBzAChiFodHRwOi8vcGtpLmdvb2cvZ3NyMi9HVFNH +SUFHMy5jcnQwKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnBraS5nb29nL0dUU0dJ +QUczMB0GA1UdDgQWBBQLovm8GG0oG91gOGCL58YPNoAlejAMBgNVHRMBAf8EAjAA +MB8GA1UdIwQYMBaAFHfCuFCaZ3Z2sS3ChtCDoH6mfrpLMCEGA1UdIAQaMBgwDAYK +KwYBBAHWeQIFAzAIBgZngQwBAgIwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovL2Ny +bC5wa2kuZ29vZy9HVFNHSUFHMy5jcmwwDQYJKoZIhvcNAQELBQADggEBAKFbmNOA +e3pJ7UVI5EmkAMZgSDRdrsLHV6F7WluuyYCyE/HFpZjBd6y8xgGtYWcask6edwrq +zrcXNEN/GY34AYre0M+p0xAs+lKSwkrJd2sCgygmzsBFtGwjW6lhjm+rg83zPHhH +mQZ0ShUR1Kp4TvzXgxj44RXOsS5ZyDe3slGiG4aw/hl+igO8Y8JMvcv/Tpzo+V75 +BkDAFmLRi08NayfeyCqK/TcRpzxKMKhS7jEHK8Pzu5P+FyFHKqIsobi+BA+psOix +5nZLhrweLdKNz387mE2lSSKzr7qeLGHSOMt+ajQtZio4YVyZqJvg4Y++J0n5+Rjw +MXp8GrvTfn1DQ+o= +-----END CERTIFICATE----- +""" +EC_CERT_EXAMPLE = parse_certificate(EC_CERT_STR) From e64de7d312816aa5a16369d57ba403072d52b436 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Mon, 25 Feb 2019 19:12:20 -0800 Subject: [PATCH 20/23] updating requirements --- requirements-dev.txt | 12 ++++++------ requirements-docs.txt | 24 ++++++++++++------------ requirements-tests.txt | 26 +++++++++++++------------- requirements.txt | 22 +++++++++++----------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f5d6be3c..1cfbe393 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile --no-index --output-file requirements-dev.txt requirements-dev.in +# pip-compile --output-file requirements-dev.txt requirements-dev.in -U --no-index # aspy.yaml==1.1.2 # via pre-commit bleach==3.1.0 # via readme-renderer @@ -11,26 +11,26 @@ cfgv==1.4.0 # via pre-commit chardet==3.0.4 # via requests docutils==0.14 # via readme-renderer flake8==3.5.0 -identify==1.2.1 # via pre-commit +identify==1.3.0 # via pre-commit idna==2.8 # via requests importlib-metadata==0.8 # via pre-commit invoke==1.2.0 mccabe==0.6.1 # via flake8 nodeenv==1.3.3 pkginfo==1.5.0.1 # via twine -pre-commit==1.14.3 +pre-commit==1.14.4 pycodestyle==2.3.1 # via flake8 pyflakes==1.6.0 # via flake8 pygments==2.3.1 # via readme-renderer -pyyaml==4.2b4 +pyyaml==5.1b1 readme-renderer==24.0 # via twine requests-toolbelt==0.9.1 # via twine requests==2.21.0 # via requests-toolbelt, twine six==1.12.0 # via bleach, cfgv, pre-commit, readme-renderer toml==0.10.0 # via pre-commit tqdm==4.31.1 # via twine -twine==1.12.1 +twine==1.13.0 urllib3==1.24.1 # via requests -virtualenv==16.4.0 # via pre-commit +virtualenv==16.4.1 # via pre-commit webencodings==0.5.1 # via bleach zipp==0.3.3 # via importlib-metadata diff --git a/requirements-docs.txt b/requirements-docs.txt index 80822929..db20a4b9 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile --no-index --output-file requirements-docs.txt requirements-docs.in +# pip-compile --output-file requirements-docs.txt requirements-docs.in -U --no-index # acme==0.31.0 alabaster==0.7.12 # via sphinx @@ -10,19 +10,19 @@ alembic-autogenerate-enums==0.0.2 alembic==1.0.7 amqp==2.4.1 aniso8601==4.1.0 -arrow==0.13.0 +arrow==0.13.1 asn1crypto==0.24.0 asyncpool==1.0 babel==2.6.0 # via sphinx bcrypt==3.1.6 billiard==3.5.0.5 blinker==1.4 -boto3==1.9.91 -botocore==1.12.91 +boto3==1.9.102 +botocore==1.12.102 celery[redis]==4.2.1 certifi==2018.11.29 certsrv==2.1.1 -cffi==1.11.5 +cffi==1.12.1 chardet==3.0.4 click==7.0 cloudflare==2.1.0 @@ -34,7 +34,7 @@ dyn==1.8.1 flask-bcrypt==0.7.1 flask-cors==3.0.7 flask-mail==0.9.1 -flask-migrate==2.3.1 +flask-migrate==2.4.0 flask-principal==0.4.0 flask-restful==0.3.7 flask-script==2.0.6 @@ -47,15 +47,15 @@ imagesize==1.1.0 # via sphinx inflection==0.3.1 itsdangerous==1.1.0 jinja2==2.10 -jmespath==0.9.3 +jmespath==0.9.4 josepy==1.1.0 jsonlines==1.2.0 kombu==4.3.0 lockfile==0.12.2 mako==1.0.7 -markupsafe==1.1.0 +markupsafe==1.1.1 marshmallow-sqlalchemy==0.16.0 -marshmallow==2.18.0 +marshmallow==2.18.1 mock==2.0.0 ndg-httpsclient==0.5.1 packaging==19.0 # via sphinx @@ -75,7 +75,7 @@ pyrfc3339==1.1 python-dateutil==2.8.0 python-editor==1.0.4 pytz==2018.9 -pyyaml==4.2b4 +pyyaml==5.1b1 raven[flask]==6.10.0 redis==2.10.6 requests-toolbelt==0.9.1 @@ -84,12 +84,12 @@ retrying==1.3.3 s3transfer==0.2.0 six==1.12.0 snowballstemmer==1.2.1 # via sphinx -sphinx-rtd-theme==0.4.2 +sphinx-rtd-theme==0.4.3 sphinx==1.8.4 sphinxcontrib-httpdomain==1.7.0 sphinxcontrib-websupport==1.1.0 # via sphinx sqlalchemy-utils==0.33.11 -sqlalchemy==1.2.17 +sqlalchemy==1.2.18 tabulate==0.8.3 urllib3==1.24.1 vine==1.2.0 diff --git a/requirements-tests.txt b/requirements-tests.txt index 60cda2d7..d1d6ae7f 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -2,17 +2,17 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile --no-index --output-file requirements-tests.txt requirements-tests.in +# pip-compile --output-file requirements-tests.txt requirements-tests.in -U --no-index # asn1crypto==0.24.0 # via cryptography atomicwrites==1.3.0 # via pytest attrs==18.2.0 # via pytest aws-xray-sdk==0.95 # via moto -boto3==1.9.91 # via moto +boto3==1.9.102 # via moto boto==2.49.0 # via moto -botocore==1.12.91 # via boto3, moto, s3transfer +botocore==1.12.102 # via boto3, moto, s3transfer certifi==2018.11.29 # via requests -cffi==1.11.5 # via cryptography +cffi==1.12.1 # via cryptography chardet==3.0.4 # via requests click==7.0 # via flask coverage==4.5.2 @@ -29,36 +29,36 @@ future==0.17.1 # via python-jose idna==2.8 # via requests itsdangerous==1.1.0 # via flask jinja2==2.10 # via flask, moto -jmespath==0.9.3 # via boto3, botocore +jmespath==0.9.4 # via boto3, botocore jsondiff==1.1.1 # via moto jsonpickle==1.1 # via aws-xray-sdk -markupsafe==1.1.0 # via jinja2 +markupsafe==1.1.1 # via jinja2 mock==2.0.0 # via moto -more-itertools==5.0.0 # via pytest +more-itertools==6.0.0 # via pytest moto==1.3.7 nose==1.3.7 pbr==5.1.2 # via mock -pluggy==0.8.1 # via pytest -py==1.7.0 # via pytest +pluggy==0.9.0 # via pytest +py==1.8.0 # via pytest pyaml==18.11.0 # via moto pycparser==2.19 # via cffi pycryptodome==3.7.3 # via python-jose pyflakes==2.1.0 pytest-flask==0.14.0 pytest-mock==1.10.1 -pytest==4.2.0 +pytest==4.3.0 python-dateutil==2.8.0 # via botocore, faker, freezegun, moto python-jose==2.0.2 # via moto pytz==2018.9 # via moto -pyyaml==4.2b4 +pyyaml==5.1b1 requests-mock==1.5.2 requests==2.21.0 # via aws-xray-sdk, docker, moto, requests-mock, responses responses==0.10.5 # via moto s3transfer==0.2.0 # via boto3 -six==1.12.0 # via cryptography, docker, docker-pycreds, faker, freezegun, mock, more-itertools, moto, pytest, python-dateutil, python-jose, requests-mock, responses, websocket-client +six==1.12.0 # via cryptography, docker, docker-pycreds, faker, freezegun, mock, moto, pytest, python-dateutil, python-jose, requests-mock, responses, websocket-client text-unidecode==1.2 # via faker urllib3==1.24.1 # via botocore, requests -websocket-client==0.54.0 # via docker +websocket-client==0.55.0 # via docker werkzeug==0.14.1 # via flask, moto, pytest-flask wrapt==1.11.1 # via aws-xray-sdk xmltodict==0.12.0 # via moto diff --git a/requirements.txt b/requirements.txt index 8bc96ac2..72a37692 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile # To update, run: # -# pip-compile --no-index --output-file requirements.txt requirements.in +# pip-compile --output-file requirements.txt requirements.in -U --no-index # acme==0.31.0 alembic-autogenerate-enums==0.0.2 alembic==1.0.7 # via flask-migrate amqp==2.4.1 # via kombu aniso8601==4.1.0 # via flask-restful -arrow==0.13.0 +arrow==0.13.1 asn1crypto==0.24.0 # via cryptography asyncpool==1.0 bcrypt==3.1.6 # via flask-bcrypt, paramiko billiard==3.5.0.5 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.9.91 -botocore==1.12.91 +boto3==1.9.102 +botocore==1.12.102 celery[redis]==4.2.1 certifi==2018.11.29 certsrv==2.1.1 -cffi==1.11.5 # via bcrypt, cryptography, pynacl +cffi==1.12.1 # via bcrypt, cryptography, pynacl chardet==3.0.4 # via requests click==7.0 # via flask cloudflare==2.1.0 @@ -32,7 +32,7 @@ dyn==1.8.1 flask-bcrypt==0.7.1 flask-cors==3.0.7 flask-mail==0.9.1 -flask-migrate==2.3.1 +flask-migrate==2.4.0 flask-principal==0.4.0 flask-restful==0.3.7 flask-script==2.0.6 @@ -44,15 +44,15 @@ idna==2.8 # via requests inflection==0.3.1 itsdangerous==1.1.0 # via flask jinja2==2.10 -jmespath==0.9.3 # via boto3, botocore +jmespath==0.9.4 # via boto3, botocore josepy==1.1.0 # via acme jsonlines==1.2.0 # via cloudflare kombu==4.3.0 # via celery lockfile==0.12.2 mako==1.0.7 # via alembic -markupsafe==1.1.0 # via jinja2, mako +markupsafe==1.1.1 # via jinja2, mako marshmallow-sqlalchemy==0.16.0 -marshmallow==2.18.0 +marshmallow==2.18.1 mock==2.0.0 # via acme ndg-httpsclient==0.5.1 paramiko==2.4.2 @@ -70,7 +70,7 @@ python-dateutil==2.8.0 # via alembic, arrow, botocore python-editor==1.0.4 # via alembic python-ldap==3.1.0 pytz==2018.9 # via acme, celery, flask-restful, pyrfc3339 -pyyaml==4.2b4 +pyyaml==5.1b1 raven[flask]==6.10.0 redis==2.10.6 requests-toolbelt==0.9.1 # via acme @@ -79,7 +79,7 @@ retrying==1.3.3 s3transfer==0.2.0 # via boto3 six==1.12.0 sqlalchemy-utils==0.33.11 -sqlalchemy==1.2.17 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils +sqlalchemy==1.2.18 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils tabulate==0.8.3 urllib3==1.24.1 # via botocore, requests vine==1.2.0 # via amqp From 16a18cc4b71d780821e3480f19787e117aec96f9 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Tue, 26 Feb 2019 16:35:49 -0800 Subject: [PATCH 21/23] adding more edge test cases for EC-certs --- lemur/tests/test_utils.py | 9 +++++- lemur/tests/vectors.py | 67 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/lemur/tests/test_utils.py b/lemur/tests/test_utils.py index c44f7b9c..74c11643 100644 --- a/lemur/tests/test_utils.py +++ b/lemur/tests/test_utils.py @@ -1,6 +1,6 @@ import pytest -from lemur.tests.vectors import SAN_CERT, INTERMEDIATE_CERT, ROOTCA_CERT, EC_CERT_EXAMPLE +from lemur.tests.vectors import SAN_CERT, INTERMEDIATE_CERT, ROOTCA_CERT, EC_CERT_EXAMPLE, ECDSA_PRIME256V1_CERT, ECDSA_SECP384r1_CERT, DSA_CERT def test_generate_private_key(): @@ -84,3 +84,10 @@ def test_is_selfsigned(selfsigned_cert): # Root CA certificates are also technically self-signed assert is_selfsigned(ROOTCA_CERT) is True assert is_selfsigned(EC_CERT_EXAMPLE) is False + + # selfsigned certs + assert is_selfsigned(ECDSA_PRIME256V1_CERT) is True + assert is_selfsigned(ECDSA_SECP384r1_CERT) is True + # unsupported algorithm (DSA) + with pytest.raises(Exception): + is_selfsigned(DSA_CERT) diff --git a/lemur/tests/vectors.py b/lemur/tests/vectors.py index 9af77bf6..06e7445a 100644 --- a/lemur/tests/vectors.py +++ b/lemur/tests/vectors.py @@ -422,3 +422,70 @@ MXp8GrvTfn1DQ+o= -----END CERTIFICATE----- """ EC_CERT_EXAMPLE = parse_certificate(EC_CERT_STR) + + +ECDSA_PRIME256V1_CERT_STR = """ +-----BEGIN CERTIFICATE----- +MIICUTCCAfYCCQCvH7H/e2nuiDAKBggqhkjOPQQDAjCBrzELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEjMCEGA1UE +CgwaTGVtdXJUcnVzdCBFbnRlcnByaXNlcyBMdGQxJjAkBgNVBAsMHVVuaXR0ZXN0 +aW5nIE9wZXJhdGlvbnMgQ2VudGVyMSowKAYDVQQDDCFMZW11clRydXN0IFVuaXR0 +ZXN0cyBSb290IENBIDIwMTkwHhcNMTkwMjI2MTgxMTUyWhcNMjkwMjIzMTgxMTUy +WjCBrzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcM +CUxvcyBHYXRvczEjMCEGA1UECgwaTGVtdXJUcnVzdCBFbnRlcnByaXNlcyBMdGQx +JjAkBgNVBAsMHVVuaXR0ZXN0aW5nIE9wZXJhdGlvbnMgQ2VudGVyMSowKAYDVQQD +DCFMZW11clRydXN0IFVuaXR0ZXN0cyBSb290IENBIDIwMTkwWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAAQsnAVUtpDCFMK/k9Chynu8BWRVUBUYbGQ9Q9xeLR60J4fD +uBt48YpTqg5RMZEclVknMReXqTmqphOBo37/YVdlMAoGCCqGSM49BAMCA0kAMEYC +IQDQZ6xfBiCTHxY4GM4+zLeG1iPBUSfIJOjkFNViFZY/XAIhAJYmrkVQb/YjWCdd +Vl89McYhmV4IV7WDgUmUhkUSFXgy +-----END CERTIFICATE----- +""" +ECDSA_PRIME256V1_CERT = parse_certificate(ECDSA_PRIME256V1_CERT_STR) + + +ECDSA_SECP384r1_CERT_STR = """ +-----BEGIN CERTIFICATE----- +MIICjjCCAhMCCQD2UadeQ7ub1jAKBggqhkjOPQQDAjCBrzELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEjMCEGA1UE +CgwaTGVtdXJUcnVzdCBFbnRlcnByaXNlcyBMdGQxJjAkBgNVBAsMHVVuaXR0ZXN0 +aW5nIE9wZXJhdGlvbnMgQ2VudGVyMSowKAYDVQQDDCFMZW11clRydXN0IFVuaXR0 +ZXN0cyBSb290IENBIDIwMTgwHhcNMTkwMjI2MTgxODU2WhcNMjkwMjIzMTgxODU2 +WjCBrzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcM +CUxvcyBHYXRvczEjMCEGA1UECgwaTGVtdXJUcnVzdCBFbnRlcnByaXNlcyBMdGQx +JjAkBgNVBAsMHVVuaXR0ZXN0aW5nIE9wZXJhdGlvbnMgQ2VudGVyMSowKAYDVQQD +DCFMZW11clRydXN0IFVuaXR0ZXN0cyBSb290IENBIDIwMTgwdjAQBgcqhkjOPQIB +BgUrgQQAIgNiAARuKyHIRp2e6PB5UcY8L/bUdavkL5Zf3IegNKvaAsvkDenhDGAI +zwWgsk3rOo7jmpMibn7yJQn404uZovwyeKcApn8uVv8ltheeYAx+ySzzn/APxNGy +cye/nv1D9cDW628wCgYIKoZIzj0EAwIDaQAwZgIxANl1ljDH4ykNK2OaRqKOkBOW +cKk1SvtiEZDS/wytiZGCeaxYteSYF+3GE8V2W1geWAIxAI8D7DY0HU5zw+oxAlTD +Uw/TeHA6q0QV4otPvrINW3V09iXDwFSPe265fTkHSfT6hQ== +-----END CERTIFICATE----- +""" +ECDSA_SECP384r1_CERT = parse_certificate(ECDSA_SECP384r1_CERT_STR) + +DSA_CERT_STR = """ +-----BEGIN CERTIFICATE----- +MIIDmTCCA1YCCQD5h/cM7xYO9jALBglghkgBZQMEAwIwga8xCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlMb3MgR2F0b3MxIzAhBgNV +BAoMGkxlbXVyVHJ1c3QgRW50ZXJwcmlzZXMgTHRkMSYwJAYDVQQLDB1Vbml0dGVz +dGluZyBPcGVyYXRpb25zIENlbnRlcjEqMCgGA1UEAwwhTGVtdXJUcnVzdCBVbml0 +dGVzdHMgUm9vdCBDQSAyMDE4MB4XDTE5MDIyNjE4MjUyMloXDTI5MDIyMzE4MjUy +Mlowga8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQH +DAlMb3MgR2F0b3MxIzAhBgNVBAoMGkxlbXVyVHJ1c3QgRW50ZXJwcmlzZXMgTHRk +MSYwJAYDVQQLDB1Vbml0dGVzdGluZyBPcGVyYXRpb25zIENlbnRlcjEqMCgGA1UE +AwwhTGVtdXJUcnVzdCBVbml0dGVzdHMgUm9vdCBDQSAyMDE4MIIBtjCCASsGByqG +SM44BAEwggEeAoGBAO2+6wO20rn9K7RtXJ7/kCSVFzYZsY1RKvmJ6BBkMFIepBkz +2pk62tRhJgNH07GKF7pyTPRRKqt38CaPK4ERUpavx3Ok6vZ3PKq8tMac/PMKBmT1 +Xfpch54KDlCdreEMJqYiCwbIyiSCR4+PCH+7xC5Uh0PIZo6otNWe3Wkk53CfAhUA +8d4YAtto6D30f7qkEa7DMAccUS8CgYAiv8r0k0aUEaeioblcCAjmhvE0v8/tD5u1 +anHO4jZIIv7uOrNFIGfqcNEOBs5AQkt5Bxn6x0b/VvtZ0FSrD0j4f36pTgro6noG +/0oRt0JngxsMSfo0LV4+bY62v21A0SneNgTgY+ugdfgGWvb0+9tpsIhiY69T+7c8 +Oa0S6OWSPAOBhAACgYB5wa+nJJNZPoTWFum27JlWGYLO2flg5EpWlOvcEE0o5RfB +FPnMM033kKQQEI0YpCAq9fIMKhhUMk1X4mKUBUTt+Nrn1pY2l/wt5G6AQdHI8QXz +P1ecBbHPNZtWe3iVnfOgz/Pd8tU9slcXP9z5XbZ7R/oGcF/TPRTtbLEkYZNaDDAL +BglghkgBZQMEAwIDMAAwLQIVANubSNMSLt8plN9ZV3cp4pe3lMYCAhQPLLE7rTgm +92X+hWfyz000QEpYEQ== +-----END CERTIFICATE----- +""" +DSA_CERT = parse_certificate(DSA_CERT_STR) From 9dbae39604a705544b541370b33e8b164bd48a28 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Tue, 26 Feb 2019 16:36:59 -0800 Subject: [PATCH 22/23] updating cryptography API call, to create right signing algorithm object. --- lemur/common/utils.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lemur/common/utils.py b/lemur/common/utils.py index 7c9269cf..f5db3d75 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -162,10 +162,10 @@ def check_cert_signature(cert, issuer_public_key): else: padder = padding.PKCS1v15() issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, padder, cert.signature_hash_algorithm) - elif isinstance(issuer_public_key, ec.EllipticCurvePublicKey) and isinstance(cert.signature_hash_algorithm, ec.ECDSA): - issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm) + elif isinstance(issuer_public_key, ec.EllipticCurvePublicKey) and isinstance(ec.ECDSA(cert.signature_hash_algorithm), ec.ECDSA): + issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, ec.ECDSA(cert.signature_hash_algorithm)) else: - raise InvalidSignature + raise UnsupportedAlgorithm("Unsupported Algorithm '{var}'.".format(var=cert.signature_algorithm_oid._name)) def is_selfsigned(cert): @@ -179,8 +179,6 @@ def is_selfsigned(cert): return True except InvalidSignature: return False - except UnsupportedAlgorithm as e: - raise Exception(e) def is_weekend(date): From 658c58e4b63ef50f5e4e4a040f4ce1bab21dab25 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Tue, 26 Feb 2019 17:04:43 -0800 Subject: [PATCH 23/23] clarifying comments --- lemur/common/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lemur/common/utils.py b/lemur/common/utils.py index f5db3d75..13e6e067 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -147,8 +147,7 @@ def generate_private_key(key_type): def check_cert_signature(cert, issuer_public_key): """ Check a certificate's signature against an issuer public key. - Before EC validation, make sure public key and signature are of the same type, - otherwise verification not possible (raise InvalidSignature) + Before EC validation, make sure we support the algorithm, otherwise raise UnsupportedAlgorithm On success, returns None; on failure, raises UnsupportedAlgorithm or InvalidSignature. """ if isinstance(issuer_public_key, rsa.RSAPublicKey):