Compare commits

..

1 Commits
1.1.0 ... 0.1.0

Author SHA1 Message Date
John R Barker
9e4728e206 Merge pull request #1 from ansible-collections/initial-conf
Correct Galaxy URLs
2020-03-09 14:46:26 +00:00
167 changed files with 6175 additions and 12170 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,3 @@
# Community.crypt specific things
/changelogs/.plugin-cache.yaml
# Created by https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
# Edit at https://www.gitignore.io/?templates=git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv

View File

@@ -1,119 +0,0 @@
==============================
Community Crypto Release Notes
==============================
.. contents:: Topics
v1.1.0
======
Release Summary
---------------
Release for Ansible 2.10.0.
Minor Changes
-------------
- acme_account - add ``external_account_binding`` option to allow creation of ACME accounts with External Account Binding (https://github.com/ansible-collections/community.crypto/issues/89).
- acme_certificate - allow new selector ``test_certificates: first`` for ``select_chain`` parameter (https://github.com/ansible-collections/community.crypto/pull/102).
- cryptography backends - support arbitrary dotted OIDs (https://github.com/ansible-collections/community.crypto/issues/39).
- get_certificate - add support for SNI (https://github.com/ansible-collections/community.crypto/issues/69).
- luks_device - add support for encryption options on container creation (https://github.com/ansible-collections/community.crypto/pull/97).
- openssh_cert - add support for PKCS#11 tokens (https://github.com/ansible-collections/community.crypto/pull/95).
- openssl_certificate - the PyOpenSSL backend now uses 160 bits of randomness for serial numbers, instead of a random number between 1000 and 99999. Please note that this is not a high quality random number (https://github.com/ansible-collections/community.crypto/issues/76).
- openssl_csr - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46).
- openssl_csr_info - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46).
Bugfixes
--------
- acme_inspect - fix problem with Python 3.5 that JSON was not decoded (https://github.com/ansible-collections/community.crypto/issues/86).
- get_certificate - fix ``ca_cert`` option handling when ``proxy_host`` is used (https://github.com/ansible-collections/community.crypto/pull/84).
- openssl_*, x509_* modules - fix handling of general names which refer to IP networks and not IP addresses (https://github.com/ansible-collections/community.crypto/pull/92).
New Modules
-----------
- openssl_signature - Sign data with openssl
- openssl_signature_info - Verify signatures with openssl
v1.0.0
======
Release Summary
---------------
This is the first proper release of the ``community.crypto`` collection. This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2.9.0.
Minor Changes
-------------
- luks_device - accept ``passphrase``, ``new_passphrase`` and ``remove_passphrase``.
- luks_device - add ``keysize`` parameter to set key size at LUKS container creation
- luks_device - added support to use UUIDs, and labels with LUKS2 containers
- luks_device - added the ``type`` option that allows user explicit define the LUKS container format version
- openssh_keypair - instead of regenerating some broken or password protected keys, fail the module. Keys can still be regenerated by calling the module with ``force=yes``.
- openssh_keypair - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys.
- openssl_* modules - the cryptography backend now properly supports ``dirName``, ``otherName`` and ``RID`` (Registered ID) names.
- openssl_certificate - Add option for changing which ACME directory to use with acme-tiny. Set the default ACME directory to Let's Encrypt instead of using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time being, so no action should be neccessary.)
- openssl_certificate - Change the required version of acme-tiny to >= 4.0.0
- openssl_certificate - allow to provide content of some input files via the ``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and ``ownca_content`` options.
- openssl_certificate - allow to return the existing/generated certificate directly as ``certificate`` by setting ``return_content`` to ``yes``.
- openssl_certificate_info - allow to provide certificate content via ``content`` option (https://github.com/ansible/ansible/issues/64776).
- openssl_csr - Add support for specifying the SAN ``otherName`` value in the OpenSSL ASN.1 UTF8 string format, ``otherName:<OID>;UTF8:string value``.
- openssl_csr - allow to provide private key content via ``private_key_content`` option.
- openssl_csr - allow to return the existing/generated CSR directly as ``csr`` by setting ``return_content`` to ``yes``.
- openssl_csr_info - allow to provide CSR content via ``content`` option.
- openssl_dhparam - allow to return the existing/generated DH params directly as ``dhparams`` by setting ``return_content`` to ``yes``.
- openssl_dhparam - now supports a ``cryptography``-based backend. Auto-detection can be overwritten with the ``select_crypto_backend`` option.
- openssl_pkcs12 - allow to return the existing/generated PKCS#12 directly as ``pkcs12`` by setting ``return_content`` to ``yes``.
- openssl_privatekey - add ``format`` and ``format_mismatch`` options.
- openssl_privatekey - allow to return the existing/generated private key directly as ``privatekey`` by setting ``return_content`` to ``yes``.
- openssl_privatekey - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys.
- openssl_privatekey_info - allow to provide private key content via ``content`` option.
- openssl_publickey - allow to provide private key content via ``private_key_content`` option.
- openssl_publickey - allow to return the existing/generated public key directly as ``publickey`` by setting ``return_content`` to ``yes``.
Deprecated Features
-------------------
- openssl_csr - all values for the ``version`` option except ``1`` are deprecated. The value 1 denotes the current only standardized CSR version.
Removed Features (previously deprecated)
----------------------------------------
- The ``letsencrypt`` module has been removed. Use ``acme_certificate`` instead.
Bugfixes
--------
- ACME modules: fix bug in ACME v1 account update code
- ACME modules: make sure some connection errors are handled properly
- ACME modules: support Buypass' ACME v1 endpoint
- acme_certificate - fix crash when module is used with Python 2.x.
- acme_certificate - fix misbehavior when ACME v1 is used with ``modify_account`` set to ``false``.
- ecs_certificate - Always specify header ``connection: keep-alive`` for ECS API connections.
- ecs_certificate - Fix formatting of contents of ``full_chain_path``.
- get_certificate - Fix cryptography backend when pyopenssl is unavailable (https://github.com/ansible/ansible/issues/67900)
- openssh_keypair - add logic to avoid breaking password protected keys.
- openssh_keypair - fixes idempotence issue with public key (https://github.com/ansible/ansible/issues/64969).
- openssh_keypair - public key's file attributes (permissions, owner, group, etc.) are now set to the same values as the private key.
- openssl_* modules - prevent crash on fingerprint determination in FIPS mode (https://github.com/ansible/ansible/issues/67213).
- openssl_certificate - When provider is ``entrust``, use a ``connection: keep-alive`` header for ECS API connections.
- openssl_certificate - ``provider`` option was documented as required, but it was not checked whether it was provided. It is now only required when ``state`` is ``present``.
- openssl_certificate - fix ``assertonly`` provider certificate verification, causing 'private key mismatch' and 'subject mismatch' errors.
- openssl_certificate and openssl_csr - fix Ed25519 and Ed448 private key support for ``cryptography`` backend. This probably needs at least cryptography 2.8, since older versions have problems with signing certificates or CSRs with such keys. (https://github.com/ansible/ansible/issues/59039, PR https://github.com/ansible/ansible/pull/63984)
- openssl_csr - a warning is issued if an unsupported value for ``version`` is used for the ``cryptography`` backend.
- openssl_csr - the module will now enforce that ``privatekey_path`` is specified when ``state=present``.
- openssl_publickey - fix a module crash caused when pyOpenSSL is not installed (https://github.com/ansible/ansible/issues/67035).
New Modules
-----------
- ecs_domain - Request validation of a domain with the Entrust Certificate Services (ECS) API
- x509_crl - Generate Certificate Revocation Lists (CRLs)
- x509_crl_info - Retrieve information on Certificate Revocation Lists (CRLs)

View File

@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@@ -664,11 +664,12 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

108
README.md
View File

@@ -1,106 +1,4 @@
# Ansible Community Crypto Collection
[![Shippable build status](https://api.shippable.com/projects/5e66776ca27f990007073a42/badge?branch=main)](https://app.shippable.com/projects/5e66776ca27f990007073a42)
[![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.crypto)](https://codecov.io/gh/ansible-collections/community.crypto)
[![GitHub Actions CI/CD build status — Collection test suite](https://github.com/ansible-collection-migration/community.crypto/workflows/Collection%20test%20suite/badge.svg?branch=master)](https://github.com/ansible-collection-migration/community.crypto/actions?query=workflow%3A%22Collection%20test%20suite%22)
Provides modules for [Ansible](https://www.ansible.com/community) for various cryptographic operations.
## Tested with Ansible
Tested with both the current Ansible 2.9 and 2.10 releases and the current development version of Ansible. Ansible versions before 2.9.10 are not supported.
## External requirements
The exact requirements for every module are listed in the module documentation.
Most modules require a recent enough version of [the Python cryptography library](https://pypi.org/project/cryptography/). See the module documentations for the minimal version supported for each module.
## Included content
- OpenSSL / PKI modules:
- openssl_csr_info
- openssl_csr
- openssl_dhparam
- openssl_pkcs12
- openssl_privatekey_info
- openssl_privatekey
- openssl_publickey
- openssl_signature_info
- openssl_signature
- x509_certificate_info
- x509_certificate
- x509_crl_info
- x509_crl
- certificate_complete_chain
- OpenSSH modules:
- openssh_cert
- openssh_keypair
- ACME modules:
- acme_account_info
- acme_account
- acme_certificate
- acme_certificate_revoke
- acme_challenge_cert_helper
- acme_inspect
- ECS modules:
- ecs_certificate
- ecs_domain
- Miscellaneous modules:
- get_certificate
- luks_device
## Using this collection
Before using the crypto community collection, you need to install the collection with the `ansible-galaxy` CLI:
ansible-galaxy collection install community.crypto
You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml` using the format:
```yaml
collections:
- name: community.crypto
```
See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
## Contributing to this collection
<!--Describe how the community can contribute to your collection. At a minimum, include how and where users can create issues to report problems or request features for this collection. List contribution requirements, including preferred workflows and necessary testing, so you can benefit from community PRs. If you are following general Ansible contributor guidelines, you can link to - [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html). -->
We're following the general Ansible contributor guidelines; see [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html).
If you want to clone this repositority (or a fork of it) to improve it, you can proceed as follows:
1. Create a directory `ansible_collections/community`;
2. In there, checkout this repository (or a fork) as `crypto`;
3. Add the directory containing `ansible_collections` to your [ANSIBLE_COLLECTIONS_PATH](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths).
See [Ansible's dev guide](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections) for more information.
## Release notes
See the [changelog](https://github.com/ansible-collections/community.crypto/blob/main/CHANGELOG.rst).
## Roadmap
We plan to regularly release minor and patch versions, whenever new features are added or bugs fixed. Our collection follows [semantic versioning](https://semver.org/), so breaking changes will only happen in major releases.
Most modules will drop PyOpenSSL support in version 2.0.0 of the collection, i.e. in the next major version. We currently plan to release 2.0.0 somewhen during 2021. Around then, the supported versions of the most common distributions will contain a new enough version of ``cryptography``.
Once 2.0.0 has been released, bugfixes will still be backported to 1.0.0 for some time, and some features might also be backported. If we do not want to backport something ourselves because we think it is not worth the effort, backport PRs by non-maintainers are usually accepted.
In 2.0.0, the following notable features will be removed:
* PyOpenSSL backends of all modules, except ``openssl_pkcs12`` which does not have a ``cryptography`` backend due to lack of support of PKCS#12 functionality in ``cryptography``.
* The ``assertonly`` provider of ``x509_certificate`` will be removed.
## More information
- [Ansible Collection overview](https://github.com/ansible-collections/overview)
- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html)
- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html)
- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html)
## Licensing
GNU General Public License v3.0 or later.
See [COPYING](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.
Ansible Collection: community.crypto
=================================================

View File

@@ -1,187 +0,0 @@
ancestor: null
releases:
1.0.0:
changes:
bugfixes:
- 'ACME modules: fix bug in ACME v1 account update code'
- 'ACME modules: make sure some connection errors are handled properly'
- 'ACME modules: support Buypass'' ACME v1 endpoint'
- acme_certificate - fix crash when module is used with Python 2.x.
- acme_certificate - fix misbehavior when ACME v1 is used with ``modify_account``
set to ``false``.
- 'ecs_certificate - Always specify header ``connection: keep-alive`` for ECS
API connections.'
- ecs_certificate - Fix formatting of contents of ``full_chain_path``.
- get_certificate - Fix cryptography backend when pyopenssl is unavailable (https://github.com/ansible/ansible/issues/67900)
- openssh_keypair - add logic to avoid breaking password protected keys.
- openssh_keypair - fixes idempotence issue with public key (https://github.com/ansible/ansible/issues/64969).
- openssh_keypair - public key's file attributes (permissions, owner, group,
etc.) are now set to the same values as the private key.
- openssl_* modules - prevent crash on fingerprint determination in FIPS mode
(https://github.com/ansible/ansible/issues/67213).
- 'openssl_certificate - When provider is ``entrust``, use a ``connection: keep-alive``
header for ECS API connections.'
- openssl_certificate - ``provider`` option was documented as required, but
it was not checked whether it was provided. It is now only required when ``state``
is ``present``.
- openssl_certificate - fix ``assertonly`` provider certificate verification,
causing 'private key mismatch' and 'subject mismatch' errors.
- openssl_certificate and openssl_csr - fix Ed25519 and Ed448 private key support
for ``cryptography`` backend. This probably needs at least cryptography 2.8,
since older versions have problems with signing certificates or CSRs with
such keys. (https://github.com/ansible/ansible/issues/59039, PR https://github.com/ansible/ansible/pull/63984)
- openssl_csr - a warning is issued if an unsupported value for ``version``
is used for the ``cryptography`` backend.
- openssl_csr - the module will now enforce that ``privatekey_path`` is specified
when ``state=present``.
- openssl_publickey - fix a module crash caused when pyOpenSSL is not installed
(https://github.com/ansible/ansible/issues/67035).
deprecated_features:
- openssl_csr - all values for the ``version`` option except ``1`` are deprecated.
The value 1 denotes the current only standardized CSR version.
minor_changes:
- luks_device - accept ``passphrase``, ``new_passphrase`` and ``remove_passphrase``.
- luks_device - add ``keysize`` parameter to set key size at LUKS container
creation
- luks_device - added support to use UUIDs, and labels with LUKS2 containers
- luks_device - added the ``type`` option that allows user explicit define the
LUKS container format version
- openssh_keypair - instead of regenerating some broken or password protected
keys, fail the module. Keys can still be regenerated by calling the module
with ``force=yes``.
- openssh_keypair - the ``regenerate`` option allows to configure the module's
behavior when it should or needs to regenerate private keys.
- openssl_* modules - the cryptography backend now properly supports ``dirName``,
``otherName`` and ``RID`` (Registered ID) names.
- openssl_certificate - Add option for changing which ACME directory to use
with acme-tiny. Set the default ACME directory to Let's Encrypt instead of
using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time
being, so no action should be neccessary.)
- openssl_certificate - Change the required version of acme-tiny to >= 4.0.0
- openssl_certificate - allow to provide content of some input files via the
``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and
``ownca_content`` options.
- openssl_certificate - allow to return the existing/generated certificate directly
as ``certificate`` by setting ``return_content`` to ``yes``.
- openssl_certificate_info - allow to provide certificate content via ``content``
option (https://github.com/ansible/ansible/issues/64776).
- openssl_csr - Add support for specifying the SAN ``otherName`` value in the
OpenSSL ASN.1 UTF8 string format, ``otherName:<OID>;UTF8:string value``.
- openssl_csr - allow to provide private key content via ``private_key_content``
option.
- openssl_csr - allow to return the existing/generated CSR directly as ``csr``
by setting ``return_content`` to ``yes``.
- openssl_csr_info - allow to provide CSR content via ``content`` option.
- openssl_dhparam - allow to return the existing/generated DH params directly
as ``dhparams`` by setting ``return_content`` to ``yes``.
- openssl_dhparam - now supports a ``cryptography``-based backend. Auto-detection
can be overwritten with the ``select_crypto_backend`` option.
- openssl_pkcs12 - allow to return the existing/generated PKCS#12 directly as
``pkcs12`` by setting ``return_content`` to ``yes``.
- openssl_privatekey - add ``format`` and ``format_mismatch`` options.
- openssl_privatekey - allow to return the existing/generated private key directly
as ``privatekey`` by setting ``return_content`` to ``yes``.
- openssl_privatekey - the ``regenerate`` option allows to configure the module's
behavior when it should or needs to regenerate private keys.
- openssl_privatekey_info - allow to provide private key content via ``content``
option.
- openssl_publickey - allow to provide private key content via ``private_key_content``
option.
- openssl_publickey - allow to return the existing/generated public key directly
as ``publickey`` by setting ``return_content`` to ``yes``.
release_summary: 'This is the first proper release of the ``community.crypto``
collection. This changelog contains all changes to the modules in this collection
that were added after the release of Ansible 2.9.0.
'
removed_features:
- The ``letsencrypt`` module has been removed. Use ``acme_certificate`` instead.
fragments:
- 1.0.0.yml
- 52408-luks-device.yaml
- 58973-luks_device_add-type-option.yml
- 58973_luks_device-add-label-and-uuid-support.yml
- 60388-openssl_privatekey-format.yml
- 61522-luks-device-add-option-to-define-keysize.yml
- 61658-openssh_keypair-public-key-permissions.yml
- 61693-acme-buypass-acme-v1.yml
- 61738-ecs-certificate-invalid-chain.yaml
- 62218-fix-to-entrust-api.yml
- 62790-openssl_certificate_fix_assert.yml
- 62991-openssl_dhparam-cryptography-backend.yml
- 63140-acme-fix-fetch-url-status-codes.yaml
- 63432-openssl_csr-version.yml
- 63984-openssl-ed25519-ed448.yml
- 64436-openssh_keypair-add-password-protected-key-check.yml
- 64501-fix-python2.x-backward-compatibility.yaml
- 64648-acme_certificate-acmev1.yml
- 65017-openssh_keypair-idempotence.yml
- 65400-openssl-output.yml
- 65435-openssl_csr-privatekey_path-required.yml
- 65633-crypto-argspec-fixup.yml
- 66384-openssl-content.yml
- 67036-openssl_publickey-backend.yml
- 67038-openssl-openssh-key-regenerate.yml
- 67109-openssl_certificate-acme-directory.yaml
- 67515-openssl-fingerprint-fips.yml
- 67669-cryptography-names.yml
- 67901-get_certificate-fix-cryptography.yml
- letsencrypt.yml
- openssl_csr-otherName.yml
modules:
- description: Request validation of a domain with the Entrust Certificate Services
(ECS) API
name: ecs_domain
namespace: ''
- description: Generate Certificate Revocation Lists (CRLs)
name: x509_crl
namespace: ''
- description: Retrieve information on Certificate Revocation Lists (CRLs)
name: x509_crl_info
namespace: ''
release_date: '2020-07-03'
1.1.0:
changes:
bugfixes:
- acme_inspect - fix problem with Python 3.5 that JSON was not decoded (https://github.com/ansible-collections/community.crypto/issues/86).
- get_certificate - fix ``ca_cert`` option handling when ``proxy_host`` is used
(https://github.com/ansible-collections/community.crypto/pull/84).
- openssl_*, x509_* modules - fix handling of general names which refer to IP
networks and not IP addresses (https://github.com/ansible-collections/community.crypto/pull/92).
minor_changes:
- acme_account - add ``external_account_binding`` option to allow creation of
ACME accounts with External Account Binding (https://github.com/ansible-collections/community.crypto/issues/89).
- 'acme_certificate - allow new selector ``test_certificates: first`` for ``select_chain``
parameter (https://github.com/ansible-collections/community.crypto/pull/102).'
- cryptography backends - support arbitrary dotted OIDs (https://github.com/ansible-collections/community.crypto/issues/39).
- get_certificate - add support for SNI (https://github.com/ansible-collections/community.crypto/issues/69).
- luks_device - add support for encryption options on container creation (https://github.com/ansible-collections/community.crypto/pull/97).
- openssh_cert - add support for PKCS#11 tokens (https://github.com/ansible-collections/community.crypto/pull/95).
- openssl_certificate - the PyOpenSSL backend now uses 160 bits of randomness
for serial numbers, instead of a random number between 1000 and 99999. Please
note that this is not a high quality random number (https://github.com/ansible-collections/community.crypto/issues/76).
- openssl_csr - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46).
- openssl_csr_info - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46).
release_summary: 'Release for Ansible 2.10.0.
'
fragments:
- 1.1.0.yml
- 100-acme-account-external-account-binding.yml
- 102-acme-certificate-select-chain-first.yml
- 87-acme_inspect-python-3.5.yml
- 90-cryptography-oids.yml
- 90-openssl_certificate-pyopenssl-serial.yml
- 92-ip-networks.yml
- 92-openssl_csr-name-constraints.yml
- get_certificate-add_support_for_SNI.yml
- luks_device-add_encryption_option_on_create.yml
- openssh_cert-pkcs11.yml
modules:
- description: Sign data with openssl
name: openssl_signature
namespace: ''
- description: Verify signatures with openssl
name: openssl_signature_info
namespace: ''
release_date: '2020-08-18'

View File

@@ -1,28 +0,0 @@
changelog_filename_template: ../CHANGELOG.rst
changelog_filename_version_depth: 0
changes_file: changelog.yaml
changes_format: combined
keep_fragments: false
mention_ancestor: true
new_plugins_after_name: removed_features
notesdir: fragments
prelude_section_name: release_summary
prelude_section_title: Release Summary
sections:
- - major_changes
- Major Changes
- - minor_changes
- Minor Changes
- - breaking_changes
- Breaking Changes / Porting Guide
- - deprecated_features
- Deprecated Features
- - removed_features
- Removed Features (previously deprecated)
- - security_fixes
- Security Fixes
- - bugfixes
- Bugfixes
- - known_issues
- Known Issues
title: Community Crypto

View File

@@ -1,30 +1,15 @@
namespace: community
name: crypto
version: 1.1.0
version: 0.1.0
readme: README.md
authors:
- Ansible (github.com/ansible)
authors: null
description: null
license: GPL-3.0-or-later
license_file: COPYING
tags:
- acme
- certificate
- community
- crl
- cryptography
- csr
- dhparam
- entrust
- letsencrypt
- luks
- openssl
- openssh
- pkcs12
repository: https://github.com/ansible-collections/community.crypto
#documentation: https://github.com/ansible-collection-migration/community.crypto/tree/main/docs
homepage: https://github.com/ansible-collections/community.crypto
issues: https://github.com/ansible-collections/community.crypto/issues
build_ignore:
- 'community-crypto-*.tar.gz'
- .gitignore
- changelogs/.plugin-cache.yaml
tags: null
dependencies:
ansible.netcommon: '>=0.1.0'
repository: https://github.com:ansible-collections/community.crypto
documentation: https://github.com/ansible-collection-migration/community.crypto/tree/master/docs
homepage: https://github.com:ansible-collections/community.crypto
issues: https://github.com:ansible-collections/community.crypto/issues

7
meta/action_groups.yml Normal file
View File

@@ -0,0 +1,7 @@
acme:
- acme_inspect
- acme_certificate_revoke
- acme_certificate
- acme_account
- acme_account_facts
- acme_account_info

6
meta/routing.yml Normal file
View File

@@ -0,0 +1,6 @@
plugin_routing:
modules:
acme_account_facts:
deprecation:
removal_date: TBD
warning_text: see plugin documentation for details

View File

@@ -1,25 +0,0 @@
requires_ansible: '>=2.9.10'
action_groups:
acme:
- acme_inspect
- acme_certificate_revoke
- acme_certificate
- acme_account
- acme_account_facts
- acme_account_info
plugin_routing:
modules:
acme_account_facts:
deprecation:
removal_version: '2.12'
warning_text: The 'community.crypto.acme_account_facts' module has been renamed to 'community.crypto.acme_account_info'.
openssl_certificate:
deprecation:
removal_version: '2.14'
warning_text: The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'
openssl_certificate_info:
deprecation:
removal_version: '2.14'
warning_text: The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'

View File

@@ -31,12 +31,8 @@ options:
description:
- "Path to a file containing the ACME account RSA or Elliptic Curve
key."
- "Private keys can be created with the
M(community.crypto.openssl_privatekey) module. If the requisites
(pyOpenSSL or cryptography) are not available, keys can also be
created directly with the C(openssl) command line tool: RSA keys
can be created with C(openssl genrsa ...). Elliptic curve keys can be
created with C(openssl ecparam -genkey ...). Any other tool creating
- "RSA keys can be created with C(openssl genrsa ...). Elliptic curve keys can
be created with C(openssl ecparam -genkey ...). Any other tool creating
private keys in PEM format can be used as well."
- "Mutually exclusive with C(account_key_content)."
- "Required if C(account_key_content) is not used."

View File

@@ -29,22 +29,20 @@ import tempfile
import traceback
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.six.moves.urllib.parse import unquote
from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
from ansible.module_utils.urls import fetch_url
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
from ansible.module_utils.six.moves.urllib.parse import unquote
try:
import cryptography
import cryptography.hazmat.backends
import cryptography.hazmat.primitives.hashes
import cryptography.hazmat.primitives.hmac
import cryptography.hazmat.primitives.serialization
import cryptography.hazmat.primitives.asymmetric.rsa
import cryptography.hazmat.primitives.asymmetric.ec
import cryptography.hazmat.primitives.asymmetric.padding
import cryptography.hazmat.primitives.asymmetric.rsa
import cryptography.hazmat.primitives.hashes
import cryptography.hazmat.primitives.asymmetric.utils
import cryptography.hazmat.primitives.serialization
import cryptography.x509
import cryptography.x509.oid
from distutils.version import LooseVersion
@@ -272,42 +270,9 @@ def _parse_key_openssl(openssl_binary, module, key_file=None, key_content=None):
}
def _create_mac_key_openssl(openssl_bin, module, alg, key):
if alg == 'HS256':
hashalg = 'sha256'
hashbytes = 32
elif alg == 'HS384':
hashalg = 'sha384'
hashbytes = 48
elif alg == 'HS512':
hashalg = 'sha512'
hashbytes = 64
else:
raise ModuleFailException('Unsupported MAC key algorithm for OpenSSL backend: {0}'.format(alg))
key_bytes = base64.urlsafe_b64decode(key)
if len(key_bytes) < hashbytes:
raise ModuleFailException(
'{0} key must be at least {1} bytes long (after Base64 decoding)'.format(alg, hashbytes))
return {
'type': 'hmac',
'alg': alg,
'jwk': {
'kty': 'oct',
'k': key,
},
'hash': hashalg,
}
def _sign_request_openssl(openssl_binary, module, payload64, protected64, key_data):
openssl_sign_cmd = [openssl_binary, "dgst", "-{0}".format(key_data['hash']), "-sign", key_data['key_file']]
sign_payload = "{0}.{1}".format(protected64, payload64).encode('utf8')
if key_data['type'] == 'hmac':
hex_key = to_native(binascii.hexlify(base64.urlsafe_b64decode(key_data['jwk']['k'])))
cmd_postfix = ["-mac", "hmac", "-macopt", "hexkey:{0}".format(hex_key), "-binary"]
else:
cmd_postfix = ["-sign", key_data['key_file']]
openssl_sign_cmd = [openssl_binary, "dgst", "-{0}".format(key_data['hash'])] + cmd_postfix
dummy, out, dummy = module.run_command(openssl_sign_cmd, data=sign_payload, check_rc=True, binary_data=True)
if key_data['type'] == 'ec':
@@ -437,43 +402,9 @@ def _parse_key_cryptography(module, key_file=None, key_content=None):
return 'unknown key type "{0}"'.format(type(key)), {}
def _create_mac_key_cryptography(module, alg, key):
if alg == 'HS256':
hashalg = cryptography.hazmat.primitives.hashes.SHA256
hashbytes = 32
elif alg == 'HS384':
hashalg = cryptography.hazmat.primitives.hashes.SHA384
hashbytes = 48
elif alg == 'HS512':
hashalg = cryptography.hazmat.primitives.hashes.SHA512
hashbytes = 64
else:
raise ModuleFailException('Unsupported MAC key algorithm for cryptography backend: {0}'.format(alg))
key_bytes = base64.urlsafe_b64decode(key)
if len(key_bytes) < hashbytes:
raise ModuleFailException(
'{0} key must be at least {1} bytes long (after Base64 decoding)'.format(alg, hashbytes))
return {
'mac_obj': lambda: cryptography.hazmat.primitives.hmac.HMAC(
key_bytes,
hashalg(),
_cryptography_backend),
'type': 'hmac',
'alg': alg,
'jwk': {
'kty': 'oct',
'k': key,
},
}
def _sign_request_cryptography(module, payload64, protected64, key_data):
sign_payload = "{0}.{1}".format(protected64, payload64).encode('utf8')
if 'mac_obj' in key_data:
mac = key_data['mac_obj']()
mac.update(sign_payload)
signature = mac.finalize()
elif isinstance(key_data['key_obj'], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
if isinstance(key_data['key_obj'], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
hashalg = cryptography.hazmat.primitives.hashes.SHA256
signature = key_data['key_obj'].sign(sign_payload, padding, hashalg())
@@ -624,13 +555,6 @@ class ACMEAccount(object):
else:
return _sign_request_openssl(self._openssl_bin, self.module, payload64, protected64, key_data)
def _create_mac_key(self, alg, key):
'''Create a MAC key.'''
if HAS_CURRENT_CRYPTOGRAPHY:
return _create_mac_key_cryptography(self.module, alg, key)
else:
return _create_mac_key_openssl(self._openssl_bin, self.module, alg, key)
def _log(self, msg, data=None):
'''
Write arguments to acme.log when logging is enabled.
@@ -758,19 +682,13 @@ class ACMEAccount(object):
self.jws_header.pop('jwk')
self.jws_header['kid'] = self.uri
def _new_reg(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True,
external_account_binding=None):
def _new_reg(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True):
'''
Registers a new ACME account. Returns a pair ``(created, data)``.
Here, ``created`` is ``True`` if the account was created and
``False`` if it already existed (e.g. it was not newly created),
or does not exist. In case the account was created or exists,
``data`` contains the account data; otherwise, it is ``None``.
If specified, ``external_account_binding`` should be a dictionary
with keys ``kid``, ``alg`` and ``key``
(https://tools.ietf.org/html/rfc8555#section-7.3.4).
https://tools.ietf.org/html/rfc8555#section-7.3
'''
contact = contact or []
@@ -784,23 +702,8 @@ class ACMEAccount(object):
new_reg['agreement'] = agreement
else:
new_reg['agreement'] = self.directory['meta']['terms-of-service']
if external_account_binding is not None:
raise ModuleFailException('External account binding is not supported for ACME v1')
url = self.directory['new-reg']
else:
if (external_account_binding is not None or self.directory['meta'].get('externalAccountRequired')) and allow_creation:
# Some ACME servers such as ZeroSSL do not like it when you try to register an existing account
# and provide external_account_binding credentials. Thus we first send a request with allow_creation=False
# to see whether the account already exists.
# Note that we pass contact here: ZeroSSL does not accept regisration calls without contacts, even
# if onlyReturnExisting is set to true.
created, data = self._new_reg(contact=contact, allow_creation=False)
if data:
# An account already exists! Return data
return created, data
# An account does not yet exist. Try to create one next.
new_reg = {
'contact': contact
}
@@ -810,21 +713,6 @@ class ACMEAccount(object):
if terms_agreed:
new_reg['termsOfServiceAgreed'] = True
url = self.directory['newAccount']
if external_account_binding is not None:
new_reg['externalAccountBinding'] = self.sign_request(
{
'alg': external_account_binding['alg'],
'kid': external_account_binding['kid'],
'url': url,
},
self.jwk,
self._create_mac_key(external_account_binding['alg'], external_account_binding['key'])
)
elif self.directory['meta'].get('externalAccountRequired') and allow_creation:
raise ModuleFailException(
'To create an account, an external account binding must be specified. '
'Use the acme_account module with the external_account_binding option.'
)
result, info = self.send_signed_request(url, new_reg)
@@ -894,9 +782,7 @@ class ACMEAccount(object):
raise ModuleFailException("Error getting account data from {2}: {0} {1}".format(info['status'], result, self.uri))
return result
def setup_account(self, contact=None, agreement=None, terms_agreed=False,
allow_creation=True, remove_account_uri_if_not_exists=False,
external_account_binding=None):
def setup_account(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True, remove_account_uri_if_not_exists=False):
'''
Detect or create an account on the ACME server. For ACME v1,
as the only way (without knowing an account URI) to test if an
@@ -916,10 +802,6 @@ class ACMEAccount(object):
The account URI will be stored in ``self.uri``; if it is ``None``,
the account does not exist.
If specified, ``external_account_binding`` should be a dictionary
with keys ``kid``, ``alg`` and ``key``
(https://tools.ietf.org/html/rfc8555#section-7.3.4).
https://tools.ietf.org/html/rfc8555#section-7.3
'''
@@ -938,8 +820,7 @@ class ACMEAccount(object):
contact,
agreement=agreement,
terms_agreed=terms_agreed,
allow_creation=allow_creation and not self.module.check_mode,
external_account_binding=external_account_binding,
allow_creation=allow_creation and not self.module.check_mode
)
if self.module.check_mode and self.uri is None and allow_creation:
created = True
@@ -1131,13 +1012,11 @@ def handle_standard_module_arguments(module, needs_acme_v2=False):
if module.params['acme_version'] is None:
module.params['acme_version'] = 1
module.deprecate("The option 'acme_version' will be required from community.crypto 2.0.0 on",
version='2.0.0', collection_name='community.crypto')
module.deprecate("The option 'acme_version' will be required from Ansible 2.14 on", version='2.14')
if module.params['acme_directory'] is None:
module.params['acme_directory'] = 'https://acme-staging.api.letsencrypt.org/directory'
module.deprecate("The option 'acme_directory' will be required from community.crypto 2.0.0 on",
version='2.0.0', collection_name='community.crypto')
module.deprecate("The option 'acme_directory' will be required from Ansible 2.14 on", version='2.14')
if needs_acme_v2 and module.params['acme_version'] < 2:
module.fail_json(msg='The {0} module requires the ACME v2 protocol!'.format(module._name))

File diff suppressed because it is too large Load Diff

View File

@@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# THIS FILE IS FOR COMPATIBILITY ONLY! YOU SHALL NOT IMPORT IT!
#
# This fill will be removed eventually, so if you're using it,
# please stop doing so.
from .basic import (
HAS_PYOPENSSL,
CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X25519_FULL,
CRYPTOGRAPHY_HAS_X448,
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,
HAS_CRYPTOGRAPHY,
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
from .cryptography_crl import (
REVOCATION_REASON_MAP,
REVOCATION_REASON_MAP_INVERSE,
cryptography_decode_revoked_certificate,
)
from .cryptography_support import (
cryptography_get_extensions_from_cert,
cryptography_get_extensions_from_csr,
cryptography_name_to_oid,
cryptography_oid_to_name,
cryptography_get_name,
cryptography_decode_name,
cryptography_parse_key_usage_params,
cryptography_get_basic_constraints,
cryptography_key_needs_digest_for_signing,
cryptography_compare_public_keys,
)
from .identify import (
identify_private_key_format,
)
from .math import (
binary_exp_mod,
simple_gcd,
quick_is_not_prime,
count_bits,
)
from ._obj2txt import obj2txt as _obj2txt
from ._objects_data import OID_MAP as _OID_MAP
from ._objects import OID_LOOKUP as _OID_LOOKUP
from ._objects import NORMALIZE_NAMES as _NORMALIZE_NAMES
from ._objects import NORMALIZE_NAMES_SHORT as _NORMALIZE_NAMES_SHORT
from .pyopenssl_support import (
pyopenssl_normalize_name,
pyopenssl_get_extensions_from_cert,
pyopenssl_get_extensions_from_csr,
)
from .support import (
get_fingerprint_of_bytes,
get_fingerprint,
load_privatekey,
load_certificate,
load_certificate_request,
parse_name_field,
convert_relative_to_datetime,
get_relative_time_option,
select_message_digest,
OpenSSLObject,
)
from ..io import (
load_file_if_exists,
write_file,
)

View File

@@ -1,153 +0,0 @@
# -*- coding: utf-8 -*-
# (c) 2020, Jordan Borean <jborean93@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible.module_utils._text import to_bytes
"""
An ASN.1 serialized as a string in the OpenSSL format:
[modifier,]type[:value]
modifier:
The modifier can be 'IMPLICIT:<tag_number><tag_class>,' or 'EXPLICIT:<tag_number><tag_class>' where IMPLICIT
changes the tag of the universal value to encode and EXPLICIT prefixes its tag to the existing universal value.
The tag_number must be set while the tag_class can be 'U', 'A', 'P', or 'C" for 'Universal', 'Application',
'Private', or 'Context Specific' with C being the default.
type:
The underlying ASN.1 type of the value specified. Currently only the following have been implemented:
UTF8: The value must be a UTF-8 encoded string.
value:
The value to encode, the format of this value depends on the <type> specified.
"""
ASN1_STRING_REGEX = re.compile(r'^((?P<tag_type>IMPLICIT|EXPLICIT):(?P<tag_number>\d+)(?P<tag_class>U|A|P|C)?,)?'
r'(?P<value_type>[\w\d]+):(?P<value>.*)')
class TagClass:
universal = 0
application = 1
context_specific = 2
private = 3
# Universal tag numbers that can be encoded.
class TagNumber:
utf8_string = 12
def _pack_octet_integer(value):
""" Packs an integer value into 1 or multiple octets. """
# NOTE: This is *NOT* the same as packing an ASN.1 INTEGER like value.
octets = bytearray()
# Continue to shift the number by 7 bits and pack into an octet until the
# value is fully packed.
while value:
octet_value = value & 0b01111111
# First round (last octet) must have the MSB set.
if len(octets):
octet_value |= 0b10000000
octets.append(octet_value)
value >>= 7
# Reverse to ensure the higher order octets are first.
octets.reverse()
return bytes(octets)
def serialize_asn1_string_as_der(value):
""" Deserializes an ASN.1 string to a DER encoded byte string. """
asn1_match = ASN1_STRING_REGEX.match(value)
if not asn1_match:
raise ValueError("The ASN.1 serialized string must be in the format [modifier,]type[:value]")
tag_type = asn1_match.group('tag_type')
tag_number = asn1_match.group('tag_number')
tag_class = asn1_match.group('tag_class') or 'C'
value_type = asn1_match.group('value_type')
asn1_value = asn1_match.group('value')
if value_type != 'UTF8':
raise ValueError('The ASN.1 serialized string is not a known type "{0}", only UTF8 types are '
'supported'.format(value_type))
b_value = to_bytes(asn1_value, encoding='utf-8', errors='surrogate_or_strict')
# We should only do a universal type tag if not IMPLICITLY tagged or the tag class is not universal.
if not tag_type or (tag_type == 'EXPLICIT' and tag_class != 'U'):
b_value = pack_asn1(TagClass.universal, False, TagNumber.utf8_string, b_value)
if tag_type:
tag_class = {
'U': TagClass.universal,
'A': TagClass.application,
'P': TagClass.private,
'C': TagClass.context_specific,
}[tag_class]
# When adding support for more types this should be looked into further. For now it works with UTF8Strings.
constructed = tag_type == 'EXPLICIT' and tag_class != TagClass.universal
b_value = pack_asn1(tag_class, constructed, int(tag_number), b_value)
return b_value
def pack_asn1(tag_class, constructed, tag_number, b_data):
"""Pack the value into an ASN.1 data structure.
The structure for an ASN.1 element is
| Identifier Octet(s) | Length Octet(s) | Data Octet(s) |
"""
b_asn1_data = bytearray()
if tag_class < 0 or tag_class > 3:
raise ValueError("tag_class must be between 0 and 3 not %s" % tag_class)
# Bit 8 and 7 denotes the class.
identifier_octets = tag_class << 6
# Bit 6 denotes whether the value is primitive or constructed.
identifier_octets |= ((1 if constructed else 0) << 5)
# Bits 5-1 contain the tag number, if it cannot be encoded in these 5 bits
# then they are set and another octet(s) is used to denote the tag number.
if tag_number < 31:
identifier_octets |= tag_number
b_asn1_data.append(identifier_octets)
else:
identifier_octets |= 31
b_asn1_data.append(identifier_octets)
b_asn1_data.extend(_pack_octet_integer(tag_number))
length = len(b_data)
# If the length can be encoded in 7 bits only 1 octet is required.
if length < 128:
b_asn1_data.append(length)
else:
# Otherwise the length must be encoded across multiple octets
length_octets = bytearray()
while length:
length_octets.append(length & 0b11111111)
length >>= 8
length_octets.reverse() # Reverse to make the higher octets first.
# The first length octet must have the MSB set alongside the number of
# octets the length was encoded in.
b_asn1_data.append(len(length_octets) | 0b10000000)
b_asn1_data.extend(length_octets)
return bytes(b_asn1_data) + b_data

View File

@@ -1,43 +0,0 @@
# This excerpt is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file at
# https://github.com/pyca/cryptography/blob/master/LICENSE for complete details.
#
# Adapted from cryptography's hazmat/backends/openssl/decode_asn1.py
#
# Copyright (c) 2015, 2016 Paul Kehrer (@reaperhulk)
# Copyright (c) 2017 Fraser Tweedale (@frasertweedale)
# Relevant commits from cryptography project (https://github.com/pyca/cryptography):
# pyca/cryptography@719d536dd691e84e208534798f2eb4f82aaa2e07
# pyca/cryptography@5ab6d6a5c05572bd1c75f05baf264a2d0001894a
# pyca/cryptography@2e776e20eb60378e0af9b7439000d0e80da7c7e3
# pyca/cryptography@fb309ed24647d1be9e319b61b1f2aa8ebb87b90b
# pyca/cryptography@2917e460993c475c72d7146c50dc3bbc2414280d
# pyca/cryptography@3057f91ea9a05fb593825006d87a391286a4d828
# pyca/cryptography@d607dd7e5bc5c08854ec0c9baff70ba4a35be36f
from __future__ import absolute_import, division, print_function
__metaclass__ = type
def obj2txt(openssl_lib, openssl_ffi, obj):
# Set to 80 on the recommendation of
# https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values
#
# But OIDs longer than this occur in real life (e.g. Active
# Directory makes some very long OIDs). So we need to detect
# and properly handle the case where the default buffer is not
# big enough.
#
buf_len = 80
buf = openssl_ffi.new("char[]", buf_len)
# 'res' is the number of bytes that *would* be written if the
# buffer is large enough. If 'res' > buf_len - 1, we need to
# alloc a big-enough buffer and go again.
res = openssl_lib.OBJ_obj2txt(buf, buf_len, obj, 1)
if res > buf_len - 1: # account for terminating null byte
buf_len = res + 1
buf = openssl_ffi.new("char[]", buf_len)
res = openssl_lib.OBJ_obj2txt(buf, buf_len, obj, 1)
return openssl_ffi.buffer(buf, res)[:].decode()

View File

@@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ._objects_data import OID_MAP
OID_LOOKUP = dict()
NORMALIZE_NAMES = dict()
NORMALIZE_NAMES_SHORT = dict()
for dotted, names in OID_MAP.items():
for name in names:
if name in NORMALIZE_NAMES and OID_LOOKUP[name] != dotted:
raise AssertionError(
'Name collision during setup: "{0}" for OIDs {1} and {2}'
.format(name, dotted, OID_LOOKUP[name])
)
NORMALIZE_NAMES[name] = names[0]
NORMALIZE_NAMES_SHORT[name] = names[-1]
OID_LOOKUP[name] = dotted
for alias, original in [('userID', 'userId')]:
if alias in NORMALIZE_NAMES:
raise AssertionError(
'Name collision during adding aliases: "{0}" (alias for "{1}") is already mapped to OID {2}'
.format(alias, original, OID_LOOKUP[alias])
)
NORMALIZE_NAMES[alias] = original
NORMALIZE_NAMES_SHORT[alias] = NORMALIZE_NAMES_SHORT[original]
OID_LOOKUP[alias] = OID_LOOKUP[original]

View File

@@ -1,178 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# (c) 2020, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from distutils.version import LooseVersion
try:
import OpenSSL # noqa
from OpenSSL import crypto # noqa
HAS_PYOPENSSL = True
except ImportError:
# Error handled in the calling module.
HAS_PYOPENSSL = False
try:
import cryptography
from cryptography import x509
# Older versions of cryptography (< 2.1) do not have __hash__ functions for
# general name objects (DNSName, IPAddress, ...), while providing overloaded
# equality and string representation operations. This makes it impossible to
# use them in hash-based data structures such as set or dict. Since we are
# actually doing that in x509_certificate, and potentially in other code,
# we need to monkey-patch __hash__ for these classes to make sure our code
# works fine.
if LooseVersion(cryptography.__version__) < LooseVersion('2.1'):
# A very simply hash function which relies on the representation
# of an object to be implemented. This is the case since at least
# cryptography 1.0, see
# https://github.com/pyca/cryptography/commit/7a9abce4bff36c05d26d8d2680303a6f64a0e84f
def simple_hash(self):
return hash(repr(self))
# The hash functions for the following types were added for cryptography 2.1:
# https://github.com/pyca/cryptography/commit/fbfc36da2a4769045f2373b004ddf0aff906cf38
x509.DNSName.__hash__ = simple_hash
x509.DirectoryName.__hash__ = simple_hash
x509.GeneralName.__hash__ = simple_hash
x509.IPAddress.__hash__ = simple_hash
x509.OtherName.__hash__ = simple_hash
x509.RegisteredID.__hash__ = simple_hash
if LooseVersion(cryptography.__version__) < LooseVersion('1.2'):
# The hash functions for the following types were added for cryptography 1.2:
# https://github.com/pyca/cryptography/commit/b642deed88a8696e5f01ce6855ccf89985fc35d0
# https://github.com/pyca/cryptography/commit/d1b5681f6db2bde7a14625538bd7907b08dfb486
x509.RFC822Name.__hash__ = simple_hash
x509.UniformResourceIdentifier.__hash__ = simple_hash
# Test whether we have support for DSA, EC, Ed25519, Ed448, RSA, X25519 and/or X448
try:
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/
import cryptography.hazmat.primitives.asymmetric.dsa
CRYPTOGRAPHY_HAS_DSA = True
try:
# added later in 1.5
cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign
CRYPTOGRAPHY_HAS_DSA_SIGN = True
except AttributeError:
CRYPTOGRAPHY_HAS_DSA_SIGN = False
except ImportError:
CRYPTOGRAPHY_HAS_DSA = False
CRYPTOGRAPHY_HAS_DSA_SIGN = False
try:
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/
import cryptography.hazmat.primitives.asymmetric.ed25519
CRYPTOGRAPHY_HAS_ED25519 = True
try:
# added with the primitive in 2.6
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.sign
CRYPTOGRAPHY_HAS_ED25519_SIGN = True
except AttributeError:
CRYPTOGRAPHY_HAS_ED25519_SIGN = False
except ImportError:
CRYPTOGRAPHY_HAS_ED25519 = False
CRYPTOGRAPHY_HAS_ED25519_SIGN = False
try:
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed448/
import cryptography.hazmat.primitives.asymmetric.ed448
CRYPTOGRAPHY_HAS_ED448 = True
try:
# added with the primitive in 2.6
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.sign
CRYPTOGRAPHY_HAS_ED448_SIGN = True
except AttributeError:
CRYPTOGRAPHY_HAS_ED448_SIGN = False
except ImportError:
CRYPTOGRAPHY_HAS_ED448 = False
CRYPTOGRAPHY_HAS_ED448_SIGN = False
try:
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
import cryptography.hazmat.primitives.asymmetric.ec
CRYPTOGRAPHY_HAS_EC = True
try:
# added later in 1.5
cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign
CRYPTOGRAPHY_HAS_EC_SIGN = True
except AttributeError:
CRYPTOGRAPHY_HAS_EC_SIGN = False
except ImportError:
CRYPTOGRAPHY_HAS_EC = False
CRYPTOGRAPHY_HAS_EC_SIGN = False
try:
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/
import cryptography.hazmat.primitives.asymmetric.rsa
CRYPTOGRAPHY_HAS_RSA = True
try:
# added later in 1.4
cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign
CRYPTOGRAPHY_HAS_RSA_SIGN = True
except AttributeError:
CRYPTOGRAPHY_HAS_RSA_SIGN = False
except ImportError:
CRYPTOGRAPHY_HAS_RSA = False
CRYPTOGRAPHY_HAS_RSA_SIGN = False
try:
# added in 2.0 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x25519/
import cryptography.hazmat.primitives.asymmetric.x25519
CRYPTOGRAPHY_HAS_X25519 = True
try:
# added later in 2.5
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.private_bytes
CRYPTOGRAPHY_HAS_X25519_FULL = True
except AttributeError:
CRYPTOGRAPHY_HAS_X25519_FULL = False
except ImportError:
CRYPTOGRAPHY_HAS_X25519 = False
CRYPTOGRAPHY_HAS_X25519_FULL = False
try:
# added in 2.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x448/
import cryptography.hazmat.primitives.asymmetric.x448
CRYPTOGRAPHY_HAS_X448 = True
except ImportError:
CRYPTOGRAPHY_HAS_X448 = False
HAS_CRYPTOGRAPHY = True
except ImportError:
# Error handled in the calling module.
CRYPTOGRAPHY_HAS_EC = False
CRYPTOGRAPHY_HAS_EC_SIGN = False
CRYPTOGRAPHY_HAS_ED25519 = False
CRYPTOGRAPHY_HAS_ED25519_SIGN = False
CRYPTOGRAPHY_HAS_ED448 = False
CRYPTOGRAPHY_HAS_ED448_SIGN = False
CRYPTOGRAPHY_HAS_DSA = False
CRYPTOGRAPHY_HAS_DSA_SIGN = False
CRYPTOGRAPHY_HAS_RSA = False
CRYPTOGRAPHY_HAS_RSA_SIGN = False
CRYPTOGRAPHY_HAS_X25519 = False
CRYPTOGRAPHY_HAS_X25519_FULL = False
CRYPTOGRAPHY_HAS_X448 = False
HAS_CRYPTOGRAPHY = False
class OpenSSLObjectError(Exception):
pass
class OpenSSLBadPassphraseError(OpenSSLObjectError):
pass

View File

@@ -1,125 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
try:
from cryptography import x509
except ImportError:
# Error handled in the calling module.
pass
from .basic import (
HAS_CRYPTOGRAPHY,
)
from .cryptography_support import (
cryptography_decode_name,
)
from ._obj2txt import (
obj2txt,
)
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
if HAS_CRYPTOGRAPHY:
REVOCATION_REASON_MAP = {
'unspecified': x509.ReasonFlags.unspecified,
'key_compromise': x509.ReasonFlags.key_compromise,
'ca_compromise': x509.ReasonFlags.ca_compromise,
'affiliation_changed': x509.ReasonFlags.affiliation_changed,
'superseded': x509.ReasonFlags.superseded,
'cessation_of_operation': x509.ReasonFlags.cessation_of_operation,
'certificate_hold': x509.ReasonFlags.certificate_hold,
'privilege_withdrawn': x509.ReasonFlags.privilege_withdrawn,
'aa_compromise': x509.ReasonFlags.aa_compromise,
'remove_from_crl': x509.ReasonFlags.remove_from_crl,
}
REVOCATION_REASON_MAP_INVERSE = dict()
for k, v in REVOCATION_REASON_MAP.items():
REVOCATION_REASON_MAP_INVERSE[v] = k
else:
REVOCATION_REASON_MAP = dict()
REVOCATION_REASON_MAP_INVERSE = dict()
def cryptography_decode_revoked_certificate(cert):
result = {
'serial_number': cert.serial_number,
'revocation_date': cert.revocation_date,
'issuer': None,
'issuer_critical': False,
'reason': None,
'reason_critical': False,
'invalidity_date': None,
'invalidity_date_critical': False,
}
try:
ext = cert.extensions.get_extension_for_class(x509.CertificateIssuer)
result['issuer'] = list(ext.value)
result['issuer_critical'] = ext.critical
except x509.ExtensionNotFound:
pass
try:
ext = cert.extensions.get_extension_for_class(x509.CRLReason)
result['reason'] = ext.value.reason
result['reason_critical'] = ext.critical
except x509.ExtensionNotFound:
pass
try:
ext = cert.extensions.get_extension_for_class(x509.InvalidityDate)
result['invalidity_date'] = ext.value.invalidity_date
result['invalidity_date_critical'] = ext.critical
except x509.ExtensionNotFound:
pass
return result
def cryptography_dump_revoked(entry):
return {
'serial_number': entry['serial_number'],
'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT),
'issuer':
[cryptography_decode_name(issuer) for issuer in entry['issuer']]
if entry['issuer'] is not None else None,
'issuer_critical': entry['issuer_critical'],
'reason': REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None,
'reason_critical': entry['reason_critical'],
'invalidity_date':
entry['invalidity_date'].strftime(TIMESTAMP_FORMAT)
if entry['invalidity_date'] is not None else None,
'invalidity_date_critical': entry['invalidity_date_critical'],
}
def cryptography_get_signature_algorithm_oid_from_crl(crl):
try:
return crl.signature_algorithm_oid
except AttributeError:
# Older cryptography versions don't have signature_algorithm_oid yet
dotted = obj2txt(
crl._backend._lib,
crl._backend._ffi,
crl._x509_crl.sig_alg.algorithm
)
return x509.oid.ObjectIdentifier(dotted)

View File

@@ -1,407 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import base64
import binascii
import re
from ansible.module_utils._text import to_text
from ._asn1 import serialize_asn1_string_as_der
try:
import cryptography
from cryptography import x509
from cryptography.hazmat.primitives import serialization
import ipaddress
except ImportError:
# Error handled in the calling module.
pass
from .basic import (
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,
OpenSSLObjectError,
)
from ._objects import (
OID_LOOKUP,
OID_MAP,
NORMALIZE_NAMES_SHORT,
NORMALIZE_NAMES,
)
from ._obj2txt import obj2txt
DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
def cryptography_get_extensions_from_cert(cert):
# Since cryptography won't give us the DER value for an extension
# (that is only stored for unrecognized extensions), we have to re-do
# the extension parsing outselves.
result = dict()
backend = cert._backend
x509_obj = cert._x509
for i in range(backend._lib.X509_get_ext_count(x509_obj)):
ext = backend._lib.X509_get_ext(x509_obj, i)
if ext == backend._ffi.NULL:
continue
crit = backend._lib.X509_EXTENSION_get_critical(ext)
data = backend._lib.X509_EXTENSION_get_data(ext)
backend.openssl_assert(data != backend._ffi.NULL)
der = backend._ffi.buffer(data.data, data.length)[:]
entry = dict(
critical=(crit == 1),
value=base64.b64encode(der),
)
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
result[oid] = entry
return result
def cryptography_get_extensions_from_csr(csr):
# Since cryptography won't give us the DER value for an extension
# (that is only stored for unrecognized extensions), we have to re-do
# the extension parsing outselves.
result = dict()
backend = csr._backend
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req)
extensions = backend._ffi.gc(
extensions,
lambda ext: backend._lib.sk_X509_EXTENSION_pop_free(
ext,
backend._ffi.addressof(backend._lib._original_lib, "X509_EXTENSION_free")
)
)
for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)):
ext = backend._lib.sk_X509_EXTENSION_value(extensions, i)
if ext == backend._ffi.NULL:
continue
crit = backend._lib.X509_EXTENSION_get_critical(ext)
data = backend._lib.X509_EXTENSION_get_data(ext)
backend.openssl_assert(data != backend._ffi.NULL)
der = backend._ffi.buffer(data.data, data.length)[:]
entry = dict(
critical=(crit == 1),
value=base64.b64encode(der),
)
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
result[oid] = entry
return result
def cryptography_name_to_oid(name):
dotted = OID_LOOKUP.get(name)
if dotted is None:
if DOTTED_OID.match(name):
return x509.oid.ObjectIdentifier(name)
raise OpenSSLObjectError('Cannot find OID for "{0}"'.format(name))
return x509.oid.ObjectIdentifier(dotted)
def cryptography_oid_to_name(oid, short=False):
dotted_string = oid.dotted_string
names = OID_MAP.get(dotted_string)
if names:
name = names[0]
else:
name = oid._name
if name == 'Unknown OID':
name = dotted_string
if short:
return NORMALIZE_NAMES_SHORT.get(name, name)
else:
return NORMALIZE_NAMES.get(name, name)
def _get_hex(bytesstr):
if bytesstr is None:
return bytesstr
data = binascii.hexlify(bytesstr)
data = to_text(b':'.join(data[i:i + 2] for i in range(0, len(data), 2)))
return data
def _parse_hex(bytesstr):
if bytesstr is None:
return bytesstr
data = ''.join([('0' * (2 - len(p)) + p) if len(p) < 2 else p for p in to_text(bytesstr).split(':')])
data = binascii.unhexlify(data)
return data
def _parse_dn(name):
'''
Parse a Distinguished Name.
Can be of the form ``CN=Test, O = Something`` or ``CN = Test,O= Something``.
'''
original_name = name
name = name.lstrip()
sep = ','
if name.startswith('/'):
sep = '/'
name = name[1:]
sep_str = sep + '\\'
result = []
start_re = re.compile(r'^ *([a-zA-z0-9]+) *= *')
while name:
m = start_re.match(name)
if not m:
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": cannot start part in "{1}"'.format(original_name, name))
oid = cryptography_name_to_oid(m.group(1))
idx = len(m.group(0))
decoded_name = []
length = len(name)
while idx < length:
i = idx
while i < length and name[i] not in sep_str:
i += 1
if i > idx:
decoded_name.append(name[idx:i])
idx = i
while idx + 1 < length and name[idx] == '\\':
decoded_name.append(name[idx + 1])
idx += 2
if idx < length and name[idx] == sep:
break
result.append(x509.NameAttribute(oid, ''.join(decoded_name)))
name = name[idx:]
if name:
if name[0] != sep or len(name) < 2:
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": unexpected end of string'.format(original_name))
name = name[1:]
return result
def cryptography_get_name(name):
'''
Given a name string, returns a cryptography x509.Name object.
Raises an OpenSSLObjectError if the name is unknown or cannot be parsed.
'''
try:
if name.startswith('DNS:'):
return x509.DNSName(to_text(name[4:]))
if name.startswith('IP:'):
address = to_text(name[3:])
if '/' in address:
return x509.IPAddress(ipaddress.ip_network(address))
return x509.IPAddress(ipaddress.ip_address(address))
if name.startswith('email:'):
return x509.RFC822Name(to_text(name[6:]))
if name.startswith('URI:'):
return x509.UniformResourceIdentifier(to_text(name[4:]))
if name.startswith('RID:'):
m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:]))
if not m:
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}"'.format(name))
return x509.RegisteredID(x509.oid.ObjectIdentifier(m.group(1)))
if name.startswith('otherName:'):
# otherName can either be a raw ASN.1 hex string or in the format that OpenSSL works with.
m = re.match(r'^([0-9]+(?:\.[0-9]+)*);([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2})*)$', to_text(name[10:]))
if m:
return x509.OtherName(x509.oid.ObjectIdentifier(m.group(1)), _parse_hex(m.group(2)))
# See https://www.openssl.org/docs/man1.0.2/man5/x509v3_config.html - Subject Alternative Name for more
# defailts on the format expected.
name = to_text(name[10:], errors='surrogate_or_strict')
if ';' not in name:
raise OpenSSLObjectError('Cannot parse Subject Alternative Name otherName "{0}", must be in the '
'format "otherName:<OID>;<ASN.1 OpenSSL Encoded String>" or '
'"otherName:<OID>;<hex string>"'.format(name))
oid, value = name.split(';', 1)
b_value = serialize_asn1_string_as_der(value)
return x509.OtherName(x509.ObjectIdentifier(oid), b_value)
if name.startswith('dirName:'):
return x509.DirectoryName(x509.Name(_parse_dn(to_text(name[8:]))))
except Exception as e:
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}": {1}'.format(name, e))
if ':' not in name:
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}" (forgot "DNS:" prefix?)'.format(name))
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}" (potentially unsupported by cryptography backend)'.format(name))
def _dn_escape_value(value):
'''
Escape Distinguished Name's attribute value.
'''
value = value.replace('\\', '\\\\')
for ch in [',', '#', '+', '<', '>', ';', '"', '=', '/']:
value = value.replace(ch, '\\%s' % ch)
if value.startswith(' '):
value = r'\ ' + value[1:]
return value
def cryptography_decode_name(name):
'''
Given a cryptography x509.Name object, returns a string.
Raises an OpenSSLObjectError if the name is not supported.
'''
if isinstance(name, x509.DNSName):
return 'DNS:{0}'.format(name.value)
if isinstance(name, x509.IPAddress):
if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
return 'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
return 'IP:{0}'.format(name.value.compressed)
if isinstance(name, x509.RFC822Name):
return 'email:{0}'.format(name.value)
if isinstance(name, x509.UniformResourceIdentifier):
return 'URI:{0}'.format(name.value)
if isinstance(name, x509.DirectoryName):
return 'dirName:' + ''.join([
'/{0}={1}'.format(cryptography_oid_to_name(attribute.oid, short=True), _dn_escape_value(attribute.value))
for attribute in name.value
])
if isinstance(name, x509.RegisteredID):
return 'RID:{0}'.format(name.value.dotted_string)
if isinstance(name, x509.OtherName):
return 'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value))
raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name))
def _cryptography_get_keyusage(usage):
'''
Given a key usage identifier string, returns the parameter name used by cryptography's x509.KeyUsage().
Raises an OpenSSLObjectError if the identifier is unknown.
'''
if usage in ('Digital Signature', 'digitalSignature'):
return 'digital_signature'
if usage in ('Non Repudiation', 'nonRepudiation'):
return 'content_commitment'
if usage in ('Key Encipherment', 'keyEncipherment'):
return 'key_encipherment'
if usage in ('Data Encipherment', 'dataEncipherment'):
return 'data_encipherment'
if usage in ('Key Agreement', 'keyAgreement'):
return 'key_agreement'
if usage in ('Certificate Sign', 'keyCertSign'):
return 'key_cert_sign'
if usage in ('CRL Sign', 'cRLSign'):
return 'crl_sign'
if usage in ('Encipher Only', 'encipherOnly'):
return 'encipher_only'
if usage in ('Decipher Only', 'decipherOnly'):
return 'decipher_only'
raise OpenSSLObjectError('Unknown key usage "{0}"'.format(usage))
def cryptography_parse_key_usage_params(usages):
'''
Given a list of key usage identifier strings, returns the parameters for cryptography's x509.KeyUsage().
Raises an OpenSSLObjectError if an identifier is unknown.
'''
params = dict(
digital_signature=False,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=False,
crl_sign=False,
encipher_only=False,
decipher_only=False,
)
for usage in usages:
params[_cryptography_get_keyusage(usage)] = True
return params
def cryptography_get_basic_constraints(constraints):
'''
Given a list of constraints, returns a tuple (ca, path_length).
Raises an OpenSSLObjectError if a constraint is unknown or cannot be parsed.
'''
ca = False
path_length = None
if constraints:
for constraint in constraints:
if constraint.startswith('CA:'):
if constraint == 'CA:TRUE':
ca = True
elif constraint == 'CA:FALSE':
ca = False
else:
raise OpenSSLObjectError('Unknown basic constraint value "{0}" for CA'.format(constraint[3:]))
elif constraint.startswith('pathlen:'):
v = constraint[len('pathlen:'):]
try:
path_length = int(v)
except Exception as e:
raise OpenSSLObjectError('Cannot parse path length constraint "{0}" ({1})'.format(v, e))
else:
raise OpenSSLObjectError('Unknown basic constraint "{0}"'.format(constraint))
return ca, path_length
def cryptography_key_needs_digest_for_signing(key):
'''Tests whether the given private key requires a digest algorithm for signing.
Ed25519 and Ed448 keys do not; they need None to be passed as the digest algorithm.
'''
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
return False
if CRYPTOGRAPHY_HAS_ED448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
return False
return True
def cryptography_compare_public_keys(key1, key2):
'''Tests whether two public keys are the same.
Needs special logic for Ed25519 and Ed448 keys, since they do not have public_numbers().
'''
if CRYPTOGRAPHY_HAS_ED25519:
a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
if a or b:
if not a or not b:
return False
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
return a == b
if CRYPTOGRAPHY_HAS_ED448:
a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
if a or b:
if not a or not b:
return False
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
return a == b
return key1.public_numbers() == key2.public_numbers()
def cryptography_serial_number_of_cert(cert):
'''Returns cert.serial_number.
Also works for old versions of cryptography.
'''
try:
return cert.serial_number
except AttributeError:
# The property was called "serial" before cryptography 1.4
return cert.serial

View File

@@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
PEM_START = '-----BEGIN '
PEM_END = '-----'
PKCS8_PRIVATEKEY_NAMES = ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY')
PKCS1_PRIVATEKEY_SUFFIX = ' PRIVATE KEY'
def identify_pem_format(content):
'''Given the contents of a binary file, tests whether this could be a PEM file.'''
try:
lines = content.decode('utf-8').splitlines(False)
if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
return True
except UnicodeDecodeError:
pass
return False
def identify_private_key_format(content):
'''Given the contents of a private key file, identifies its format.'''
# See https://github.com/openssl/openssl/blob/master/crypto/pem/pem_pkey.c#L40-L85
# (PEM_read_bio_PrivateKey)
# and https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L46-L47
# (PEM_STRING_PKCS8, PEM_STRING_PKCS8INF)
try:
lines = content.decode('utf-8').splitlines(False)
if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
name = lines[0][len(PEM_START):-len(PEM_END)]
if name in PKCS8_PRIVATEKEY_NAMES:
return 'pkcs8'
if len(name) > len(PKCS1_PRIVATEKEY_SUFFIX) and name.endswith(PKCS1_PRIVATEKEY_SUFFIX):
return 'pkcs1'
return 'unknown-pem'
except UnicodeDecodeError:
pass
return 'raw'

View File

@@ -1,81 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import sys
def binary_exp_mod(f, e, m):
'''Computes f^e mod m in O(log e) multiplications modulo m.'''
# Compute len_e = floor(log_2(e))
len_e = -1
x = e
while x > 0:
x >>= 1
len_e += 1
# Compute f**e mod m
result = 1
for k in range(len_e, -1, -1):
result = (result * result) % m
if ((e >> k) & 1) != 0:
result = (result * f) % m
return result
def simple_gcd(a, b):
'''Compute GCD of its two inputs.'''
while b != 0:
a, b = b, a % b
return a
def quick_is_not_prime(n):
'''Does some quick checks to see if we can poke a hole into the primality of n.
A result of `False` does **not** mean that the number is prime; it just means
that we couldn't detect quickly whether it is not prime.
'''
if n <= 2:
return True
# The constant in the next line is the product of all primes < 200
if simple_gcd(n, 7799922041683461553249199106329813876687996789903550945093032474868511536164700810) > 1:
return True
# TODO: maybe do some iterations of Miller-Rabin to increase confidence
# (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test)
return False
python_version = (sys.version_info[0], sys.version_info[1])
if python_version >= (2, 7) or python_version >= (3, 1):
# Ansible still supports Python 2.6 on remote nodes
def count_bits(no):
no = abs(no)
if no == 0:
return 0
return no.bit_length()
else:
# Slow, but works
def count_bits(no):
no = abs(no)
count = 0
while no > 0:
no >>= 1
count += 1
return count

View File

@@ -1,154 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import base64
from ansible.module_utils._text import to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
try:
import OpenSSL
except ImportError:
# Error handled in the calling module.
pass
from ._objects import (
NORMALIZE_NAMES_SHORT,
NORMALIZE_NAMES,
)
from ._obj2txt import obj2txt
from .basic import (
OpenSSLObjectError,
)
def pyopenssl_normalize_name(name, short=False):
nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(name))
if nid != 0:
b_name = OpenSSL._util.lib.OBJ_nid2ln(nid)
name = to_text(OpenSSL._util.ffi.string(b_name))
if short:
return NORMALIZE_NAMES_SHORT.get(name, name)
else:
return NORMALIZE_NAMES.get(name, name)
def pyopenssl_normalize_name_attribute(san):
# apparently openssl returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
# although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
if san.startswith('IP Address:'):
san = 'IP:' + san[len('IP Address:'):]
if san.startswith('IP:'):
address = san[3:]
if '/' in address:
ip = compat_ipaddress.ip_network(address)
san = 'IP:{0}/{1}'.format(ip.network_address.compressed, ip.prefixlen)
else:
ip = compat_ipaddress.ip_address(address)
san = 'IP:{0}'.format(ip.compressed)
if san.startswith('Registered ID:'):
san = 'RID:' + san[len('Registered ID:'):]
# Some versions of OpenSSL apparently forgot the colon. Happens in CI with Ubuntu 16.04 and FreeBSD 11.1
if san.startswith('Registered ID'):
san = 'RID:' + san[len('Registered ID'):]
return san
def pyopenssl_get_extensions_from_cert(cert):
# While pyOpenSSL allows us to get an extension's DER value, it won't
# give us the dotted string for an OID. So we have to do some magic to
# get hold of it.
result = dict()
ext_count = cert.get_extension_count()
for i in range(0, ext_count):
ext = cert.get_extension(i)
entry = dict(
critical=bool(ext.get_critical()),
value=base64.b64encode(ext.get_data()),
)
oid = obj2txt(
OpenSSL._util.lib,
OpenSSL._util.ffi,
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
)
# This could also be done a bit simpler:
#
# oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid))
#
# Unfortunately this gives the wrong result in case the linked OpenSSL
# doesn't know the OID. That's why we have to get the OID dotted string
# similarly to how cryptography does it.
result[oid] = entry
return result
def pyopenssl_get_extensions_from_csr(csr):
# While pyOpenSSL allows us to get an extension's DER value, it won't
# give us the dotted string for an OID. So we have to do some magic to
# get hold of it.
result = dict()
for ext in csr.get_extensions():
entry = dict(
critical=bool(ext.get_critical()),
value=base64.b64encode(ext.get_data()),
)
oid = obj2txt(
OpenSSL._util.lib,
OpenSSL._util.ffi,
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
)
# This could also be done a bit simpler:
#
# oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid))
#
# Unfortunately this gives the wrong result in case the linked OpenSSL
# doesn't know the OID. That's why we have to get the OID dotted string
# similarly to how cryptography does it.
result[oid] = entry
return result
def pyopenssl_parse_name_constraints(name_constraints_extension):
lines = to_text(name_constraints_extension, errors='surrogate_or_strict').splitlines()
exclude = None
excluded = []
permitted = []
for line in lines:
if line.startswith(' ') or line.startswith('\t'):
name = pyopenssl_normalize_name_attribute(line.strip())
if exclude is True:
excluded.append(name)
elif exclude is False:
permitted.append(name)
else:
raise OpenSSLObjectError('Unexpected nameConstraint line: "{0}"'.format(line))
else:
line_lc = line.lower()
if line_lc.startswith('exclud'):
exclude = True
elif line_lc.startswith('includ') or line_lc.startswith('permitt'):
exclude = False
else:
raise OpenSSLObjectError('Cannot parse nameConstraint line: "{0}"'.format(line))
return permitted, excluded

View File

@@ -1,354 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import abc
import datetime
import errno
import hashlib
import os
import re
from ansible.module_utils import six
from ansible.module_utils._text import to_native, to_bytes
try:
from OpenSSL import crypto
HAS_PYOPENSSL = True
except ImportError:
# Error handled in the calling module.
HAS_PYOPENSSL = False
try:
from cryptography import x509
from cryptography.hazmat.backends import default_backend as cryptography_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
except ImportError:
# Error handled in the calling module.
pass
from .basic import (
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
def get_fingerprint_of_bytes(source):
"""Generate the fingerprint of the given bytes."""
fingerprint = {}
try:
algorithms = hashlib.algorithms
except AttributeError:
try:
algorithms = hashlib.algorithms_guaranteed
except AttributeError:
return None
for algo in algorithms:
f = getattr(hashlib, algo)
try:
h = f(source)
except ValueError:
# This can happen for hash algorithms not supported in FIPS mode
# (https://github.com/ansible/ansible/issues/67213)
continue
try:
# Certain hash functions have a hexdigest() which expects a length parameter
pubkey_digest = h.hexdigest()
except TypeError:
pubkey_digest = h.hexdigest(32)
fingerprint[algo] = ':'.join(pubkey_digest[i:i + 2] for i in range(0, len(pubkey_digest), 2))
return fingerprint
def get_fingerprint(path, passphrase=None, content=None, backend='pyopenssl'):
"""Generate the fingerprint of the public key. """
privatekey = load_privatekey(path, passphrase=passphrase, content=content, check_passphrase=False, backend=backend)
if backend == 'pyopenssl':
try:
publickey = crypto.dump_publickey(crypto.FILETYPE_ASN1, privatekey)
except AttributeError:
# If PyOpenSSL < 16.0 crypto.dump_publickey() will fail.
try:
bio = crypto._new_mem_buf()
rc = crypto._lib.i2d_PUBKEY_bio(bio, privatekey._pkey)
if rc != 1:
crypto._raise_current_error()
publickey = crypto._bio_to_string(bio)
except AttributeError:
# By doing this we prevent the code from raising an error
# yet we return no value in the fingerprint hash.
return None
elif backend == 'cryptography':
publickey = privatekey.public_key().public_bytes(
serialization.Encoding.DER,
serialization.PublicFormat.SubjectPublicKeyInfo
)
return get_fingerprint_of_bytes(publickey)
def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='pyopenssl'):
"""Load the specified OpenSSL private key.
The content can also be specified via content; in that case,
this function will not load the key from disk.
"""
try:
if content is None:
with open(path, 'rb') as b_priv_key_fh:
priv_key_detail = b_priv_key_fh.read()
else:
priv_key_detail = content
if backend == 'pyopenssl':
# First try: try to load with real passphrase (resp. empty string)
# Will work if this is the correct passphrase, or the key is not
# password-protected.
try:
result = crypto.load_privatekey(crypto.FILETYPE_PEM,
priv_key_detail,
to_bytes(passphrase or ''))
except crypto.Error as e:
if len(e.args) > 0 and len(e.args[0]) > 0:
if e.args[0][0][2] in ('bad decrypt', 'bad password read'):
# This happens in case we have the wrong passphrase.
if passphrase is not None:
raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key!')
else:
raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!')
raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e))
if check_passphrase:
# Next we want to make sure that the key is actually protected by
# a passphrase (in case we did try the empty string before, make
# sure that the key is not protected by the empty string)
try:
crypto.load_privatekey(crypto.FILETYPE_PEM,
priv_key_detail,
to_bytes('y' if passphrase == 'x' else 'x'))
if passphrase is not None:
# Since we can load the key without an exception, the
# key isn't password-protected
raise OpenSSLBadPassphraseError('Passphrase provided, but private key is not password-protected!')
except crypto.Error as e:
if passphrase is None and len(e.args) > 0 and len(e.args[0]) > 0:
if e.args[0][0][2] in ('bad decrypt', 'bad password read'):
# The key is obviously protected by the empty string.
# Don't do this at home (if it's possible at all)...
raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!')
elif backend == 'cryptography':
try:
result = load_pem_private_key(priv_key_detail,
None if passphrase is None else to_bytes(passphrase),
cryptography_backend())
except TypeError:
raise OpenSSLBadPassphraseError('Wrong or empty passphrase provided for private key')
except ValueError:
raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key')
return result
except (IOError, OSError) as exc:
raise OpenSSLObjectError(exc)
def load_certificate(path, content=None, backend='pyopenssl'):
"""Load the specified certificate."""
try:
if content is None:
with open(path, 'rb') as cert_fh:
cert_content = cert_fh.read()
else:
cert_content = content
if backend == 'pyopenssl':
return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content)
elif backend == 'cryptography':
return x509.load_pem_x509_certificate(cert_content, cryptography_backend())
except (IOError, OSError) as exc:
raise OpenSSLObjectError(exc)
def load_certificate_request(path, content=None, backend='pyopenssl'):
"""Load the specified certificate signing request."""
try:
if content is None:
with open(path, 'rb') as csr_fh:
csr_content = csr_fh.read()
else:
csr_content = content
except (IOError, OSError) as exc:
raise OpenSSLObjectError(exc)
if backend == 'pyopenssl':
return crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_content)
elif backend == 'cryptography':
return x509.load_pem_x509_csr(csr_content, cryptography_backend())
def parse_name_field(input_dict):
"""Take a dict with key: value or key: list_of_values mappings and return a list of tuples"""
result = []
for key in input_dict:
if isinstance(input_dict[key], list):
for entry in input_dict[key]:
result.append((key, entry))
else:
result.append((key, input_dict[key]))
return result
def convert_relative_to_datetime(relative_time_string):
"""Get a datetime.datetime or None from a string in the time format described in sshd_config(5)"""
parsed_result = re.match(
r"^(?P<prefix>[+-])((?P<weeks>\d+)[wW])?((?P<days>\d+)[dD])?((?P<hours>\d+)[hH])?((?P<minutes>\d+)[mM])?((?P<seconds>\d+)[sS]?)?$",
relative_time_string)
if parsed_result is None or len(relative_time_string) == 1:
# not matched or only a single "+" or "-"
return None
offset = datetime.timedelta(0)
if parsed_result.group("weeks") is not None:
offset += datetime.timedelta(weeks=int(parsed_result.group("weeks")))
if parsed_result.group("days") is not None:
offset += datetime.timedelta(days=int(parsed_result.group("days")))
if parsed_result.group("hours") is not None:
offset += datetime.timedelta(hours=int(parsed_result.group("hours")))
if parsed_result.group("minutes") is not None:
offset += datetime.timedelta(
minutes=int(parsed_result.group("minutes")))
if parsed_result.group("seconds") is not None:
offset += datetime.timedelta(
seconds=int(parsed_result.group("seconds")))
if parsed_result.group("prefix") == "+":
return datetime.datetime.utcnow() + offset
else:
return datetime.datetime.utcnow() - offset
def get_relative_time_option(input_string, input_name, backend='cryptography'):
"""Return an absolute timespec if a relative timespec or an ASN1 formatted
string is provided.
The return value will be a datetime object for the cryptography backend,
and a ASN1 formatted string for the pyopenssl backend."""
result = to_native(input_string)
if result is None:
raise OpenSSLObjectError(
'The timespec "%s" for %s is not valid' %
input_string, input_name)
# Relative time
if result.startswith("+") or result.startswith("-"):
result_datetime = convert_relative_to_datetime(result)
if backend == 'pyopenssl':
return result_datetime.strftime("%Y%m%d%H%M%SZ")
elif backend == 'cryptography':
return result_datetime
# Absolute time
if backend == 'pyopenssl':
return input_string
elif backend == 'cryptography':
for date_fmt in ['%Y%m%d%H%M%SZ', '%Y%m%d%H%MZ', '%Y%m%d%H%M%S%z', '%Y%m%d%H%M%z']:
try:
return datetime.datetime.strptime(result, date_fmt)
except ValueError:
pass
raise OpenSSLObjectError(
'The time spec "%s" for %s is invalid' %
(input_string, input_name)
)
def select_message_digest(digest_string):
digest = None
if digest_string == 'sha256':
digest = hashes.SHA256()
elif digest_string == 'sha384':
digest = hashes.SHA384()
elif digest_string == 'sha512':
digest = hashes.SHA512()
elif digest_string == 'sha1':
digest = hashes.SHA1()
elif digest_string == 'md5':
digest = hashes.MD5()
return digest
@six.add_metaclass(abc.ABCMeta)
class OpenSSLObject(object):
def __init__(self, path, state, force, check_mode):
self.path = path
self.state = state
self.force = force
self.name = os.path.basename(path)
self.changed = False
self.check_mode = check_mode
def check(self, module, perms_required=True):
"""Ensure the resource is in its desired state."""
def _check_state():
return os.path.exists(self.path)
def _check_perms(module):
file_args = module.load_file_common_arguments(module.params)
return not module.set_fs_attributes_if_different(file_args, False)
if not perms_required:
return _check_state()
return _check_state() and _check_perms(module)
@abc.abstractmethod
def dump(self):
"""Serialize the object into a dictionary."""
pass
@abc.abstractmethod
def generate(self):
"""Generate the resource."""
pass
def remove(self, module):
"""Remove the resource from the filesystem."""
try:
os.remove(self.path)
self.changed = True
except OSError as exc:
if exc.errno != errno.ENOENT:
raise OpenSSLObjectError(exc)
else:
pass

View File

@@ -1,101 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import errno
import os
import tempfile
def load_file_if_exists(path, module=None, ignore_errors=False):
'''
Load the file as a bytes string. If the file does not exist, ``None`` is returned.
If ``ignore_errors`` is ``True``, will ignore errors. Otherwise, errors are
raised as exceptions if ``module`` is not specified, and result in ``module.fail_json``
being called when ``module`` is specified.
'''
try:
with open(path, 'rb') as f:
return f.read()
except EnvironmentError as exc:
if exc.errno == errno.ENOENT:
return None
if ignore_errors:
return None
if module is None:
raise
module.fail_json('Error while loading {0} - {1}'.format(path, str(exc)))
except Exception as exc:
if ignore_errors:
return None
if module is None:
raise
module.fail_json('Error while loading {0} - {1}'.format(path, str(exc)))
def write_file(module, content, default_mode=None, path=None):
'''
Writes content into destination file as securely as possible.
Uses file arguments from module.
'''
# Find out parameters for file
try:
file_args = module.load_file_common_arguments(module.params, path=path)
except TypeError:
# The path argument is only supported in Ansible 2.10+. Fall back to
# pre-2.10 behavior of module_utils/crypto.py for older Ansible versions.
file_args = module.load_file_common_arguments(module.params)
if path is not None:
file_args['path'] = path
if file_args['mode'] is None:
file_args['mode'] = default_mode
# Create tempfile name
tmp_fd, tmp_name = tempfile.mkstemp(prefix=b'.ansible_tmp')
try:
os.close(tmp_fd)
except Exception:
pass
module.add_cleanup_file(tmp_name) # if we fail, let Ansible try to remove the file
try:
try:
# Create tempfile
file = os.open(tmp_name, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
os.write(file, content)
os.close(file)
except Exception as e:
try:
os.remove(tmp_name)
except Exception:
pass
module.fail_json(msg='Error while writing result into temporary file: {0}'.format(e))
# Update destination to wanted permissions
if os.path.exists(file_args['path']):
module.set_fs_attributes_if_different(file_args, False)
# Move tempfile to final destination
module.atomic_move(tmp_name, file_args['path'])
# Try to update permissions again
module.set_fs_attributes_if_different(file_args, False)
except Exception as e:
try:
os.remove(tmp_name)
except Exception:
pass
module.fail_json(msg='Error while writing result: {0}'.format(e))

View File

@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: acme_account
@@ -19,19 +24,19 @@ description:
such as L(Let's Encrypt,https://letsencrypt.org/)."
- "This module only works with the ACME v2 protocol."
notes:
- "The M(community.crypto.acme_certificate) module also allows to do basic account management.
- "The M(acme_certificate) module also allows to do basic account management.
When using both modules, it is recommended to disable account management
for M(community.crypto.acme_certificate). For that, use the C(modify_account) option of
M(community.crypto.acme_certificate)."
for M(acme_certificate). For that, use the C(modify_account) option of
M(acme_certificate)."
seealso:
- name: Automatic Certificate Management Environment (ACME)
description: The specification of the ACME protocol (RFC 8555).
link: https://tools.ietf.org/html/rfc8555
- module: community.crypto.acme_account_info
- module: acme_account_info
description: Retrieves facts about an ACME account.
- module: community.crypto.openssl_privatekey
- module: openssl_privatekey
description: Can be used to create a private account key.
- module: community.crypto.acme_inspect
- module: acme_inspect
description: Allows to debug problems.
extends_documentation_fragment:
- community.crypto.acme
@@ -86,38 +91,11 @@ options:
- "Mutually exclusive with C(new_account_key_src)."
- "Required if C(new_account_key_src) is not used and state is C(changed_key)."
type: str
external_account_binding:
description:
- Allows to provide external account binding data during account creation.
- This is used by CAs like Sectigo to bind a new ACME account to an existing CA-specific
account, to be able to properly identify a customer.
- Only used when creating a new account. Can not be specified for ACME v1.
type: dict
suboptions:
kid:
description:
- The key identifier provided by the CA.
type: str
required: true
alg:
description:
- The MAC algorithm provided by the CA.
- If not specified by the CA, this is probably C(HS256).
type: str
required: true
choices: [ HS256, HS384, HS512 ]
key:
description:
- Base64 URL encoded value of the MAC key provided by the CA.
- Padding (C(=) symbols at the end) can be omitted.
type: str
required: true
version_added: 1.1.0
'''
EXAMPLES = '''
- name: Make sure account exists and has given contacts. We agree to TOS.
community.crypto.acme_account:
acme_account:
account_key_src: /etc/pki/cert/private/account.key
state: present
terms_agreed: yes
@@ -126,7 +104,7 @@ EXAMPLES = '''
- mailto:myself@example.org
- name: Make sure account has given email address. Don't create account if it doesn't exist
community.crypto.acme_account:
acme_account:
account_key_src: /etc/pki/cert/private/account.key
state: present
allow_creation: no
@@ -134,13 +112,13 @@ EXAMPLES = '''
- mailto:me@example.com
- name: Change account's key to the one stored in the variable new_account_key
community.crypto.acme_account:
acme_account:
account_key_src: /etc/pki/cert/private/account.key
new_account_key_content: '{{ new_account_key }}'
state: changed_key
- name: Delete account (we have to use the new key)
community.crypto.acme_account:
acme_account:
account_key_content: '{{ new_account_key }}'
state: absent
'''
@@ -152,10 +130,6 @@ account_uri:
type: str
'''
import base64
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.crypto.plugins.module_utils.acme import (
ModuleFailException,
ACMEAccount,
@@ -163,6 +137,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
get_default_argspec,
)
from ansible.module_utils.basic import AnsibleModule
def main():
argument_spec = get_default_argspec()
@@ -173,11 +149,6 @@ def main():
contact=dict(type='list', elements='str', default=[]),
new_account_key_src=dict(type='path'),
new_account_key_content=dict(type='str', no_log=True),
external_account_binding=dict(type='dict', options=dict(
kid=dict(type='str', required=True),
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
key=dict(type='str', required=True, no_log=True),
))
))
module = AnsibleModule(
argument_spec=argument_spec,
@@ -197,18 +168,6 @@ def main():
)
handle_standard_module_arguments(module, needs_acme_v2=True)
if module.params['external_account_binding']:
# Make sure padding is there
key = module.params['external_account_binding']['key']
if len(key) % 4 != 0:
key = key + ('=' * (4 - (len(key) % 4)))
# Make sure key is Base64 encoded
try:
base64.urlsafe_b64decode(key)
except Exception as e:
module.fail_json(msg='Key for external_account_binding must be Base64 URL encoded (%s)' % e)
module.params['external_account_binding']['key'] = key
try:
account = ACMEAccount(module)
changed = False
@@ -235,14 +194,13 @@ def main():
changed = True
elif state == 'present':
allow_creation = module.params.get('allow_creation')
# Make sure contact is a list of strings (unfortunately, Ansible doesn't do that for us)
contact = [str(v) for v in module.params.get('contact')]
terms_agreed = module.params.get('terms_agreed')
external_account_binding = module.params.get('external_account_binding')
created, account_data = account.setup_account(
contact,
terms_agreed=terms_agreed,
allow_creation=allow_creation,
external_account_binding=external_account_binding,
)
if account_data is None:
raise ModuleFailException(msg='Account does not exist or is deactivated.')

View File

@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: acme_account_info
@@ -19,8 +24,7 @@ description:
such as L(Let's Encrypt,https://letsencrypt.org/)."
- "This module only works with the ACME v2 protocol."
notes:
- "The M(community.crypto.acme_account) module allows to modify, create and delete ACME
accounts."
- "The M(acme_account) module allows to modify, create and delete ACME accounts."
- "This module was called C(acme_account_facts) before Ansible 2.8. The usage
did not change."
options:
@@ -38,7 +42,7 @@ options:
- object_list
default: ignore
seealso:
- module: community.crypto.acme_account
- module: acme_account
description: Allows to create, modify or delete an ACME account.
extends_documentation_fragment:
- community.crypto.acme
@@ -47,7 +51,7 @@ extends_documentation_fragment:
EXAMPLES = '''
- name: Check whether an account with the given account key exists
community.crypto.acme_account_info:
acme_account_info:
account_key_src: /etc/pki/cert/private/account.key
register: account_data
- name: Verify that account exists
@@ -192,8 +196,6 @@ orders:
returned: when certificate was issued
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.crypto.plugins.module_utils.acme import (
ModuleFailException,
ACMEAccount,
@@ -202,6 +204,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
get_default_argspec,
)
from ansible.module_utils.basic import AnsibleModule
def get_orders_list(module, account, orders_url):
'''
@@ -255,9 +259,8 @@ def main():
),
supports_check_mode=True,
)
if module._name in ('acme_account_facts', 'community.crypto.acme_account_facts'):
module.deprecate("The 'acme_account_facts' module has been renamed to 'acme_account_info'",
version='2.0.0', collection_name='community.crypto')
if module._name == 'acme_account_facts':
module.deprecate("The 'acme_account_facts' module has been renamed to 'acme_account_info'", version='2.12')
handle_standard_module_arguments(module, needs_acme_v2=True)
try:

View File

@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: acme_certificate
@@ -38,9 +43,9 @@ description:
notes:
- "At least one of C(dest) and C(fullchain_dest) must be specified."
- "This module includes basic account management functionality.
If you want to have more control over your ACME account, use the
M(community.crypto.acme_account) module and disable account management
for this module using the C(modify_account) option."
If you want to have more control over your ACME account, use the M(acme_account)
module and disable account management for this module using the C(modify_account)
option."
- "This module was called C(letsencrypt) before Ansible 2.6. The usage
did not change."
seealso:
@@ -58,19 +63,19 @@ seealso:
- name: ACME TLS ALPN Challenge Extension
description: The specification of the C(tls-alpn-01) challenge (RFC 8737).
link: https://www.rfc-editor.org/rfc/rfc8737.html-05
- module: community.crypto.acme_challenge_cert_helper
- module: acme_challenge_cert_helper
description: Helps preparing C(tls-alpn-01) challenges.
- module: community.crypto.openssl_privatekey
- module: openssl_privatekey
description: Can be used to create private keys (both for certificates and accounts).
- module: community.crypto.openssl_csr
- module: openssl_csr
description: Can be used to create a Certificate Signing Request (CSR).
- module: community.crypto.certificate_complete_chain
- module: certificate_complete_chain
description: Allows to find the root certificate for the returned fullchain.
- module: community.crypto.acme_certificate_revoke
- module: acme_certificate_revoke
description: Allows to revoke certificates.
- module: community.crypto.acme_account
- module: acme_account
description: Allows to create, modify or delete an ACME account.
- module: community.crypto.acme_inspect
- module: acme_inspect
description: Allows to debug problems.
extends_documentation_fragment:
- community.crypto.acme
@@ -81,7 +86,7 @@ options:
- "The email address associated with this account."
- "It will be used for certificate expiration warnings."
- "Note that when C(modify_account) is not set to C(no) and you also
used the M(community.crypto.acme_account) module to specify more than one contact
used the M(acme_account) module to specify more than one contact
for your account, this module will update your account and restrict
it to the (at most one) contact email address specified here."
type: str
@@ -103,9 +108,9 @@ options:
description:
- "Boolean indicating whether the module should create the account if
necessary, and update its contact data."
- "Set to C(no) if you want to use the M(community.crypto.acme_account) module to manage
- "Set to C(no) if you want to use the M(acme_account) module to manage
your account instead, and to avoid accidental creation of a new account
using an old key if you changed the account key with M(community.crypto.acme_account)."
using an old key if you changed the account key with M(acme_account)."
- "If set to C(no), C(terms_agreed) and C(account_email) are ignored."
type: bool
default: yes
@@ -212,21 +217,17 @@ options:
to the same certificate in the chain."
- "This option can only be used with the C(cryptography) backend."
type: list
elements: dict
version_added: '1.0.0'
suboptions:
test_certificates:
description:
- "Determines which certificates in the chain will be tested."
- "I(all) tests all certificates in the chain (excluding the leaf, which is
identical in all chains)."
- "I(first) only tests the first certificate in the chain, i.e. the one which
signed the leaf."
- "I(last) only tests the last certificate in the chain, i.e. the one furthest
away from the leaf. Its issuer is the root certificate of this chain."
type: str
default: all
choices: [first, last, all]
choices: [last, all]
issuer:
description:
- "Allows to specify parts of the issuer of a certificate in the chain must
@@ -261,7 +262,7 @@ EXAMPLES = r'''
### Example with HTTP challenge ###
- name: Create a challenge for sample.com using a account key from a variable.
community.crypto.acme_certificate:
acme_certificate:
account_key_content: "{{ account_private_key }}"
csr: /etc/pki/cert/csr/sample.com.csr
dest: /etc/httpd/ssl/sample.com.crt
@@ -269,7 +270,7 @@ EXAMPLES = r'''
# Alternative first step:
- name: Create a challenge for sample.com using a account key from hashi vault.
community.crypto.acme_certificate:
acme_certificate:
account_key_content: "{{ lookup('hashi_vault', 'secret=secret/account_private_key:value') }}"
csr: /etc/pki/cert/csr/sample.com.csr
fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt
@@ -277,7 +278,7 @@ EXAMPLES = r'''
# Alternative first step:
- name: Create a challenge for sample.com using a account key file.
community.crypto.acme_certificate:
acme_certificate:
account_key_src: /etc/pki/cert/private/account.key
csr: /etc/pki/cert/csr/sample.com.csr
dest: /etc/httpd/ssl/sample.com.crt
@@ -290,18 +291,10 @@ EXAMPLES = r'''
# - copy:
# dest: /var/www/html/{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource'] }}
# content: "{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}"
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge['challenge_data']
#
# Alternative way:
#
# - copy:
# dest: /var/www/{{ item.key }}/{{ item.value['http-01']['resource'] }}
# content: "{{ item.value['http-01']['resource_value'] }}"
# loop: "{{ sample_com_challenge.challenge_data | dictsort }}"
# when: sample_com_challenge is changed
# when: sample_com_challenge is changed
- name: Let the challenge be validated and retrieve the cert and intermediate certificate
community.crypto.acme_certificate:
acme_certificate:
account_key_src: /etc/pki/cert/private/account.key
csr: /etc/pki/cert/csr/sample.com.csr
dest: /etc/httpd/ssl/sample.com.crt
@@ -312,7 +305,7 @@ EXAMPLES = r'''
### Example with DNS challenge against production ACME server ###
- name: Create a challenge for sample.com using a account key file.
community.crypto.acme_certificate:
acme_certificate:
account_key_src: /etc/pki/cert/private/account.key
account_email: myself@sample.com
src: /etc/pki/cert/csr/sample.com.csr
@@ -326,7 +319,7 @@ EXAMPLES = r'''
# perform the necessary steps to fulfill the challenge
# for example:
#
# - community.aws.route53:
# - route53:
# zone: sample.com
# record: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].record }}"
# type: TXT
@@ -335,11 +328,11 @@ EXAMPLES = r'''
# wait: yes
# # Note: route53 requires TXT entries to be enclosed in quotes
# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | regex_replace('^(.*)$', '\"\\1\"') }}"
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge.challenge_data
# when: sample_com_challenge is changed
#
# Alternative way:
#
# - community.aws.route53:
# - route53:
# zone: sample.com
# record: "{{ item.key }}"
# type: TXT
@@ -353,7 +346,7 @@ EXAMPLES = r'''
# when: sample_com_challenge is changed
- name: Let the challenge be validated and retrieve the cert and intermediate certificate
community.crypto.acme_certificate:
acme_certificate:
account_key_src: /etc/pki/cert/private/account.key
account_email: myself@sample.com
src: /etc/pki/cert/csr/sample.com.csr
@@ -368,7 +361,7 @@ EXAMPLES = r'''
# Alternative second step:
- name: Let the challenge be validated and retrieve the cert and intermediate certificate
community.crypto.acme_certificate:
acme_certificate:
account_key_src: /etc/pki/cert/private/account.key
account_email: myself@sample.com
src: /etc/pki/cert/csr/sample.com.csr
@@ -419,6 +412,7 @@ challenge_data:
returned: changed and challenge is C(tls-alpn-01)
type: str
sample: DNS:example.com
version_added: "2.8"
resource_value:
description:
- The value the resource has to produce for the validation.
@@ -437,12 +431,14 @@ challenge_data:
returned: changed and challenge is C(dns-01)
type: str
sample: _acme-challenge.example.com
version_added: "2.5"
challenge_data_dns:
description:
- List of TXT values per DNS record, in case challenge is C(dns-01).
- Since Ansible 2.8.5, only challenges which are not yet valid are returned.
returned: changed
type: dict
version_added: "2.5"
authorizations:
description:
- ACME authorization data.
@@ -454,14 +450,17 @@ order_uri:
description: ACME order URI.
returned: changed
type: str
version_added: "2.5"
finalization_uri:
description: ACME finalization URI.
returned: changed
type: str
version_added: "2.5"
account_uri:
description: ACME account URI.
returned: changed
type: str
version_added: "2.5"
all_chains:
description:
- When I(retrieve_all_alternates) is set to C(yes), the module will query the ACME server
@@ -490,28 +489,6 @@ all_chains:
returned: always
'''
import base64
import binascii
import hashlib
import os
import re
import textwrap
import time
import traceback
from datetime import datetime
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
parse_name_field,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_name_to_oid,
)
from ansible_collections.community.crypto.plugins.module_utils.acme import (
ModuleFailException,
write_file,
@@ -526,7 +503,21 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
process_links,
get_default_argspec,
)
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
import base64
import binascii
import hashlib
import os
import re
import textwrap
import time
import traceback
from datetime import datetime
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes, to_native
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
try:
import cryptography
@@ -1005,15 +996,13 @@ class ACMEClient(object):
'''
if criterium['test_certificates'] == 'last':
chain = chain[-1:]
elif criterium['test_certificates'] == 'first':
chain = chain[:1]
for cert in chain:
try:
x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography.hazmat.backends.default_backend())
matches = True
if criterium['subject']:
for k, v in parse_name_field(criterium['subject']):
oid = cryptography_name_to_oid(k)
for k, v in crypto_utils.parse_name_field(criterium['subject']):
oid = crypto_utils.cryptography_name_to_oid(k)
value = to_native(v)
found = False
for attribute in x509.subject:
@@ -1024,8 +1013,8 @@ class ACMEClient(object):
matches = False
break
if criterium['issuer']:
for k, v in parse_name_field(criterium['issuer']):
oid = cryptography_name_to_oid(k)
for k, v in crypto_utils.parse_name_field(criterium['issuer']):
oid = crypto_utils.cryptography_name_to_oid(k)
value = to_native(v)
found = False
for attribute in x509.issuer:
@@ -1187,7 +1176,7 @@ def main():
force=dict(type='bool', default=False),
retrieve_all_alternates=dict(type='bool', default=False),
select_chain=dict(type='list', elements='dict', options=dict(
test_certificates=dict(type='str', default='all', choices=['first', 'last', 'all']),
test_certificates=dict(type='str', default='all', choices=['last', 'all']),
issuer=dict(type='dict'),
subject=dict(type='dict'),
subject_key_identifier=dict(type='str'),

View File

@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: acme_certificate_revoke
@@ -33,7 +38,7 @@ seealso:
- name: Automatic Certificate Management Environment (ACME)
description: The specification of the ACME protocol (RFC 8555).
link: https://tools.ietf.org/html/rfc8555
- module: community.crypto.acme_inspect
- module: acme_inspect
description: Allows to debug problems.
extends_documentation_fragment:
- community.crypto.acme
@@ -104,12 +109,12 @@ options:
EXAMPLES = '''
- name: Revoke certificate with account key
community.crypto.acme_certificate_revoke:
acme_certificate_revoke:
account_key_src: /etc/pki/cert/private/account.key
certificate: /etc/httpd/ssl/sample.com.crt
- name: Revoke certificate with certificate's private key
community.crypto.acme_certificate_revoke:
acme_certificate_revoke:
private_key_src: /etc/httpd/ssl/sample.com.key
certificate: /etc/httpd/ssl/sample.com.crt
'''
@@ -117,8 +122,6 @@ EXAMPLES = '''
RETURN = '''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.crypto.plugins.module_utils.acme import (
ModuleFailException,
ACMEAccount,
@@ -128,6 +131,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
get_default_argspec,
)
from ansible.module_utils.basic import AnsibleModule
def main():
argument_spec = get_default_argspec()

View File

@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: acme_challenge_cert_helper
@@ -15,7 +20,7 @@ author: "Felix Fontein (@felixfontein)"
short_description: Prepare certificates required for ACME challenges such as C(tls-alpn-01)
description:
- "Prepares certificates for ACME challenges such as C(tls-alpn-01)."
- "The raw data is provided by the M(community.crypto.acme_certificate) module, and needs to be
- "The raw data is provided by the M(acme_certificate) module, and needs to be
converted to a certificate to be used for challenge validation. This module
provides a simple way to generate the required certificates."
seealso:
@@ -37,8 +42,7 @@ options:
- tls-alpn-01
challenge_data:
description:
- "The C(challenge_data) entry provided by M(community.crypto.acme_certificate) for the
challenge."
- "The C(challenge_data) entry provided by M(acme_certificate) for the challenge."
type: dict
required: yes
private_key_src:
@@ -56,7 +60,7 @@ options:
EXAMPLES = '''
- name: Create challenges for a given CRT for sample.com
community.crypto.acme_certificate:
acme_certificate:
account_key_src: /etc/pki/cert/private/account.key
challenge: tls-alpn-01
csr: /etc/pki/cert/csr/sample.com.csr
@@ -64,7 +68,7 @@ EXAMPLES = '''
register: sample_com_challenge
- name: Create certificates for challenges
community.crypto.acme_challenge_cert_helper:
acme_challenge_cert_helper:
challenge: tls-alpn-01
challenge_data: "{{ item.value['tls-alpn-01'] }}"
private_key_src: /etc/pki/cert/key/sample.com.key
@@ -88,7 +92,7 @@ EXAMPLES = '''
loop: "{{ sample_com_challenge_certs.results }}"
- name: Create certificate for a given CSR for sample.com
community.crypto.acme_certificate:
acme_certificate:
account_key_src: /etc/pki/cert/private/account.key
challenge: tls-alpn-01
csr: /etc/pki/cert/csr/sample.com.csr
@@ -109,12 +113,14 @@ identifier_type:
or C(ip)."
returned: always
type: str
version_added: "2.8"
identifier:
description:
- "The identifier for the actual resource. Will be a domain name if the
type is C(dns), or an IP address if the type is C(ip)."
returned: always
type: str
version_added: "2.8"
challenge_certificate:
description:
- "The challenge certificate in PEM format."
@@ -130,19 +136,19 @@ regular_certificate:
type: str
'''
import base64
import datetime
import sys
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.acme import (
ModuleFailException,
read_file,
)
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes, to_text
import base64
import datetime
import sys
import traceback
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography

View File

@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: acme_inspect
@@ -18,14 +23,14 @@ description:
L(ACME protocol,https://tools.ietf.org/html/rfc8555),
which is supported by CAs such as L(Let's Encrypt,https://letsencrypt.org/)."
- "This module can be used to debug failed certificate request attempts,
for example when M(community.crypto.acme_certificate) fails or encounters a problem which
for example when M(acme_certificate) fails or encounters a problem which
you wish to investigate."
- "The module can also be used to directly access features of an ACME servers
which are not yet supported by the Ansible ACME modules."
notes:
- "The I(account_uri) option must be specified for properly authenticated
ACME v2 requests (except a C(new-account) request)."
- "Using the C(ansible) tool, M(community.crypto.acme_inspect) can be used to directly execute
- "Using the C(ansible) tool, M(acme_inspect) can be used to directly execute
ACME requests without the need of writing a playbook. For example, the
following command retrieves the ACME account with ID 1 from Let's Encrypt
(assuming C(/path/to/key) is the correct private account key):
@@ -80,14 +85,14 @@ options:
EXAMPLES = r'''
- name: Get directory
community.crypto.acme_inspect:
acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
method: directory-only
register: directory
- name: Create an account
community.crypto.acme_inspect:
acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
@@ -99,7 +104,7 @@ EXAMPLES = r'''
# if creation was successful
- name: Get account information
community.crypto.acme_inspect:
acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
@@ -108,7 +113,7 @@ EXAMPLES = r'''
method: get
- name: Update account contacts
community.crypto.acme_inspect:
acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
@@ -124,7 +129,7 @@ EXAMPLES = r'''
- mailto:me@example.com
- name: Create certificate order
community.crypto.acme_certificate:
acme_certificate:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
@@ -138,7 +143,7 @@ EXAMPLES = r'''
# the order URI.
- name: Get order information
community.crypto.acme_inspect:
acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
@@ -148,7 +153,7 @@ EXAMPLES = r'''
register: order
- name: Get first authz for order
community.crypto.acme_inspect:
acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
@@ -158,7 +163,7 @@ EXAMPLES = r'''
register: authz
- name: Get HTTP-01 challenge for authz
community.crypto.acme_inspect:
acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
@@ -168,7 +173,7 @@ EXAMPLES = r'''
register: http01challenge
- name: Activate HTTP-01 challenge manually
community.crypto.acme_inspect:
acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
@@ -240,11 +245,6 @@ output_json:
- ...
'''
import json
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native, to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.acme import (
ModuleFailException,
ACMEAccount,
@@ -252,6 +252,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
get_default_argspec,
)
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native, to_bytes
import json
def main():
argument_spec = get_default_argspec()
@@ -299,8 +304,7 @@ def main():
))
# See if we can parse the result as JSON
try:
# to_text() is needed only for Python 3.5 (and potentially 3.0 to 3.4 as well)
result['output_json'] = json.loads(to_text(data))
result['output_json'] = json.loads(data)
except Exception as dummy:
pass
# Fail if error was returned

View File

@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: certificate_complete_chain
@@ -18,7 +23,7 @@ description:
intermediate certificates from a given set of certificates, until it finds a root
certificate in another given set of certificates."
- "This can for example be used to find the root certificate for a certificate chain
returned by M(community.crypto.acme_certificate)."
returned by M(acme_certificate)."
- "Note that this module does I(not) check for validity of the chains. It only
checks that issuer and subject match, and that the signature is correct. It
ignores validity dates and key usage completely. If you need to verify that a
@@ -65,7 +70,7 @@ EXAMPLES = '''
# Given a leaf certificate for www.ansible.com and one or more intermediate
# certificates, finds the associated root certificate.
- name: Find root certificate
community.crypto.certificate_complete_chain:
certificate_complete_chain:
input_chain: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com-fullchain.pem') }}"
root_certificates:
- /etc/ca-certificates/
@@ -78,7 +83,7 @@ EXAMPLES = '''
# Given a leaf certificate for www.ansible.com, and a list of intermediate
# certificates, finds the associated root certificate.
- name: Find root certificate
community.crypto.certificate_complete_chain:
certificate_complete_chain:
input_chain: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com.pem') }}"
intermediate_certificates:
- /etc/ssl/csr/www.ansible.com-chain.pem

View File

@@ -8,6 +8,10 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ecs_certificate
@@ -332,9 +336,9 @@ options:
type: str
choices: [ P1Y, P2Y, P3Y ]
seealso:
- module: community.crypto.openssl_privatekey
- module: openssl_privatekey
description: Can be used to create private keys (both for certificates and accounts).
- module: community.crypto.openssl_csr
- module: openssl_csr
description: Can be used to create a Certificate Signing Request (CSR).
extends_documentation_fragment:
- community.crypto.ecs_credential
@@ -345,7 +349,7 @@ EXAMPLES = r'''
- name: Request a new certificate from Entrust with bare minimum parameters.
Will request a new certificate if current one is valid but within 30
days of expiry. If replacing an existing file in path, will back it up.
community.crypto.ecs_certificate:
ecs_certificate:
backup: true
path: /etc/ssl/crt/ansible.com.crt
full_chain_path: /etc/ssl/crt/ansible.com.chain.crt
@@ -363,7 +367,7 @@ EXAMPLES = r'''
of type EV_SSL. Otherwise, if there is an Entrust managed certificate
in path and it is within 63 days of expiration, request a renew of that
certificate.
community.crypto.ecs_certificate:
ecs_certificate:
path: /etc/ssl/crt/ansible.com.crt
csr: /etc/ssl/csr/ansible.com.csr
cert_type: EV_SSL
@@ -383,7 +387,7 @@ EXAMPLES = r'''
certificate is within 79 days of expiration, request a renew of that
certificate and save it in path. This can be used to "migrate" a
certificate to be Ansible managed.
community.crypto.ecs_certificate:
ecs_certificate:
path: /etc/ssl/crt/ansible.com.crt
csr: /etc/ssl/csr/ansible.com.csr
tracking_id: 2378915
@@ -395,7 +399,7 @@ EXAMPLES = r'''
entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key
- name: Force a reissue of the certificate specified by tracking_id.
community.crypto.ecs_certificate:
ecs_certificate:
path: /etc/ssl/crt/ansible.com.crt
force: true
tracking_id: 2378915
@@ -409,7 +413,7 @@ EXAMPLES = r'''
issued certificate will have it's Subject Distinguished Name use the
organization details associated with that client, rather than what is
in the CSR.
community.crypto.ecs_certificate:
ecs_certificate:
path: /etc/ssl/crt/ansible.com.crt
csr: /etc/ssl/csr/ansible.com.csr
client_id: 2
@@ -423,7 +427,7 @@ EXAMPLES = r'''
- name: Request a new certificate with a number of CSR parameters overridden
and tracking information
community.crypto.ecs_certificate:
ecs_certificate:
path: /etc/ssl/crt/ansible.com.crt
full_chain_path: /etc/ssl/crt/ansible.com.chain.crt
csr: /etc/ssl/csr/ansible.com.csr
@@ -514,24 +518,17 @@ from ansible_collections.community.crypto.plugins.module_utils.ecs.api import (
)
import datetime
import json
import os
import re
import time
import traceback
from distutils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.io import (
write_file,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_certificate,
)
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
@@ -603,7 +600,7 @@ class EcsCertificate(object):
self.ecs_client = None
if self.path and os.path.exists(self.path):
try:
self.cert = load_certificate(self.path, backend='cryptography')
self.cert = crypto_utils.load_certificate(self.path, backend='cryptography')
except Exception as dummy:
self.cert = None
# Instantiate the ECS client and then try a no-op connection to verify credentials are valid
@@ -775,20 +772,20 @@ class EcsCertificate(object):
if self.request_type != 'validate_only':
if self.backup:
self.backup_file = module.backup_local(self.path)
write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
crypto_utils.write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
if self.full_chain_path and self.cert_details.get('chainCerts'):
if self.backup:
self.backup_full_chain_file = module.backup_local(self.full_chain_path)
chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n'
write_file(module, to_bytes(chain_string), path=self.full_chain_path)
crypto_utils.write_file(module, to_bytes(chain_string), path=self.full_chain_path)
self.changed = True
# If there is no certificate present in path but a tracking ID was specified, save it to disk
elif not os.path.exists(self.path) and self.tracking_id:
if not module.check_mode:
write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
crypto_utils.write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
if self.full_chain_path and self.cert_details.get('chainCerts'):
chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n'
write_file(module, to_bytes(chain_string), path=self.full_chain_path)
crypto_utils.write_file(module, to_bytes(chain_string), path=self.full_chain_path)
self.changed = True
def dump(self):

View File

@@ -8,12 +8,16 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ecs_domain
author:
- Chris Trufan (@ctrufan)
version_added: '1.0.0'
short_description: Request validation of a domain with the Entrust Certificate Services (ECS) API
description:
- Request validation or re-validation of a domain with the Entrust Certificate Services (ECS) API.
@@ -74,9 +78,9 @@ options:
- Only allowed if C(verification_method=email)
type: str
seealso:
- module: community.crypto.x509_certificate
- module: openssl_certificate
description: Can be used to request certificates from ECS, with C(provider=entrust).
- module: community.crypto.ecs_certificate
- module: ecs_certificate
description: Can be used to request a Certificate from ECS using a verified domain.
extends_documentation_fragment:
- community.crypto.ecs_credential
@@ -85,7 +89,7 @@ extends_documentation_fragment:
EXAMPLES = r'''
- name: Request domain validation using email validation for client ID of 2.
community.crypto.ecs_domain:
ecs_domain:
domain_name: ansible.com
client_id: 2
verification_method: email
@@ -97,7 +101,7 @@ EXAMPLES = r'''
- name: Request domain validation using DNS. If domain is already valid,
request revalidation if expires within 90 days
community.crypto.ecs_domain:
ecs_domain:
domain_name: ansible.com
verification_method: dns
entrust_api_user: apiusername
@@ -107,7 +111,7 @@ EXAMPLES = r'''
- name: Request domain validation using web server validation, and revalidate
if fewer than 60 days remaining of EV eligibility.
community.crypto.ecs_domain:
ecs_domain:
domain_name: ansible.com
verification_method: web_server
entrust_api_user: apiusername
@@ -116,7 +120,7 @@ EXAMPLES = r'''
entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key
- name: Request domain validation using manual validation.
community.crypto.ecs_domain:
ecs_domain:
domain_name: ansible.com
verification_method: manual
entrust_api_user: apiusername
@@ -198,12 +202,6 @@ ev_days_remaining:
'''
import datetime
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.community.crypto.plugins.module_utils.ecs.api import (
ecs_client_argument_spec,
ECSClient,
@@ -211,6 +209,11 @@ from ansible_collections.community.crypto.plugins.module_utils.ecs.api import (
SessionConfigurationException,
)
import datetime
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
def calculate_days_remaining(expiry_date):
days_remaining = None

View File

@@ -6,6 +6,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
@@ -14,11 +17,10 @@ author: "John Westcott IV (@john-westcott-iv)"
short_description: Get a certificate from a host:port
description:
- Makes a secure connection and returns information about the presented certificate
- "The module can use the cryptography Python library, or the pyOpenSSL Python
- The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL
backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
- Support SNI only with python >= 2.7
backend was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
options:
host:
description:
@@ -126,7 +128,7 @@ version:
EXAMPLES = '''
- name: Get the cert from an RDP port
community.crypto.get_certificate:
get_certificate:
host: "1.2.3.4"
port: 3389
delegate_to: localhost
@@ -134,7 +136,7 @@ EXAMPLES = '''
register: cert
- name: Get a cert from an https port
community.crypto.get_certificate:
get_certificate:
host: "www.google.com"
port: 443
delegate_to: localhost
@@ -148,24 +150,20 @@ EXAMPLES = '''
expire_days: "{{ (( cert.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')) ).days }}"
'''
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from distutils.version import LooseVersion
from os.path import isfile
from socket import setdefaulttimeout, socket
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_OPTIONAL
import atexit
import base64
import datetime
import traceback
from distutils.version import LooseVersion
from os.path import isfile
from socket import create_connection, setdefaulttimeout, socket
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_oid_to_name,
cryptography_get_extensions_from_cert,
)
MINIMAL_PYOPENSSL_VERSION = '0.15'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
@@ -246,8 +244,7 @@ def main():
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
@@ -264,47 +261,37 @@ def main():
if not isfile(ca_cert):
module.fail_json(msg="ca_cert file does not exist")
if not HAS_CREATE_DEFAULT_CONTEXT:
# Python < 2.7.9
if proxy_host:
if proxy_host:
if not HAS_CREATE_DEFAULT_CONTEXT:
module.fail_json(msg='To use proxy_host, you must run the get_certificate module with Python 2.7 or newer.',
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
try:
# Note: get_server_certificate does not support SNI!
cert = get_server_certificate((host, port), ca_certs=ca_cert)
except Exception as e:
module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e))
else:
# Python >= 2.7.9
try:
if proxy_host:
connect = "CONNECT %s:%s HTTP/1.0\r\n\r\n" % (host, port)
sock = socket()
atexit.register(sock.close)
sock.connect((proxy_host, proxy_port))
sock.send(connect.encode())
sock.recv(8192)
else:
sock = create_connection((host, port))
atexit.register(sock.close)
connect = "CONNECT %s:%s HTTP/1.0\r\n\r\n" % (host, port)
sock = socket()
atexit.register(sock.close)
sock.connect((proxy_host, proxy_port))
sock.send(connect.encode())
sock.recv(8192)
ctx = create_default_context()
ctx.check_hostname = False
ctx.verify_mode = CERT_NONE
if ca_cert:
ctx = create_default_context(cafile=ca_cert)
ctx.check_hostname = False
ctx.verify_mode = CERT_REQUIRED
else:
ctx = create_default_context()
ctx.check_hostname = False
ctx.verify_mode = CERT_NONE
ctx.verify_mode = CERT_OPTIONAL
ctx.load_verify_locations(cafile=ca_cert)
cert = ctx.wrap_socket(sock, server_hostname=host).getpeercert(True)
cert = DER_cert_to_PEM_cert(cert)
except Exception as e:
if proxy_host:
module.fail_json(msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format(
proxy_host, proxy_port, host, port, e))
else:
module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e))
module.fail_json(msg="Failed to get cert from port with error: {0}".format(e))
else:
try:
cert = get_server_certificate((host, port), ca_certs=ca_cert)
except Exception as e:
module.fail_json(msg="Failed to get cert from port with error: {0}".format(e))
result['cert'] = cert
@@ -342,28 +329,28 @@ def main():
x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography_backend())
result['subject'] = {}
for attribute in x509.subject:
result['subject'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
result['subject'][crypto_utils.cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
result['expired'] = x509.not_valid_after < datetime.datetime.utcnow()
result['extensions'] = []
for dotted_number, entry in cryptography_get_extensions_from_cert(x509).items():
for dotted_number, entry in crypto_utils.cryptography_get_extensions_from_cert(x509).items():
oid = cryptography.x509.oid.ObjectIdentifier(dotted_number)
result['extensions'].append({
'critical': entry['critical'],
'asn1_data': base64.b64decode(entry['value']),
'name': cryptography_oid_to_name(oid, short=True),
'name': crypto_utils.cryptography_oid_to_name(oid, short=True),
})
result['issuer'] = {}
for attribute in x509.issuer:
result['issuer'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
result['issuer'][crypto_utils.cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
result['not_after'] = x509.not_valid_after.strftime('%Y%m%d%H%M%SZ')
result['not_before'] = x509.not_valid_before.strftime('%Y%m%d%H%M%SZ')
result['serial_number'] = x509.serial_number
result['signature_algorithm'] = cryptography_oid_to_name(x509.signature_algorithm_oid)
result['signature_algorithm'] = crypto_utils.cryptography_oid_to_name(x509.signature_algorithm_oid)
# We need the -1 offset to get the same values as pyOpenSSL
if x509.version == cryptography.x509.Version.v1:

View File

@@ -5,6 +5,9 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
@@ -69,12 +72,10 @@ options:
I(keyfile) is needed for most of the operations. Parameter
value is a string with the passphrase."
type: str
version_added: '1.0.0'
keysize:
description:
- "Sets the key size only if LUKS container does not exist."
type: int
version_added: '1.0.0'
new_keyfile:
description:
- "Adds additional key to given container on I(device).
@@ -97,7 +98,6 @@ options:
new keyslot will be used even if another keyslot already exists
for this passphrase."
type: str
version_added: '1.0.0'
remove_keyfile:
description:
- "Removes given key from the container on I(device). Does not
@@ -121,7 +121,6 @@ options:
container, the I(force_remove_last_key) option must be set
to C(yes)."
type: str
version_added: '1.0.0'
force_remove_last_key:
description:
- "If set to C(yes), allows removing the last key from a container."
@@ -138,36 +137,19 @@ options:
not specified."
- "This cannot be specified if I(type) is set to C(luks1)."
type: str
version_added: '1.0.0'
uuid:
description:
- "With this option user can identify the LUKS container by UUID."
- "Will only be used when I(device) and I(label) are not specified."
type: str
version_added: '1.0.0'
type:
description:
- "This option allow the user explicit define the format of LUKS
container that wants to work with. Options are C(luks1) or C(luks2)"
type: str
choices: [luks1, luks2]
version_added: '1.0.0'
cipher:
description:
- "This option allows the user to define the cipher specification
string for the LUKS container."
- "Will only be used on container creation."
- "For pre-2.6.10 kernels, use C(aes-plain) as they don't understand
the new cipher spec strings. To use ESSIV, use C(aes-cbc-essiv:sha256)."
type: str
version_added: '1.1.0'
hash:
description:
- "This option allows the user to specify the hash function used in LUKS
key setup scheme and volume key digest."
- "Will only be used on container creation."
type: str
version_added: '1.1.0'
requirements:
- "cryptsetup"
@@ -180,99 +162,92 @@ author: Jan Pokorny (@japokorn)
EXAMPLES = '''
- name: Create LUKS container (remains unchanged if it already exists)
community.crypto.luks_device:
- name: create LUKS container (remains unchanged if it already exists)
luks_device:
device: "/dev/loop0"
state: "present"
keyfile: "/vault/keyfile"
- name: Create LUKS container with a passphrase
community.crypto.luks_device:
- name: create LUKS container with a passphrase
luks_device:
device: "/dev/loop0"
state: "present"
passphrase: "foo"
- name: Create LUKS container with specific encryption
community.crypto.luks_device:
device: "/dev/loop0"
state: "present"
cipher: "aes"
hash: "sha256"
- name: (Create and) open the LUKS container; name it "mycrypt"
community.crypto.luks_device:
- name: (create and) open the LUKS container; name it "mycrypt"
luks_device:
device: "/dev/loop0"
state: "opened"
name: "mycrypt"
keyfile: "/vault/keyfile"
- name: Close the existing LUKS container "mycrypt"
community.crypto.luks_device:
- name: close the existing LUKS container "mycrypt"
luks_device:
state: "closed"
name: "mycrypt"
- name: Make sure LUKS container exists and is closed
community.crypto.luks_device:
- name: make sure LUKS container exists and is closed
luks_device:
device: "/dev/loop0"
state: "closed"
keyfile: "/vault/keyfile"
- name: Create container if it does not exist and add new key to it
community.crypto.luks_device:
- name: create container if it does not exist and add new key to it
luks_device:
device: "/dev/loop0"
state: "present"
keyfile: "/vault/keyfile"
new_keyfile: "/vault/keyfile2"
- name: Add new key to the LUKS container (container has to exist)
community.crypto.luks_device:
- name: add new key to the LUKS container (container has to exist)
luks_device:
device: "/dev/loop0"
keyfile: "/vault/keyfile"
new_keyfile: "/vault/keyfile2"
- name: Add new passphrase to the LUKS container
community.crypto.luks_device:
- name: add new passphrase to the LUKS container
luks_device:
device: "/dev/loop0"
keyfile: "/vault/keyfile"
new_passphrase: "foo"
- name: Remove existing keyfile from the LUKS container
community.crypto.luks_device:
- name: remove existing keyfile from the LUKS container
luks_device:
device: "/dev/loop0"
remove_keyfile: "/vault/keyfile2"
- name: Remove existing passphrase from the LUKS container
community.crypto.luks_device:
- name: remove existing passphrase from the LUKS container
luks_device:
device: "/dev/loop0"
remove_passphrase: "foo"
- name: Completely remove the LUKS container and its contents
community.crypto.luks_device:
- name: completely remove the LUKS container and its contents
luks_device:
device: "/dev/loop0"
state: "absent"
- name: Create a container with label
community.crypto.luks_device:
- name: create a container with label
luks_device:
device: "/dev/loop0"
state: "present"
keyfile: "/vault/keyfile"
label: personalLabelName
- name: Open the LUKS container based on label without device; name it "mycrypt"
community.crypto.luks_device:
- name: open the LUKS container based on label without device; name it "mycrypt"
luks_device:
label: "personalLabelName"
state: "opened"
name: "mycrypt"
keyfile: "/vault/keyfile"
- name: Close container based on UUID
community.crypto.luks_device:
- name: close container based on UUID
luks_device:
uuid: 03ecd578-fad4-4e6c-9348-842e3e8fa340
state: "closed"
name: "mycrypt"
- name: Create a container using luks2 format
community.crypto.luks_device:
- name: create a container using luks2 format
luks_device:
device: "/dev/loop0"
state: "present"
keyfile: "/vault/keyfile"
@@ -397,7 +372,7 @@ class CryptHandler(Handler):
result = self._run_command([self._cryptsetup_bin, 'isLuks', device])
return result[RETURN_CODE] == 0
def run_luks_create(self, device, keyfile, passphrase, keysize, cipher, hash_):
def run_luks_create(self, device, keyfile, passphrase, keysize):
# create a new luks container; use batch mode to auto confirm
luks_type = self._module.params['type']
label = self._module.params['label']
@@ -410,10 +385,6 @@ class CryptHandler(Handler):
luks_type = 'luks2'
if luks_type is not None:
options.extend(['--type', luks_type])
if cipher is not None:
options.extend(['--cipher', cipher])
if hash_ is not None:
options.extend(['--hash', hash_])
args = [self._cryptsetup_bin, 'luksFormat']
args.extend(options)
@@ -665,8 +636,6 @@ def run_module():
label=dict(type='str'),
uuid=dict(type='str'),
type=dict(type='str', choices=['luks1', 'luks2']),
cipher=dict(type='str'),
hash=dict(type='str'),
)
mutually_exclusive = [
@@ -711,10 +680,7 @@ def run_module():
crypt.run_luks_create(conditions.device,
module.params['keyfile'],
module.params['passphrase'],
module.params['keysize'],
module.params['cipher'],
module.params['hash'],
)
module.params['keysize'])
except ValueError as e:
module.fail_json(msg="luks_device error: %s" % e)
result['changed'] = True

View File

@@ -8,6 +8,12 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: openssh_cert
@@ -43,16 +49,8 @@ options:
signing_key:
description:
- The path to the private openssh key that is used for signing the public key in order to generate the certificate.
- If the private key is on a PKCS#11 token (I(pkcs11_provider)), set this to the path to the public key instead.
- Required if I(state) is C(present).
type: path
pkcs11_provider:
description:
- To use a signing key that resides on a PKCS#11 token, set this to the name (or full path) of the shared library to use with the token.
Usually C(libpkcs11.so).
- If this is set, I(signing_key) needs to point to a file containing the public key of the CA.
type: str
version_added: 1.1.0
public_key:
description:
- The path to the public key that will be signed with the signing key in order to generate the certificate.
@@ -124,8 +122,8 @@ extends_documentation_fragment: files
'''
EXAMPLES = '''
- name: Generate an OpenSSH user certificate that is valid forever and for all users
community.crypto.openssh_cert:
# Generate an OpenSSH user certificate that is valid forever and for all users
- openssh_cert:
type: user
signing_key: /path/to/private_key
public_key: /path/to/public_key.pub
@@ -135,8 +133,7 @@ EXAMPLES = '''
# Generate an OpenSSH host certificate that is valid for 32 weeks from now and will be regenerated
# if it is valid for less than 2 weeks from the time the module is being run
- name: Generate an OpenSSH host certificate with valid_from, valid_to and valid_at parameters
community.crypto.openssh_cert:
- openssh_cert:
type: host
signing_key: /path/to/private_key
public_key: /path/to/public_key.pub
@@ -145,8 +142,8 @@ EXAMPLES = '''
valid_to: +32w
valid_at: +2w
- name: Generate an OpenSSH host certificate that is valid forever and only for example.com and examplehost
community.crypto.openssh_cert:
# Generate an OpenSSH host certificate that is valid forever and only for example.com and examplehost
- openssh_cert:
type: host
signing_key: /path/to/private_key
public_key: /path/to/public_key.pub
@@ -157,8 +154,8 @@ EXAMPLES = '''
- example.com
- examplehost
- name: Generate an OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019
community.crypto.openssh_cert:
# Generate an OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019
- openssh_cert:
type: host
signing_key: /path/to/private_key
public_key: /path/to/public_key.pub
@@ -166,8 +163,8 @@ EXAMPLES = '''
valid_from: "2001-01-21"
valid_to: "2019-01-21"
- name: Generate an OpenSSH user Certificate with clear and force-command option
community.crypto.openssh_cert:
# Generate an OpenSSH user Certificate with clear and force-command option:
- openssh_cert:
type: user
signing_key: /path/to/private_key
public_key: /path/to/public_key.pub
@@ -178,16 +175,6 @@ EXAMPLES = '''
- "clear"
- "force-command=/tmp/bla/foo"
- name: Generate an OpenSSH user certificate using a PKCS#11 token
community.crypto.openssh_cert:
type: user
signing_key: /path/to/ca_public_key.pub
pkcs11_provider: libpkcs11.so
public_key: /path/to/public_key.pub
path: /path/to/certificate
valid_from: always
valid_to: forever
'''
RETURN = '''
@@ -209,20 +196,19 @@ info:
'''
import errno
import os
import errno
import re
import tempfile
from datetime import datetime
from datetime import MINYEAR, MAXYEAR
from shutil import copy2, rmtree
from shutil import copy2
from shutil import rmtree
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.crypto.plugins.module_utils.crypto import convert_relative_to_datetime
from ansible.module_utils._text import to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import convert_relative_to_datetime
class CertificateError(Exception):
pass
@@ -235,7 +221,6 @@ class Certificate(object):
self.force = module.params['force']
self.type = module.params['type']
self.signing_key = module.params['signing_key']
self.pkcs11_provider = module.params['pkcs11_provider']
self.public_key = module.params['public_key']
self.path = module.params['path']
self.identifier = module.params['identifier']
@@ -270,9 +255,6 @@ class Certificate(object):
'-s', self.signing_key
]
if self.pkcs11_provider:
args.extend(['-D', self.pkcs11_provider])
validity = ""
if not (self.valid_from == "always" and self.valid_to == "forever"):
@@ -547,7 +529,6 @@ def main():
force=dict(type='bool', default=False),
type=dict(type='str', choices=['host', 'user']),
signing_key=dict(type='path'),
pkcs11_provider=dict(type='str'),
public_key=dict(type='path'),
path=dict(type='path', required=True),
identifier=dict(type='str'),

View File

@@ -8,6 +8,12 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: openssh_keypair
@@ -53,7 +59,7 @@ options:
required: true
comment:
description:
- Provides a new comment to the public key.
- Provides a new comment to the public key. When checking if the key is in the correct state this will be ignored.
type: str
regenerate:
description:
@@ -87,31 +93,29 @@ options:
- full_idempotence
- always
default: partial_idempotence
version_added: '1.0.0'
notes:
- In case the ssh key is broken or password protected, the module will fail.
Set the I(force) option to C(yes) if you want to regenerate the keypair.
- In case the ssh key is broken or password protected, the module will fail. Set the I(force) option to C(yes) if you want to regenerate the keypair.
extends_documentation_fragment: files
'''
EXAMPLES = '''
- name: Generate an OpenSSH keypair with the default values (4096 bits, rsa)
community.crypto.openssh_keypair:
# Generate an OpenSSH keypair with the default values (4096 bits, rsa)
- openssh_keypair:
path: /tmp/id_ssh_rsa
- name: Generate an OpenSSH rsa keypair with a different size (2048 bits)
community.crypto.openssh_keypair:
# Generate an OpenSSH rsa keypair with a different size (2048 bits)
- openssh_keypair:
path: /tmp/id_ssh_rsa
size: 2048
- name: Force regenerate an OpenSSH keypair if it already exists
community.crypto.openssh_keypair:
# Force regenerate an OpenSSH keypair if it already exists
- openssh_keypair:
path: /tmp/id_ssh_rsa
force: True
- name: Generate an OpenSSH keypair with a different algorithm (dsa)
community.crypto.openssh_keypair:
# Generate an OpenSSH keypair with a different algorithm (dsa)
- openssh_keypair:
path: /tmp/id_ssh_dsa
type: dsa
'''
@@ -149,9 +153,9 @@ comment:
sample: test@comment
'''
import errno
import os
import stat
import errno
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native

View File

@@ -1 +0,0 @@
x509_certificate.py

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
x509_certificate_info.py

View File

@@ -0,0 +1,861 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: openssl_certificate_info
short_description: Provide information of OpenSSL X.509 certificates
description:
- This module allows one to query information on OpenSSL certificates.
- It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the
cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with
C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9
and will be removed in Ansible 2.13.
requirements:
- PyOpenSSL >= 0.15 or cryptography >= 1.6
author:
- Felix Fontein (@felixfontein)
- Yanis Guenane (@Spredzy)
- Markus Teufelberger (@MarkusTeufelberger)
options:
path:
description:
- Remote absolute path where the certificate file is loaded from.
- Either I(path) or I(content) must be specified, but not both.
type: path
content:
description:
- Content of the X.509 certificate in PEM format.
- Either I(path) or I(content) must be specified, but not both.
type: str
valid_at:
description:
- A dict of names mapping to time specifications. Every time specified here
will be checked whether the certificate is valid at this point. See the
C(valid_at) return value for informations on the result.
- Time can be specified either as relative time or as absolute timestamp.
- Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h), and ASN.1 TIME (i.e. pattern C(YYYYMMDDHHMMSSZ)).
Note that all timestamps will be treated as being in UTC.
type: dict
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
From that point on, only the C(cryptography) backend will be available.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
notes:
- All timestamp values are provided in ASN.1 TIME format, i.e. following the C(YYYYMMDDHHMMSSZ) pattern.
They are all in UTC.
seealso:
- module: openssl_certificate
'''
EXAMPLES = r'''
- name: Generate a Self Signed OpenSSL certificate
openssl_certificate:
path: /etc/ssl/crt/ansible.com.crt
privatekey_path: /etc/ssl/private/ansible.com.pem
csr_path: /etc/ssl/csr/ansible.com.csr
provider: selfsigned
# Get information on the certificate
- name: Get information on generated certificate
openssl_certificate_info:
path: /etc/ssl/crt/ansible.com.crt
register: result
- name: Dump information
debug:
var: result
# Check whether the certificate is valid or not valid at certain times, fail
# if this is not the case. The first task (openssl_certificate_info) collects
# the information, and the second task (assert) validates the result and
# makes the playbook fail in case something is not as expected.
- name: Test whether that certificate is valid tomorrow and/or in three weeks
openssl_certificate_info:
path: /etc/ssl/crt/ansible.com.crt
valid_at:
point_1: "+1d"
point_2: "+3w"
register: result
- name: Validate that certificate is valid tomorrow, but not in three weeks
assert:
that:
- result.valid_at.point_1 # valid in one day
- not result.valid_at.point_2 # not valid in three weeks
'''
RETURN = r'''
expired:
description: Whether the certificate is expired (i.e. C(notAfter) is in the past)
returned: success
type: bool
basic_constraints:
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
returned: success
type: list
elements: str
sample: "[CA:TRUE, pathlen:1]"
basic_constraints_critical:
description: Whether the C(basic_constraints) extension is critical.
returned: success
type: bool
extended_key_usage:
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
returned: success
type: list
elements: str
sample: "[Biometric Info, DVCS, Time Stamping]"
extended_key_usage_critical:
description: Whether the C(extended_key_usage) extension is critical.
returned: success
type: bool
extensions_by_oid:
description: Returns a dictionary for every extension OID
returned: success
type: dict
contains:
critical:
description: Whether the extension is critical.
returned: success
type: bool
value:
description: The Base64 encoded value (in DER format) of the extension
returned: success
type: str
sample: "MAMCAQU="
sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}'
key_usage:
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
returned: success
type: str
sample: "[Key Agreement, Data Encipherment]"
key_usage_critical:
description: Whether the C(key_usage) extension is critical.
returned: success
type: bool
subject_alt_name:
description: Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
returned: success
type: list
elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
subject_alt_name_critical:
description: Whether the C(subject_alt_name) extension is critical.
returned: success
type: bool
ocsp_must_staple:
description: C(yes) if the OCSP Must Staple extension is present, C(none) otherwise.
returned: success
type: bool
ocsp_must_staple_critical:
description: Whether the C(ocsp_must_staple) extension is critical.
returned: success
type: bool
issuer:
description:
- The certificate's issuer.
- Note that for repeated values, only the last one will be returned.
returned: success
type: dict
sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}'
issuer_ordered:
description: The certificate's issuer as an ordered list of tuples.
returned: success
type: list
elements: list
sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]'
version_added: "2.9"
subject:
description:
- The certificate's subject as a dictionary.
- Note that for repeated values, only the last one will be returned.
returned: success
type: dict
sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}'
subject_ordered:
description: The certificate's subject as an ordered list of tuples.
returned: success
type: list
elements: list
sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]'
version_added: "2.9"
not_after:
description: C(notAfter) date as ASN.1 TIME
returned: success
type: str
sample: 20190413202428Z
not_before:
description: C(notBefore) date as ASN.1 TIME
returned: success
type: str
sample: 20190331202428Z
public_key:
description: Certificate's public key in PEM format
returned: success
type: str
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
public_key_fingerprints:
description:
- Fingerprints of certificate's public key.
- For every hash algorithm available, the fingerprint is computed.
returned: success
type: dict
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
signature_algorithm:
description: The signature algorithm used to sign the certificate.
returned: success
type: str
sample: sha256WithRSAEncryption
serial_number:
description: The certificate's serial number.
returned: success
type: int
sample: 1234
version:
description: The certificate version.
returned: success
type: int
sample: 3
valid_at:
description: For every time stamp provided in the I(valid_at) option, a
boolean whether the certificate is valid at that point in time
or not.
returned: success
type: dict
subject_key_identifier:
description:
- The certificate's subject key identifier.
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
version_added: "2.9"
authority_key_identifier:
description:
- The certificate's authority key identifier.
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
version_added: "2.9"
authority_cert_issuer:
description:
- The certificate's authority cert issuer as a list of general names.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: list
elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
version_added: "2.9"
authority_cert_serial_number:
description:
- The certificate's authority cert serial number.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: int
sample: '12345'
version_added: "2.9"
ocsp_uri:
description: The OCSP responder URI, if included in the certificate. Will be
C(none) if no OCSP responder URI is included.
returned: success
type: str
version_added: "2.9"
'''
import abc
import binascii
import datetime
import os
import re
import traceback
from distutils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
MINIMAL_PYOPENSSL_VERSION = '0.15'
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
# OpenSSL 1.1.0 or newer
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
else:
# OpenSSL 1.0.x or older
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
except ImportError:
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
from cryptography import x509
from cryptography.hazmat.primitives import serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
else:
CRYPTOGRAPHY_FOUND = True
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
class CertificateInfo(crypto_utils.OpenSSLObject):
def __init__(self, module, backend):
super(CertificateInfo, self).__init__(
module.params['path'] or '',
'present',
False,
module.check_mode,
)
self.backend = backend
self.module = module
self.content = module.params['content']
if self.content is not None:
self.content = self.content.encode('utf-8')
self.valid_at = module.params['valid_at']
if self.valid_at:
for k, v in self.valid_at.items():
if not isinstance(v, string_types):
self.module.fail_json(
msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v))
)
self.valid_at[k] = crypto_utils.get_relative_time_option(v, 'valid_at.{0}'.format(k))
def generate(self):
# Empty method because crypto_utils.OpenSSLObject wants this
pass
def dump(self):
# Empty method because crypto_utils.OpenSSLObject wants this
pass
@abc.abstractmethod
def _get_signature_algorithm(self):
pass
@abc.abstractmethod
def _get_subject_ordered(self):
pass
@abc.abstractmethod
def _get_issuer_ordered(self):
pass
@abc.abstractmethod
def _get_version(self):
pass
@abc.abstractmethod
def _get_key_usage(self):
pass
@abc.abstractmethod
def _get_extended_key_usage(self):
pass
@abc.abstractmethod
def _get_basic_constraints(self):
pass
@abc.abstractmethod
def _get_ocsp_must_staple(self):
pass
@abc.abstractmethod
def _get_subject_alt_name(self):
pass
@abc.abstractmethod
def _get_not_before(self):
pass
@abc.abstractmethod
def _get_not_after(self):
pass
@abc.abstractmethod
def _get_public_key(self, binary):
pass
@abc.abstractmethod
def _get_subject_key_identifier(self):
pass
@abc.abstractmethod
def _get_authority_key_identifier(self):
pass
@abc.abstractmethod
def _get_serial_number(self):
pass
@abc.abstractmethod
def _get_all_extensions(self):
pass
@abc.abstractmethod
def _get_ocsp_uri(self):
pass
def get_info(self):
result = dict()
self.cert = crypto_utils.load_certificate(self.path, content=self.content, backend=self.backend)
result['signature_algorithm'] = self._get_signature_algorithm()
subject = self._get_subject_ordered()
issuer = self._get_issuer_ordered()
result['subject'] = dict()
for k, v in subject:
result['subject'][k] = v
result['subject_ordered'] = subject
result['issuer'] = dict()
for k, v in issuer:
result['issuer'][k] = v
result['issuer_ordered'] = issuer
result['version'] = self._get_version()
result['key_usage'], result['key_usage_critical'] = self._get_key_usage()
result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage()
result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints()
result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple()
result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name()
not_before = self._get_not_before()
not_after = self._get_not_after()
result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT)
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
result['expired'] = not_after < datetime.datetime.utcnow()
result['valid_at'] = dict()
if self.valid_at:
for k, v in self.valid_at.items():
result['valid_at'][k] = not_before <= v <= not_after
result['public_key'] = self._get_public_key(binary=False)
pk = self._get_public_key(binary=True)
result['public_key_fingerprints'] = crypto_utils.get_fingerprint_of_bytes(pk) if pk is not None else dict()
if self.backend != 'pyopenssl':
ski = self._get_subject_key_identifier()
if ski is not None:
ski = to_native(binascii.hexlify(ski))
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
result['subject_key_identifier'] = ski
aki, aci, acsn = self._get_authority_key_identifier()
if aki is not None:
aki = to_native(binascii.hexlify(aki))
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
result['authority_key_identifier'] = aki
result['authority_cert_issuer'] = aci
result['authority_cert_serial_number'] = acsn
result['serial_number'] = self._get_serial_number()
result['extensions_by_oid'] = self._get_all_extensions()
result['ocsp_uri'] = self._get_ocsp_uri()
return result
class CertificateInfoCryptography(CertificateInfo):
"""Validate the supplied cert, using the cryptography backend"""
def __init__(self, module):
super(CertificateInfoCryptography, self).__init__(module, 'cryptography')
def _get_signature_algorithm(self):
return crypto_utils.cryptography_oid_to_name(self.cert.signature_algorithm_oid)
def _get_subject_ordered(self):
result = []
for attribute in self.cert.subject:
result.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
return result
def _get_issuer_ordered(self):
result = []
for attribute in self.cert.issuer:
result.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
return result
def _get_version(self):
if self.cert.version == x509.Version.v1:
return 1
if self.cert.version == x509.Version.v3:
return 3
return "unknown"
def _get_key_usage(self):
try:
current_key_ext = self.cert.extensions.get_extension_for_class(x509.KeyUsage)
current_key_usage = current_key_ext.value
key_usage = dict(
digital_signature=current_key_usage.digital_signature,
content_commitment=current_key_usage.content_commitment,
key_encipherment=current_key_usage.key_encipherment,
data_encipherment=current_key_usage.data_encipherment,
key_agreement=current_key_usage.key_agreement,
key_cert_sign=current_key_usage.key_cert_sign,
crl_sign=current_key_usage.crl_sign,
encipher_only=False,
decipher_only=False,
)
if key_usage['key_agreement']:
key_usage.update(dict(
encipher_only=current_key_usage.encipher_only,
decipher_only=current_key_usage.decipher_only
))
key_usage_names = dict(
digital_signature='Digital Signature',
content_commitment='Non Repudiation',
key_encipherment='Key Encipherment',
data_encipherment='Data Encipherment',
key_agreement='Key Agreement',
key_cert_sign='Certificate Sign',
crl_sign='CRL Sign',
encipher_only='Encipher Only',
decipher_only='Decipher Only',
)
return sorted([
key_usage_names[name] for name, value in key_usage.items() if value
]), current_key_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_extended_key_usage(self):
try:
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
return sorted([
crypto_utils.cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
]), ext_keyusage_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_basic_constraints(self):
try:
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.BasicConstraints)
result = []
result.append('CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE'))
if ext_keyusage_ext.value.path_length is not None:
result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length))
return sorted(result), ext_keyusage_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_ocsp_must_staple(self):
try:
try:
# This only works with cryptography >= 2.1
tlsfeature_ext = self.cert.extensions.get_extension_for_class(x509.TLSFeature)
value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value
except AttributeError as dummy:
# Fallback for cryptography < 2.1
oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24")
tlsfeature_ext = self.cert.extensions.get_extension_for_oid(oid)
value = tlsfeature_ext.value.value == b"\x30\x03\x02\x01\x05"
return value, tlsfeature_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_subject_alt_name(self):
try:
san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
result = [crypto_utils.cryptography_decode_name(san) for san in san_ext.value]
return result, san_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_not_before(self):
return self.cert.not_valid_before
def _get_not_after(self):
return self.cert.not_valid_after
def _get_public_key(self, binary):
return self.cert.public_key().public_bytes(
serialization.Encoding.DER if binary else serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo
)
def _get_subject_key_identifier(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
return ext.value.digest
except cryptography.x509.ExtensionNotFound:
return None
def _get_authority_key_identifier(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [crypto_utils.cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
except cryptography.x509.ExtensionNotFound:
return None, None, None
def _get_serial_number(self):
return self.cert.serial_number
def _get_all_extensions(self):
return crypto_utils.cryptography_get_extensions_from_cert(self.cert)
def _get_ocsp_uri(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
for desc in ext.value:
if desc.access_method == x509.oid.AuthorityInformationAccessOID.OCSP:
if isinstance(desc.access_location, x509.UniformResourceIdentifier):
return desc.access_location.value
except x509.ExtensionNotFound as dummy:
pass
return None
class CertificateInfoPyOpenSSL(CertificateInfo):
"""validate the supplied certificate."""
def __init__(self, module):
super(CertificateInfoPyOpenSSL, self).__init__(module, 'pyopenssl')
def _get_signature_algorithm(self):
return to_text(self.cert.get_signature_algorithm())
def __get_name(self, name):
result = []
for sub in name.get_components():
result.append([crypto_utils.pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
return result
def _get_subject_ordered(self):
return self.__get_name(self.cert.get_subject())
def _get_issuer_ordered(self):
return self.__get_name(self.cert.get_issuer())
def _get_version(self):
# Version numbers in certs are off by one:
# v1: 0, v2: 1, v3: 2 ...
return self.cert.get_version() + 1
def _get_extension(self, short_name):
for extension_idx in range(0, self.cert.get_extension_count()):
extension = self.cert.get_extension(extension_idx)
if extension.get_short_name() == short_name:
result = [
crypto_utils.pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
]
return sorted(result), bool(extension.get_critical())
return None, False
def _get_key_usage(self):
return self._get_extension(b'keyUsage')
def _get_extended_key_usage(self):
return self._get_extension(b'extendedKeyUsage')
def _get_basic_constraints(self):
return self._get_extension(b'basicConstraints')
def _get_ocsp_must_staple(self):
extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())]
oms_ext = [
ext for ext in extensions
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
]
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
# Older versions of libssl don't know about OCSP Must Staple
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
if oms_ext:
return True, bool(oms_ext[0].get_critical())
else:
return None, False
def _normalize_san(self, san):
if san.startswith('IP Address:'):
san = 'IP:' + san[len('IP Address:'):]
if san.startswith('IP:'):
ip = compat_ipaddress.ip_address(san[3:])
san = 'IP:{0}'.format(ip.compressed)
return san
def _get_subject_alt_name(self):
for extension_idx in range(0, self.cert.get_extension_count()):
extension = self.cert.get_extension(extension_idx)
if extension.get_short_name() == b'subjectAltName':
result = [self._normalize_san(altname.strip()) for altname in
to_text(extension, errors='surrogate_or_strict').split(', ')]
return result, bool(extension.get_critical())
return None, False
def _get_not_before(self):
time_string = to_native(self.cert.get_notBefore())
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
def _get_not_after(self):
time_string = to_native(self.cert.get_notAfter())
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
def _get_public_key(self, binary):
try:
return crypto.dump_publickey(
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM,
self.cert.get_pubkey()
)
except AttributeError:
try:
# pyOpenSSL < 16.0:
bio = crypto._new_mem_buf()
if binary:
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.cert.get_pubkey()._pkey)
else:
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
if rc != 1:
crypto._raise_current_error()
return crypto._bio_to_string(bio)
except AttributeError:
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
def _get_subject_key_identifier(self):
# Won't be implemented
return None
def _get_authority_key_identifier(self):
# Won't be implemented
return None, None, None
def _get_serial_number(self):
return self.cert.get_serial_number()
def _get_all_extensions(self):
return crypto_utils.pyopenssl_get_extensions_from_cert(self.cert)
def _get_ocsp_uri(self):
for i in range(self.cert.get_extension_count()):
ext = self.cert.get_extension(i)
if ext.get_short_name() == b'authorityInfoAccess':
v = str(ext)
m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE)
if m:
return m.group(1)
return None
def main():
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path'),
content=dict(type='str'),
valid_at=dict(type='dict'),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
),
required_one_of=(
['path', 'content'],
),
mutually_exclusive=(
['path', 'content'],
),
supports_check_mode=True,
)
try:
if module.params['path'] is not None:
base_dir = os.path.dirname(module.params['path']) or '.'
if not os.path.isdir(base_dir):
module.fail_json(
name=base_dir,
msg='The directory %s does not exist or the file is not a directory' % base_dir
)
backend = module.params['select_crypto_backend']
if backend == 'auto':
# Detect what backend we can use
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# If cryptography is available we'll use it
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
# Fail if no backend has been found
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
if backend == 'pyopenssl':
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
try:
getattr(crypto.X509Req, 'get_extensions')
except AttributeError:
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
certificate = CertificateInfoPyOpenSSL(module)
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
certificate = CertificateInfoCryptography(module)
result = certificate.get_info()
module.exit_json(**result)
except crypto_utils.OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))
if __name__ == "__main__":
main()

View File

@@ -7,6 +7,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
@@ -20,10 +23,10 @@ description:
- "Please note that the module regenerates existing CSR if it doesn't match the module's
options, or if it seems to be corrupt. If you are concerned that this could overwrite
your existing CSR, consider using the I(backup) option."
- "The module can use the cryptography Python library, or the pyOpenSSL Python
- The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. Please note that the
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
requirements:
- Either cryptography >= 1.3
- Or pyOpenSSL >= 0.15
@@ -51,7 +54,6 @@ options:
- The content of the private key to use when signing the certificate signing request.
- Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both.
type: str
version_added: "1.0.0"
privatekey_passphrase:
description:
- The passphrase for the private key.
@@ -120,7 +122,7 @@ options:
- SAN extension to attach to the certificate signing request.
- This can either be a 'comma separated string' or a YAML list.
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName) and the ones specific to your CA).
C(otherName) and the ones specific to your CA)
- Note that if no SAN is specified, but a common name, the common
name will be added as a SAN except if C(useCommonNameForSAN) is
set to I(false).
@@ -183,43 +185,20 @@ options:
aliases: [ ocspMustStaple ]
ocsp_must_staple_critical:
description:
- Should the OCSP Must Staple extension be considered as critical.
- Should the OCSP Must Staple extension be considered as critical
- Note that according to the RFC, this extension should not be marked
as critical, as old clients not knowing about OCSP Must Staple
are required to reject such certificates
(see U(https://tools.ietf.org/html/rfc7633#section-4)).
type: bool
aliases: [ ocspMustStaple_critical ]
name_constraints_permitted:
description:
- For CA certificates, this specifies a list of identifiers which describe
subtrees of names that this CA is allowed to issue certificates for.
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName) and the ones specific to your CA).
type: list
elements: str
version_added: 1.1.0
name_constraints_excluded:
description:
- For CA certificates, this specifies a list of identifiers which describe
subtrees of names that this CA is *not* allowed to issue certificates for.
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName) and the ones specific to your CA).
type: list
elements: str
version_added: 1.1.0
name_constraints_critical:
description:
- Should the Name Constraints extension be considered as critical.
type: bool
version_added: 1.1.0
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
From that point on, only the C(cryptography) backend will be available.
type: str
default: auto
@@ -291,7 +270,6 @@ options:
- If set to C(yes), will return the (current or generated) CSR's content as I(csr).
type: bool
default: no
version_added: "1.0.0"
extends_documentation_fragment:
- files
notes:
@@ -299,35 +277,35 @@ notes:
keyUsage, extendedKeyUsage and basicConstraints only contain the requested values, whether
OCSP Must Staple is as requested, and if the request was signed by the given private key.
seealso:
- module: community.crypto.x509_certificate
- module: community.crypto.openssl_dhparam
- module: community.crypto.openssl_pkcs12
- module: community.crypto.openssl_privatekey
- module: community.crypto.openssl_publickey
- module: openssl_certificate
- module: openssl_dhparam
- module: openssl_pkcs12
- module: openssl_privatekey
- module: openssl_publickey
'''
EXAMPLES = r'''
- name: Generate an OpenSSL Certificate Signing Request
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
common_name: www.ansible.com
- name: Generate an OpenSSL Certificate Signing Request with an inline key
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_content: "{{ private_key_content }}"
common_name: www.ansible.com
- name: Generate an OpenSSL Certificate Signing Request with a passphrase protected private key
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
privatekey_passphrase: ansible
common_name: www.ansible.com
- name: Generate an OpenSSL Certificate Signing Request with Subject information
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
country_name: FR
@@ -336,13 +314,13 @@ EXAMPLES = r'''
common_name: www.ansible.com
- name: Generate an OpenSSL Certificate Signing Request with subjectAltName extension
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
subject_alt_name: 'DNS:www.ansible.com,DNS:m.ansible.com'
- name: Generate an OpenSSL CSR with subjectAltName extension with dynamic list
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
subject_alt_name: "{{ item.value | map('regex_replace', '^', 'DNS:') | list }}"
@@ -352,14 +330,14 @@ EXAMPLES = r'''
- m.ansible.com
- name: Force regenerate an OpenSSL Certificate Signing Request
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
force: yes
common_name: www.ansible.com
- name: Generate an OpenSSL Certificate Signing Request with special key usages
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
common_name: www.ansible.com
@@ -370,20 +348,11 @@ EXAMPLES = r'''
- clientAuth
- name: Generate an OpenSSL Certificate Signing Request with OCSP Must Staple
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
common_name: www.ansible.com
ocsp_must_staple: yes
- name: Generate an OpenSSL Certificate Signing Request for WinRM Certificate authentication
community.crypto.openssl_csr:
path: /etc/ssl/csr/winrm.auth.csr
privatekey_path: /etc/ssl/private/winrm.auth.pem
common_name: username
extended_key_usage:
- clientAuth
subject_alt_name: otherName:1.3.6.1.4.1.311.20.2.3;UTF8:username@localhost
'''
RETURN = r'''
@@ -435,20 +404,6 @@ ocsp_must_staple:
returned: changed or success
type: bool
sample: false
name_constraints_permitted:
description: List of permitted subtrees to sign certificates for.
returned: changed or success
type: list
elements: str
sample: ['email:.somedomain.com']
version_added: 1.1.0
name_constraints_excluded:
description: List of excluded subtrees the CA cannot sign certificates for.
returned: changed or success
type: list
elements: str
sample: ['email:.com']
version_added: 1.1.0
backup_file:
description: Name of backup file created.
returned: changed and if I(backup) is C(yes)
@@ -458,48 +413,19 @@ csr:
description: The (current or generated) CSR's content.
returned: if I(state) is C(present) and I(return_content) is C(yes)
type: str
version_added: "1.0.0"
version_added: "2.10"
'''
import abc
import binascii
import os
import traceback
from distutils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native, to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file_if_exists,
write_file,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_privatekey,
load_certificate_request,
parse_name_field,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_get_basic_constraints,
cryptography_get_name,
cryptography_name_to_oid,
cryptography_key_needs_digest_for_signing,
cryptography_parse_key_usage_params,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
pyopenssl_normalize_name_attribute,
pyopenssl_parse_name_constraints,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
MINIMAL_PYOPENSSL_VERSION = '0.15'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
@@ -542,11 +468,11 @@ else:
CRYPTOGRAPHY_MUST_STAPLE_VALUE = b"\x30\x03\x02\x01\x05"
class CertificateSigningRequestError(OpenSSLObjectError):
class CertificateSigningRequestError(crypto_utils.OpenSSLObjectError):
pass
class CertificateSigningRequestBase(OpenSSLObject):
class CertificateSigningRequestBase(crypto_utils.OpenSSLObject):
def __init__(self, module):
super(CertificateSigningRequestBase, self).__init__(
@@ -572,9 +498,6 @@ class CertificateSigningRequestBase(OpenSSLObject):
self.basicConstraints_critical = module.params['basic_constraints_critical']
self.ocspMustStaple = module.params['ocsp_must_staple']
self.ocspMustStaple_critical = module.params['ocsp_must_staple_critical']
self.name_constraints_permitted = module.params['name_constraints_permitted'] or []
self.name_constraints_excluded = module.params['name_constraints_excluded'] or []
self.name_constraints_critical = module.params['name_constraints_critical']
self.create_subject_key_identifier = module.params['create_subject_key_identifier']
self.subject_key_identifier = module.params['subject_key_identifier']
self.authority_key_identifier = module.params['authority_key_identifier']
@@ -602,7 +525,7 @@ class CertificateSigningRequestBase(OpenSSLObject):
]
if module.params['subject']:
self.subject = self.subject + parse_name_field(module.params['subject'])
self.subject = self.subject + crypto_utils.parse_name_field(module.params['subject'])
self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]]
if not self.subjectAltName and module.params['use_common_name_for_san']:
@@ -635,7 +558,7 @@ class CertificateSigningRequestBase(OpenSSLObject):
self.backup_file = module.backup_local(self.path)
if self.return_content:
self.csr_bytes = result
write_file(module, result)
crypto_utils.write_file(module, result)
self.changed = True
file_args = module.load_file_common_arguments(module.params)
@@ -678,15 +601,13 @@ class CertificateSigningRequestBase(OpenSSLObject):
'extendedKeyUsage': self.extendedKeyUsage,
'basicConstraints': self.basicConstraints,
'ocspMustStaple': self.ocspMustStaple,
'changed': self.changed,
'name_constraints_permitted': self.name_constraints_permitted,
'name_constraints_excluded': self.name_constraints_excluded,
'changed': self.changed
}
if self.backup_file:
result['backup_file'] = self.backup_file
if self.return_content:
if self.csr_bytes is None:
self.csr_bytes = load_file_if_exists(self.path, ignore_errors=True)
self.csr_bytes = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
result['csr'] = self.csr_bytes.decode('utf-8') if self.csr_bytes else None
return result
@@ -740,13 +661,6 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
usages = ', '.join(self.basicConstraints)
extensions.append(crypto.X509Extension(b"basicConstraints", self.basicConstraints_critical, usages.encode('ascii')))
if self.name_constraints_permitted or self.name_constraints_excluded:
usages = ', '.join(
['permitted;{0}'.format(name) for name in self.name_constraints_permitted] +
['excluded;{0}'.format(name) for name in self.name_constraints_excluded]
)
extensions.append(crypto.X509Extension(b"nameConstraints", self.name_constraints_critical, usages.encode('ascii')))
if self.ocspMustStaple:
extensions.append(crypto.X509Extension(OPENSSL_MUST_STAPLE_NAME, self.ocspMustStaple_critical, OPENSSL_MUST_STAPLE_VALUE))
@@ -761,14 +675,24 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
def _load_private_key(self):
try:
self.privatekey = load_privatekey(
self.privatekey = crypto_utils.load_privatekey(
path=self.privatekey_path,
content=self.privatekey_content,
passphrase=self.privatekey_passphrase
)
except OpenSSLBadPassphraseError as exc:
except crypto_utils.OpenSSLBadPassphraseError as exc:
raise CertificateSigningRequestError(exc)
def _normalize_san(self, san):
# Apparently OpenSSL returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
# although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
if san.startswith('IP Address:'):
san = 'IP:' + san[len('IP Address:'):]
if san.startswith('IP:'):
ip = compat_ipaddress.ip_address(san[3:])
san = 'IP:{0}'.format(ip.compressed)
return san
def _check_csr(self):
def _check_subject(csr):
subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in self.subject]
@@ -780,10 +704,10 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
def _check_subjectAltName(extensions):
altnames_ext = next((ext for ext in extensions if ext.get_short_name() == b'subjectAltName'), '')
altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
altnames = [self._normalize_san(altname.strip()) for altname in
to_text(altnames_ext, errors='surrogate_or_strict').split(',') if altname.strip()]
if self.subjectAltName:
if (set(altnames) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.subjectAltName]) or
if (set(altnames) != set([self._normalize_san(to_text(name)) for name in self.subjectAltName]) or
altnames_ext.get_critical() != self.subjectAltName_critical):
return False
else:
@@ -823,22 +747,6 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
def _check_basicConstraints(extensions):
return _check_keyUsage_(extensions, b'basicConstraints', self.basicConstraints, self.basicConstraints_critical)
def _check_nameConstraints(extensions):
nc_ext = next((ext for ext in extensions if ext.get_short_name() == b'nameConstraints'), '')
permitted, excluded = pyopenssl_parse_name_constraints(nc_ext)
if self.name_constraints_permitted or self.name_constraints_excluded:
if set(permitted) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_permitted]):
return False
if set(excluded) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_excluded]):
return False
if nc_ext.get_critical() != self.name_constraints_critical:
return False
else:
if permitted or excluded:
return False
return True
def _check_ocspMustStaple(extensions):
oms_ext = [ext for ext in extensions if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE]
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
@@ -853,7 +761,7 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
extensions = csr.get_extensions()
return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
_check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
_check_ocspMustStaple(extensions) and _check_nameConstraints(extensions))
_check_ocspMustStaple(extensions))
def _check_signature(csr):
try:
@@ -862,7 +770,7 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
return False
try:
csr = load_certificate_request(self.path, backend='pyopenssl')
csr = crypto_utils.load_certificate_request(self.path, backend='pyopenssl')
except Exception as dummy:
return False
@@ -882,27 +790,27 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
csr = cryptography.x509.CertificateSigningRequestBuilder()
try:
csr = csr.subject_name(cryptography.x509.Name([
cryptography.x509.NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject
cryptography.x509.NameAttribute(crypto_utils.cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject
]))
except ValueError as e:
raise CertificateSigningRequestError(e)
if self.subjectAltName:
csr = csr.add_extension(cryptography.x509.SubjectAlternativeName([
cryptography_get_name(name) for name in self.subjectAltName
crypto_utils.cryptography_get_name(name) for name in self.subjectAltName
]), critical=self.subjectAltName_critical)
if self.keyUsage:
params = cryptography_parse_key_usage_params(self.keyUsage)
params = crypto_utils.cryptography_parse_key_usage_params(self.keyUsage)
csr = csr.add_extension(cryptography.x509.KeyUsage(**params), critical=self.keyUsage_critical)
if self.extendedKeyUsage:
usages = [cryptography_name_to_oid(usage) for usage in self.extendedKeyUsage]
usages = [crypto_utils.cryptography_name_to_oid(usage) for usage in self.extendedKeyUsage]
csr = csr.add_extension(cryptography.x509.ExtendedKeyUsage(usages), critical=self.extendedKeyUsage_critical)
if self.basicConstraints:
params = {}
ca, path_length = cryptography_get_basic_constraints(self.basicConstraints)
ca, path_length = crypto_utils.cryptography_get_basic_constraints(self.basicConstraints)
csr = csr.add_extension(cryptography.x509.BasicConstraints(ca, path_length), critical=self.basicConstraints_critical)
if self.ocspMustStaple:
@@ -915,15 +823,6 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
critical=self.ocspMustStaple_critical
)
if self.name_constraints_permitted or self.name_constraints_excluded:
try:
csr = csr.add_extension(cryptography.x509.NameConstraints(
[cryptography_get_name(name) for name in self.name_constraints_permitted],
[cryptography_get_name(name) for name in self.name_constraints_excluded],
), critical=self.name_constraints_critical)
except TypeError as e:
raise OpenSSLObjectError('Error while parsing name constraint: {0}'.format(e))
if self.create_subject_key_identifier:
csr = csr.add_extension(
cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()),
@@ -935,14 +834,14 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
if self.authority_key_identifier is not None or self.authority_cert_issuer is not None or self.authority_cert_serial_number is not None:
issuers = None
if self.authority_cert_issuer is not None:
issuers = [cryptography_get_name(n) for n in self.authority_cert_issuer]
issuers = [crypto_utils.cryptography_get_name(n) for n in self.authority_cert_issuer]
csr = csr.add_extension(
cryptography.x509.AuthorityKeyIdentifier(self.authority_key_identifier, issuers, self.authority_cert_serial_number),
critical=False
)
digest = None
if cryptography_key_needs_digest_for_signing(self.privatekey):
if crypto_utils.cryptography_key_needs_digest_for_signing(self.privatekey):
if self.digest == 'sha256':
digest = cryptography.hazmat.primitives.hashes.SHA256()
elif self.digest == 'sha384':
@@ -982,7 +881,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
def _check_csr(self):
def _check_subject(csr):
subject = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.subject]
subject = [(crypto_utils.cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.subject]
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
return set(subject) == set(current_subject)
@@ -995,7 +894,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
def _check_subjectAltName(extensions):
current_altnames_ext = _find_extension(extensions, cryptography.x509.SubjectAlternativeName)
current_altnames = [str(altname) for altname in current_altnames_ext.value] if current_altnames_ext else []
altnames = [str(cryptography_get_name(altname)) for altname in self.subjectAltName] if self.subjectAltName else []
altnames = [str(crypto_utils.cryptography_get_name(altname)) for altname in self.subjectAltName] if self.subjectAltName else []
if set(altnames) != set(current_altnames):
return False
if altnames:
@@ -1009,7 +908,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
return current_keyusage_ext is None
elif current_keyusage_ext is None:
return False
params = cryptography_parse_key_usage_params(self.keyUsage)
params = crypto_utils.cryptography_parse_key_usage_params(self.keyUsage)
for param in params:
if getattr(current_keyusage_ext.value, '_' + param) != params[param]:
return False
@@ -1020,7 +919,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
def _check_extenededKeyUsage(extensions):
current_usages_ext = _find_extension(extensions, cryptography.x509.ExtendedKeyUsage)
current_usages = [str(usage) for usage in current_usages_ext.value] if current_usages_ext else []
usages = [str(cryptography_name_to_oid(usage)) for usage in self.extendedKeyUsage] if self.extendedKeyUsage else []
usages = [str(crypto_utils.cryptography_name_to_oid(usage)) for usage in self.extendedKeyUsage] if self.extendedKeyUsage else []
if set(current_usages) != set(usages):
return False
if usages:
@@ -1032,7 +931,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
bc_ext = _find_extension(extensions, cryptography.x509.BasicConstraints)
current_ca = bc_ext.value.ca if bc_ext else False
current_path_length = bc_ext.value.path_length if bc_ext else None
ca, path_length = cryptography_get_basic_constraints(self.basicConstraints)
ca, path_length = crypto_utils.cryptography_get_basic_constraints(self.basicConstraints)
# Check CA flag
if ca != current_ca:
return False
@@ -1066,19 +965,6 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
else:
return tlsfeature_ext is None
def _check_nameConstraints(extensions):
current_nc_ext = _find_extension(extensions, cryptography.x509.NameConstraints)
current_nc_perm = [str(altname) for altname in current_nc_ext.value.permitted_subtrees] if current_nc_ext else []
current_nc_excl = [str(altname) for altname in current_nc_ext.value.excluded_subtrees] if current_nc_ext else []
nc_perm = [str(cryptography_get_name(altname)) for altname in self.name_constraints_permitted]
nc_excl = [str(cryptography_get_name(altname)) for altname in self.name_constraints_excluded]
if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(current_nc_excl):
return False
if nc_perm or nc_excl:
if current_nc_ext.critical != self.name_constraints_critical:
return False
return True
def _check_subject_key_identifier(extensions):
ext = _find_extension(extensions, cryptography.x509.SubjectKeyIdentifier)
if self.create_subject_key_identifier or self.subject_key_identifier is not None:
@@ -1100,7 +986,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
aci = None
csr_aci = None
if self.authority_cert_issuer is not None:
aci = [str(cryptography_get_name(n)) for n in self.authority_cert_issuer]
aci = [str(crypto_utils.cryptography_get_name(n)) for n in self.authority_cert_issuer]
if ext.value.authority_cert_issuer is not None:
csr_aci = [str(n) for n in ext.value.authority_cert_issuer]
return (ext.value.key_identifier == self.authority_key_identifier
@@ -1114,7 +1000,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
_check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
_check_ocspMustStaple(extensions) and _check_subject_key_identifier(extensions) and
_check_authority_key_identifier(extensions) and _check_nameConstraints(extensions))
_check_authority_key_identifier(extensions))
def _check_signature(csr):
if not csr.is_signature_valid:
@@ -1132,7 +1018,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
return key_a == key_b
try:
csr = load_certificate_request(self.path, backend='cryptography')
csr = crypto_utils.load_certificate_request(self.path, backend='cryptography')
except Exception as dummy:
return False
@@ -1169,9 +1055,6 @@ def main():
basic_constraints_critical=dict(type='bool', default=False, aliases=['basicConstraints_critical']),
ocsp_must_staple=dict(type='bool', default=False, aliases=['ocspMustStaple']),
ocsp_must_staple_critical=dict(type='bool', default=False, aliases=['ocspMustStaple_critical']),
name_constraints_permitted=dict(type='list', elements='str'),
name_constraints_excluded=dict(type='list', elements='str'),
name_constraints_critical=dict(type='bool', default=False),
backup=dict(type='bool', default=False),
create_subject_key_identifier=dict(type='bool', default=False),
subject_key_identifier=dict(type='str'),
@@ -1191,9 +1074,8 @@ def main():
)
if module.params['version'] != 1:
module.deprecate('The version option will only support allowed values from community.crypto 2.0.0 on. '
'Currently, only the value 1 is allowed by RFC 2986',
version='2.0.0', collection_name='community.crypto')
module.deprecate('The version option will only support allowed values from Ansible 2.14 on. '
'Currently, only the value 1 is allowed by RFC 2986', version='2.14')
base_dir = os.path.dirname(module.params['path']) or '.'
if not os.path.isdir(base_dir):
@@ -1227,8 +1109,7 @@ def main():
except AttributeError:
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
csr = CertificateSigningRequestPyOpenSSL(module)
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
@@ -1254,7 +1135,7 @@ def main():
result = csr.dump()
module.exit_json(**result)
except OpenSSLObjectError as exc:
except crypto_utils.OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))

View File

@@ -8,6 +8,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
@@ -21,7 +24,7 @@ description:
cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with
C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9
and will be removed in community.crypto 2.0.0.
and will be removed in Ansible 2.13.
requirements:
- PyOpenSSL >= 0.15 or cryptography >= 1.3
author:
@@ -38,32 +41,31 @@ options:
- Content of the CSR file.
- Either I(path) or I(content) must be specified, but not both.
type: str
version_added: "1.0.0"
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
From that point on, only the C(cryptography) backend will be available.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
seealso:
- module: community.crypto.openssl_csr
- module: openssl_csr
'''
EXAMPLES = r'''
- name: Generate an OpenSSL Certificate Signing Request
community.crypto.openssl_csr:
openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
common_name: www.ansible.com
- name: Get information on the CSR
community.crypto.openssl_csr_info:
openssl_csr_info:
path: /etc/ssl/csr/www.ansible.com.csr
register: result
@@ -141,29 +143,6 @@ ocsp_must_staple_critical:
description: Whether the C(ocsp_must_staple) extension is critical.
returned: success
type: bool
name_constraints_permitted:
description: List of permitted subtrees to sign certificates for.
returned: success
type: list
elements: str
sample: ['email:.somedomain.com']
version_added: 1.1.0
name_constraints_excluded:
description:
- List of excluded subtrees the CA cannot sign certificates for.
- Is C(none) if extension is not present.
returned: success
type: list
elements: str
sample: ['email:.com']
version_added: 1.1.0
name_constraints_critical:
description:
- Whether the C(name_constraints) extension is critical.
- Is C(none) if extension is not present.
returned: success
type: bool
version_added: 1.1.0
subject:
description:
- The CSR's subject as a dictionary.
@@ -177,6 +156,7 @@ subject_ordered:
type: list
elements: list
sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]'
version_added: "2.9"
public_key:
description: CSR's public key in PEM format
returned: success
@@ -198,6 +178,7 @@ subject_key_identifier:
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
version_added: "2.9"
authority_key_identifier:
description:
- The CSR's authority key identifier.
@@ -206,6 +187,7 @@ authority_key_identifier:
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
version_added: "2.9"
authority_cert_issuer:
description:
- The CSR's authority cert issuer as a list of general names.
@@ -214,6 +196,7 @@ authority_cert_issuer:
type: list
elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
version_added: "2.9"
authority_cert_serial_number:
description:
- The CSR's authority cert serial number.
@@ -221,6 +204,7 @@ authority_cert_serial_number:
returned: success and if the pyOpenSSL backend is I(not) used
type: int
sample: '12345'
version_added: "2.9"
'''
@@ -228,34 +212,12 @@ import abc
import binascii
import os
import traceback
from distutils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_certificate_request,
get_fingerprint_of_bytes,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_decode_name,
cryptography_get_extensions_from_csr,
cryptography_oid_to_name,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
pyopenssl_get_extensions_from_csr,
pyopenssl_normalize_name,
pyopenssl_normalize_name_attribute,
pyopenssl_parse_name_constraints,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
MINIMAL_PYOPENSSL_VERSION = '0.15'
@@ -295,7 +257,7 @@ else:
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
class CertificateSigningRequestInfo(OpenSSLObject):
class CertificateSigningRequestInfo(crypto_utils.OpenSSLObject):
def __init__(self, module, backend):
super(CertificateSigningRequestInfo, self).__init__(
module.params['path'] or '',
@@ -310,11 +272,11 @@ class CertificateSigningRequestInfo(OpenSSLObject):
self.content = self.content.encode('utf-8')
def generate(self):
# Empty method because OpenSSLObject wants this
# Empty method because crypto_utils.OpenSSLObject wants this
pass
def dump(self):
# Empty method because OpenSSLObject wants this
# Empty method because crypto_utils.OpenSSLObject wants this
pass
@abc.abstractmethod
@@ -341,10 +303,6 @@ class CertificateSigningRequestInfo(OpenSSLObject):
def _get_subject_alt_name(self):
pass
@abc.abstractmethod
def _get_name_constraints(self):
pass
@abc.abstractmethod
def _get_public_key(self, binary):
pass
@@ -367,7 +325,7 @@ class CertificateSigningRequestInfo(OpenSSLObject):
def get_info(self):
result = dict()
self.csr = load_certificate_request(self.path, content=self.content, backend=self.backend)
self.csr = crypto_utils.load_certificate_request(self.path, content=self.content, backend=self.backend)
subject = self._get_subject_ordered()
result['subject'] = dict()
@@ -379,15 +337,10 @@ class CertificateSigningRequestInfo(OpenSSLObject):
result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints()
result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple()
result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name()
(
result['name_constraints_permitted'],
result['name_constraints_excluded'],
result['name_constraints_critical'],
) = self._get_name_constraints()
result['public_key'] = self._get_public_key(binary=False)
pk = self._get_public_key(binary=True)
result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict()
result['public_key_fingerprints'] = crypto_utils.get_fingerprint_of_bytes(pk) if pk is not None else dict()
if self.backend != 'pyopenssl':
ski = self._get_subject_key_identifier()
@@ -423,7 +376,7 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
def _get_subject_ordered(self):
result = []
for attribute in self.csr.subject:
result.append([cryptography_oid_to_name(attribute.oid), attribute.value])
result.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
return result
def _get_key_usage(self):
@@ -468,7 +421,7 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
try:
ext_keyusage_ext = self.csr.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
return sorted([
cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
crypto_utils.cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
]), ext_keyusage_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
@@ -502,20 +455,11 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
def _get_subject_alt_name(self):
try:
san_ext = self.csr.extensions.get_extension_for_class(x509.SubjectAlternativeName)
result = [cryptography_decode_name(san) for san in san_ext.value]
result = [crypto_utils.cryptography_decode_name(san) for san in san_ext.value]
return result, san_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_name_constraints(self):
try:
nc_ext = self.csr.extensions.get_extension_for_class(x509.NameConstraints)
permitted = [cryptography_decode_name(san) for san in nc_ext.value.permitted_subtrees or []]
excluded = [cryptography_decode_name(san) for san in nc_ext.value.excluded_subtrees or []]
return permitted, excluded, nc_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, None, False
def _get_public_key(self, binary):
return self.csr.public_key().public_bytes(
serialization.Encoding.DER if binary else serialization.Encoding.PEM,
@@ -534,13 +478,13 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
ext = self.csr.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
issuer = [crypto_utils.cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
except cryptography.x509.ExtensionNotFound:
return None, None, None
def _get_all_extensions(self):
return cryptography_get_extensions_from_csr(self.csr)
return crypto_utils.cryptography_get_extensions_from_csr(self.csr)
def _is_signature_valid(self):
return self.csr.is_signature_valid
@@ -555,7 +499,7 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
def __get_name(self, name):
result = []
for sub in name.get_components():
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
result.append([crypto_utils.pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
return result
def _get_subject_ordered(self):
@@ -565,7 +509,7 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
for extension in self.csr.get_extensions():
if extension.get_short_name() == short_name:
result = [
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
crypto_utils.pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
]
return sorted(result), bool(extension.get_critical())
return None, False
@@ -593,21 +537,24 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
else:
return None, False
def _normalize_san(self, san):
# apparently openssl returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
# although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
if san.startswith('IP Address:'):
san = 'IP:' + san[len('IP Address:'):]
if san.startswith('IP:'):
ip = compat_ipaddress.ip_address(san[3:])
san = 'IP:{0}'.format(ip.compressed)
return san
def _get_subject_alt_name(self):
for extension in self.csr.get_extensions():
if extension.get_short_name() == b'subjectAltName':
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
result = [self._normalize_san(altname.strip()) for altname in
to_text(extension, errors='surrogate_or_strict').split(', ')]
return result, bool(extension.get_critical())
return None, False
def _get_name_constraints(self):
for extension in self.csr.get_extensions():
if extension.get_short_name() == b'nameConstraints':
permitted, excluded = pyopenssl_parse_name_constraints(extension)
return permitted, excluded, bool(extension.get_critical())
return None, None, False
def _get_public_key(self, binary):
try:
return crypto.dump_publickey(
@@ -637,7 +584,7 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
return None, None, None
def _get_all_extensions(self):
return pyopenssl_get_extensions_from_csr(self.csr)
return crypto_utils.pyopenssl_get_extensions_from_csr(self.csr)
def _is_signature_valid(self):
try:
@@ -700,8 +647,7 @@ def main():
except AttributeError:
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
certificate = CertificateSigningRequestInfoPyOpenSSL(module)
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
@@ -711,7 +657,7 @@ def main():
result = certificate.get_info()
module.exit_json(**result)
except OpenSSLObjectError as exc:
except crypto_utils.OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))

View File

@@ -7,6 +7,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
@@ -64,35 +67,33 @@ options:
type: str
default: auto
choices: [ auto, cryptography, openssl ]
version_added: "1.0.0"
return_content:
description:
- If set to C(yes), will return the (current or generated) DH params' content as I(dhparams).
type: bool
default: no
version_added: "1.0.0"
extends_documentation_fragment:
- files
seealso:
- module: community.crypto.x509_certificate
- module: community.crypto.openssl_csr
- module: community.crypto.openssl_pkcs12
- module: community.crypto.openssl_privatekey
- module: community.crypto.openssl_publickey
- module: openssl_certificate
- module: openssl_csr
- module: openssl_pkcs12
- module: openssl_privatekey
- module: openssl_publickey
'''
EXAMPLES = r'''
- name: Generate Diffie-Hellman parameters with the default size (4096 bits)
community.crypto.openssl_dhparam:
openssl_dhparam:
path: /etc/ssl/dhparams.pem
- name: Generate DH Parameters with a different size (2048 bits)
community.crypto.openssl_dhparam:
openssl_dhparam:
path: /etc/ssl/dhparams.pem
size: 2048
- name: Force regenerate an DH parameters if they already exist
community.crypto.openssl_dhparam:
openssl_dhparam:
path: /etc/ssl/dhparams.pem
force: yes
'''
@@ -117,7 +118,7 @@ dhparams:
description: The (current or generated) DH params' content.
returned: if I(state) is C(present) and I(return_content) is C(yes)
type: str
version_added: "1.0.0"
version_added: "2.10"
'''
import abc
@@ -125,20 +126,12 @@ import os
import re
import tempfile
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file_if_exists,
write_file,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import (
count_bits,
)
MINIMAL_CRYPTOGRAPHY_VERSION = '2.0'
@@ -234,7 +227,7 @@ class DHParameterBase(object):
if self.backup_file:
result['backup_file'] = self.backup_file
if self.return_content:
content = load_file_if_exists(self.path, ignore_errors=True)
content = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
result['dhparams'] = content.decode('utf-8') if content else None
return result
@@ -323,7 +316,7 @@ class DHParameterCryptography(DHParameterBase):
# Write result
if self.backup:
self.backup_file = module.backup_local(self.path)
write_file(module, result)
crypto_utils.write_file(module, result)
def _check_params_valid(self, module):
"""Check if the params are in the correct state"""
@@ -335,7 +328,7 @@ class DHParameterCryptography(DHParameterBase):
except Exception as dummy:
return False
# Check parameters
bits = count_bits(params.parameter_numbers().p)
bits = crypto_utils.count_bits(params.parameter_numbers().p)
return bits == self.size

View File

@@ -7,6 +7,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
@@ -95,20 +98,19 @@ options:
- If set to C(yes), will return the (current or generated) PKCS#12's content as I(pkcs12).
type: bool
default: no
version_added: "1.0.0"
extends_documentation_fragment:
- files
seealso:
- module: community.crypto.x509_certificate
- module: community.crypto.openssl_csr
- module: community.crypto.openssl_dhparam
- module: community.crypto.openssl_privatekey
- module: community.crypto.openssl_publickey
- module: openssl_certificate
- module: openssl_csr
- module: openssl_dhparam
- module: openssl_privatekey
- module: openssl_publickey
'''
EXAMPLES = r'''
- name: Generate PKCS#12 file
community.crypto.openssl_pkcs12:
openssl_pkcs12:
action: export
path: /opt/certs/ansible.p12
friendly_name: raclette
@@ -118,7 +120,7 @@ EXAMPLES = r'''
state: present
- name: Change PKCS#12 file permission
community.crypto.openssl_pkcs12:
openssl_pkcs12:
action: export
path: /opt/certs/ansible.p12
friendly_name: raclette
@@ -129,7 +131,7 @@ EXAMPLES = r'''
mode: '0600'
- name: Regen PKCS#12 file
community.crypto.openssl_pkcs12:
openssl_pkcs12:
action: export
src: /opt/certs/ansible.p12
path: /opt/certs/ansible.p12
@@ -142,14 +144,14 @@ EXAMPLES = r'''
force: yes
- name: Dump/Parse PKCS#12 file
community.crypto.openssl_pkcs12:
openssl_pkcs12:
action: parse
src: /opt/certs/ansible.p12
path: /opt/certs/ansible.pem
state: present
- name: Remove PKCS#12 file
community.crypto.openssl_pkcs12:
openssl_pkcs12:
path: /opt/certs/ansible.p12
state: absent
'''
@@ -174,33 +176,14 @@ pkcs12:
description: The (current or generated) PKCS#12's content Base64 encoded.
returned: if I(state) is C(present) and I(return_content) is C(yes)
type: str
version_added: "1.0.0"
version_added: "2.10"
'''
import base64
import os
import stat
import os
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file_if_exists,
write_file,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_privatekey,
load_certificate,
)
PYOPENSSL_IMP_ERR = None
try:
from OpenSSL import crypto
@@ -210,12 +193,16 @@ except ImportError:
else:
pyopenssl_found = True
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils._text import to_bytes, to_native
class PkcsError(OpenSSLObjectError):
class PkcsError(crypto_utils.OpenSSLObjectError):
pass
class Pkcs(OpenSSLObject):
class Pkcs(crypto_utils.OpenSSLObject):
def __init__(self, module):
super(Pkcs, self).__init__(
@@ -252,10 +239,11 @@ class Pkcs(OpenSSLObject):
def _check_pkey_passphrase():
if self.privatekey_passphrase:
try:
load_privatekey(self.privatekey_path, self.privatekey_passphrase)
crypto_utils.load_privatekey(self.privatekey_path,
self.privatekey_passphrase)
except crypto.Error:
return False
except OpenSSLBadPassphraseError:
except crypto_utils.OpenSSLBadPassphraseError:
return False
return True
@@ -319,7 +307,7 @@ class Pkcs(OpenSSLObject):
result['backup_file'] = self.backup_file
if self.return_content:
if self.pkcs12_bytes is None:
self.pkcs12_bytes = load_file_if_exists(self.path, ignore_errors=True)
self.pkcs12_bytes = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
result['pkcs12'] = base64.b64encode(self.pkcs12_bytes) if self.pkcs12_bytes else None
return result
@@ -329,20 +317,24 @@ class Pkcs(OpenSSLObject):
self.pkcs12 = crypto.PKCS12()
if self.other_certificates:
other_certs = [load_certificate(other_cert) for other_cert
other_certs = [crypto_utils.load_certificate(other_cert) for other_cert
in self.other_certificates]
self.pkcs12.set_ca_certificates(other_certs)
if self.certificate_path:
self.pkcs12.set_certificate(load_certificate(self.certificate_path))
self.pkcs12.set_certificate(crypto_utils.load_certificate(
self.certificate_path))
if self.friendly_name:
self.pkcs12.set_friendlyname(to_bytes(self.friendly_name))
if self.privatekey_path:
try:
self.pkcs12.set_privatekey(load_privatekey(self.privatekey_path, self.privatekey_passphrase))
except OpenSSLBadPassphraseError as exc:
self.pkcs12.set_privatekey(crypto_utils.load_privatekey(
self.privatekey_path,
self.privatekey_passphrase)
)
except crypto_utils.OpenSSLBadPassphraseError as exc:
raise PkcsError(exc)
return self.pkcs12.export(self.passphrase, self.iter_size, self.maciter_size)
@@ -380,7 +372,7 @@ class Pkcs(OpenSSLObject):
"""Write the PKCS#12 file."""
if self.backup:
self.backup_file = module.backup_local(self.path)
write_file(module, content, mode)
crypto_utils.write_file(module, content, mode)
if self.return_content:
self.pkcs12_bytes = content
@@ -467,7 +459,7 @@ def main():
result['mode'] = file_mode
module.exit_json(**result)
except OpenSSLObjectError as exc:
except crypto_utils.OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))

View File

@@ -7,6 +7,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
@@ -24,7 +27,7 @@ description:
(or specify none), change the keysize, etc., the private key will be
regenerated. If you are concerned that this could **overwrite your private key**,
consider using the I(backup) option."
- "The module can use the cryptography Python library, or the pyOpenSSL Python
- The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. Please note that the
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
@@ -110,7 +113,7 @@ options:
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
From that point on, only the C(cryptography) backend will be available.
type: str
default: auto
@@ -129,7 +132,6 @@ options:
type: str
default: auto_ignore
choices: [ pkcs1, pkcs8, raw, auto, auto_ignore ]
version_added: '1.0.0'
format_mismatch:
description:
- Determines behavior of the module if the format of a private key does not match the expected format, but all
@@ -140,7 +142,6 @@ options:
type: str
default: regenerate
choices: [ regenerate, convert ]
version_added: '1.0.0'
backup:
description:
- Create a backup file including a timestamp so you can get
@@ -154,7 +155,6 @@ options:
value is treated appropriately and not accidentally written to logs etc.! Use with care!
type: bool
default: no
version_added: '1.0.0'
regenerate:
description:
- Allows to configure in which situations the module is allowed to regenerate private keys.
@@ -187,40 +187,39 @@ options:
- full_idempotence
- always
default: full_idempotence
version_added: '1.0.0'
extends_documentation_fragment:
- files
seealso:
- module: community.crypto.x509_certificate
- module: community.crypto.openssl_csr
- module: community.crypto.openssl_dhparam
- module: community.crypto.openssl_pkcs12
- module: community.crypto.openssl_publickey
- module: openssl_certificate
- module: openssl_csr
- module: openssl_dhparam
- module: openssl_pkcs12
- module: openssl_publickey
'''
EXAMPLES = r'''
- name: Generate an OpenSSL private key with the default values (4096 bits, RSA)
community.crypto.openssl_privatekey:
openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
- name: Generate an OpenSSL private key with the default values (4096 bits, RSA) and a passphrase
community.crypto.openssl_privatekey:
openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
passphrase: ansible
cipher: aes256
- name: Generate an OpenSSL private key with a different size (2048 bits)
community.crypto.openssl_privatekey:
openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
size: 2048
- name: Force regenerate an OpenSSL private key if it already exists
community.crypto.openssl_privatekey:
openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
force: yes
- name: Generate an OpenSSL private key with a different algorithm (DSA)
community.crypto.openssl_privatekey:
openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
type: DSA
'''
@@ -270,45 +269,15 @@ privatekey:
- Will be Base64-encoded if the key is in raw format.
returned: if I(state) is C(present) and I(return_content) is C(yes)
type: str
version_added: '1.0.0'
version_added: "2.10"
'''
import abc
import base64
import os
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file_if_exists,
write_file,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X25519_FULL,
CRYPTOGRAPHY_HAS_X448,
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_privatekey,
get_fingerprint,
get_fingerprint_of_bytes,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
identify_private_key_format,
)
MINIMAL_PYOPENSSL_VERSION = '0.6'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
@@ -340,12 +309,24 @@ except ImportError:
else:
CRYPTOGRAPHY_FOUND = True
from ansible_collections.community.crypto.plugins.module_utils.crypto import (
CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X25519_FULL,
CRYPTOGRAPHY_HAS_X448,
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,
)
class PrivateKeyError(OpenSSLObjectError):
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils._text import to_native, to_bytes
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
class PrivateKeyError(crypto_utils.OpenSSLObjectError):
pass
class PrivateKeyBase(OpenSSLObject):
class PrivateKeyBase(crypto_utils.OpenSSLObject):
def __init__(self, module):
super(PrivateKeyBase, self).__init__(
@@ -403,7 +384,7 @@ class PrivateKeyBase(OpenSSLObject):
privatekey_data = self._get_private_key_data()
if self.return_content:
self.privatekey_bytes = privatekey_data
write_file(module, privatekey_data, 0o600)
crypto_utils.write_file(module, privatekey_data, 0o600)
self.changed = True
elif not self.check(module, perms_required=False, ignore_conversion=False):
# Convert
@@ -413,7 +394,7 @@ class PrivateKeyBase(OpenSSLObject):
privatekey_data = self._get_private_key_data()
if self.return_content:
self.privatekey_bytes = privatekey_data
write_file(module, privatekey_data, 0o600)
crypto_utils.write_file(module, privatekey_data, 0o600)
self.changed = True
self.fingerprint = self._get_fingerprint()
@@ -491,9 +472,9 @@ class PrivateKeyBase(OpenSSLObject):
result['backup_file'] = self.backup_file
if self.return_content:
if self.privatekey_bytes is None:
self.privatekey_bytes = load_file_if_exists(self.path, ignore_errors=True)
self.privatekey_bytes = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
if self.privatekey_bytes:
if identify_private_key_format(self.privatekey_bytes) == 'raw':
if crypto_utils.identify_private_key_format(self.privatekey_bytes) == 'raw':
result['privatekey'] = base64.b64encode(self.privatekey_bytes)
else:
result['privatekey'] = self.privatekey_bytes.decode('utf-8')
@@ -531,8 +512,8 @@ class PrivateKeyPyOpenSSL(PrivateKeyBase):
"""Make sure that the private key has been loaded."""
if self.privatekey is None:
try:
self.privatekey = privatekey = load_privatekey(self.path, self.passphrase)
except OpenSSLBadPassphraseError as exc:
self.privatekey = privatekey = crypto_utils.load_privatekey(self.path, self.passphrase)
except crypto_utils.OpenSSLBadPassphraseError as exc:
raise PrivateKeyError(exc)
def _get_private_key_data(self):
@@ -544,11 +525,11 @@ class PrivateKeyPyOpenSSL(PrivateKeyBase):
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.privatekey)
def _get_fingerprint(self):
return get_fingerprint(self.path, self.passphrase)
return crypto_utils.get_fingerprint(self.path, self.passphrase)
def _check_passphrase(self):
try:
load_privatekey(self.path, self.passphrase)
crypto_utils.load_privatekey(self.path, self.passphrase)
return True
except Exception as dummy:
return False
@@ -737,7 +718,7 @@ class PrivateKeyCryptography(PrivateKeyBase):
with open(self.path, 'rb') as f:
data = f.read()
# Interpret bytes depending on format.
format = identify_private_key_format(data)
format = crypto_utils.identify_private_key_format(data)
if format == 'raw':
if len(data) == 56 and CRYPTOGRAPHY_HAS_X448:
return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(data)
@@ -772,13 +753,13 @@ class PrivateKeyCryptography(PrivateKeyBase):
cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo
)
# Get fingerprints of public_key_bytes
return get_fingerprint_of_bytes(public_key_bytes)
return crypto_utils.get_fingerprint_of_bytes(public_key_bytes)
def _check_passphrase(self):
try:
with open(self.path, 'rb') as f:
data = f.read()
format = identify_private_key_format(data)
format = crypto_utils.identify_private_key_format(data)
if format == 'raw':
# Raw keys cannot be encrypted. To avoid incompatibilities, we try to
# actually load the key (and return False when this fails).
@@ -825,7 +806,7 @@ class PrivateKeyCryptography(PrivateKeyBase):
try:
with open(self.path, 'rb') as f:
content = f.read()
format = identify_private_key_format(content)
format = crypto_utils.identify_private_key_format(content)
return format == self._get_wanted_format()
except Exception as dummy:
return False
@@ -917,8 +898,7 @@ def main():
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
private_key = PrivateKeyPyOpenSSL(module)
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
@@ -945,7 +925,7 @@ def main():
result = private_key.dump()
module.exit_json(**result)
except OpenSSLObjectError as exc:
except crypto_utils.OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))

View File

@@ -8,6 +8,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
@@ -23,7 +26,7 @@ description:
cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with
C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9
and will be removed in community.crypto 2.0.0.
and will be removed in Ansible 2.13.
requirements:
- PyOpenSSL >= 0.15 or cryptography >= 1.2.3
author:
@@ -39,7 +42,6 @@ options:
- Content of the private key file.
- Either I(path) or I(content) must be specified, but not both.
type: str
version_added: '1.0.0'
passphrase:
description:
- The passphrase for the private key.
@@ -59,23 +61,23 @@ options:
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
From that point on, only the C(cryptography) backend will be available.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
seealso:
- module: community.crypto.openssl_privatekey
- module: openssl_privatekey
'''
EXAMPLES = r'''
- name: Generate an OpenSSL private key with the default values (4096 bits, RSA)
community.crypto.openssl_privatekey:
openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
- name: Get information on generated key
community.crypto.openssl_privatekey_info:
openssl_privatekey_info:
path: /etc/ssl/private/ansible.com.pem
register: result
@@ -137,32 +139,12 @@ private_data:
import abc
import os
import traceback
from distutils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X448,
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_privatekey,
get_fingerprint_of_bytes,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import (
binary_exp_mod,
quick_is_not_prime,
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
MINIMAL_PYOPENSSL_VERSION = '0.15'
@@ -182,6 +164,26 @@ try:
import cryptography
from cryptography.hazmat.primitives import serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
try:
import cryptography.hazmat.primitives.asymmetric.x25519
CRYPTOGRAPHY_HAS_X25519 = True
except ImportError:
CRYPTOGRAPHY_HAS_X25519 = False
try:
import cryptography.hazmat.primitives.asymmetric.x448
CRYPTOGRAPHY_HAS_X448 = True
except ImportError:
CRYPTOGRAPHY_HAS_X448 = False
try:
import cryptography.hazmat.primitives.asymmetric.ed25519
CRYPTOGRAPHY_HAS_ED25519 = True
except ImportError:
CRYPTOGRAPHY_HAS_ED25519 = False
try:
import cryptography.hazmat.primitives.asymmetric.ed448
CRYPTOGRAPHY_HAS_ED448 = True
except ImportError:
CRYPTOGRAPHY_HAS_ED448 = False
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
@@ -250,13 +252,13 @@ def _check_dsa_consistency(key_public_data, key_private_data):
if (p - 1) % q != 0:
return False
# Check that g**q mod p == 1
if binary_exp_mod(g, q, p) != 1:
if crypto_utils.binary_exp_mod(g, q, p) != 1:
return False
# Check whether g**x mod p == y
if binary_exp_mod(g, x, p) != y:
if crypto_utils.binary_exp_mod(g, x, p) != y:
return False
# Check (quickly) whether p or q are not primes
if quick_is_not_prime(q) or quick_is_not_prime(p):
if crypto_utils.quick_is_not_prime(q) or crypto_utils.quick_is_not_prime(p):
return False
return True
@@ -316,7 +318,7 @@ def _is_cryptography_key_consistent(key, key_public_data, key_private_data):
return None
class PrivateKeyInfo(OpenSSLObject):
class PrivateKeyInfo(crypto_utils.OpenSSLObject):
def __init__(self, module, backend):
super(PrivateKeyInfo, self).__init__(
module.params['path'] or '',
@@ -332,11 +334,11 @@ class PrivateKeyInfo(OpenSSLObject):
self.return_private_key_data = module.params['return_private_key_data']
def generate(self):
# Empty method because OpenSSLObject wants this
# Empty method because crypto_utils.OpenSSLObject wants this
pass
def dump(self):
# Empty method because OpenSSLObject wants this
# Empty method because crypto_utils.OpenSSLObject wants this
pass
@abc.abstractmethod
@@ -368,19 +370,19 @@ class PrivateKeyInfo(OpenSSLObject):
except (IOError, OSError) as exc:
self.module.fail_json(msg=to_native(exc), **result)
try:
self.key = load_privatekey(
self.key = crypto_utils.load_privatekey(
path=None,
content=priv_key_detail,
passphrase=to_bytes(self.passphrase) if self.passphrase is not None else self.passphrase,
backend=self.backend
)
result['can_parse_key'] = True
except OpenSSLObjectError as exc:
except crypto_utils.OpenSSLObjectError as exc:
self.module.fail_json(msg=to_native(exc), **result)
result['public_key'] = self._get_public_key(binary=False)
pk = self._get_public_key(binary=True)
result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict()
result['public_key_fingerprints'] = crypto_utils.get_fingerprint_of_bytes(pk) if pk is not None else dict()
key_type, key_public_data, key_private_data = self._get_key_info()
result['type'] = key_type
@@ -629,8 +631,7 @@ def main():
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
privatekey = PrivateKeyInfoPyOpenSSL(module)
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
@@ -640,7 +641,7 @@ def main():
result = privatekey.get_info()
module.exit_json(**result)
except OpenSSLObjectError as exc:
except crypto_utils.OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))

View File

@@ -7,6 +7,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
@@ -15,11 +18,11 @@ short_description: Generate an OpenSSL public key from its private key.
description:
- This module allows one to (re)generate OpenSSL public keys from their private keys.
- Keys are generated in PEM or OpenSSH format.
- "The module can use the cryptography Python library, or the pyOpenSSL Python
- The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. When I(format) is C(OpenSSH),
the C(cryptography) backend has to be used. Please note that the PyOpenSSL backend
was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
requirements:
- Either cryptography >= 1.2.3 (older versions might work as well)
- Or pyOpenSSL >= 16.0.0
@@ -62,7 +65,6 @@ options:
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
If I(state) is C(present), one of them is required.
type: str
version_added: '1.0.0'
privatekey_passphrase:
description:
- The passphrase for the private key.
@@ -87,48 +89,47 @@ options:
- If set to C(yes), will return the (current or generated) public key's content as I(publickey).
type: bool
default: no
version_added: '1.0.0'
extends_documentation_fragment:
- files
seealso:
- module: community.crypto.x509_certificate
- module: community.crypto.openssl_csr
- module: community.crypto.openssl_dhparam
- module: community.crypto.openssl_pkcs12
- module: community.crypto.openssl_privatekey
- module: openssl_certificate
- module: openssl_csr
- module: openssl_dhparam
- module: openssl_pkcs12
- module: openssl_privatekey
'''
EXAMPLES = r'''
- name: Generate an OpenSSL public key in PEM format
community.crypto.openssl_publickey:
openssl_publickey:
path: /etc/ssl/public/ansible.com.pem
privatekey_path: /etc/ssl/private/ansible.com.pem
- name: Generate an OpenSSL public key in PEM format from an inline key
community.crypto.openssl_publickey:
openssl_publickey:
path: /etc/ssl/public/ansible.com.pem
privatekey_content: "{{ private_key_content }}"
- name: Generate an OpenSSL public key in OpenSSH v2 format
community.crypto.openssl_publickey:
openssl_publickey:
path: /etc/ssl/public/ansible.com.pem
privatekey_path: /etc/ssl/private/ansible.com.pem
format: OpenSSH
- name: Generate an OpenSSL public key with a passphrase protected private key
community.crypto.openssl_publickey:
openssl_publickey:
path: /etc/ssl/public/ansible.com.pem
privatekey_path: /etc/ssl/private/ansible.com.pem
privatekey_passphrase: ansible
- name: Force regenerate an OpenSSL public key if it already exists
community.crypto.openssl_publickey:
openssl_publickey:
path: /etc/ssl/public/ansible.com.pem
privatekey_path: /etc/ssl/private/ansible.com.pem
force: yes
- name: Remove an OpenSSL public key
community.crypto.openssl_publickey:
openssl_publickey:
path: /etc/ssl/public/ansible.com.pem
state: absent
'''
@@ -173,33 +174,13 @@ publickey:
description: The (current or generated) public key's content.
returned: if I(state) is C(present) and I(return_content) is C(yes)
type: str
version_added: '1.0.0'
version_added: "2.10"
'''
import os
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file_if_exists,
write_file,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_privatekey,
get_fingerprint,
)
MINIMAL_PYOPENSSL_VERSION = '16.0.0'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH = '1.4'
@@ -227,12 +208,16 @@ except ImportError:
else:
CRYPTOGRAPHY_FOUND = True
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
class PublicKeyError(OpenSSLObjectError):
class PublicKeyError(crypto_utils.OpenSSLObjectError):
pass
class PublicKey(OpenSSLObject):
class PublicKey(crypto_utils.OpenSSLObject):
def __init__(self, module, backend):
super(PublicKey, self).__init__(
@@ -257,7 +242,7 @@ class PublicKey(OpenSSLObject):
self.backup_file = None
def _create_publickey(self, module):
self.privatekey = load_privatekey(
self.privatekey = crypto_utils.load_privatekey(
path=self.privatekey_path,
content=self.privatekey_content,
passphrase=self.privatekey_passphrase,
@@ -296,15 +281,15 @@ class PublicKey(OpenSSLObject):
if self.backup:
self.backup_file = module.backup_local(self.path)
write_file(module, publickey_content)
crypto_utils.write_file(module, publickey_content)
self.changed = True
except OpenSSLBadPassphraseError as exc:
except crypto_utils.OpenSSLBadPassphraseError as exc:
raise PublicKeyError(exc)
except (IOError, OSError) as exc:
raise PublicKeyError(exc)
self.fingerprint = get_fingerprint(
self.fingerprint = crypto_utils.get_fingerprint(
path=self.privatekey_path,
content=self.privatekey_content,
passphrase=self.privatekey_passphrase,
@@ -352,7 +337,7 @@ class PublicKey(OpenSSLObject):
try:
desired_publickey = self._create_publickey(module)
except OpenSSLBadPassphraseError as exc:
except crypto_utils.OpenSSLBadPassphraseError as exc:
raise PublicKeyError(exc)
return publickey_content == desired_publickey
@@ -381,7 +366,7 @@ class PublicKey(OpenSSLObject):
result['backup_file'] = self.backup_file
if self.return_content:
if self.publickey_bytes is None:
self.publickey_bytes = load_file_if_exists(self.path, ignore_errors=True)
self.publickey_bytes = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
result['publickey'] = self.publickey_bytes.decode('utf-8') if self.publickey_bytes else None
return result
@@ -445,8 +430,7 @@ def main():
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(minimal_cryptography_version)),
@@ -479,7 +463,7 @@ def main():
result = public_key.dump()
module.exit_json(**result)
except OpenSSLObjectError as exc:
except crypto_utils.OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))

View File

@@ -1,322 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Patrick Pichler <ppichler+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: openssl_signature
version_added: 1.1.0
short_description: Sign data with openssl
description:
- This module allows one to sign data using a private key.
- The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL backend
was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0.
requirements:
- Either cryptography >= 1.4 (some key types require newer versions)
- Or pyOpenSSL >= 0.11 (Ed25519 and Ed448 keys are not supported with this backend)
author:
- Patrick Pichler (@aveexy)
- Markus Teufelberger (@MarkusTeufelberger)
options:
privatekey_path:
description:
- The path to the private key to use when signing.
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
type: path
privatekey_content:
description:
- The content of the private key to use when signing the certificate signing request.
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
type: str
privatekey_passphrase:
description:
- The passphrase for the private key.
- This is required if the private key is password protected.
type: str
path:
description:
- The file to sign.
- This file will only be read and not modified.
type: path
required: true
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
notes:
- |
When using the C(cryptography) backend, the following key types require at least the following C(cryptography) version:
RSA keys: C(cryptography) >= 1.4
DSA and ECDSA keys: C(cryptography) >= 1.5
ed448 and ed25519 keys: C(cryptography) >= 2.6
seealso:
- module: community.crypto.openssl_signature_info
- module: community.crypto.openssl_privatekey
'''
EXAMPLES = r'''
- name: Sign example file
community.crypto.openssl_signature:
privatekey_path: private.key
path: /tmp/example_file
register: sig
- name: Verify signature of example file
community.crypto.openssl_signature_info:
certificate_path: cert.pem
path: /tmp/example_file
signature: "{{ sig.signature }}"
register: verify
- name: Make sure the signature is valid
assert:
that:
- verify.valid
'''
RETURN = r'''
signature:
description: Base64 encoded signature.
returned: success
type: str
'''
import os
import traceback
from distutils.version import LooseVersion
import base64
MINIMAL_PYOPENSSL_VERSION = '0.11'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError:
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
import cryptography.hazmat.primitives.asymmetric.padding
import cryptography.hazmat.primitives.hashes
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
else:
CRYPTOGRAPHY_FOUND = True
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
CRYPTOGRAPHY_HAS_DSA_SIGN,
CRYPTOGRAPHY_HAS_EC_SIGN,
CRYPTOGRAPHY_HAS_ED25519_SIGN,
CRYPTOGRAPHY_HAS_ED448_SIGN,
CRYPTOGRAPHY_HAS_RSA_SIGN,
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_privatekey,
)
from ansible.module_utils._text import to_native, to_bytes
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
class SignatureBase(OpenSSLObject):
def __init__(self, module, backend):
super(SignatureBase, self).__init__(
path=module.params['path'],
state='present',
force=False,
check_mode=module.check_mode
)
self.backend = backend
self.privatekey_path = module.params['privatekey_path']
self.privatekey_content = module.params['privatekey_content']
if self.privatekey_content is not None:
self.privatekey_content = self.privatekey_content.encode('utf-8')
self.privatekey_passphrase = module.params['privatekey_passphrase']
def generate(self):
# Empty method because OpenSSLObject wants this
pass
def dump(self):
# Empty method because OpenSSLObject wants this
pass
# Implementation with using pyOpenSSL
class SignaturePyOpenSSL(SignatureBase):
def __init__(self, module, backend):
super(SignaturePyOpenSSL, self).__init__(module, backend)
def run(self):
result = dict()
try:
with open(self.path, "rb") as f:
_in = f.read()
private_key = load_privatekey(
path=self.privatekey_path,
content=self.privatekey_content,
passphrase=self.privatekey_passphrase,
backend=self.backend,
)
signature = OpenSSL.crypto.sign(private_key, _in, "sha256")
result['signature'] = base64.b64encode(signature)
return result
except Exception as e:
raise OpenSSLObjectError(e)
# Implementation with using cryptography
class SignatureCryptography(SignatureBase):
def __init__(self, module, backend):
super(SignatureCryptography, self).__init__(module, backend)
def run(self):
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
_hash = cryptography.hazmat.primitives.hashes.SHA256()
result = dict()
try:
with open(self.path, "rb") as f:
_in = f.read()
private_key = load_privatekey(
path=self.privatekey_path,
content=self.privatekey_content,
passphrase=self.privatekey_passphrase,
backend=self.backend,
)
signature = None
if CRYPTOGRAPHY_HAS_DSA_SIGN:
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
signature = private_key.sign(_in, _hash)
if CRYPTOGRAPHY_HAS_EC_SIGN:
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
signature = private_key.sign(_in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash))
if CRYPTOGRAPHY_HAS_ED25519_SIGN:
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
signature = private_key.sign(_in)
if CRYPTOGRAPHY_HAS_ED448_SIGN:
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
signature = private_key.sign(_in)
if CRYPTOGRAPHY_HAS_RSA_SIGN:
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
signature = private_key.sign(_in, _padding, _hash)
if signature is None:
self.module.fail_json(
msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION)
)
result['signature'] = base64.b64encode(signature)
return result
except Exception as e:
raise OpenSSLObjectError(e)
def main():
module = AnsibleModule(
argument_spec=dict(
privatekey_path=dict(type='path'),
privatekey_content=dict(type='str'),
privatekey_passphrase=dict(type='str', no_log=True),
path=dict(type='path', required=True),
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
),
mutually_exclusive=(
['privatekey_path', 'privatekey_content'],
),
required_one_of=(
['privatekey_path', 'privatekey_content'],
),
supports_check_mode=True,
)
if not os.path.isfile(module.params['path']):
module.fail_json(
name=module.params['path'],
msg='The file {0} does not exist'.format(module.params['path'])
)
backend = module.params['select_crypto_backend']
if backend == 'auto':
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# Decision
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
# Success?
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
try:
if backend == 'pyopenssl':
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
_sign = SignaturePyOpenSSL(module, backend)
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
_sign = SignatureCryptography(module, backend)
result = _sign.run()
module.exit_json(**result)
except OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))
if __name__ == '__main__':
main()

View File

@@ -1,354 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Patrick Pichler <ppichler+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: openssl_signature_info
version_added: 1.1.0
short_description: Verify signatures with openssl
description:
- This module allows one to verify a signature for a file via a certificate.
- The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL backend
was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0.
requirements:
- Either cryptography >= 1.4 (some key types require newer versions)
- Or pyOpenSSL >= 0.11 (Ed25519 and Ed448 keys are not supported with this backend)
author:
- Patrick Pichler (@aveexy)
- Markus Teufelberger (@MarkusTeufelberger)
options:
path:
description:
- The signed file to verify.
- This file will only be read and not modified.
type: path
required: true
certificate_path:
description:
- The path to the certificate used to verify the signature.
- Either I(certificate_path) or I(certificate_content) must be specified, but not both.
type: path
certificate_content:
description:
- The content of the certificate used to verify the signature.
- Either I(certificate_path) or I(certificate_content) must be specified, but not both.
type: str
signature:
description: Base64 encoded signature.
type: str
required: true
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
notes:
- |
When using the C(cryptography) backend, the following key types require at least the following C(cryptography) version:
RSA keys: C(cryptography) >= 1.4
DSA and ECDSA keys: C(cryptography) >= 1.5
ed448 and ed25519 keys: C(cryptography) >= 2.6
seealso:
- module: community.crypto.openssl_signature
- module: community.crypto.x509_certificate
'''
EXAMPLES = r'''
- name: Sign example file
community.crypto.openssl_signature:
privatekey_path: private.key
path: /tmp/example_file
register: sig
- name: Verify signature of example file
community.crypto.openssl_signature_info:
certificate_path: cert.pem
path: /tmp/example_file
signature: "{{ sig.signature }}"
register: verify
- name: Make sure the signature is valid
assert:
that:
- verify.valid
'''
RETURN = r'''
valid:
description: C(true) means the signature was valid for the given file, C(false) means it wasn't.
returned: success
type: bool
'''
import os
import traceback
from distutils.version import LooseVersion
import base64
MINIMAL_PYOPENSSL_VERSION = '0.11'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError:
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
import cryptography.hazmat.primitives.asymmetric.padding
import cryptography.hazmat.primitives.hashes
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
else:
CRYPTOGRAPHY_FOUND = True
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
CRYPTOGRAPHY_HAS_DSA_SIGN,
CRYPTOGRAPHY_HAS_EC_SIGN,
CRYPTOGRAPHY_HAS_ED25519_SIGN,
CRYPTOGRAPHY_HAS_ED448_SIGN,
CRYPTOGRAPHY_HAS_RSA_SIGN,
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_certificate,
)
from ansible.module_utils._text import to_native, to_bytes
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
class SignatureInfoBase(OpenSSLObject):
def __init__(self, module, backend):
super(SignatureInfoBase, self).__init__(
path=module.params['path'],
state='present',
force=False,
check_mode=module.check_mode
)
self.backend = backend
self.signature = module.params['signature']
self.certificate_path = module.params['certificate_path']
self.certificate_content = module.params['certificate_content']
if self.certificate_content is not None:
self.certificate_content = self.certificate_content.encode('utf-8')
def generate(self):
# Empty method because OpenSSLObject wants this
pass
def dump(self):
# Empty method because OpenSSLObject wants this
pass
# Implementation with using pyOpenSSL
class SignatureInfoPyOpenSSL(SignatureInfoBase):
def __init__(self, module, backend):
super(SignatureInfoPyOpenSSL, self).__init__(module, backend)
def run(self):
result = dict()
try:
with open(self.path, "rb") as f:
_in = f.read()
_signature = base64.b64decode(self.signature)
certificate = load_certificate(
path=self.certificate_path,
content=self.certificate_content,
backend=self.backend,
)
try:
OpenSSL.crypto.verify(certificate, _signature, _in, 'sha256')
result['valid'] = True
except Exception:
result['valid'] = False
return result
except Exception as e:
raise OpenSSLObjectError(e)
# Implementation with using cryptography
class SignatureInfoCryptography(SignatureInfoBase):
def __init__(self, module, backend):
super(SignatureInfoCryptography, self).__init__(module, backend)
def run(self):
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
_hash = cryptography.hazmat.primitives.hashes.SHA256()
result = dict()
try:
with open(self.path, "rb") as f:
_in = f.read()
_signature = base64.b64decode(self.signature)
certificate = load_certificate(
path=self.certificate_path,
content=self.certificate_content,
backend=self.backend,
)
public_key = certificate.public_key()
verified = False
valid = False
if CRYPTOGRAPHY_HAS_DSA_SIGN:
try:
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey):
public_key.verify(_signature, _in, _hash)
verified = True
valid = True
except cryptography.exceptions.InvalidSignature:
verified = True
valid = False
if CRYPTOGRAPHY_HAS_EC_SIGN:
try:
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
public_key.verify(_signature, _in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash))
verified = True
valid = True
except cryptography.exceptions.InvalidSignature:
verified = True
valid = False
if CRYPTOGRAPHY_HAS_ED25519_SIGN:
try:
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey):
public_key.verify(_signature, _in)
verified = True
valid = True
except cryptography.exceptions.InvalidSignature:
verified = True
valid = False
if CRYPTOGRAPHY_HAS_ED448_SIGN:
try:
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey):
public_key.verify(_signature, _in)
verified = True
valid = True
except cryptography.exceptions.InvalidSignature:
verified = True
valid = False
if CRYPTOGRAPHY_HAS_RSA_SIGN:
try:
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
public_key.verify(_signature, _in, _padding, _hash)
verified = True
valid = True
except cryptography.exceptions.InvalidSignature:
verified = True
valid = False
if not verified:
self.module.fail_json(
msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION)
)
result['valid'] = valid
return result
except Exception as e:
raise OpenSSLObjectError(e)
def main():
module = AnsibleModule(
argument_spec=dict(
certificate_path=dict(type='path'),
certificate_content=dict(type='str'),
path=dict(type='path', required=True),
signature=dict(type='str', required=True),
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
),
mutually_exclusive=(
['certificate_path', 'certificate_content'],
),
required_one_of=(
['certificate_path', 'certificate_content'],
),
supports_check_mode=True,
)
if not os.path.isfile(module.params['path']):
module.fail_json(
name=module.params['path'],
msg='The file {0} does not exist'.format(module.params['path'])
)
backend = module.params['select_crypto_backend']
if backend == 'auto':
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# Decision
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
# Success?
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
try:
if backend == 'pyopenssl':
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
_sign = SignatureInfoPyOpenSSL(module, backend)
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
_sign = SignatureInfoCryptography(module, backend)
result = _sign.run()
module.exit_json(**result)
except OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -1,881 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: x509_certificate_info
short_description: Provide information of OpenSSL X.509 certificates
description:
- This module allows one to query information on OpenSSL certificates.
- It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the
cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with
C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9
and will be removed in community.crypto 2.0.0.
- Note that this module was called C(openssl_certificate_info) when included directly in Ansible
up to version 2.9. When moved to the collection C(community.crypto), it was renamed to
M(community.crypto.x509_certificate_info). From Ansible 2.10 on, it can still be used by the
old short name (or by C(ansible.builtin.openssl_certificate_info)), which redirects to
C(community.crypto.x509_certificate_info). When using FQCNs or when using the
L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook)
keyword, the new name M(community.crypto.x509_certificate_info) should be used to avoid
a deprecation warning.
requirements:
- PyOpenSSL >= 0.15 or cryptography >= 1.6
author:
- Felix Fontein (@felixfontein)
- Yanis Guenane (@Spredzy)
- Markus Teufelberger (@MarkusTeufelberger)
options:
path:
description:
- Remote absolute path where the certificate file is loaded from.
- Either I(path) or I(content) must be specified, but not both.
type: path
content:
description:
- Content of the X.509 certificate in PEM format.
- Either I(path) or I(content) must be specified, but not both.
type: str
version_added: '1.0.0'
valid_at:
description:
- A dict of names mapping to time specifications. Every time specified here
will be checked whether the certificate is valid at this point. See the
C(valid_at) return value for informations on the result.
- Time can be specified either as relative time or as absolute timestamp.
- Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h), and ASN.1 TIME (i.e. pattern C(YYYYMMDDHHMMSSZ)).
Note that all timestamps will be treated as being in UTC.
type: dict
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
From that point on, only the C(cryptography) backend will be available.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
notes:
- All timestamp values are provided in ASN.1 TIME format, i.e. following the C(YYYYMMDDHHMMSSZ) pattern.
They are all in UTC.
seealso:
- module: community.crypto.x509_certificate
'''
EXAMPLES = r'''
- name: Generate a Self Signed OpenSSL certificate
community.crypto.x509_certificate:
path: /etc/ssl/crt/ansible.com.crt
privatekey_path: /etc/ssl/private/ansible.com.pem
csr_path: /etc/ssl/csr/ansible.com.csr
provider: selfsigned
# Get information on the certificate
- name: Get information on generated certificate
community.crypto.x509_certificate_info:
path: /etc/ssl/crt/ansible.com.crt
register: result
- name: Dump information
debug:
var: result
# Check whether the certificate is valid or not valid at certain times, fail
# if this is not the case. The first task (x509_certificate_info) collects
# the information, and the second task (assert) validates the result and
# makes the playbook fail in case something is not as expected.
- name: Test whether that certificate is valid tomorrow and/or in three weeks
community.crypto.x509_certificate_info:
path: /etc/ssl/crt/ansible.com.crt
valid_at:
point_1: "+1d"
point_2: "+3w"
register: result
- name: Validate that certificate is valid tomorrow, but not in three weeks
assert:
that:
- result.valid_at.point_1 # valid in one day
- not result.valid_at.point_2 # not valid in three weeks
'''
RETURN = r'''
expired:
description: Whether the certificate is expired (i.e. C(notAfter) is in the past)
returned: success
type: bool
basic_constraints:
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
returned: success
type: list
elements: str
sample: "[CA:TRUE, pathlen:1]"
basic_constraints_critical:
description: Whether the C(basic_constraints) extension is critical.
returned: success
type: bool
extended_key_usage:
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
returned: success
type: list
elements: str
sample: "[Biometric Info, DVCS, Time Stamping]"
extended_key_usage_critical:
description: Whether the C(extended_key_usage) extension is critical.
returned: success
type: bool
extensions_by_oid:
description: Returns a dictionary for every extension OID
returned: success
type: dict
contains:
critical:
description: Whether the extension is critical.
returned: success
type: bool
value:
description: The Base64 encoded value (in DER format) of the extension
returned: success
type: str
sample: "MAMCAQU="
sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}'
key_usage:
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
returned: success
type: str
sample: "[Key Agreement, Data Encipherment]"
key_usage_critical:
description: Whether the C(key_usage) extension is critical.
returned: success
type: bool
subject_alt_name:
description: Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
returned: success
type: list
elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
subject_alt_name_critical:
description: Whether the C(subject_alt_name) extension is critical.
returned: success
type: bool
ocsp_must_staple:
description: C(yes) if the OCSP Must Staple extension is present, C(none) otherwise.
returned: success
type: bool
ocsp_must_staple_critical:
description: Whether the C(ocsp_must_staple) extension is critical.
returned: success
type: bool
issuer:
description:
- The certificate's issuer.
- Note that for repeated values, only the last one will be returned.
returned: success
type: dict
sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}'
issuer_ordered:
description: The certificate's issuer as an ordered list of tuples.
returned: success
type: list
elements: list
sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]'
subject:
description:
- The certificate's subject as a dictionary.
- Note that for repeated values, only the last one will be returned.
returned: success
type: dict
sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}'
subject_ordered:
description: The certificate's subject as an ordered list of tuples.
returned: success
type: list
elements: list
sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]'
not_after:
description: C(notAfter) date as ASN.1 TIME
returned: success
type: str
sample: 20190413202428Z
not_before:
description: C(notBefore) date as ASN.1 TIME
returned: success
type: str
sample: 20190331202428Z
public_key:
description: Certificate's public key in PEM format
returned: success
type: str
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
public_key_fingerprints:
description:
- Fingerprints of certificate's public key.
- For every hash algorithm available, the fingerprint is computed.
returned: success
type: dict
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
signature_algorithm:
description: The signature algorithm used to sign the certificate.
returned: success
type: str
sample: sha256WithRSAEncryption
serial_number:
description: The certificate's serial number.
returned: success
type: int
sample: 1234
version:
description: The certificate version.
returned: success
type: int
sample: 3
valid_at:
description: For every time stamp provided in the I(valid_at) option, a
boolean whether the certificate is valid at that point in time
or not.
returned: success
type: dict
subject_key_identifier:
description:
- The certificate's subject key identifier.
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
authority_key_identifier:
description:
- The certificate's authority key identifier.
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
authority_cert_issuer:
description:
- The certificate's authority cert issuer as a list of general names.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: list
elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
authority_cert_serial_number:
description:
- The certificate's authority cert serial number.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: int
sample: '12345'
ocsp_uri:
description: The OCSP responder URI, if included in the certificate. Will be
C(none) if no OCSP responder URI is included.
returned: success
type: str
'''
import abc
import binascii
import datetime
import os
import re
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
get_relative_time_option,
load_certificate,
get_fingerprint_of_bytes,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_decode_name,
cryptography_get_extensions_from_cert,
cryptography_oid_to_name,
cryptography_serial_number_of_cert,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
pyopenssl_get_extensions_from_cert,
pyopenssl_normalize_name,
pyopenssl_normalize_name_attribute,
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
MINIMAL_PYOPENSSL_VERSION = '0.15'
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
# OpenSSL 1.1.0 or newer
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
else:
# OpenSSL 1.0.x or older
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
except ImportError:
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
from cryptography import x509
from cryptography.hazmat.primitives import serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
else:
CRYPTOGRAPHY_FOUND = True
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
class CertificateInfo(OpenSSLObject):
def __init__(self, module, backend):
super(CertificateInfo, self).__init__(
module.params['path'] or '',
'present',
False,
module.check_mode,
)
self.backend = backend
self.module = module
self.content = module.params['content']
if self.content is not None:
self.content = self.content.encode('utf-8')
self.valid_at = module.params['valid_at']
if self.valid_at:
for k, v in self.valid_at.items():
if not isinstance(v, string_types):
self.module.fail_json(
msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v))
)
self.valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k))
def generate(self):
# Empty method because OpenSSLObject wants this
pass
def dump(self):
# Empty method because OpenSSLObject wants this
pass
@abc.abstractmethod
def _get_signature_algorithm(self):
pass
@abc.abstractmethod
def _get_subject_ordered(self):
pass
@abc.abstractmethod
def _get_issuer_ordered(self):
pass
@abc.abstractmethod
def _get_version(self):
pass
@abc.abstractmethod
def _get_key_usage(self):
pass
@abc.abstractmethod
def _get_extended_key_usage(self):
pass
@abc.abstractmethod
def _get_basic_constraints(self):
pass
@abc.abstractmethod
def _get_ocsp_must_staple(self):
pass
@abc.abstractmethod
def _get_subject_alt_name(self):
pass
@abc.abstractmethod
def _get_not_before(self):
pass
@abc.abstractmethod
def _get_not_after(self):
pass
@abc.abstractmethod
def _get_public_key(self, binary):
pass
@abc.abstractmethod
def _get_subject_key_identifier(self):
pass
@abc.abstractmethod
def _get_authority_key_identifier(self):
pass
@abc.abstractmethod
def _get_serial_number(self):
pass
@abc.abstractmethod
def _get_all_extensions(self):
pass
@abc.abstractmethod
def _get_ocsp_uri(self):
pass
def get_info(self):
result = dict()
self.cert = load_certificate(self.path, content=self.content, backend=self.backend)
result['signature_algorithm'] = self._get_signature_algorithm()
subject = self._get_subject_ordered()
issuer = self._get_issuer_ordered()
result['subject'] = dict()
for k, v in subject:
result['subject'][k] = v
result['subject_ordered'] = subject
result['issuer'] = dict()
for k, v in issuer:
result['issuer'][k] = v
result['issuer_ordered'] = issuer
result['version'] = self._get_version()
result['key_usage'], result['key_usage_critical'] = self._get_key_usage()
result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage()
result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints()
result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple()
result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name()
not_before = self._get_not_before()
not_after = self._get_not_after()
result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT)
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
result['expired'] = not_after < datetime.datetime.utcnow()
result['valid_at'] = dict()
if self.valid_at:
for k, v in self.valid_at.items():
result['valid_at'][k] = not_before <= v <= not_after
result['public_key'] = self._get_public_key(binary=False)
pk = self._get_public_key(binary=True)
result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict()
if self.backend != 'pyopenssl':
ski = self._get_subject_key_identifier()
if ski is not None:
ski = to_native(binascii.hexlify(ski))
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
result['subject_key_identifier'] = ski
aki, aci, acsn = self._get_authority_key_identifier()
if aki is not None:
aki = to_native(binascii.hexlify(aki))
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
result['authority_key_identifier'] = aki
result['authority_cert_issuer'] = aci
result['authority_cert_serial_number'] = acsn
result['serial_number'] = self._get_serial_number()
result['extensions_by_oid'] = self._get_all_extensions()
result['ocsp_uri'] = self._get_ocsp_uri()
return result
class CertificateInfoCryptography(CertificateInfo):
"""Validate the supplied cert, using the cryptography backend"""
def __init__(self, module):
super(CertificateInfoCryptography, self).__init__(module, 'cryptography')
def _get_signature_algorithm(self):
return cryptography_oid_to_name(self.cert.signature_algorithm_oid)
def _get_subject_ordered(self):
result = []
for attribute in self.cert.subject:
result.append([cryptography_oid_to_name(attribute.oid), attribute.value])
return result
def _get_issuer_ordered(self):
result = []
for attribute in self.cert.issuer:
result.append([cryptography_oid_to_name(attribute.oid), attribute.value])
return result
def _get_version(self):
if self.cert.version == x509.Version.v1:
return 1
if self.cert.version == x509.Version.v3:
return 3
return "unknown"
def _get_key_usage(self):
try:
current_key_ext = self.cert.extensions.get_extension_for_class(x509.KeyUsage)
current_key_usage = current_key_ext.value
key_usage = dict(
digital_signature=current_key_usage.digital_signature,
content_commitment=current_key_usage.content_commitment,
key_encipherment=current_key_usage.key_encipherment,
data_encipherment=current_key_usage.data_encipherment,
key_agreement=current_key_usage.key_agreement,
key_cert_sign=current_key_usage.key_cert_sign,
crl_sign=current_key_usage.crl_sign,
encipher_only=False,
decipher_only=False,
)
if key_usage['key_agreement']:
key_usage.update(dict(
encipher_only=current_key_usage.encipher_only,
decipher_only=current_key_usage.decipher_only
))
key_usage_names = dict(
digital_signature='Digital Signature',
content_commitment='Non Repudiation',
key_encipherment='Key Encipherment',
data_encipherment='Data Encipherment',
key_agreement='Key Agreement',
key_cert_sign='Certificate Sign',
crl_sign='CRL Sign',
encipher_only='Encipher Only',
decipher_only='Decipher Only',
)
return sorted([
key_usage_names[name] for name, value in key_usage.items() if value
]), current_key_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_extended_key_usage(self):
try:
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
return sorted([
cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
]), ext_keyusage_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_basic_constraints(self):
try:
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.BasicConstraints)
result = []
result.append('CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE'))
if ext_keyusage_ext.value.path_length is not None:
result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length))
return sorted(result), ext_keyusage_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_ocsp_must_staple(self):
try:
try:
# This only works with cryptography >= 2.1
tlsfeature_ext = self.cert.extensions.get_extension_for_class(x509.TLSFeature)
value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value
except AttributeError as dummy:
# Fallback for cryptography < 2.1
oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24")
tlsfeature_ext = self.cert.extensions.get_extension_for_oid(oid)
value = tlsfeature_ext.value.value == b"\x30\x03\x02\x01\x05"
return value, tlsfeature_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_subject_alt_name(self):
try:
san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
result = [cryptography_decode_name(san) for san in san_ext.value]
return result, san_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_not_before(self):
return self.cert.not_valid_before
def _get_not_after(self):
return self.cert.not_valid_after
def _get_public_key(self, binary):
return self.cert.public_key().public_bytes(
serialization.Encoding.DER if binary else serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo
)
def _get_subject_key_identifier(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
return ext.value.digest
except cryptography.x509.ExtensionNotFound:
return None
def _get_authority_key_identifier(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
except cryptography.x509.ExtensionNotFound:
return None, None, None
def _get_serial_number(self):
return cryptography_serial_number_of_cert(self.cert)
def _get_all_extensions(self):
return cryptography_get_extensions_from_cert(self.cert)
def _get_ocsp_uri(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
for desc in ext.value:
if desc.access_method == x509.oid.AuthorityInformationAccessOID.OCSP:
if isinstance(desc.access_location, x509.UniformResourceIdentifier):
return desc.access_location.value
except x509.ExtensionNotFound as dummy:
pass
return None
class CertificateInfoPyOpenSSL(CertificateInfo):
"""validate the supplied certificate."""
def __init__(self, module):
super(CertificateInfoPyOpenSSL, self).__init__(module, 'pyopenssl')
def _get_signature_algorithm(self):
return to_text(self.cert.get_signature_algorithm())
def __get_name(self, name):
result = []
for sub in name.get_components():
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
return result
def _get_subject_ordered(self):
return self.__get_name(self.cert.get_subject())
def _get_issuer_ordered(self):
return self.__get_name(self.cert.get_issuer())
def _get_version(self):
# Version numbers in certs are off by one:
# v1: 0, v2: 1, v3: 2 ...
return self.cert.get_version() + 1
def _get_extension(self, short_name):
for extension_idx in range(0, self.cert.get_extension_count()):
extension = self.cert.get_extension(extension_idx)
if extension.get_short_name() == short_name:
result = [
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
]
return sorted(result), bool(extension.get_critical())
return None, False
def _get_key_usage(self):
return self._get_extension(b'keyUsage')
def _get_extended_key_usage(self):
return self._get_extension(b'extendedKeyUsage')
def _get_basic_constraints(self):
return self._get_extension(b'basicConstraints')
def _get_ocsp_must_staple(self):
extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())]
oms_ext = [
ext for ext in extensions
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
]
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
# Older versions of libssl don't know about OCSP Must Staple
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
if oms_ext:
return True, bool(oms_ext[0].get_critical())
else:
return None, False
def _get_subject_alt_name(self):
for extension_idx in range(0, self.cert.get_extension_count()):
extension = self.cert.get_extension(extension_idx)
if extension.get_short_name() == b'subjectAltName':
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
to_text(extension, errors='surrogate_or_strict').split(', ')]
return result, bool(extension.get_critical())
return None, False
def _get_not_before(self):
time_string = to_native(self.cert.get_notBefore())
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
def _get_not_after(self):
time_string = to_native(self.cert.get_notAfter())
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
def _get_public_key(self, binary):
try:
return crypto.dump_publickey(
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM,
self.cert.get_pubkey()
)
except AttributeError:
try:
# pyOpenSSL < 16.0:
bio = crypto._new_mem_buf()
if binary:
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.cert.get_pubkey()._pkey)
else:
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
if rc != 1:
crypto._raise_current_error()
return crypto._bio_to_string(bio)
except AttributeError:
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
def _get_subject_key_identifier(self):
# Won't be implemented
return None
def _get_authority_key_identifier(self):
# Won't be implemented
return None, None, None
def _get_serial_number(self):
return self.cert.get_serial_number()
def _get_all_extensions(self):
return pyopenssl_get_extensions_from_cert(self.cert)
def _get_ocsp_uri(self):
for i in range(self.cert.get_extension_count()):
ext = self.cert.get_extension(i)
if ext.get_short_name() == b'authorityInfoAccess':
v = str(ext)
m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE)
if m:
return m.group(1)
return None
def main():
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path'),
content=dict(type='str'),
valid_at=dict(type='dict'),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
),
required_one_of=(
['path', 'content'],
),
mutually_exclusive=(
['path', 'content'],
),
supports_check_mode=True,
)
if module._name == 'community.crypto.openssl_certificate_info':
module.deprecate("The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'",
version='2.0.0', collection_name='community.crypto')
try:
if module.params['path'] is not None:
base_dir = os.path.dirname(module.params['path']) or '.'
if not os.path.isdir(base_dir):
module.fail_json(
name=base_dir,
msg='The directory %s does not exist or the file is not a directory' % base_dir
)
backend = module.params['select_crypto_backend']
if backend == 'auto':
# Detect what backend we can use
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# If cryptography is available we'll use it
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
# Fail if no backend has been found
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
if backend == 'pyopenssl':
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
try:
getattr(crypto.X509Req, 'get_extensions')
except AttributeError:
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
certificate = CertificateInfoPyOpenSSL(module)
elif backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
certificate = CertificateInfoCryptography(module)
result = certificate.get_info()
module.exit_json(**result)
except OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))
if __name__ == "__main__":
main()

View File

@@ -7,11 +7,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: x509_crl
version_added: '1.0.0'
short_description: Generate Certificate Revocation Lists (CRLs)
description:
- This module allows one to (re)generate or update Certificate Revocation Lists (CRLs).
@@ -61,15 +63,6 @@ options:
type: path
required: yes
format:
description:
- Whether the CRL file should be in PEM or DER format.
- If an existing CRL file does match everything but I(format), it will be converted to the correct format
instead of regenerated.
type: str
choices: [pem, der]
default: pem
privatekey_path:
description:
- Path to the CA's private key to use when signing the CRL.
@@ -237,7 +230,7 @@ notes:
EXAMPLES = r'''
- name: Generate a CRL
community.crypto.x509_crl:
x509_crl:
path: /etc/ssl/my-ca.crl
privatekey_path: /etc/ssl/private/my-ca.pem
issuer:
@@ -273,12 +266,6 @@ privatekey:
returned: changed or success
type: str
sample: /path/to/my-ca.pem
format:
description:
- Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)).
returned: success
type: str
sample: pem
issuer:
description:
- The CRL's issuer.
@@ -353,60 +340,19 @@ revoked_certificates:
type: bool
sample: no
crl:
description:
- The (current or generated) CRL's content.
- Will be the CRL itself if I(format) is C(pem), and Base64 of the
CRL if I(format) is C(der).
description: The (current or generated) CRL's content.
returned: if I(state) is C(present) and I(return_content) is C(yes)
type: str
'''
import base64
import os
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils._text import to_native, to_text
from ansible_collections.community.crypto.plugins.module_utils.io import (
write_file,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
load_privatekey,
load_certificate,
parse_name_field,
get_relative_time_option,
select_message_digest,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_get_name,
cryptography_name_to_oid,
cryptography_oid_to_name,
cryptography_serial_number_of_cert,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_crl import (
REVOCATION_REASON_MAP,
TIMESTAMP_FORMAT,
cryptography_decode_revoked_certificate,
cryptography_dump_revoked,
cryptography_get_signature_algorithm_oid_from_crl,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
identify_pem_format,
)
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2'
@@ -430,11 +376,14 @@ else:
CRYPTOGRAPHY_FOUND = True
class CRLError(OpenSSLObjectError):
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
class CRLError(crypto_utils.OpenSSLObjectError):
pass
class CRL(OpenSSLObject):
class CRL(crypto_utils.OpenSSLObject):
def __init__(self, module):
super(CRL, self).__init__(
@@ -444,8 +393,6 @@ class CRL(OpenSSLObject):
module.check_mode
)
self.format = module.params['format']
self.update = module.params['mode'] == 'update'
self.ignore_timestamps = module.params['ignore_timestamps']
self.return_content = module.params['return_content']
@@ -457,13 +404,13 @@ class CRL(OpenSSLObject):
self.privatekey_content = self.privatekey_content.encode('utf-8')
self.privatekey_passphrase = module.params['privatekey_passphrase']
self.issuer = parse_name_field(module.params['issuer'])
self.issuer = crypto_utils.parse_name_field(module.params['issuer'])
self.issuer = [(entry[0], entry[1]) for entry in self.issuer if entry[1]]
self.last_update = get_relative_time_option(module.params['last_update'], 'last_update')
self.next_update = get_relative_time_option(module.params['next_update'], 'next_update')
self.last_update = crypto_utils.get_relative_time_option(module.params['last_update'], 'last_update')
self.next_update = crypto_utils.get_relative_time_option(module.params['next_update'], 'next_update')
self.digest = select_message_digest(module.params['digest'])
self.digest = crypto_utils.select_message_digest(module.params['digest'])
if self.digest is None:
raise CRLError('The digest "{0}" is not supported'.format(module.params['digest']))
@@ -485,9 +432,13 @@ class CRL(OpenSSLObject):
try:
if rc['content'] is not None:
rc['content'] = rc['content'].encode('utf-8')
cert = load_certificate(rc['path'], content=rc['content'], backend='cryptography')
result['serial_number'] = cryptography_serial_number_of_cert(cert)
except OpenSSLObjectError as e:
cert = crypto_utils.load_certificate(rc['path'], content=rc['content'], backend='cryptography')
try:
result['serial_number'] = cert.serial_number
except AttributeError:
# The property was called "serial" before cryptography 1.4
result['serial_number'] = cert.serial
except crypto_utils.OpenSSLObjectError as e:
if rc['content'] is not None:
module.fail_json(
msg='Cannot parse certificate from {0}content: {1}'.format(path_prefix, to_native(e))
@@ -501,17 +452,17 @@ class CRL(OpenSSLObject):
result['serial_number'] = rc['serial_number']
# All other options
if rc['issuer']:
result['issuer'] = [cryptography_get_name(issuer) for issuer in rc['issuer']]
result['issuer'] = [crypto_utils.cryptography_get_name(issuer) for issuer in rc['issuer']]
result['issuer_critical'] = rc['issuer_critical']
result['revocation_date'] = get_relative_time_option(
result['revocation_date'] = crypto_utils.get_relative_time_option(
rc['revocation_date'],
path_prefix + 'revocation_date'
)
if rc['reason']:
result['reason'] = REVOCATION_REASON_MAP[rc['reason']]
result['reason'] = crypto_utils.REVOCATION_REASON_MAP[rc['reason']]
result['reason_critical'] = rc['reason_critical']
if rc['invalidity_date']:
result['invalidity_date'] = get_relative_time_option(
result['invalidity_date'] = crypto_utils.get_relative_time_option(
rc['invalidity_date'],
path_prefix + 'invalidity_date'
)
@@ -524,31 +475,24 @@ class CRL(OpenSSLObject):
self.backup_file = None
try:
self.privatekey = load_privatekey(
self.privatekey = crypto_utils.load_privatekey(
path=self.privatekey_path,
content=self.privatekey_content,
passphrase=self.privatekey_passphrase,
backend='cryptography'
)
except OpenSSLBadPassphraseError as exc:
except crypto_utils.OpenSSLBadPassphraseError as exc:
raise CRLError(exc)
self.crl = None
try:
with open(self.path, 'rb') as f:
data = f.read()
self.actual_format = 'pem' if identify_pem_format(data) else 'der'
if self.actual_format == 'pem':
self.crl = x509.load_pem_x509_crl(data, default_backend())
if self.return_content:
self.crl_content = data
else:
self.crl = x509.load_der_x509_crl(data, default_backend())
if self.return_content:
self.crl_content = base64.b64encode(data)
self.crl = x509.load_pem_x509_crl(data, default_backend())
if self.return_content:
self.crl_content = data
except Exception as dummy:
self.crl_content = None
self.actual_format = self.format
def remove(self):
if self.backup:
@@ -579,7 +523,7 @@ class CRL(OpenSSLObject):
entry['invalidity_date_critical'],
)
def check(self, perms_required=True, ignore_conversion=True):
def check(self, perms_required=True):
"""Ensure the resource is in its desired state."""
state_and_perms = super(CRL, self).check(self.module, perms_required)
@@ -597,11 +541,11 @@ class CRL(OpenSSLObject):
if self.digest.name != self.crl.signature_hash_algorithm.name:
return False
want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer]
want_issuer = [(crypto_utils.cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer]
if want_issuer != [(sub.oid, sub.value) for sub in self.crl.issuer]:
return False
old_entries = [self._compress_entry(cryptography_decode_revoked_certificate(cert)) for cert in self.crl]
old_entries = [self._compress_entry(crypto_utils.cryptography_decode_revoked_certificate(cert)) for cert in self.crl]
new_entries = [self._compress_entry(cert) for cert in self.revoked_certificates]
if self.update:
# We don't simply use a set so that duplicate entries are treated correctly
@@ -614,9 +558,6 @@ class CRL(OpenSSLObject):
if old_entries != new_entries:
return False
if self.format != self.actual_format and not ignore_conversion:
return False
return True
def _generate_crl(self):
@@ -625,7 +566,7 @@ class CRL(OpenSSLObject):
try:
crl = crl.issuer_name(Name([
NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1]))
NameAttribute(crypto_utils.cryptography_name_to_oid(entry[0]), to_text(entry[1]))
for entry in self.issuer
]))
except ValueError as e:
@@ -637,7 +578,7 @@ class CRL(OpenSSLObject):
if self.update and self.crl:
new_entries = set([self._compress_entry(entry) for entry in self.revoked_certificates])
for entry in self.crl:
decoded_entry = self._compress_entry(cryptography_decode_revoked_certificate(entry))
decoded_entry = self._compress_entry(crypto_utils.cryptography_decode_revoked_certificate(entry))
if decoded_entry not in new_entries:
crl = crl.add_revoked_certificate(entry)
for entry in self.revoked_certificates:
@@ -647,7 +588,7 @@ class CRL(OpenSSLObject):
if entry['issuer'] is not None:
revoked_cert = revoked_cert.add_extension(
x509.CertificateIssuer([
cryptography_get_name(name) for name in entry['issuer']
crypto_utils.cryptography_get_name(name) for name in self.entry['issuer']
]),
entry['issuer_critical']
)
@@ -664,42 +605,43 @@ class CRL(OpenSSLObject):
crl = crl.add_revoked_certificate(revoked_cert.build(backend))
self.crl = crl.sign(self.privatekey, self.digest, backend=backend)
if self.format == 'pem':
return self.crl.public_bytes(Encoding.PEM)
else:
return self.crl.public_bytes(Encoding.DER)
return self.crl.public_bytes(Encoding.PEM)
def generate(self):
result = None
if not self.check(perms_required=False, ignore_conversion=True) or self.force:
if not self.check(perms_required=False) or self.force:
result = self._generate_crl()
elif not self.check(perms_required=False, ignore_conversion=False) and self.crl:
if self.format == 'pem':
result = self.crl.public_bytes(Encoding.PEM)
else:
result = self.crl.public_bytes(Encoding.DER)
if result is not None:
if self.return_content:
if self.format == 'pem':
self.crl_content = result
else:
self.crl_content = base64.b64encode(result)
self.crl_content = result
if self.backup:
self.backup_file = self.module.backup_local(self.path)
write_file(self.module, result)
crypto_utils.write_file(self.module, result)
self.changed = True
file_args = self.module.load_file_common_arguments(self.module.params)
if self.module.set_fs_attributes_if_different(file_args, False):
self.changed = True
def _dump_revoked(self, entry):
return {
'serial_number': entry['serial_number'],
'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT),
'issuer':
[crypto_utils.cryptography_decode_name(issuer) for issuer in entry['issuer']]
if entry['issuer'] is not None else None,
'issuer_critical': entry['issuer_critical'],
'reason': crypto_utils.REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None,
'reason_critical': entry['reason_critical'],
'invalidity_date':
entry['invalidity_date'].strftime(TIMESTAMP_FORMAT)
if entry['invalidity_date'] is not None else None,
'invalidity_date_critical': entry['invalidity_date_critical'],
}
def dump(self, check_mode=False):
result = {
'changed': self.changed,
'filename': self.path,
'privatekey': self.privatekey_path,
'format': self.format,
'last_update': None,
'next_update': None,
'digest': None,
@@ -713,7 +655,7 @@ class CRL(OpenSSLObject):
if check_mode:
result['last_update'] = self.last_update.strftime(TIMESTAMP_FORMAT)
result['next_update'] = self.next_update.strftime(TIMESTAMP_FORMAT)
# result['digest'] = cryptography_oid_to_name(self.crl.signature_algorithm_oid)
# result['digest'] = crypto_utils.cryptography_oid_to_name(self.crl.signature_algorithm_oid)
result['digest'] = self.module.params['digest']
result['issuer_ordered'] = self.issuer
result['issuer'] = {}
@@ -721,22 +663,32 @@ class CRL(OpenSSLObject):
result['issuer'][k] = v
result['revoked_certificates'] = []
for entry in self.revoked_certificates:
result['revoked_certificates'].append(cryptography_dump_revoked(entry))
result['revoked_certificates'].append(self._dump_revoked(entry))
elif self.crl:
result['last_update'] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
result['next_update'] = self.crl.next_update.strftime(TIMESTAMP_FORMAT)
result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl))
try:
result['digest'] = crypto_utils.cryptography_oid_to_name(self.crl.signature_algorithm_oid)
except AttributeError:
# Older cryptography versions don't have signature_algorithm_oid yet
dotted = crypto_utils._obj2txt(
self.crl._backend._lib,
self.crl._backend._ffi,
self.crl._x509_crl.sig_alg.algorithm
)
oid = x509.oid.ObjectIdentifier(dotted)
result['digest'] = crypto_utils.cryptography_oid_to_name(oid)
issuer = []
for attribute in self.crl.issuer:
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
issuer.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
result['issuer_ordered'] = issuer
result['issuer'] = {}
for k, v in issuer:
result['issuer'][k] = v
result['revoked_certificates'] = []
for cert in self.crl:
entry = cryptography_decode_revoked_certificate(cert)
result['revoked_certificates'].append(cryptography_dump_revoked(entry))
entry = crypto_utils.cryptography_decode_revoked_certificate(cert)
result['revoked_certificates'].append(self._dump_revoked(entry))
if self.return_content:
result['crl'] = self.crl_content
@@ -752,7 +704,6 @@ def main():
force=dict(type='bool', default=False),
backup=dict(type='bool', default=False),
path=dict(type='path', required=True),
format=dict(type='str', default='pem', choices=['pem', 'der']),
privatekey_path=dict(type='path'),
privatekey_content=dict(type='str'),
privatekey_passphrase=dict(type='str', no_log=True),
@@ -809,7 +760,7 @@ def main():
if module.params['state'] == 'present':
if module.check_mode:
result = crl.dump(check_mode=True)
result['changed'] = module.params['force'] or not crl.check() or not crl.check(ignore_conversion=False)
result['changed'] = module.params['force'] or not crl.check()
module.exit_json(**result)
crl.generate()
@@ -823,7 +774,7 @@ def main():
result = crl.dump()
module.exit_json(**result)
except OpenSSLObjectError as exc:
except crypto_utils.OpenSSLObjectError as exc:
module.fail_json(msg=to_native(exc))

View File

@@ -7,11 +7,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: x509_crl_info
version_added: '1.0.0'
short_description: Retrieve information on Certificate Revocation Lists (CRLs)
description:
- This module allows one to retrieve information on Certificate Revocation Lists (CRLs).
@@ -27,7 +29,7 @@ options:
type: path
content:
description:
- Content of the X.509 CRL in PEM format, or Base64-encoded X.509 CRL.
- Content of the X.509 certificate in PEM format.
- Either I(path) or I(content) must be specified, but not both.
type: str
@@ -35,12 +37,12 @@ notes:
- All timestamp values are provided in ASN.1 TIME format, i.e. following the C(YYYYMMDDHHMMSSZ) pattern.
They are all in UTC.
seealso:
- module: community.crypto.x509_crl
- module: x509_crl
'''
EXAMPLES = r'''
- name: Get information on CRL
community.crypto.x509_crl_info:
x509_crl_info:
path: /etc/ssl/my-ca.crl
register: result
@@ -49,12 +51,6 @@ EXAMPLES = r'''
'''
RETURN = r'''
format:
description:
- Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)).
returned: success
type: str
sample: pem
issuer:
description:
- The CRL's issuer.
@@ -131,38 +127,12 @@ revoked_certificates:
'''
import base64
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
from ansible.module_utils._text import to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
OpenSSLObject,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_oid_to_name,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_crl import (
TIMESTAMP_FORMAT,
cryptography_decode_revoked_certificate,
cryptography_dump_revoked,
cryptography_get_signature_algorithm_oid_from_crl,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
identify_pem_format,
)
# crypto_utils
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2'
@@ -179,11 +149,14 @@ else:
CRYPTOGRAPHY_FOUND = True
class CRLError(OpenSSLObjectError):
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
class CRLError(crypto_utils.OpenSSLObjectError):
pass
class CRLInfo(OpenSSLObject):
class CRLInfo(crypto_utils.OpenSSLObject):
"""The main module implementation."""
def __init__(self, module):
@@ -207,22 +180,31 @@ class CRLInfo(OpenSSLObject):
self.module.fail_json(msg='Error while reading CRL file from disk: {0}'.format(e))
else:
data = self.content.encode('utf-8')
if not identify_pem_format(data):
data = base64.b64decode(self.content)
self.crl_pem = identify_pem_format(data)
try:
if self.crl_pem:
self.crl = x509.load_pem_x509_crl(data, default_backend())
else:
self.crl = x509.load_der_x509_crl(data, default_backend())
self.crl = x509.load_pem_x509_crl(data, default_backend())
except Exception as e:
self.module.fail_json(msg='Error while decoding CRL: {0}'.format(e))
def _dump_revoked(self, entry):
return {
'serial_number': entry['serial_number'],
'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT),
'issuer':
[crypto_utils.cryptography_decode_name(issuer) for issuer in entry['issuer']]
if entry['issuer'] is not None else None,
'issuer_critical': entry['issuer_critical'],
'reason': crypto_utils.REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None,
'reason_critical': entry['reason_critical'],
'invalidity_date':
entry['invalidity_date'].strftime(TIMESTAMP_FORMAT)
if entry['invalidity_date'] is not None else None,
'invalidity_date_critical': entry['invalidity_date_critical'],
}
def get_info(self):
result = {
'changed': False,
'format': 'pem' if self.crl_pem else 'der',
'last_update': None,
'next_update': None,
'digest': None,
@@ -233,27 +215,37 @@ class CRLInfo(OpenSSLObject):
result['last_update'] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
result['next_update'] = self.crl.next_update.strftime(TIMESTAMP_FORMAT)
result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl))
try:
result['digest'] = crypto_utils.cryptography_oid_to_name(self.crl.signature_algorithm_oid)
except AttributeError:
# Older cryptography versions don't have signature_algorithm_oid yet
dotted = crypto_utils._obj2txt(
self.crl._backend._lib,
self.crl._backend._ffi,
self.crl._x509_crl.sig_alg.algorithm
)
oid = x509.oid.ObjectIdentifier(dotted)
result['digest'] = crypto_utils.cryptography_oid_to_name(oid)
issuer = []
for attribute in self.crl.issuer:
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
issuer.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
result['issuer_ordered'] = issuer
result['issuer'] = {}
for k, v in issuer:
result['issuer'][k] = v
result['revoked_certificates'] = []
for cert in self.crl:
entry = cryptography_decode_revoked_certificate(cert)
result['revoked_certificates'].append(cryptography_dump_revoked(entry))
entry = crypto_utils.cryptography_decode_revoked_certificate(cert)
result['revoked_certificates'].append(self._dump_revoked(entry))
return result
def generate(self):
# Empty method because OpenSSLObject wants this
# Empty method because crypto_utils.OpenSSLObject wants this
pass
def dump(self):
# Empty method because OpenSSLObject wants this
# Empty method because crypto_utils.OpenSSLObject wants this
pass
@@ -280,7 +272,7 @@ def main():
crl = CRLInfo(module)
result = crl.get_info()
module.exit_json(**result)
except OpenSSLObjectError as e:
except crypto_utils.OpenSSLObjectError as e:
module.fail_json(msg=to_native(e))

View File

@@ -1,78 +0,0 @@
language: python
env:
matrix:
- T=none
matrix:
exclude:
- env: T=none
include:
- env: T=devel/sanity/1
- env: T=devel/sanity/extra
- env: T=devel/units/2.6/1
- env: T=devel/units/2.7/1
- env: T=devel/units/3.5/1
- env: T=devel/units/3.6/1
- env: T=devel/units/3.7/1
- env: T=devel/units/3.8/1
- env: T=devel/units/3.9/1
- env: T=devel/osx/10.11/1
- env: T=devel/rhel/7.8/1
- env: T=devel/rhel/8.2/1
- env: T=devel/freebsd/11.1/1
- env: T=devel/freebsd/12.1/1
- env: T=devel/linux/centos6/1
- env: T=devel/linux/centos7/1
- env: T=devel/linux/centos8/1
- env: T=devel/linux/fedora30/1
- env: T=devel/linux/fedora31/1
- env: T=devel/linux/fedora32/1
- env: T=devel/linux/opensuse15py2/1
- env: T=devel/linux/opensuse15/1
- env: T=devel/linux/ubuntu1604/1
- env: T=devel/linux/ubuntu1804/1
- env: T=devel/cloud/2.6/1
- env: T=devel/cloud/2.7/1
- env: T=devel/cloud/3.5/1
- env: T=devel/cloud/3.6/1
- env: T=devel/cloud/3.7/1
- env: T=devel/cloud/3.8/1
- env: T=devel/cloud/3.9/1
# For Ansible 2.10, use a combination of different targets
- env: T=2.10/sanity/1
- env: T=2.10/units/2.7/1
- env: T=2.10/units/3.8/1
- env: T=2.10/rhel/7.8/1
- env: T=2.10/linux/ubuntu1804/1
- env: T=2.10/cloud/3.6/1
# For Ansible 2.9, use a combination of different targets
- env: T=2.9/sanity/1
- env: T=2.9/units/2.7/1
- env: T=2.9/units/3.8/1
- env: T=2.9/rhel/7.8/1
- env: T=2.9/linux/ubuntu1804/1
- env: T=2.9/cloud/3.5/1
branches:
except:
- "*-patch-*"
- "revert-*-*"
build:
ci:
- tests/utils/shippable/timing.sh tests/utils/shippable/shippable.sh $T
integrations:
notifications:
- integrationName: email
type: email
on_success: never
on_failure: never
on_start: never
on_pull_request: never

View File

@@ -1,20 +1,8 @@
- name: Generate account keys
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/{{ item }}.pem
loop:
- accountkey
- accountkey2
- accountkey3
- accountkey4
- accountkey5
- name: Generate account key
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/accountkey.pem
- name: Parse account keys (to ease debugging some test failures)
command: openssl ec -in {{ output_dir }}/{{ item }}.pem -noout -text
loop:
- accountkey
- accountkey2
- accountkey3
- accountkey4
- accountkey5
- name: Parse account key (to ease debugging some test failures)
command: openssl ec -in {{ output_dir }}/accountkey.pem -noout -text
- name: Do not try to create account
acme_account:
@@ -165,6 +153,12 @@
contact: []
register: account_modified_2_idempotent
- name: Generate new account key
command: openssl ecparam -name secp384r1 -genkey -out {{ output_dir }}/accountkey2.pem
- name: Parse account key (to ease debugging some test failures)
command: openssl ec -in {{ output_dir }}/accountkey2.pem -noout -text
- name: Change account key (check mode, diff)
acme_account:
select_crypto_backend: "{{ select_crypto_backend }}"
@@ -248,36 +242,3 @@
allow_creation: no
ignore_errors: yes
register: account_not_created_3
- name: Create account with External Account Binding
acme_account:
select_crypto_backend: "{{ select_crypto_backend }}"
account_key_src: "{{ output_dir }}/{{ item.account }}.pem"
acme_version: 2
acme_directory: https://{{ acme_host }}:14000/dir
validate_certs: no
state: present
allow_creation: yes
terms_agreed: yes
contact:
- mailto:example@example.org
external_account_binding:
kid: "{{ item.kid }}"
alg: "{{ item.alg }}"
key: "{{ item.key }}"
register: account_created_eab
ignore_errors: yes
loop:
- account: accountkey3
kid: kid-1
alg: HS256
key: zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W
- account: accountkey4
kid: kid-2
alg: HS384
key: b10lLJs8l1GPIzsLP0s6pMt8O0XVGnfTaCeROxQM0BIt2XrJMDHJZBM5NuQmQJQH
- account: accountkey5
kid: kid-3
alg: HS512
key: zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W
- debug: var=account_created_eab

View File

@@ -127,11 +127,3 @@
that:
- account_not_created_3 is failed
- account_not_created_3.msg == 'Account does not exist or is deactivated.'
- name: Validate that the account with External Account Binding has been created
assert:
that:
- account_created_eab.results[0] is changed
- account_created_eab.results[1] is changed
- account_created_eab.results[2] is failed
- "'HS512 key must be at least 64 bytes long' in account_created_eab.results[2].msg"

View File

@@ -251,7 +251,7 @@
# the first chain will be found, and we need a second condition to
# make sure that the first condition actually works. (The second
# condition has been tested above.)
- test_certificates: first
- test_certificates: last
subject_key_identifier: "{{ acme_intermediates[0].subject_key_identifier }}"
- test_certificates: last
issuer: "{{ acme_roots[1].subject }}"
@@ -372,35 +372,35 @@
register: cert_8_text
# Dump certificate info
- name: Dumping cert 1
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/cert-1.pem"
register: cert_1_info
- name: Dumping cert 2
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/cert-2.pem"
register: cert_2_info
- name: Dumping cert 3
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/cert-3.pem"
register: cert_3_info
- name: Dumping cert 4
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/cert-4.pem"
register: cert_4_info
- name: Dumping cert 5
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/cert-5.pem"
register: cert_5_info
- name: Dumping cert 6
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/cert-6.pem"
register: cert_6_info
- name: Dumping cert 7
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/cert-7.pem"
register: cert_7_info
- name: Dumping cert 8
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/cert-8.pem"
register: cert_8_info
## GET ACCOUNT ORDERS #########################################################################

View File

@@ -7,13 +7,13 @@
loop: "{{ query('nested', types, root_numbers) }}"
- name: Analyze root certificates
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/acme-root-{{ item }}.pem"
loop: "{{ root_numbers }}"
register: acme_roots
- name: Analyze intermediate certificates
x509_certificate_info:
openssl_certificate_info:
path: "{{ output_dir }}/acme-intermediate-{{ item }}.pem"
loop: "{{ root_numbers }}"
register: acme_intermediates

View File

@@ -1 +1,2 @@
shippable/posix/group1
skip/aix

View File

@@ -2,16 +2,18 @@
command: '{{ ansible_python.executable }} -c ''import cryptography; print(cryptography.__version__)'''
register: cryptography_version
- block:
- name: Make sure testhost directory exists
- name: Archive test files
community.general.archive:
path: '{{ role_path }}/files/'
dest: '{{ output_dir }}/files.tgz'
- name: Create temporary directory to store files
file:
path: '{{ remote_tmp_dir }}/files/'
state: directory
when: ansible_version.string is version('2.10', '<')
- name: Copy test files to testhost
copy:
src: '{{ role_path }}/files/'
path: '{{ remote_tmp_dir }}/files/'
- name: Unarchive test files on testhost
unarchive:
src: '{{ output_dir }}/files.tgz'
dest: '{{ remote_tmp_dir }}/files/'
remote_src: yes
- name: Find root for cert 1
certificate_complete_chain:
input_chain: '{{ lookup(''file'', ''cert1-fullchain.pem'', rstrip=False) }}'

View File

@@ -1,3 +1,4 @@
shippable/posix/group1
destructive
needs/httptester
skip/aix

View File

@@ -0,0 +1,2 @@
hidden

View File

@@ -0,0 +1,40 @@
---
- name: Include OS-specific variables
include_vars: '{{ ansible_os_family }}.yml'
when: not ansible_os_family == "Darwin"
- name: Install OpenSSL
become: True
package:
name: '{{ openssl_package_name }}'
when: not ansible_os_family == 'Darwin'
- name: Install pyOpenSSL (Python 3)
become: True
package:
name: '{{ pyopenssl_package_name_python3 }}'
when: not ansible_os_family == 'Darwin' and ansible_python_version is version('3.0', '>=')
- name: Install pyOpenSSL (Python 2)
become: True
package:
name: '{{ pyopenssl_package_name }}'
when: not ansible_os_family == 'Darwin' and ansible_python_version is version('3.0', '<')
- name: Install pyOpenSSL (Darwin)
become: True
pip:
name: pyOpenSSL
when: ansible_os_family == 'Darwin'
- name: register pyOpenSSL version
command: "{{ ansible_python.executable }} -c 'import OpenSSL; print(OpenSSL.__version__)'"
register: pyopenssl_version
- name: register openssl version
shell: "openssl version | cut -d' ' -f2"
register: openssl_version
- name: register cryptography version
command: "{{ ansible_python.executable }} -c 'import cryptography; print(cryptography.__version__)'"
register: cryptography_version

View File

@@ -0,0 +1,3 @@
pyopenssl_package_name: python-openssl
pyopenssl_package_name_python3: python3-openssl
openssl_package_name: openssl

View File

@@ -0,0 +1,3 @@
pyopenssl_package_name: py27-openssl
pyopenssl_package_name_python3: py36-openssl
openssl_package_name: openssl

View File

@@ -0,0 +1,3 @@
pyopenssl_package_name: pyOpenSSL
pyopenssl_package_name_python3: python3-pyOpenSSL
openssl_package_name: openssl

View File

@@ -0,0 +1,3 @@
pyopenssl_package_name: python-pyOpenSSL
pyopenssl_package_name_python3: python3-pyOpenSSL
openssl_package_name: openssl

View File

@@ -0,0 +1,4 @@
x509_crl_info
shippable/posix/incidental
destructive
skip/aix

View File

@@ -0,0 +1,2 @@
dependencies:
- incidental_setup_openssl

View File

@@ -0,0 +1,289 @@
---
- name: Create CRL 1 (check mode)
x509_crl:
path: '{{ output_dir }}/ca-crl1.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: 20191013000000Z
next_update: 20191113000000Z
revoked_certificates:
- path: '{{ output_dir }}/cert-1.pem'
revocation_date: 20191013000000Z
- path: '{{ output_dir }}/cert-2.pem'
revocation_date: 20191013000000Z
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
revocation_date: 20191001000000Z
check_mode: yes
register: crl_1_check
- name: Create CRL 1
x509_crl:
path: '{{ output_dir }}/ca-crl1.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: 20191013000000Z
next_update: 20191113000000Z
revoked_certificates:
- path: '{{ output_dir }}/cert-1.pem'
revocation_date: 20191013000000Z
- path: '{{ output_dir }}/cert-2.pem'
revocation_date: 20191013000000Z
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
revocation_date: 20191001000000Z
register: crl_1
- name: Retrieve CRL 1 infos
x509_crl_info:
path: '{{ output_dir }}/ca-crl1.crl'
register: crl_1_info_1
- name: Retrieve CRL 1 infos via file content
x509_crl_info:
content: '{{ lookup("file", output_dir ~ "/ca-crl1.crl") }}'
register: crl_1_info_2
- name: Create CRL 1 (idempotent, check mode)
x509_crl:
path: '{{ output_dir }}/ca-crl1.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: 20191013000000Z
next_update: 20191113000000Z
revoked_certificates:
- path: '{{ output_dir }}/cert-1.pem'
revocation_date: 20191013000000Z
- path: '{{ output_dir }}/cert-2.pem'
revocation_date: 20191013000000Z
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
revocation_date: 20191001000000Z
check_mode: yes
register: crl_1_idem_check
- name: Create CRL 1 (idempotent)
x509_crl:
path: '{{ output_dir }}/ca-crl1.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: 20191013000000Z
next_update: 20191113000000Z
revoked_certificates:
- path: '{{ output_dir }}/cert-1.pem'
revocation_date: 20191013000000Z
- path: '{{ output_dir }}/cert-2.pem'
revocation_date: 20191013000000Z
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
revocation_date: 20191001000000Z
register: crl_1_idem
- name: Create CRL 1 (idempotent with content, check mode)
x509_crl:
path: '{{ output_dir }}/ca-crl1.crl'
privatekey_content: "{{ lookup('file', output_dir ~ '/ca.key') }}"
issuer:
CN: Ansible
last_update: 20191013000000Z
next_update: 20191113000000Z
revoked_certificates:
- content: "{{ lookup('file', output_dir ~ '/cert-1.pem') }}"
revocation_date: 20191013000000Z
- content: "{{ lookup('file', output_dir ~ '/cert-2.pem') }}"
revocation_date: 20191013000000Z
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
revocation_date: 20191001000000Z
check_mode: yes
register: crl_1_idem_content_check
- name: Create CRL 1 (idempotent with content)
x509_crl:
path: '{{ output_dir }}/ca-crl1.crl'
privatekey_content: "{{ lookup('file', output_dir ~ '/ca.key') }}"
issuer:
CN: Ansible
last_update: 20191013000000Z
next_update: 20191113000000Z
revoked_certificates:
- content: "{{ lookup('file', output_dir ~ '/cert-1.pem') }}"
revocation_date: 20191013000000Z
- content: "{{ lookup('file', output_dir ~ '/cert-2.pem') }}"
revocation_date: 20191013000000Z
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
revocation_date: 20191001000000Z
register: crl_1_idem_content
- name: Create CRL 2 (check mode)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- path: '{{ output_dir }}/cert-1.pem'
- path: '{{ output_dir }}/cert-2.pem'
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
check_mode: yes
register: crl_2_check
- name: Create CRL 2
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- path: '{{ output_dir }}/cert-1.pem'
- path: '{{ output_dir }}/cert-2.pem'
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
register: crl_2
- name: Create CRL 2 (idempotent, check mode)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- path: '{{ output_dir }}/cert-1.pem'
- path: '{{ output_dir }}/cert-2.pem'
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
ignore_timestamps: yes
check_mode: yes
register: crl_2_idem_check
- name: Create CRL 2 (idempotent)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- path: '{{ output_dir }}/cert-1.pem'
- path: '{{ output_dir }}/cert-2.pem'
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
ignore_timestamps: yes
register: crl_2_idem
- name: Create CRL 2 (idempotent update, check mode)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- serial_number: 1235
ignore_timestamps: yes
mode: update
check_mode: yes
register: crl_2_idem_update_change_check
- name: Create CRL 2 (idempotent update)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- serial_number: 1235
ignore_timestamps: yes
mode: update
register: crl_2_idem_update_change
- name: Create CRL 2 (idempotent update, check mode)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- path: '{{ output_dir }}/cert-2.pem'
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
ignore_timestamps: yes
mode: update
check_mode: yes
register: crl_2_idem_update_check
- name: Create CRL 2 (idempotent update)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- path: '{{ output_dir }}/cert-2.pem'
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
ignore_timestamps: yes
mode: update
register: crl_2_idem_update
- name: Create CRL 2 (changed timestamps, check mode)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- path: '{{ output_dir }}/cert-2.pem'
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
ignore_timestamps: no
mode: update
check_mode: yes
register: crl_2_change_check
- name: Create CRL 2 (changed timestamps)
x509_crl:
path: '{{ output_dir }}/ca-crl2.crl'
privatekey_path: '{{ output_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- path: '{{ output_dir }}/cert-2.pem'
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
ignore_timestamps: no
mode: update
return_content: yes
register: crl_2_change

View File

@@ -0,0 +1,83 @@
---
- set_fact:
certificates:
- name: ca
subject:
commonName: Ansible
is_ca: yes
- name: ca-2
subject:
commonName: Ansible Other CA
is_ca: yes
- name: cert-1
subject_alt_name:
- DNS:ansible.com
- name: cert-2
subject_alt_name:
- DNS:example.com
- name: cert-3
subject_alt_name:
- DNS:example.org
- IP:1.2.3.4
- name: cert-4
subject_alt_name:
- DNS:test.ansible.com
- DNS:b64.ansible.com
- name: Generate private keys
openssl_privatekey:
path: '{{ output_dir }}/{{ item.name }}.key'
type: ECC
curve: secp256r1
loop: "{{ certificates }}"
- name: Generate CSRs
openssl_csr:
path: '{{ output_dir }}/{{ item.name }}.csr'
privatekey_path: '{{ output_dir }}/{{ item.name }}.key'
subject: "{{ item.subject | default(omit) }}"
subject_alt_name: "{{ item.subject_alt_name | default(omit) }}"
basic_constraints: "{{ 'CA:TRUE' if item.is_ca | default(false) else omit }}"
use_common_name_for_san: no
loop: "{{ certificates }}"
- name: Generate CA certificates
openssl_certificate:
path: '{{ output_dir }}/{{ item.name }}.pem'
csr_path: '{{ output_dir }}/{{ item.name }}.csr'
privatekey_path: '{{ output_dir }}/{{ item.name }}.key'
provider: selfsigned
loop: "{{ certificates }}"
when: item.is_ca | default(false)
- name: Generate other certificates
openssl_certificate:
path: '{{ output_dir }}/{{ item.name }}.pem'
csr_path: '{{ output_dir }}/{{ item.name }}.csr'
provider: ownca
ownca_path: '{{ output_dir }}/ca.pem'
ownca_privatekey_path: '{{ output_dir }}/ca.key'
loop: "{{ certificates }}"
when: not (item.is_ca | default(false))
- name: Get certificate infos
openssl_certificate_info:
path: '{{ output_dir }}/{{ item }}.pem'
loop:
- cert-1
- cert-2
- cert-3
- cert-4
register: certificate_infos
- block:
- name: Running tests with cryptography backend
include_tasks: impl.yml
vars:
select_crypto_backend: cryptography
- import_tasks: ../tests/validate.yml
vars:
select_crypto_backend: cryptography
when: cryptography_version.stdout is version('1.2', '>=')

View File

@@ -0,0 +1,61 @@
---
- name: Validate CRL 1
assert:
that:
- crl_1_check is changed
- crl_1 is changed
- crl_1_idem_check is not changed
- crl_1_idem is not changed
- crl_1_idem_content_check is not changed
- crl_1_idem_content is not changed
- name: Validate CRL 1 info
assert:
that:
- crl_1_info_1 == crl_1_info_2
- crl_1_info_1.digest == 'ecdsa-with-SHA256'
- crl_1_info_1.issuer | length == 1
- crl_1_info_1.issuer.commonName == 'Ansible'
- crl_1_info_1.issuer_ordered | length == 1
- crl_1_info_1.last_update == '20191013000000Z'
- crl_1_info_1.next_update == '20191113000000Z'
- crl_1_info_1.revoked_certificates | length == 3
- crl_1_info_1.revoked_certificates[0].invalidity_date is none
- crl_1_info_1.revoked_certificates[0].invalidity_date_critical == false
- crl_1_info_1.revoked_certificates[0].issuer is none
- crl_1_info_1.revoked_certificates[0].issuer_critical == false
- crl_1_info_1.revoked_certificates[0].reason is none
- crl_1_info_1.revoked_certificates[0].reason_critical == false
- crl_1_info_1.revoked_certificates[0].revocation_date == '20191013000000Z'
- crl_1_info_1.revoked_certificates[0].serial_number == certificate_infos.results[0].serial_number
- crl_1_info_1.revoked_certificates[1].invalidity_date == '20191012000000Z'
- crl_1_info_1.revoked_certificates[1].invalidity_date_critical == false
- crl_1_info_1.revoked_certificates[1].issuer is none
- crl_1_info_1.revoked_certificates[1].issuer_critical == false
- crl_1_info_1.revoked_certificates[1].reason == 'key_compromise'
- crl_1_info_1.revoked_certificates[1].reason_critical == true
- crl_1_info_1.revoked_certificates[1].revocation_date == '20191013000000Z'
- crl_1_info_1.revoked_certificates[1].serial_number == certificate_infos.results[1].serial_number
- crl_1_info_1.revoked_certificates[2].invalidity_date is none
- crl_1_info_1.revoked_certificates[2].invalidity_date_critical == false
- crl_1_info_1.revoked_certificates[2].issuer is none
- crl_1_info_1.revoked_certificates[2].issuer_critical == false
- crl_1_info_1.revoked_certificates[2].reason is none
- crl_1_info_1.revoked_certificates[2].reason_critical == false
- crl_1_info_1.revoked_certificates[2].revocation_date == '20191001000000Z'
- crl_1_info_1.revoked_certificates[2].serial_number == 1234
- name: Validate CRL 2
assert:
that:
- crl_2_check is changed
- crl_2 is changed
- crl_2_idem_check is not changed
- crl_2_idem is not changed
- crl_2_idem_update_change_check is changed
- crl_2_idem_update_change is changed
- crl_2_idem_update_check is not changed
- crl_2_idem_update is not changed
- crl_2_change_check is changed
- crl_2_change is changed
- crl_2_change.crl == lookup('file', output_dir ~ '/ca-crl2.crl', rstrip=False)

View File

@@ -1,4 +1,5 @@
shippable/posix/group1
shippable/posix/group4
skip/aix
skip/osx
skip/freebsd
skip/docker

View File

@@ -1,2 +1,3 @@
shippable/posix/group1
destructive
skip/aix

View File

@@ -1,2 +1,3 @@
shippable/posix/group1
destructive
skip/aix

View File

@@ -0,0 +1,3 @@
shippable/posix/group5
destructive
skip/aix

View File

@@ -31,7 +31,7 @@
useCommonNameForSAN: no
- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (no extensions)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
csr_path: '{{ output_dir }}/csr_noext.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -40,7 +40,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (with SANs)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_sans.pem'
csr_path: '{{ output_dir }}/csr_sans.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -49,7 +49,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (should fail)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
provider: assertonly
subject_alt_name:
@@ -59,7 +59,7 @@
register: extension_missing_san
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_sans.pem'
provider: assertonly
subject_alt_name:
@@ -70,7 +70,7 @@
register: extension_san
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (strict)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_sans.pem'
provider: assertonly
subject_alt_name:
@@ -82,7 +82,7 @@
register: extension_san_strict
- name: (Assertonly, {{select_crypto_backend}}) - Assert that key_usage is there (should fail)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
provider: assertonly
key_usage:
@@ -92,7 +92,7 @@
register: extension_missing_ku
- name: (Assertonly, {{select_crypto_backend}}) - Assert that extended_key_usage is there (should fail)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
provider: assertonly
extended_key_usage:
@@ -113,7 +113,7 @@
- "'Found no extendedKeyUsage extension' in extension_missing_eku.msg"
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 1
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
privatekey_path: '{{ output_dir }}/privatekey.pem'
privatekey_passphrase: hunter2
@@ -123,7 +123,7 @@
register: passphrase_error_1
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 2
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
privatekey_passphrase: wrong_password
@@ -133,7 +133,7 @@
register: passphrase_error_2
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 3
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
provider: assertonly

View File

@@ -11,7 +11,7 @@
commonName: www.example.com
- name: (Expired, {{select_crypto_backend}}) Generate expired selfsigned certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/has_expired_cert.pem'
csr_path: '{{ output_dir }}/has_expired_csr.csr'
privatekey_path: '{{ output_dir }}/has_expired_privatekey.pem'
@@ -27,7 +27,7 @@
when: select_crypto_backend == 'cryptography' # So we create it with 'command'
- name: "(Expired) Check task fails because cert is expired (has_expired: false)"
x509_certificate:
openssl_certificate:
provider: assertonly
path: "{{ output_dir }}/has_expired_cert.pem"
has_expired: false
@@ -40,7 +40,7 @@
that: expired_cert_check is failed
- name: "(Expired) Check expired cert check is ignored (has_expired: true)"
x509_certificate:
openssl_certificate:
provider: assertonly
path: "{{ output_dir }}/has_expired_cert.pem"
has_expired: true

View File

@@ -34,7 +34,7 @@
basic_constraints_critical: yes
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ca_cert.pem'
csr_path: '{{ output_dir }}/ca_csr.csr'
privatekey_path: '{{ output_dir }}/ca_privatekey.pem'
@@ -43,7 +43,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate (privatekey passphrase)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ca_cert_pw.pem'
csr_path: '{{ output_dir }}/ca_csr_pw.csr'
privatekey_path: '{{ output_dir }}/ca_privatekey_pw.pem'
@@ -53,7 +53,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -66,7 +66,7 @@
register: ownca_certificate
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -79,7 +79,7 @@
register: ownca_certificate_idempotence
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (check mode)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -91,7 +91,7 @@
check_mode: yes
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert.pem'
privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: assertonly
@@ -107,7 +107,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca v2 certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_v2.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -121,7 +121,7 @@
ignore_errors: true
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate2
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert2.pem'
csr_path: '{{ output_dir }}/csr2.csr'
privatekey_path: '{{ output_dir }}/privatekey2.pem'
@@ -132,7 +132,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate2
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert2.pem'
privatekey_path: '{{ output_dir }}/privatekey2.pem'
provider: assertonly
@@ -160,7 +160,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with notBefore and notAfter
x509_certificate:
openssl_certificate:
provider: ownca
ownca_not_before: 20181023133742Z
ownca_not_after: 20191023133742Z
@@ -172,7 +172,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with relative notBefore and notAfter
x509_certificate:
openssl_certificate:
provider: ownca
ownca_not_before: +1s
ownca_not_after: +52w
@@ -184,7 +184,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca ECC certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ecc.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -196,7 +196,7 @@
register: ownca_certificate_ecc
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned certificate (privatekey passphrase)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ecc_2.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert_pw.pem'
@@ -208,7 +208,7 @@
register: selfsigned_certificate_passphrase
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 1)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_pw1.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -221,7 +221,7 @@
register: passphrase_error_1
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 2)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_pw2.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -234,7 +234,7 @@
register: passphrase_error_2
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 3)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_pw3.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -250,7 +250,7 @@
dest: "{{ output_dir }}/ownca_broken.pem"
content: "broken"
- name: Regenerate broken cert
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_broken.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -261,7 +261,7 @@
register: ownca_broken
- name: (OwnCA, {{select_crypto_backend}}) Backup test
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_backup.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -272,7 +272,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: ownca_backup_1
- name: (OwnCA, {{select_crypto_backend}}) Backup test (idempotent)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_backup.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -283,7 +283,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: ownca_backup_2
- name: (OwnCA, {{select_crypto_backend}}) Backup test (change)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_backup.pem'
csr_path: '{{ output_dir }}/csr.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -294,7 +294,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: ownca_backup_3
- name: (OwnCA, {{select_crypto_backend}}) Backup test (remove)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_backup.pem'
state: absent
provider: ownca
@@ -302,7 +302,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: ownca_backup_4
- name: (OwnCA, {{select_crypto_backend}}) Backup test (remove, idempotent)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_backup.pem'
state: absent
provider: ownca
@@ -311,7 +311,7 @@
register: ownca_backup_5
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -324,7 +324,7 @@
register: ownca_subject_key_identifier_1
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (idempotency)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -337,7 +337,7 @@
register: ownca_subject_key_identifier_2
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -350,7 +350,7 @@
register: ownca_subject_key_identifier_3
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove idempotency)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -363,7 +363,7 @@
register: ownca_subject_key_identifier_4
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (re-enable)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -376,7 +376,7 @@
register: ownca_subject_key_identifier_5
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -389,7 +389,7 @@
register: ownca_authority_key_identifier_1
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (idempotency)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -402,7 +402,7 @@
register: ownca_authority_key_identifier_2
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -415,7 +415,7 @@
register: ownca_authority_key_identifier_3
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove idempotency)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -428,7 +428,7 @@
register: ownca_authority_key_identifier_4
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (re-add)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -469,7 +469,7 @@
ignore_errors: yes
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_{{ item }}.pem'
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -484,7 +484,7 @@
ignore_errors: yes
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_{{ item }}.pem'
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
@@ -529,7 +529,7 @@
ignore_errors: yes
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ca_cert_{{ item }}.pem'
csr_path: '{{ output_dir }}/ca_csr_{{ item }}.csr'
privatekey_path: '{{ output_dir }}/ca_privatekey_{{ item }}.pem'
@@ -542,7 +542,7 @@
ignore_errors: yes
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_{{ item }}_2.pem'
csr_path: '{{ output_dir }}/csr.csr'
ownca_path: '{{ output_dir }}/ca_cert_{{ item }}.pem'
@@ -558,7 +558,7 @@
ignore_errors: yes
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_{{ item }}_2.pem'
csr_path: '{{ output_dir }}/csr.csr'
ownca_path: '{{ output_dir }}/ca_cert_{{ item }}.pem'

View File

@@ -9,7 +9,7 @@
privatekey_path: '{{ output_dir }}/removal_privatekey.pem'
- name: (Removal, {{select_crypto_backend}}) Generate selfsigned certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/removal_cert.pem'
csr_path: '{{ output_dir }}/removal_csr.csr'
privatekey_path: '{{ output_dir }}/removal_privatekey.pem'
@@ -23,7 +23,7 @@
register: removal_1_prestat
- name: "(Removal, {{select_crypto_backend}}) Remove certificate"
x509_certificate:
openssl_certificate:
path: "{{ output_dir }}/removal_cert.pem"
state: absent
select_crypto_backend: '{{ select_crypto_backend }}'
@@ -36,7 +36,7 @@
register: removal_1_poststat
- name: "(Removal, {{select_crypto_backend}}) Remove certificate (idempotent)"
x509_certificate:
openssl_certificate:
path: "{{ output_dir }}/removal_cert.pem"
state: absent
select_crypto_backend: '{{ select_crypto_backend }}'

View File

@@ -25,7 +25,7 @@
commonName: www.example.org
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -36,7 +36,7 @@
register: selfsigned_certificate
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate - idempotency
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -47,7 +47,7 @@
register: selfsigned_certificate_idempotence
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (check mode)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -57,7 +57,7 @@
check_mode: yes
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (check mode, other CSR)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert.pem'
csr_path: '{{ output_dir }}/csr_minimal_change.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -68,7 +68,7 @@
register: selfsigned_certificate_csr_minimal_change
- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert.pem'
privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: assertonly
@@ -82,7 +82,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned v2 certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_v2.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -117,7 +117,7 @@
- biometricInfo
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate2
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert2.pem'
csr_path: '{{ output_dir }}/csr2.csr'
privatekey_path: '{{ output_dir }}/privatekey2.pem'
@@ -126,7 +126,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate2
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert2.pem'
privatekey_path: '{{ output_dir }}/privatekey2.pem'
provider: assertonly
@@ -163,7 +163,7 @@
path: "{{ output_dir }}/csr3.pem"
- name: (Selfsigned, {{select_crypto_backend}}) Create certificate3 with notBefore and notAfter
x509_certificate:
openssl_certificate:
provider: selfsigned
selfsigned_not_before: 20181023133742Z
selfsigned_not_after: 20191023133742Z
@@ -187,7 +187,7 @@
commonName: www.example.com
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_ecc.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -205,7 +205,7 @@
commonName: www.example.com
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (privatekey passphrase)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_pass.pem'
csr_path: '{{ output_dir }}/csr_pass.csr'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
@@ -216,7 +216,7 @@
register: selfsigned_certificate_passphrase
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 1)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_pw1.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -228,7 +228,7 @@
register: passphrase_error_1
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 2)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_pw2.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
@@ -240,7 +240,7 @@
register: passphrase_error_2
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 3)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_pw3.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
@@ -255,7 +255,7 @@
dest: "{{ output_dir }}/cert_broken.pem"
content: "broken"
- name: Regenerate broken cert
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_broken.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -264,7 +264,7 @@
register: selfsigned_broken
- name: (Selfsigned, {{select_crypto_backend}}) Backup test
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -274,7 +274,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: selfsigned_backup_1
- name: (Selfsigned, {{select_crypto_backend}}) Backup test (idempotent)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -284,7 +284,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: selfsigned_backup_2
- name: (Selfsigned, {{select_crypto_backend}}) Backup test (change)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
csr_path: '{{ output_dir }}/csr.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
@@ -294,7 +294,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: selfsigned_backup_3
- name: (Selfsigned, {{select_crypto_backend}}) Backup test (remove)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
state: absent
provider: selfsigned
@@ -302,7 +302,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: selfsigned_backup_4
- name: (Selfsigned, {{select_crypto_backend}}) Backup test (remove, idempotent)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
state: absent
provider: selfsigned
@@ -311,7 +311,7 @@
register: selfsigned_backup_5
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -323,7 +323,7 @@
register: selfsigned_subject_key_identifier_1
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (idempotency)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -335,7 +335,7 @@
register: selfsigned_subject_key_identifier_2
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -347,7 +347,7 @@
register: selfsigned_subject_key_identifier_3
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove idempotency)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -359,7 +359,7 @@
register: selfsigned_subject_key_identifier_4
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (re-enable)
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
@@ -399,7 +399,7 @@
ignore_errors: yes
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_{{ item }}.pem'
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem'
@@ -413,7 +413,7 @@
ignore_errors: yes
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate - idempotency
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_{{ item }}.pem'
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem'

View File

@@ -3,7 +3,7 @@
msg: "Executing tests with backend {{ select_crypto_backend }}"
- name: ({{select_crypto_backend}}) Get certificate info
x509_certificate_info:
openssl_certificate_info:
path: '{{ output_dir }}/cert_1.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
register: result
@@ -36,7 +36,7 @@
info_results: "{{ info_results + [result] }}"
- name: ({{select_crypto_backend}}) Get certificate info directly
x509_certificate_info:
openssl_certificate_info:
content: '{{ lookup("file", output_dir ~ "/cert_1.pem") }}'
select_crypto_backend: '{{ select_crypto_backend }}'
register: result_direct
@@ -47,7 +47,7 @@
- result == result_direct
- name: ({{select_crypto_backend}}) Get certificate info
x509_certificate_info:
openssl_certificate_info:
path: '{{ output_dir }}/cert_2.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
valid_at:
@@ -66,7 +66,7 @@
info_results: "{{ info_results + [result] }}"
- name: ({{select_crypto_backend}}) Get certificate info
x509_certificate_info:
openssl_certificate_info:
path: '{{ output_dir }}/cert_3.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
register: result
@@ -88,7 +88,7 @@
info_results: "{{ info_results + [result] }}"
- name: ({{select_crypto_backend}}) Get certificate info
x509_certificate_info:
openssl_certificate_info:
path: '{{ output_dir }}/cert_4.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
register: result
@@ -106,7 +106,7 @@
info_results: "{{ info_results + [result] }}"
- name: ({{select_crypto_backend}}) Get certificate info for packaged cert 1
x509_certificate_info:
openssl_certificate_info:
path: '{{ role_path }}/files/cert1.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
register: result

View File

@@ -113,7 +113,7 @@
authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
- name: Generate selfsigned certificates
x509_certificate:
openssl_certificate:
path: '{{ output_dir }}/cert_{{ item }}.pem'
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'

View File

@@ -1,2 +1,3 @@
shippable/posix/group1
destructive
skip/aix

View File

@@ -550,17 +550,29 @@
- Encipher Only
- decipherOnly
key_usage_critical: yes
extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}'
subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}'
extended_key_usage:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
subject_alt_name:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
basic_constraints:
- "CA:TRUE"
- "pathlen:23"
basic_constraints_critical: yes
name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}'
name_constraints_excluded:
- "DNS:.example.com"
- "DNS:.org"
name_constraints_critical: yes
ocsp_must_staple: yes
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
@@ -568,61 +580,9 @@
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
select_crypto_backend: '{{ select_crypto_backend }}'
vars:
value_for_extended_key_usage_pyopenssl:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
value_for_extended_key_usage:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
- 1.2.3.4.5.6
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
value_for_san_pyopenssl:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
- "RID:1.2.3.4"
value_for_san:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
- "RID:1.2.3.4"
- "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71"
- "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost"
- "dirName:O = Example Net, CN = example.net"
- "dirName:/O=Example Com/CN=example.com"
value_for_name_constraints_permitted:
- "DNS:www.example.com"
- "IP:1.2.3.0/24"
- "IP:::1:0:0/112"
value_for_name_constraints_permitted_pyopenssl:
- "DNS:www.example.com"
- "IP:1.2.3.0/255.255.255.0"
register: everything_1
- name: Generate CSR with everything (idempotent, check mode)
@@ -658,17 +618,29 @@
- Encipher Only
- decipherOnly
key_usage_critical: yes
extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}'
subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}'
extended_key_usage:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
subject_alt_name:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
basic_constraints:
- "CA:TRUE"
- "pathlen:23"
basic_constraints_critical: yes
name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}'
name_constraints_excluded:
- "DNS:.org"
- "DNS:.example.com"
name_constraints_critical: yes
ocsp_must_staple: yes
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
@@ -676,61 +648,9 @@
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
select_crypto_backend: '{{ select_crypto_backend }}'
vars:
value_for_extended_key_usage_pyopenssl:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
value_for_extended_key_usage:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
- 1.2.3.4.5.6
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
value_for_san_pyopenssl:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
- "RID:1.2.3.4"
value_for_san:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
- "RID:1.2.3.4"
- "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71"
- "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost"
- "dirName:O=Example Net,CN=example.net"
- "dirName:/O = Example Com/CN = example.com"
value_for_name_constraints_permitted:
- "DNS:www.example.com"
- "IP:1.2.3.0/255.255.255.0"
- "IP:0::0:1:0:0/112"
value_for_name_constraints_permitted_pyopenssl:
- "DNS:www.example.com"
- "IP:1.2.3.0/255.255.255.0"
check_mode: yes
register: everything_2
@@ -767,17 +687,29 @@
- Encipher Only
- decipherOnly
key_usage_critical: yes
extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}'
subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}'
extended_key_usage:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
subject_alt_name:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
basic_constraints:
- "CA:TRUE"
- "pathlen:23"
basic_constraints_critical: yes
name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}'
name_constraints_excluded:
- "DNS:.org"
- "DNS:.example.com"
name_constraints_critical: yes
ocsp_must_staple: yes
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
@@ -785,69 +717,11 @@
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
select_crypto_backend: '{{ select_crypto_backend }}'
vars:
value_for_extended_key_usage_pyopenssl:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
value_for_extended_key_usage:
- serverAuth # the same as "TLS Web Server Authentication"
- TLS Web Server Authentication
- TLS Web Client Authentication
- Code Signing
- E-mail Protection
- timeStamping
- OCSPSigning
- Any Extended Key Usage
- qcStatements
- DVCS
- IPSec User
- biometricInfo
- 1.2.3.4.5.6
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
value_for_san_pyopenssl:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
- "RID:1.2.3.4"
value_for_san:
- "DNS:www.ansible.com"
- "IP:1.2.3.4"
- "IP:::1"
- "email:test@example.org"
- "URI:https://example.org/test/index.html"
- "RID:1.2.3.4"
- "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71"
- "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost"
- "dirName:O =Example Net, CN= example.net"
- "dirName:/O =Example Com/CN= example.com"
value_for_name_constraints_permitted:
- "DNS:www.example.com"
- "IP:1.2.3.0/255.255.255.0"
- "IP:0::0:1:0:0/112"
value_for_name_constraints_permitted_pyopenssl:
- "DNS:www.example.com"
- "IP:1.2.3.0/255.255.255.0"
register: everything_3
- name: Get info from CSR with everything
community.crypto.openssl_csr_info:
path: '{{ output_dir }}/csr_everything.csr'
select_crypto_backend: '{{ select_crypto_backend }}'
register: everything_info
- name: Ed25519 and Ed448 tests (for cryptography >= 2.6)
block:
- name: Generate privatekeys

Some files were not shown because too many files have changed in this diff Show More