Skip to content

Commit

Permalink
Extend organization module to generate CRL from CA (contributes to #179
Browse files Browse the repository at this point in the history
…) (#248)

Signed-off-by: Simon Stone <[email protected]>
  • Loading branch information
Simon Stone authored May 26, 2020
1 parent 3d40df8 commit f3aab44
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 5 deletions.
21 changes: 21 additions & 0 deletions plugins/module_utils/cert_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from cryptography.x509.oid import ExtensionOID

import base64
import hashlib


def load_cert(cert):
Expand Down Expand Up @@ -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)
3 changes: 3 additions & 0 deletions plugins/module_utils/certificate_authorities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
41 changes: 36 additions & 5 deletions plugins/modules/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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():

Expand All @@ -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()),
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions roles/endorsing_organization/tasks/create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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) }}"

Expand Down
1 change: 1 addition & 0 deletions roles/ordering_organization/tasks/create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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) }}"

Expand Down

0 comments on commit f3aab44

Please sign in to comment.