Merge branch 'master' into removing-outdated-language
This commit is contained in:
commit
5db1d31668
|
@ -39,6 +39,22 @@ def update(authority_id, description, owner, active, roles):
|
||||||
return database.update(authority)
|
return database.update(authority)
|
||||||
|
|
||||||
|
|
||||||
|
def update_options(authority_id, options):
|
||||||
|
"""
|
||||||
|
Update an authority with new options.
|
||||||
|
|
||||||
|
:param authority_id:
|
||||||
|
:param options: the new options to be saved into the authority
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
authority = get(authority_id)
|
||||||
|
|
||||||
|
authority.options = options
|
||||||
|
|
||||||
|
return database.update(authority)
|
||||||
|
|
||||||
|
|
||||||
def mint(**kwargs):
|
def mint(**kwargs):
|
||||||
"""
|
"""
|
||||||
Creates the authority based on the plugin provided.
|
Creates the authority based on the plugin provided.
|
||||||
|
|
|
@ -23,6 +23,7 @@ from lemur.domains.schemas import DomainNestedOutputSchema
|
||||||
from lemur.notifications import service as notification_service
|
from lemur.notifications import service as notification_service
|
||||||
from lemur.notifications.schemas import NotificationNestedOutputSchema
|
from lemur.notifications.schemas import NotificationNestedOutputSchema
|
||||||
from lemur.policies.schemas import RotationPolicyNestedOutputSchema
|
from lemur.policies.schemas import RotationPolicyNestedOutputSchema
|
||||||
|
from lemur.roles import service as roles_service
|
||||||
from lemur.roles.schemas import RoleNestedOutputSchema
|
from lemur.roles.schemas import RoleNestedOutputSchema
|
||||||
from lemur.schemas import (
|
from lemur.schemas import (
|
||||||
AssociatedAuthoritySchema,
|
AssociatedAuthoritySchema,
|
||||||
|
@ -184,13 +185,33 @@ class CertificateEditInputSchema(CertificateSchema):
|
||||||
data["replaces"] = data[
|
data["replaces"] = data[
|
||||||
"replacements"
|
"replacements"
|
||||||
] # TODO remove when field is deprecated
|
] # TODO remove when field is deprecated
|
||||||
|
|
||||||
|
if data.get("owner"):
|
||||||
|
# Check if role already exists. This avoids adding duplicate role.
|
||||||
|
if data.get("roles") and any(r.get("name") == data["owner"] for r in data["roles"]):
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Add required role
|
||||||
|
owner_role = roles_service.get_or_create(
|
||||||
|
data["owner"],
|
||||||
|
description=f"Auto generated role based on owner: {data['owner']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Put role info in correct format using RoleNestedOutputSchema
|
||||||
|
owner_role_dict = RoleNestedOutputSchema().dump(owner_role).data
|
||||||
|
if data.get("roles"):
|
||||||
|
data["roles"].append(owner_role_dict)
|
||||||
|
else:
|
||||||
|
data["roles"] = [owner_role_dict]
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@post_load
|
@post_load
|
||||||
def enforce_notifications(self, data):
|
def enforce_notifications(self, data):
|
||||||
"""
|
"""
|
||||||
Ensures that when an owner changes, default notifications are added for the new owner.
|
Add default notification for current owner if none exist.
|
||||||
Old owner notifications are retained unless explicitly removed.
|
This ensures that the default notifications are added in the event of owner change.
|
||||||
|
Old owner notifications are retained unless explicitly removed later in the code path.
|
||||||
:param data:
|
:param data:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
@ -198,11 +219,18 @@ class CertificateEditInputSchema(CertificateSchema):
|
||||||
notification_name = "DEFAULT_{0}".format(
|
notification_name = "DEFAULT_{0}".format(
|
||||||
data["owner"].split("@")[0].upper()
|
data["owner"].split("@")[0].upper()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Even if one default role exists, return
|
||||||
|
# This allows a User to remove unwanted default notification for current owner
|
||||||
|
if any(n.label.startswith(notification_name) for n in data["notifications"]):
|
||||||
|
return data
|
||||||
|
|
||||||
data[
|
data[
|
||||||
"notifications"
|
"notifications"
|
||||||
] += notification_service.create_default_expiration_notifications(
|
] += notification_service.create_default_expiration_notifications(
|
||||||
notification_name, [data["owner"]]
|
notification_name, [data["owner"]]
|
||||||
)
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -256,6 +256,12 @@ def update(cert_id, **kwargs):
|
||||||
return database.update(cert)
|
return database.update(cert)
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_owner_roles_notification(owner_name, kwargs):
|
||||||
|
kwargs["roles"] = [r for r in kwargs["roles"] if r.name != owner_name]
|
||||||
|
notification_prefix = f"DEFAULT_{owner_name.split('@')[0].upper()}"
|
||||||
|
kwargs["notifications"] = [n for n in kwargs["notifications"] if not n.label.startswith(notification_prefix)]
|
||||||
|
|
||||||
|
|
||||||
def update_notify(cert, notify_flag):
|
def update_notify(cert, notify_flag):
|
||||||
"""
|
"""
|
||||||
Toggle notification value which is a boolean
|
Toggle notification value which is a boolean
|
||||||
|
@ -268,16 +274,11 @@ def update_notify(cert, notify_flag):
|
||||||
|
|
||||||
|
|
||||||
def create_certificate_roles(**kwargs):
|
def create_certificate_roles(**kwargs):
|
||||||
# create an role for the owner and assign it
|
# create a role for the owner and assign it
|
||||||
owner_role = role_service.get_by_name(kwargs["owner"])
|
owner_role = role_service.get_or_create(
|
||||||
|
kwargs["owner"],
|
||||||
if not owner_role:
|
description=f"Auto generated role based on owner: {kwargs['owner']}"
|
||||||
owner_role = role_service.create(
|
)
|
||||||
kwargs["owner"],
|
|
||||||
description="Auto generated role based on owner: {0}".format(
|
|
||||||
kwargs["owner"]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# ensure that the authority's owner is also associated with the certificate
|
# ensure that the authority's owner is also associated with the certificate
|
||||||
if kwargs.get("authority"):
|
if kwargs.get("authority"):
|
||||||
|
|
|
@ -884,6 +884,10 @@ class Certificates(AuthenticatedResource):
|
||||||
400,
|
400,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# if owner is changed, remove all notifications and roles associated with old owner
|
||||||
|
if cert.owner != data["owner"]:
|
||||||
|
service.cleanup_owner_roles_notification(cert.owner, data)
|
||||||
|
|
||||||
cert = service.update(certificate_id, **data)
|
cert = service.update(certificate_id, **data)
|
||||||
log_service.create(g.current_user, "update_cert", certificate=cert)
|
log_service.create(g.current_user, "update_cert", certificate=cert)
|
||||||
return cert
|
return cert
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
|
import pem
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
|
@ -16,7 +17,7 @@ from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
|
from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key, Encoding, pkcs7
|
||||||
from flask_restful.reqparse import RequestParser
|
from flask_restful.reqparse import RequestParser
|
||||||
from sqlalchemy import and_, func
|
from sqlalchemy import and_, func
|
||||||
|
|
||||||
|
@ -357,3 +358,19 @@ def find_matching_certificates_by_hash(cert, matching_certs):
|
||||||
):
|
):
|
||||||
matching.append(c)
|
matching.append(c)
|
||||||
return matching
|
return matching
|
||||||
|
|
||||||
|
|
||||||
|
def convert_pkcs7_bytes_to_pem(certs_pkcs7):
|
||||||
|
"""
|
||||||
|
Given a list of certificates in pkcs7 encoding (bytes), covert them into a list of PEM encoded files
|
||||||
|
:raises ValueError or ValidationError
|
||||||
|
:param certs_pkcs7:
|
||||||
|
:return: list of certs in PEM format
|
||||||
|
"""
|
||||||
|
|
||||||
|
certificates = pkcs7.load_pem_pkcs7_certificates(certs_pkcs7)
|
||||||
|
certificates_pem = []
|
||||||
|
for cert in certificates:
|
||||||
|
certificates_pem.append(pem.parse(cert.public_bytes(encoding=Encoding.PEM))[0])
|
||||||
|
|
||||||
|
return certificates_pem
|
||||||
|
|
|
@ -32,6 +32,7 @@ from lemur.extensions import metrics, sentry
|
||||||
from lemur.plugins import lemur_acme as acme
|
from lemur.plugins import lemur_acme as acme
|
||||||
from lemur.plugins.bases import IssuerPlugin
|
from lemur.plugins.bases import IssuerPlugin
|
||||||
from lemur.plugins.lemur_acme import cloudflare, dyn, route53, ultradns, powerdns
|
from lemur.plugins.lemur_acme import cloudflare, dyn, route53, ultradns, powerdns
|
||||||
|
from lemur.authorities import service as authorities_service
|
||||||
from retrying import retry
|
from retrying import retry
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,6 +241,7 @@ class AcmeHandler(object):
|
||||||
existing_regr = options.get("acme_regr", current_app.config.get("ACME_REGR"))
|
existing_regr = options.get("acme_regr", current_app.config.get("ACME_REGR"))
|
||||||
|
|
||||||
if existing_key and existing_regr:
|
if existing_key and existing_regr:
|
||||||
|
current_app.logger.debug("Reusing existing ACME account")
|
||||||
# Reuse the same account for each certificate issuance
|
# Reuse the same account for each certificate issuance
|
||||||
key = jose.JWK.json_loads(existing_key)
|
key = jose.JWK.json_loads(existing_key)
|
||||||
regr = messages.RegistrationResource.json_loads(existing_regr)
|
regr = messages.RegistrationResource.json_loads(existing_regr)
|
||||||
|
@ -253,6 +255,7 @@ class AcmeHandler(object):
|
||||||
# Create an account for each certificate issuance
|
# Create an account for each certificate issuance
|
||||||
key = jose.JWKRSA(key=generate_private_key("RSA2048"))
|
key = jose.JWKRSA(key=generate_private_key("RSA2048"))
|
||||||
|
|
||||||
|
current_app.logger.debug("Creating a new ACME account")
|
||||||
current_app.logger.debug(
|
current_app.logger.debug(
|
||||||
"Connecting with directory at {0}".format(directory_url)
|
"Connecting with directory at {0}".format(directory_url)
|
||||||
)
|
)
|
||||||
|
@ -262,6 +265,27 @@ class AcmeHandler(object):
|
||||||
registration = client.new_account_and_tos(
|
registration = client.new_account_and_tos(
|
||||||
messages.NewRegistration.from_data(email=email)
|
messages.NewRegistration.from_data(email=email)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# if store_account is checked, add the private_key and registration resources to the options
|
||||||
|
if options['store_account']:
|
||||||
|
new_options = json.loads(authority.options)
|
||||||
|
# the key returned by fields_to_partial_json is missing the key type, so we add it manually
|
||||||
|
key_dict = key.fields_to_partial_json()
|
||||||
|
key_dict["kty"] = "RSA"
|
||||||
|
acme_private_key = {
|
||||||
|
"name": "acme_private_key",
|
||||||
|
"value": json.dumps(key_dict)
|
||||||
|
}
|
||||||
|
new_options.append(acme_private_key)
|
||||||
|
|
||||||
|
acme_regr = {
|
||||||
|
"name": "acme_regr",
|
||||||
|
"value": json.dumps({"body": {}, "uri": registration.uri})
|
||||||
|
}
|
||||||
|
new_options.append(acme_regr)
|
||||||
|
|
||||||
|
authorities_service.update_options(authority.id, options=json.dumps(new_options))
|
||||||
|
|
||||||
current_app.logger.debug("Connected: {0}".format(registration.uri))
|
current_app.logger.debug("Connected: {0}".format(registration.uri))
|
||||||
|
|
||||||
return client, registration
|
return client, registration
|
||||||
|
@ -447,6 +471,13 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
||||||
"validation": "/^-----BEGIN CERTIFICATE-----/",
|
"validation": "/^-----BEGIN CERTIFICATE-----/",
|
||||||
"helpMessage": "Certificate to use",
|
"helpMessage": "Certificate to use",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "store_account",
|
||||||
|
"type": "bool",
|
||||||
|
"required": False,
|
||||||
|
"helpMessage": "Disable to create a new account for each ACME request",
|
||||||
|
"default": False,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
|
import josepy as jose
|
||||||
from cryptography.x509 import DNSName
|
from cryptography.x509 import DNSName
|
||||||
from lemur.plugins.lemur_acme import plugin
|
from lemur.plugins.lemur_acme import plugin
|
||||||
|
from lemur.common.utils import generate_private_key
|
||||||
from mock import MagicMock
|
from mock import MagicMock
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,11 +167,65 @@ class TestAcme(unittest.TestCase):
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
self.acme.setup_acme_client(mock_authority)
|
self.acme.setup_acme_client(mock_authority)
|
||||||
|
|
||||||
|
@patch("lemur.plugins.lemur_acme.plugin.jose.JWK.json_loads")
|
||||||
@patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2")
|
@patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2")
|
||||||
@patch("lemur.plugins.lemur_acme.plugin.current_app")
|
@patch("lemur.plugins.lemur_acme.plugin.current_app")
|
||||||
def test_setup_acme_client_success(self, mock_current_app, mock_acme):
|
def test_setup_acme_client_success_load_account_from_authority(self, mock_current_app, mock_acme, mock_key_json_load):
|
||||||
mock_authority = Mock()
|
mock_authority = Mock()
|
||||||
mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}]'
|
mock_authority.id = 2
|
||||||
|
mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}, ' \
|
||||||
|
'{"name": "store_account", "value": true},' \
|
||||||
|
'{"name": "acme_private_key", "value": "{\\"n\\": \\"PwIOkViO\\", \\"kty\\": \\"RSA\\"}"}, ' \
|
||||||
|
'{"name": "acme_regr", "value": "{\\"body\\": {}, \\"uri\\": \\"http://test.com\\"}"}]'
|
||||||
|
mock_client = Mock()
|
||||||
|
mock_acme.return_value = mock_client
|
||||||
|
mock_current_app.config = {}
|
||||||
|
|
||||||
|
mock_key_json_load.return_value = jose.JWKRSA(key=generate_private_key("RSA2048"))
|
||||||
|
|
||||||
|
result_client, result_registration = self.acme.setup_acme_client(mock_authority)
|
||||||
|
|
||||||
|
mock_acme.new_account_and_tos.assert_not_called()
|
||||||
|
assert result_client
|
||||||
|
assert not result_registration
|
||||||
|
|
||||||
|
@patch("lemur.plugins.lemur_acme.plugin.jose.JWKRSA.fields_to_partial_json")
|
||||||
|
@patch("lemur.plugins.lemur_acme.plugin.authorities_service")
|
||||||
|
@patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2")
|
||||||
|
@patch("lemur.plugins.lemur_acme.plugin.current_app")
|
||||||
|
def test_setup_acme_client_success_store_new_account(self, mock_current_app, mock_acme, mock_authorities_service,
|
||||||
|
mock_key_generation):
|
||||||
|
mock_authority = Mock()
|
||||||
|
mock_authority.id = 2
|
||||||
|
mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}, ' \
|
||||||
|
'{"name": "store_account", "value": true}]'
|
||||||
|
mock_client = Mock()
|
||||||
|
mock_registration = Mock()
|
||||||
|
mock_registration.uri = "http://test.com"
|
||||||
|
mock_client.register = mock_registration
|
||||||
|
mock_client.agree_to_tos = Mock(return_value=True)
|
||||||
|
mock_client.new_account_and_tos.return_value = mock_registration
|
||||||
|
mock_acme.return_value = mock_client
|
||||||
|
mock_current_app.config = {}
|
||||||
|
|
||||||
|
mock_key_generation.return_value = {"n": "PwIOkViO"}
|
||||||
|
|
||||||
|
mock_authorities_service.update_options = Mock(return_value=True)
|
||||||
|
|
||||||
|
self.acme.setup_acme_client(mock_authority)
|
||||||
|
|
||||||
|
mock_authorities_service.update_options.assert_called_with(2, options='[{"name": "mock_name", "value": "mock_value"}, '
|
||||||
|
'{"name": "store_account", "value": true}, '
|
||||||
|
'{"name": "acme_private_key", "value": "{\\"n\\": \\"PwIOkViO\\", \\"kty\\": \\"RSA\\"}"}, '
|
||||||
|
'{"name": "acme_regr", "value": "{\\"body\\": {}, \\"uri\\": \\"http://test.com\\"}"}]')
|
||||||
|
|
||||||
|
@patch("lemur.plugins.lemur_acme.plugin.authorities_service")
|
||||||
|
@patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2")
|
||||||
|
@patch("lemur.plugins.lemur_acme.plugin.current_app")
|
||||||
|
def test_setup_acme_client_success(self, mock_current_app, mock_acme, mock_authorities_service):
|
||||||
|
mock_authority = Mock()
|
||||||
|
mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}, ' \
|
||||||
|
'{"name": "store_account", "value": false}]'
|
||||||
mock_client = Mock()
|
mock_client = Mock()
|
||||||
mock_registration = Mock()
|
mock_registration = Mock()
|
||||||
mock_registration.uri = "http://test.com"
|
mock_registration.uri = "http://test.com"
|
||||||
|
@ -178,6 +234,7 @@ class TestAcme(unittest.TestCase):
|
||||||
mock_acme.return_value = mock_client
|
mock_acme.return_value = mock_client
|
||||||
mock_current_app.config = {}
|
mock_current_app.config = {}
|
||||||
result_client, result_registration = self.acme.setup_acme_client(mock_authority)
|
result_client, result_registration = self.acme.setup_acme_client(mock_authority)
|
||||||
|
mock_authorities_service.update_options.assert_not_called()
|
||||||
assert result_client
|
assert result_client
|
||||||
assert result_registration
|
assert result_registration
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import requests
|
||||||
import sys
|
import sys
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from flask import current_app, g
|
from flask import current_app, g
|
||||||
from lemur.common.utils import validate_conf
|
from lemur.common.utils import validate_conf, convert_pkcs7_bytes_to_pem
|
||||||
from lemur.extensions import metrics
|
from lemur.extensions import metrics
|
||||||
from lemur.plugins import lemur_digicert as digicert
|
from lemur.plugins import lemur_digicert as digicert
|
||||||
from lemur.plugins.bases import IssuerPlugin, SourcePlugin
|
from lemur.plugins.bases import IssuerPlugin, SourcePlugin
|
||||||
|
@ -235,15 +235,18 @@ def get_certificate_id(session, base_url, order_id):
|
||||||
|
|
||||||
@retry(stop_max_attempt_number=10, wait_fixed=10000)
|
@retry(stop_max_attempt_number=10, wait_fixed=10000)
|
||||||
def get_cis_certificate(session, base_url, order_id):
|
def get_cis_certificate(session, base_url, order_id):
|
||||||
"""Retrieve certificate order id from Digicert API."""
|
"""Retrieve certificate order id from Digicert API, including the chain"""
|
||||||
certificate_url = "{0}/platform/cis/certificate/{1}".format(base_url, order_id)
|
certificate_url = "{0}/platform/cis/certificate/{1}/download".format(base_url, order_id)
|
||||||
session.headers.update({"Accept": "application/x-pem-file"})
|
session.headers.update({"Accept": "application/x-pkcs7-certificates"})
|
||||||
response = session.get(certificate_url)
|
response = session.get(certificate_url)
|
||||||
|
|
||||||
if response.status_code == 404:
|
if response.status_code == 404:
|
||||||
raise Exception("Order not in issued state.")
|
raise Exception("Order not in issued state.")
|
||||||
|
|
||||||
return response.content
|
cert_chain_pem = convert_pkcs7_bytes_to_pem(response.content)
|
||||||
|
if len(cert_chain_pem) < 3:
|
||||||
|
raise Exception("Missing the certificate chain")
|
||||||
|
return cert_chain_pem
|
||||||
|
|
||||||
|
|
||||||
class DigiCertSourcePlugin(SourcePlugin):
|
class DigiCertSourcePlugin(SourcePlugin):
|
||||||
|
@ -447,7 +450,6 @@ class DigiCertCISSourcePlugin(SourcePlugin):
|
||||||
"DIGICERT_CIS_API_KEY",
|
"DIGICERT_CIS_API_KEY",
|
||||||
"DIGICERT_CIS_URL",
|
"DIGICERT_CIS_URL",
|
||||||
"DIGICERT_CIS_ROOTS",
|
"DIGICERT_CIS_ROOTS",
|
||||||
"DIGICERT_CIS_INTERMEDIATES",
|
|
||||||
"DIGICERT_CIS_PROFILE_NAMES",
|
"DIGICERT_CIS_PROFILE_NAMES",
|
||||||
]
|
]
|
||||||
validate_conf(current_app, required_vars)
|
validate_conf(current_app, required_vars)
|
||||||
|
@ -522,7 +524,6 @@ class DigiCertCISIssuerPlugin(IssuerPlugin):
|
||||||
"DIGICERT_CIS_API_KEY",
|
"DIGICERT_CIS_API_KEY",
|
||||||
"DIGICERT_CIS_URL",
|
"DIGICERT_CIS_URL",
|
||||||
"DIGICERT_CIS_ROOTS",
|
"DIGICERT_CIS_ROOTS",
|
||||||
"DIGICERT_CIS_INTERMEDIATES",
|
|
||||||
"DIGICERT_CIS_PROFILE_NAMES",
|
"DIGICERT_CIS_PROFILE_NAMES",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -552,22 +553,15 @@ class DigiCertCISIssuerPlugin(IssuerPlugin):
|
||||||
data = handle_cis_response(response)
|
data = handle_cis_response(response)
|
||||||
|
|
||||||
# retrieve certificate
|
# retrieve certificate
|
||||||
certificate_pem = get_cis_certificate(self.session, base_url, data["id"])
|
certificate_chain_pem = get_cis_certificate(self.session, base_url, data["id"])
|
||||||
|
|
||||||
self.session.headers.pop("Accept")
|
self.session.headers.pop("Accept")
|
||||||
end_entity = pem.parse(certificate_pem)[0]
|
end_entity = certificate_chain_pem[0]
|
||||||
|
intermediate = certificate_chain_pem[1]
|
||||||
|
|
||||||
if "ECC" in issuer_options["key_type"]:
|
|
||||||
return (
|
|
||||||
"\n".join(str(end_entity).splitlines()),
|
|
||||||
current_app.config.get("DIGICERT_ECC_CIS_INTERMEDIATES", {}).get(issuer_options['authority'].name),
|
|
||||||
data["id"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# By default return RSA
|
|
||||||
return (
|
return (
|
||||||
"\n".join(str(end_entity).splitlines()),
|
"\n".join(str(end_entity).splitlines()),
|
||||||
current_app.config.get("DIGICERT_CIS_INTERMEDIATES", {}).get(issuer_options['authority'].name),
|
"\n".join(str(intermediate).splitlines()),
|
||||||
data["id"],
|
data["id"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -128,3 +128,11 @@ def render(args):
|
||||||
query = database.filter(query, Role, terms)
|
query = database.filter(query, Role, terms)
|
||||||
|
|
||||||
return database.sort_and_page(query, Role, args)
|
return database.sort_and_page(query, Role, args)
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create(role_name, description):
|
||||||
|
role = get_by_name(role_name)
|
||||||
|
if not role:
|
||||||
|
role = create(name=role_name, description=description)
|
||||||
|
|
||||||
|
return role
|
||||||
|
|
|
@ -99,7 +99,6 @@ DIGICERT_CIS_URL = "mock://www.digicert.com"
|
||||||
DIGICERT_CIS_PROFILE_NAMES = {"sha2-rsa-ecc-root": "ssl_plus"}
|
DIGICERT_CIS_PROFILE_NAMES = {"sha2-rsa-ecc-root": "ssl_plus"}
|
||||||
DIGICERT_CIS_API_KEY = "api-key"
|
DIGICERT_CIS_API_KEY = "api-key"
|
||||||
DIGICERT_CIS_ROOTS = {"root": "ROOT"}
|
DIGICERT_CIS_ROOTS = {"root": "ROOT"}
|
||||||
DIGICERT_CIS_INTERMEDIATES = {"inter": "INTERMEDIATE_CA_CERT"}
|
|
||||||
|
|
||||||
VERISIGN_URL = "http://example.com"
|
VERISIGN_URL = "http://example.com"
|
||||||
VERISIGN_PEM_PATH = "~/"
|
VERISIGN_PEM_PATH = "~/"
|
||||||
|
|
|
@ -180,7 +180,10 @@ def test_certificate_edit_schema(session):
|
||||||
|
|
||||||
input_data = {"owner": "bob@example.com"}
|
input_data = {"owner": "bob@example.com"}
|
||||||
data, errors = CertificateEditInputSchema().load(input_data)
|
data, errors = CertificateEditInputSchema().load(input_data)
|
||||||
|
|
||||||
|
assert not errors
|
||||||
assert len(data["notifications"]) == 3
|
assert len(data["notifications"]) == 3
|
||||||
|
assert data["roles"][0].name == input_data["owner"]
|
||||||
|
|
||||||
|
|
||||||
def test_authority_key_identifier_schema():
|
def test_authority_key_identifier_schema():
|
||||||
|
@ -970,6 +973,9 @@ def test_certificate_put_with_data(client, certificate, issuer_plugin):
|
||||||
headers=VALID_ADMIN_HEADER_TOKEN,
|
headers=VALID_ADMIN_HEADER_TOKEN,
|
||||||
)
|
)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
assert len(certificate.notifications) == 3
|
||||||
|
assert certificate.roles[0].name == "bob@example.com"
|
||||||
|
assert certificate.notify
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
@ -10,6 +10,7 @@ from lemur.tests.vectors import (
|
||||||
ECDSA_SECP384r1_CERT,
|
ECDSA_SECP384r1_CERT,
|
||||||
ECDSA_SECP384r1_CERT_STR,
|
ECDSA_SECP384r1_CERT_STR,
|
||||||
DSA_CERT,
|
DSA_CERT,
|
||||||
|
CERT_CHAIN_PKCS7_PEM
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,3 +115,16 @@ def test_get_key_type_from_certificate():
|
||||||
from lemur.common.utils import get_key_type_from_certificate
|
from lemur.common.utils import get_key_type_from_certificate
|
||||||
assert (get_key_type_from_certificate(SAN_CERT_STR) == "RSA2048")
|
assert (get_key_type_from_certificate(SAN_CERT_STR) == "RSA2048")
|
||||||
assert (get_key_type_from_certificate(ECDSA_SECP384r1_CERT_STR) == "ECCSECP384R1")
|
assert (get_key_type_from_certificate(ECDSA_SECP384r1_CERT_STR) == "ECCSECP384R1")
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert_pkcs7_bytes_to_pem():
|
||||||
|
from lemur.common.utils import convert_pkcs7_bytes_to_pem
|
||||||
|
from lemur.common.utils import parse_certificate
|
||||||
|
cert_chain = convert_pkcs7_bytes_to_pem(CERT_CHAIN_PKCS7_PEM)
|
||||||
|
assert(len(cert_chain) == 3)
|
||||||
|
|
||||||
|
leaf = cert_chain[1]
|
||||||
|
root = cert_chain[2]
|
||||||
|
|
||||||
|
assert(parse_certificate("\n".join(str(root).splitlines())) == ROOTCA_CERT)
|
||||||
|
assert (parse_certificate("\n".join(str(leaf).splitlines())) == INTERMEDIATE_CERT)
|
||||||
|
|
|
@ -512,3 +512,78 @@ BglghkgBZQMEAwIDMAAwLQIVANubSNMSLt8plN9ZV3cp4pe3lMYCAhQPLLE7rTgm
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
"""
|
"""
|
||||||
DSA_CERT = parse_certificate(DSA_CERT_STR)
|
DSA_CERT = parse_certificate(DSA_CERT_STR)
|
||||||
|
|
||||||
|
|
||||||
|
CERT_CHAIN_PKCS7_STR = """
|
||||||
|
-----BEGIN PKCS7-----
|
||||||
|
MIIMfwYJKoZIhvcNAQcCoIIMcDCCDGwCAQExADALBgkqhkiG9w0BBwGgggxSMIIE
|
||||||
|
FjCCAv6gAwIBAgIQbIbX/Ap0Roqzf5HeN5akmzANBgkqhkiG9w0BAQsFADCBpDEq
|
||||||
|
MCgGA1UEAwwhTGVtdXJUcnVzdCBVbml0dGVzdHMgUm9vdCBDQSAyMDE4MSMwIQYD
|
||||||
|
VQQKDBpMZW11clRydXN0IEVudGVycHJpc2VzIEx0ZDEmMCQGA1UECwwdVW5pdHRl
|
||||||
|
c3RpbmcgT3BlcmF0aW9ucyBDZW50ZXIxCzAJBgNVBAYTAkVFMQwwCgYDVQQIDANO
|
||||||
|
L0ExDjAMBgNVBAcMBUVhcnRoMB4XDTE3MTIzMTIyMDAwMFoXDTQ3MTIzMTIyMDAw
|
||||||
|
MFowgaQxKjAoBgNVBAMMIUxlbXVyVHJ1c3QgVW5pdHRlc3RzIFJvb3QgQ0EgMjAx
|
||||||
|
ODEjMCEGA1UECgwaTGVtdXJUcnVzdCBFbnRlcnByaXNlcyBMdGQxJjAkBgNVBAsM
|
||||||
|
HVVuaXR0ZXN0aW5nIE9wZXJhdGlvbnMgQ2VudGVyMQswCQYDVQQGEwJFRTEMMAoG
|
||||||
|
A1UECAwDTi9BMQ4wDAYDVQQHDAVFYXJ0aDCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||||
|
ADCCAQoCggEBAL8laXtLXyM64t5dz2B9q+4VvOsChefBi2PlGudqxDuRN3l0Kmcf
|
||||||
|
un6x2Gng24pTlGdtmiTEWA0a2F8HRLv4YBWhuYleVeBPtf1fF1/SuYgkJOWT7S5q
|
||||||
|
k/od/tUOLHS0Y067st3FydnFQTKpAuYveEkxleFrMS8hX8cuEgbER+8ybiXKn4Gs
|
||||||
|
yM/om6lsTyBoaLp5yTAoQb4jAWDbiz1xcjPSkvH2lm7rLGtKoylCYwxRsMh2nZcR
|
||||||
|
r1OXVhYHXwpYHVB/jVAjy7PAWQ316hi6mpPYbBV+yfn2GUfGuytqyoXLEsrM3iEE
|
||||||
|
AkU0mJjQmYsCDM3r7ONHTM+UFEk47HCZJccCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
|
||||||
|
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFFL12SFeOTTDdGKsHKozeByG
|
||||||
|
HY6nMA0GCSqGSIb3DQEBCwUAA4IBAQAJfe0/uAHobkxth38dqrSFmTo+D5/TMlRt
|
||||||
|
3hdgjlah6sD2+/DObCyut/XhQWCgTNWyRi4xTKgLh5KSoeJ9EMkADGEgDkU2vjBg
|
||||||
|
5FmGZsxg6bqjxehK+2HvASJoTH8r41xmTioav7a2i3wNhaNSntw2QRTQBQEDOIzH
|
||||||
|
RpPDQ2quErjA8nSifE2xmAAr3g+FuookTTJuv37s2cS59zRYsg+WC3+TtPpRssvo
|
||||||
|
bJ6Xe2D4cCVjUmsqtFEztMgdqgmlcWyGdUKeXdi7CMoeTb4uO+9qRQq46wYWn7K1
|
||||||
|
z+W0Kp5yhnnPAoOioAP4vjASDx3z3RnLaZvMmcO7YdCIwhE5oGV0MIIEGjCCAwKg
|
||||||
|
AwIBAgIRAJ96dbOdrkw/lSTGiwbaagwwDQYJKoZIhvcNAQELBQAwgaQxKjAoBgNV
|
||||||
|
BAMMIUxlbXVyVHJ1c3QgVW5pdHRlc3RzIFJvb3QgQ0EgMjAxODEjMCEGA1UECgwa
|
||||||
|
TGVtdXJUcnVzdCBFbnRlcnByaXNlcyBMdGQxJjAkBgNVBAsMHVVuaXR0ZXN0aW5n
|
||||||
|
IE9wZXJhdGlvbnMgQ2VudGVyMQswCQYDVQQGEwJFRTEMMAoGA1UECAwDTi9BMQ4w
|
||||||
|
DAYDVQQHDAVFYXJ0aDAeFw0xNzEyMzEyMjAwMDBaFw00NzEyMzEyMjAwMDBaMIGn
|
||||||
|
MS0wKwYDVQQDDCRMZW11clRydXN0IFVuaXR0ZXN0cyBDbGFzcyAxIENBIDIwMTgx
|
||||||
|
IzAhBgNVBAoMGkxlbXVyVHJ1c3QgRW50ZXJwcmlzZXMgTHRkMSYwJAYDVQQLDB1V
|
||||||
|
bml0dGVzdGluZyBPcGVyYXRpb25zIENlbnRlcjELMAkGA1UEBhMCRUUxDDAKBgNV
|
||||||
|
BAgMA04vQTEOMAwGA1UEBwwFRWFydGgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||||
|
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
|
||||||
|
-----END PKCS7-----
|
||||||
|
"""
|
||||||
|
|
||||||
|
CERT_CHAIN_PKCS7_PEM = CERT_CHAIN_PKCS7_STR.encode('utf-8')
|
||||||
|
|
Loading…
Reference in New Issue