diff --git a/plugins/module_utils/cert_utils.py b/plugins/module_utils/cert_utils.py index 10f7be68..f1c218a4 100644 --- a/plugins/module_utils/cert_utils.py +++ b/plugins/module_utils/cert_utils.py @@ -12,6 +12,7 @@ from cryptography.x509.oid import ExtensionOID import base64 +import hashlib def load_cert(cert): @@ -63,3 +64,23 @@ def split_ca_chain(chain): else: intermediate_certs.append(serialized_cert) return (root_certs, intermediate_certs) + + +def load_crl(crl): + parsed_crl = base64.b64decode(crl) + return x509.load_pem_x509_crl(parsed_crl, default_backend()) + + +def hash_crl(crl): + m = hashlib.sha256() + m.update(str(len(crl)).encode('utf-8')) + for entry in crl: + loaded_crl = load_crl(entry) + m.update(str(len(loaded_crl)).encode('utf-8')) + for revoked_cert in loaded_crl: + m.update(str(revoked_cert.serial_number).encode('utf-8')) + return m.hexdigest() + + +def equal_crls(crl1, crl2): + return hash_crl(crl1) == hash_crl(crl2) diff --git a/plugins/module_utils/certificate_authorities.py b/plugins/module_utils/certificate_authorities.py index 692c8b8f..821e0b18 100644 --- a/plugins/module_utils/certificate_authorities.py +++ b/plugins/module_utils/certificate_authorities.py @@ -181,6 +181,9 @@ def get_certificates(self, registrar, enrollment_id): raise CertificateAuthorityException(result['errors'][0]['code'], result['errors'][0]['message']) return result['result'] + def generate_crl(self, registrar): + return self.ca_service.generateCRL(None, None, None, None, self._get_enrollment(registrar)) + def _get_enrollment(self, identity): private_key = serialization.load_pem_private_key(identity.private_key, password=None, backend=default_backend()) return Enrollment(private_key, identity.cert, service=self.ca_service) diff --git a/plugins/modules/organization.py b/plugins/modules/organization.py index 7fc77e73..49728c96 100644 --- a/plugins/modules/organization.py +++ b/plugins/modules/organization.py @@ -6,10 +6,10 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -from ..module_utils.cert_utils import split_ca_chain +from ..module_utils.cert_utils import split_ca_chain, equal_crls from ..module_utils.dict_utils import copy_dict, diff_dicts, equal_dicts, merge_dicts from ..module_utils.organizations import Organization -from ..module_utils.utils import get_console, get_certificate_authority_by_module +from ..module_utils.utils import get_console, get_certificate_authority_by_module, get_identity_by_module from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.urls import open_url @@ -93,6 +93,16 @@ - You can also pass a dictionary, which must match the result format of one of the M(certificate_authority_info) or M(certificate_authority) modules. type: raw + registrar: + description: + - The identity to use when interacting with the certificate authority. If you want + a CRL (Certificate Revocation List) generated from the certificate authority, you + must supply an identity to use as the registrar. + - You can pass a string, which is the path to the JSON file where the enrolled + identity is stored. + - You can also pass a dict, which must match the result format of one of the + M(enrolled_identity_info) or M(enrolled_identity) modules. + type: raw root_certs: description: - The list of root certificates for this organization. @@ -382,14 +392,24 @@ def get_from_certificate_authority(console, module): tlsca_chain = tlscainfo['result']['CAChain'] (tls_root_certs, tls_intermediate_certs) = split_ca_chain(tlsca_chain) - # Return the certificate authority information. - return { + # Build the return information. + result = { 'root_certs': root_certs, 'intermediate_certs': intermediate_certs, 'tls_root_certs': tls_root_certs, 'tls_intermediate_certs': tls_intermediate_certs } + # Generate a revocation list if a registrar has been provided. + if module.params['registrar']: + registrar = get_identity_by_module(module, 'registrar') + with certificate_authority.connect() as connection: + revocation_list = connection.generate_crl(registrar) + result['revocation_list'] = [revocation_list] + + # Return the information retrieved from the certificate authority. + return result + def main(): @@ -405,6 +425,7 @@ def main(): name=dict(type='str', required=True), msp_id=dict(type='str'), certificate_authority=dict(type='raw'), + registrar=dict(type='raw'), root_certs=dict(type='list', elements='str', default=list()), intermediate_certs=dict(type='list', elements='str', default=list()), admins=dict(type='list', elements='str', default=list()), @@ -476,12 +497,16 @@ def main(): # Merge any certificate authority certificates. if certificate_authority_certs is not None: - # Extend the root certificate lists. + # Extend the root and intermediate certificate lists. expected_organization['root_certs'].extend(certificate_authority_certs['root_certs']) expected_organization['intermediate_certs'].extend(certificate_authority_certs['intermediate_certs']) expected_organization['tls_root_certs'].extend(certificate_authority_certs['tls_root_certs']) expected_organization['tls_intermediate_certs'].extend(certificate_authority_certs['tls_intermediate_certs']) + # If a revocation list has been generated, extend that as well. + if 'revocation_list' in certificate_authority_certs: + expected_organization['revocation_list'].extend(certificate_authority_certs['revocation_list']) + # Check to see if NodeOU support is enabled. node_ous_enabled = expected_organization['fabric_node_ous']['enable'] if node_ous_enabled: @@ -521,6 +546,12 @@ def main(): if banned_change in diff: raise Exception(f'{banned_change} cannot be changed from {organization[banned_change]} to {new_organization[banned_change]} for existing organization') + # Check to see if the revocation list has actually changed (in terms of actual serial numbers). + # We need to do this because the revocation list is generated every time. + if 'revocation_list' in organization and 'revocation_list' in new_organization: + if equal_crls(organization['revocation_list'], new_organization['revocation_list']): + new_organization['revocation_list'] = organization['revocation_list'] + # If the organization has changed, apply the changes. organization_changed = not equal_dicts(organization, new_organization) if organization_changed: diff --git a/roles/endorsing_organization/tasks/create.yml b/roles/endorsing_organization/tasks/create.yml index 66577156..5e59e307 100644 --- a/roles/endorsing_organization/tasks/create.yml +++ b/roles/endorsing_organization/tasks/create.yml @@ -102,6 +102,7 @@ name: "{{ organization_name }}" msp_id: "{{ organization_msp_id }}" certificate_authority: "{{ ca_name }}" + registrar: "{{ playbook_dir }}/{{ ca_name }} Admin.json" admins: - "{{ org_admin.enrolled_identity.cert | default(omit) }}" diff --git a/roles/ordering_organization/tasks/create.yml b/roles/ordering_organization/tasks/create.yml index cd61d54b..78c7cc8d 100644 --- a/roles/ordering_organization/tasks/create.yml +++ b/roles/ordering_organization/tasks/create.yml @@ -102,6 +102,7 @@ name: "{{ organization_name }}" msp_id: "{{ organization_msp_id }}" certificate_authority: "{{ ca_name }}" + registrar: "{{ playbook_dir }}/{{ ca_name }} Admin.json" admins: - "{{ org_admin.enrolled_identity.cert | default(omit) }}"