openssl_pkcs12: Add support for certificate_content and other_certificates_content (#848)

* openssl_pkcs12: Add support for `certificate_content` and `other_certificates_content`

Co-authored-by: Felix Fontein <felix@fontein.de>

* Added minimal tests.

The tests are minimal because internally it always ends up with the
_content variants, so even when supplying a file most of the internal
code paths then use the content.

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Florian Apolloner
2025-03-10 21:44:31 +01:00
committed by GitHub
parent 260bdb1572
commit ba55ba7381
4 changed files with 83 additions and 12 deletions

View File

@@ -0,0 +1,2 @@
minor_changes:
- openssl_pkcs12 - the module now supports ``certificate_content``/``other_certificates_content`` for cases where the data already exists in memory and not yet in a file (https://github.com/ansible-collections/community.crypto/issues/847, https://github.com/ansible-collections/community.crypto/pull/848).

View File

@@ -48,13 +48,22 @@ options:
- List of other certificates to include. Pre Ansible 2.8 this parameter was called O(ca_certificates).
- Assumes there is one PEM-encoded certificate per file. If a file contains multiple PEM certificates, set O(other_certificates_parse_all)
to V(true).
- Mutually exclusive with O(other_certificates_content).
type: list
elements: path
aliases: [ca_certificates]
other_certificates_content:
description:
- List of other certificates to include.
- Assumes there is one PEM-encoded certificate per item. If an item contains multiple PEM certificates, set O(other_certificates_parse_all)
- Mutually exclusive with O(other_certificates).
type: list
elements: str
version_added: "2.26.0"
other_certificates_parse_all:
description:
- If set to V(true), assumes that the files mentioned in O(other_certificates) can contain more than one certificate
per file (or even none per file).
- If set to V(true), assumes that the files mentioned in O(other_certificates)/O(other_certificates_content) can contain more than one
certificate per file/item (or even none per file/item).
type: bool
default: false
version_added: 1.4.0
@@ -62,7 +71,14 @@ options:
description:
- The path to read certificates and private keys from.
- Must be in PEM format.
- Mutually exclusive with O(certificate_content).
type: path
certificate_content:
description:
- Content of the certificate file in PEM format.
- Mutually exclusive with O(certificate_path).
type: str
version_added: "2.26.0"
force:
description:
- Should the file be regenerated even if it already exists.
@@ -264,6 +280,7 @@ pkcs12:
import abc
import base64
import itertools
import os
import stat
import traceback
@@ -363,7 +380,9 @@ class Pkcs(OpenSSLObject):
self.action = module.params['action']
self.other_certificates = module.params['other_certificates']
self.other_certificates_parse_all = module.params['other_certificates_parse_all']
self.other_certificates_content = module.params['other_certificates_content']
self.certificate_path = module.params['certificate_path']
self.certificate_content = module.params['certificate_content']
self.friendly_name = module.params['friendly_name']
self.iter_size = module.params['iter_size'] or iter_size_default
self.maciter_size = module.params['maciter_size'] or 1
@@ -383,6 +402,15 @@ class Pkcs(OpenSSLObject):
self.backup = module.params['backup']
self.backup_file = None
if self.certificate_path is not None:
try:
with open(self.certificate_path, 'rb') as fh:
self.certificate_content = fh.read()
except (IOError, OSError) as exc:
raise PkcsError(exc)
elif self.certificate_content is not None:
self.certificate_content = to_bytes(self.certificate_content)
if self.privatekey_path is not None:
try:
with open(self.privatekey_path, 'rb') as fh:
@@ -402,6 +430,13 @@ class Pkcs(OpenSSLObject):
self.other_certificates = [
load_certificate(other_cert, backend=self.backend) for other_cert in self.other_certificates
]
elif self.other_certificates_content:
certs = self.other_certificates_content
if self.other_certificates_parse_all:
certs = list(itertools.chain.from_iterable(split_pem_list(content) for content in certs))
self.other_certificates = [
load_certificate(None, content=to_bytes(other_cert), backend=self.backend) for other_cert in certs
]
@abc.abstractmethod
def generate_bytes(self, module):
@@ -458,11 +493,11 @@ class Pkcs(OpenSSLObject):
elif bool(pkcs12_privatekey) != bool(self.privatekey_content):
return False
if (pkcs12_certificate is not None) and (self.certificate_path is not None):
if (pkcs12_certificate is not None) and (self.certificate_content is not None):
expected_cert = self._dump_certificate(self.pkcs12)
if pkcs12_certificate != expected_cert:
return False
elif bool(pkcs12_certificate) != bool(self.certificate_path):
elif bool(pkcs12_certificate) != bool(self.certificate_content):
return False
if (pkcs12_other_certificates is not None) and (self.other_certificates is not None):
@@ -554,8 +589,8 @@ class PkcsPyOpenSSL(Pkcs):
if self.other_certificates:
self.pkcs12.set_ca_certificates(self.other_certificates)
if self.certificate_path:
self.pkcs12.set_certificate(load_certificate(self.certificate_path, backend=self.backend))
if self.certificate_content:
self.pkcs12.set_certificate(load_certificate(None, content=self.certificate_content, backend=self.backend))
if self.friendly_name:
self.pkcs12.set_friendlyname(to_bytes(self.friendly_name))
@@ -628,8 +663,8 @@ class PkcsCryptography(Pkcs):
raise PkcsError(exc)
cert = None
if self.certificate_path:
cert = load_certificate(self.certificate_path, backend=self.backend)
if self.certificate_content:
cert = load_certificate(None, content=self.certificate_content, backend=self.backend)
friendly_name = to_bytes(self.friendly_name) if self.friendly_name is not None else None
@@ -759,7 +794,9 @@ def main():
action=dict(type='str', default='export', choices=['export', 'parse']),
other_certificates=dict(type='list', elements='path', aliases=['ca_certificates']),
other_certificates_parse_all=dict(type='bool', default=False),
other_certificates_content=dict(type='list', elements='str'),
certificate_path=dict(type='path'),
certificate_content=dict(type='str'),
force=dict(type='bool', default=False),
friendly_name=dict(type='str', aliases=['name']),
encryption_level=dict(type='str', choices=['auto', 'compatibility2022'], default='auto'),
@@ -783,6 +820,8 @@ def main():
mutually_exclusive = [
['privatekey_path', 'privatekey_content'],
['certificate_path', 'certificate_content'],
['other_certificates', 'other_certificates_content'],
]
module = AnsibleModule(

View File

@@ -67,13 +67,18 @@
src: '{{ remote_tmp_dir }}/ansible_pkey1.pem'
register: ansible_pkey_content
- name: "({{ select_crypto_backend }}) Read ansible1.crt"
slurp:
src: '{{ remote_tmp_dir }}/ansible1.crt'
register: ansible_crt_content
- name: "({{ select_crypto_backend }}) Generate PKCS#12 file again, idempotency (private key from file)"
openssl_pkcs12:
select_crypto_backend: '{{ select_crypto_backend }}'
path: '{{ remote_tmp_dir }}/ansible.p12'
friendly_name: abracadabra
privatekey_content: '{{ ansible_pkey_content.content | b64decode }}'
certificate_path: '{{ remote_tmp_dir }}/ansible1.crt'
certificate_content: '{{ ansible_crt_content.content | b64decode }}'
state: present
return_content: true
register: p12_standard_idempotency_2
@@ -154,6 +159,14 @@
state: present
register: p12_multiple_certs
- name: "({{ select_crypto_backend }}) Read ansible2.crt / ansible3.crt.crt"
slurp:
src: "{{ item }}"
loop:
- "{{ remote_tmp_dir ~ '/ansible2.crt' }}"
- "{{ remote_tmp_dir ~ '/ansible3.crt' }}"
register: ansible_other_content
- name: "({{ select_crypto_backend }}) Generate PKCS#12 file with multiple certs and passphrase, again (idempotency)"
openssl_pkcs12:
select_crypto_backend: '{{ select_crypto_backend }}'
@@ -162,9 +175,9 @@
passphrase: hunter3
privatekey_path: '{{ remote_tmp_dir }}/ansible_pkey1.pem'
certificate_path: '{{ remote_tmp_dir }}/ansible1.crt'
other_certificates:
- '{{ remote_tmp_dir }}/ansible2.crt'
- '{{ remote_tmp_dir }}/ansible3.crt'
other_certificates_content:
- "{{ ansible_other_content.results[0].content | b64decode }}"
- "{{ ansible_other_content.results[1].content | b64decode }}"
state: present
register: p12_multiple_certs_idempotency
@@ -323,6 +336,22 @@
state: present
register: p12_empty_concat_idem
- name: "({{ select_crypto_backend }}) Read ansible23.crt"
slurp:
src: "{{ remote_tmp_dir ~ '/ansible23.crt' }}"
register: ansible_other_content_concat
- name: "({{ select_crypto_backend }}) Generate 'empty' PKCS#12 file (idempotent, concatenated other certificates)"
openssl_pkcs12:
select_crypto_backend: '{{ select_crypto_backend }}'
path: '{{ remote_tmp_dir }}/ansible_empty.p12'
friendly_name: abracadabra
other_certificates_content:
- "{{ ansible_other_content_concat.content | b64decode }}"
other_certificates_parse_all: true
state: present
register: p12_empty_concat_content_idem
- name: "({{ select_crypto_backend }}) Generate 'empty' PKCS#12 file (parse)"
openssl_pkcs12:
select_crypto_backend: '{{ select_crypto_backend }}'

View File

@@ -90,6 +90,7 @@
- p12_empty is changed
- p12_empty_idem is not changed
- p12_empty_concat_idem is not changed
- p12_empty_concat_content_idem is not changed
- (empty_contents == empty_expected_cryptography) or (empty_contents == empty_expected_pyopenssl and select_crypto_backend == 'pyopenssl')
- name: '({{ select_crypto_backend }}) PKCS#12 with compatibility2022 settings'