mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-06 13:22:58 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5acd27c9b | ||
|
|
e6cd66df53 | ||
|
|
589e7c72ef | ||
|
|
ecbd44df22 | ||
|
|
4ab2ed8b77 | ||
|
|
eb8dabce84 | ||
|
|
c5df302faa | ||
|
|
a581f1ebcd | ||
|
|
78b27ffedb | ||
|
|
e735bdab60 | ||
|
|
5f1efb6f7e | ||
|
|
c68bfedbaa | ||
|
|
871a185ecb | ||
|
|
ed03841fd1 | ||
|
|
d6c0d53442 | ||
|
|
a2a7d94055 | ||
|
|
2a7e452cf8 | ||
|
|
74ae95038c | ||
|
|
57c364fe87 | ||
|
|
04958ece31 | ||
|
|
838bdd711b | ||
|
|
f644db3c79 | ||
|
|
24e7d07973 |
@@ -19,6 +19,11 @@ schedules:
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- cron: 0 12 * * 0
|
||||
displayName: Weekly (old stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-*
|
||||
|
||||
variables:
|
||||
@@ -108,12 +113,8 @@ stages:
|
||||
parameters:
|
||||
testFormat: devel/linux/{0}/1
|
||||
targets:
|
||||
- name: CentOS 6
|
||||
test: centos6
|
||||
- name: CentOS 7
|
||||
test: centos7
|
||||
- name: CentOS 8
|
||||
test: centos8
|
||||
- name: Fedora 33
|
||||
test: fedora33
|
||||
- name: Fedora 34
|
||||
@@ -134,6 +135,8 @@ stages:
|
||||
parameters:
|
||||
testFormat: 2.12/linux/{0}/1
|
||||
targets:
|
||||
- name: CentOS 6
|
||||
test: centos6
|
||||
- name: CentOS 8
|
||||
test: centos8
|
||||
- name: Fedora 33
|
||||
@@ -172,8 +175,6 @@ stages:
|
||||
test: centos6
|
||||
- name: Fedora 31
|
||||
test: fedora31
|
||||
- name: Ubuntu 16.04
|
||||
test: ubuntu1604
|
||||
- stage: Docker_2_9
|
||||
displayName: Docker 2.9
|
||||
dependsOn: []
|
||||
@@ -188,8 +189,8 @@ stages:
|
||||
test: centos7
|
||||
- name: Fedora 31
|
||||
test: fedora31
|
||||
- name: Ubuntu 16.04
|
||||
test: ubuntu1604
|
||||
- name: openSUSE 15 py3
|
||||
test: opensuse15
|
||||
- name: Ubuntu 18.04
|
||||
test: ubuntu1804
|
||||
|
||||
@@ -274,7 +275,6 @@ stages:
|
||||
nameFormat: Python {0}
|
||||
testFormat: devel/cloud/{0}/1
|
||||
targets:
|
||||
- test: 2.6
|
||||
- test: 2.7
|
||||
- test: 3.5
|
||||
- test: 3.6
|
||||
@@ -291,6 +291,7 @@ stages:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.12/cloud/{0}/1
|
||||
targets:
|
||||
- test: 2.6
|
||||
- test: 3.9
|
||||
- stage: Cloud_2_11
|
||||
displayName: Cloud 2.11
|
||||
|
||||
5
.github/patchback.yml
vendored
Normal file
5
.github/patchback.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
backport_branch_prefix: patchback/backports/
|
||||
backport_label_prefix: backport-
|
||||
target_branch_prefix: stable-
|
||||
...
|
||||
@@ -5,6 +5,81 @@ Community Crypto Release Notes
|
||||
.. contents:: Topics
|
||||
|
||||
|
||||
v2.0.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
A new major release of the ``community.crypto`` collection. The main changes are removal of the PyOpenSSL backends for almost all modules (``openssl_pkcs12`` being the only exception), and removal of the ``assertonly`` provider in the ``x509_certificate`` provider. There are also some other breaking changes which should improve the user interface/experience of this collection long-term.
|
||||
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- acme_certificate - the ``subject`` and ``issuer`` fields in in the ``select_chain`` entries are now more strictly validated (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_csr, openssl_csr_pipe - provide a new ``subject_ordered`` option if the order of the components in the subject is of importance (https://github.com/ansible-collections/community.crypto/issues/291, https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_csr, openssl_csr_pipe - there is now stricter validation of the values of the ``subject`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_privatekey_info - add ``check_consistency`` option to request private key consistency checks to be done (https://github.com/ansible-collections/community.crypto/pull/309).
|
||||
- x509_certificate, x509_certificate_pipe - add ``ignore_timestamps`` option which allows to enable idempotency for 'not before' and 'not after' options (https://github.com/ansible-collections/community.crypto/issues/295, https://github.com/ansible-collections/community.crypto/pull/317).
|
||||
- x509_crl - provide a new ``issuer_ordered`` option if the order of the components in the issuer is of importance (https://github.com/ansible-collections/community.crypto/issues/291, https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- x509_crl - there is now stricter validation of the values of the ``issuer`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
|
||||
Breaking Changes / Porting Guide
|
||||
--------------------------------
|
||||
|
||||
- Adjust ``dirName`` text parsing and to text converting code to conform to `Sections 2 and 3 of RFC 4514 <https://datatracker.ietf.org/doc/html/rfc4514.html>`_. This is similar to how `cryptography handles this <https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Name.rfc4514_string>`_ (https://github.com/ansible-collections/community.crypto/pull/274).
|
||||
- acme module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_* modules - removed vendored copy of the Python library ``ipaddress``. If you are using Python 2.x, please make sure to install the library (https://github.com/ansible-collections/community.crypto/pull/287).
|
||||
- compatibility module_utils - removed vendored copy of the Python library ``ipaddress`` (https://github.com/ansible-collections/community.crypto/pull/287).
|
||||
- crypto module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- get_certificate, openssl_csr_info, x509_certificate_info - depending on the ``cryptography`` version used, the modules might not return the ASN.1 value for an extension as contained in the certificate respectively CSR, but a re-encoded version of it. This should usually be identical to the value contained in the source file, unless the value was malformed. For extensions not handled by C(cryptography) the value contained in the source file is always returned unaltered (https://github.com/ansible-collections/community.crypto/pull/318).
|
||||
- module_utils - removed various PyOpenSSL support functions and default backend values that are not needed for the openssl_pkcs12 module (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr, openssl_csr_pipe, x509_crl - the ``subject`` respectively ``issuer`` fields no longer ignore empty values, but instead fail when encountering them (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_privatekey_info - by default consistency checks are not run; they need to be explicitly requested by passing ``check_consistency=true`` (https://github.com/ansible-collections/community.crypto/pull/309).
|
||||
- x509_crl - for idempotency checks, the ``issuer`` order is ignored. If order is important, use the new ``issuer_ordered`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- acme_* modules - ACME version 1 is now deprecated and support for it will be removed in community.crypto 2.0.0 (https://github.com/ansible-collections/community.crypto/pull/288).
|
||||
|
||||
Removed Features (previously deprecated)
|
||||
----------------------------------------
|
||||
|
||||
- acme_* modules - the ``acme_directory`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_* modules - the ``acme_version`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_account_facts - the deprecated redirect has been removed. Use community.crypto.acme_account_info instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_account_info - ``retrieve_orders=url_list`` no longer returns the return value ``orders``. Use the ``order_uris`` return value instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- crypto.info module utils - the deprecated redirect has been removed. Use ``crypto.pem`` instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- get_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_certificate - the deprecated redirect has been removed. Use community.crypto.x509_certificate instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_certificate_info - the deprecated redirect has been removed. Use community.crypto.x509_certificate_info instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_csr - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr and openssl_csr_pipe - ``version`` now only accepts the (default) value 1 (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_csr_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_publickey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_publickey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_signature - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_signature_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate - remove ``assertonly`` provider (https://github.com/ansible-collections/community.crypto/pull/289).
|
||||
- x509_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- cryptography backend - improve Unicode handling for Python 2 (https://github.com/ansible-collections/community.crypto/pull/313).
|
||||
- get_certificate - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
|
||||
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
|
||||
- openssl_pkcs12 - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/296).
|
||||
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
|
||||
|
||||
v1.9.4
|
||||
======
|
||||
|
||||
|
||||
@@ -562,3 +562,115 @@ releases:
|
||||
- 279-acme-openssl.yml
|
||||
- 282-acme_challenge_cert_helper-error.yml
|
||||
release_date: '2021-09-28'
|
||||
2.0.0:
|
||||
changes:
|
||||
breaking_changes:
|
||||
- Adjust ``dirName`` text parsing and to text converting code to conform to
|
||||
`Sections 2 and 3 of RFC 4514 <https://datatracker.ietf.org/doc/html/rfc4514.html>`_.
|
||||
This is similar to how `cryptography handles this <https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Name.rfc4514_string>`_
|
||||
(https://github.com/ansible-collections/community.crypto/pull/274).
|
||||
- acme module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_* modules - removed vendored copy of the Python library ``ipaddress``.
|
||||
If you are using Python 2.x, please make sure to install the library (https://github.com/ansible-collections/community.crypto/pull/287).
|
||||
- compatibility module_utils - removed vendored copy of the Python library ``ipaddress``
|
||||
(https://github.com/ansible-collections/community.crypto/pull/287).
|
||||
- crypto module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- get_certificate, openssl_csr_info, x509_certificate_info - depending on the
|
||||
``cryptography`` version used, the modules might not return the ASN.1 value
|
||||
for an extension as contained in the certificate respectively CSR, but a re-encoded
|
||||
version of it. This should usually be identical to the value contained in
|
||||
the source file, unless the value was malformed. For extensions not handled
|
||||
by C(cryptography) the value contained in the source file is always returned
|
||||
unaltered (https://github.com/ansible-collections/community.crypto/pull/318).
|
||||
- module_utils - removed various PyOpenSSL support functions and default backend
|
||||
values that are not needed for the openssl_pkcs12 module (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr, openssl_csr_pipe, x509_crl - the ``subject`` respectively ``issuer``
|
||||
fields no longer ignore empty values, but instead fail when encountering them
|
||||
(https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_privatekey_info - by default consistency checks are not run; they
|
||||
need to be explicitly requested by passing ``check_consistency=true`` (https://github.com/ansible-collections/community.crypto/pull/309).
|
||||
- x509_crl - for idempotency checks, the ``issuer`` order is ignored. If order
|
||||
is important, use the new ``issuer_ordered`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
bugfixes:
|
||||
- cryptography backend - improve Unicode handling for Python 2 (https://github.com/ansible-collections/community.crypto/pull/313).
|
||||
- get_certificate - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
|
||||
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release
|
||||
(https://github.com/ansible-collections/community.crypto/pull/294).
|
||||
- openssl_pkcs12 - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/296).
|
||||
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release
|
||||
(https://github.com/ansible-collections/community.crypto/pull/294).
|
||||
deprecated_features:
|
||||
- acme_* modules - ACME version 1 is now deprecated and support for it will
|
||||
be removed in community.crypto 2.0.0 (https://github.com/ansible-collections/community.crypto/pull/288).
|
||||
minor_changes:
|
||||
- acme_certificate - the ``subject`` and ``issuer`` fields in in the ``select_chain``
|
||||
entries are now more strictly validated (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_csr, openssl_csr_pipe - provide a new ``subject_ordered`` option if
|
||||
the order of the components in the subject is of importance (https://github.com/ansible-collections/community.crypto/issues/291,
|
||||
https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_csr, openssl_csr_pipe - there is now stricter validation of the values
|
||||
of the ``subject`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_privatekey_info - add ``check_consistency`` option to request private
|
||||
key consistency checks to be done (https://github.com/ansible-collections/community.crypto/pull/309).
|
||||
- x509_certificate, x509_certificate_pipe - add ``ignore_timestamps`` option
|
||||
which allows to enable idempotency for 'not before' and 'not after' options
|
||||
(https://github.com/ansible-collections/community.crypto/issues/295, https://github.com/ansible-collections/community.crypto/pull/317).
|
||||
- x509_crl - provide a new ``issuer_ordered`` option if the order of the components
|
||||
in the issuer is of importance (https://github.com/ansible-collections/community.crypto/issues/291,
|
||||
https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- x509_crl - there is now stricter validation of the values of the ``issuer``
|
||||
option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
release_summary: 'A new major release of the ``community.crypto`` collection.
|
||||
The main changes are removal of the PyOpenSSL backends for almost all modules
|
||||
(``openssl_pkcs12`` being the only exception), and removal of the ``assertonly``
|
||||
provider in the ``x509_certificate`` provider. There are also some other breaking
|
||||
changes which should improve the user interface/experience of this collection
|
||||
long-term.
|
||||
|
||||
'
|
||||
removed_features:
|
||||
- acme_* modules - the ``acme_directory`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_* modules - the ``acme_version`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_account_facts - the deprecated redirect has been removed. Use community.crypto.acme_account_info
|
||||
instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_account_info - ``retrieve_orders=url_list`` no longer returns the return
|
||||
value ``orders``. Use the ``order_uris`` return value instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- crypto.info module utils - the deprecated redirect has been removed. Use ``crypto.pem``
|
||||
instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- get_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_certificate - the deprecated redirect has been removed. Use community.crypto.x509_certificate
|
||||
instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_certificate_info - the deprecated redirect has been removed. Use community.crypto.x509_certificate_info
|
||||
instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_csr - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr and openssl_csr_pipe - ``version`` now only accepts the (default)
|
||||
value 1 (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_csr_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_publickey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_publickey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_signature - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_signature_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate - remove ``assertonly`` provider (https://github.com/ansible-collections/community.crypto/pull/289).
|
||||
- x509_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
fragments:
|
||||
- 2.0.0.yml
|
||||
- 273-pyopenssl-removal.yml
|
||||
- 274-dirname-rfc4514.yml
|
||||
- 287-remove-ipaddress.yml
|
||||
- 288-depecate-acme-v1.yml
|
||||
- 289-assertonly-removed.yml
|
||||
- 290-remove-deprecations.yml
|
||||
- 294-cryptography-35.0.0.yml
|
||||
- 296-openssl_pkcs12-cryptography-35.yml
|
||||
- 309-openssl_privatekey_info-consistency.yml
|
||||
- 313-unicode-names.yml
|
||||
- 315-ordered-names.yml
|
||||
- 317-ignore-timestamps.yml
|
||||
- 318-extension-value-note.yml
|
||||
release_date: '2021-11-01'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace: community
|
||||
name: crypto
|
||||
version: 1.9.4
|
||||
version: 2.0.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (github.com/ansible)
|
||||
|
||||
@@ -12,20 +12,19 @@ action_groups:
|
||||
plugin_routing:
|
||||
modules:
|
||||
acme_account_facts:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: The 'community.crypto.acme_account_facts' module has been renamed to 'community.crypto.acme_account_info'.
|
||||
openssl_certificate:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'
|
||||
openssl_certificate_info:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'
|
||||
module_utils:
|
||||
crypto.identify:
|
||||
redirect: community.crypto.crypto.pem
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: The 'crypto/identify.py' module_utils has been renamed 'crypto/pem.py'. Please update your imports
|
||||
|
||||
@@ -24,8 +24,8 @@ notes:
|
||||
principle be used with any CA providing an ACME endpoint, such as
|
||||
L(Buypass Go SSL,https://www.buypass.com/ssl/products/acme)."
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
- either openssl or L(cryptography,https://cryptography.io/) >= 1.5
|
||||
- ipaddress
|
||||
options:
|
||||
account_key_src:
|
||||
description:
|
||||
@@ -33,7 +33,7 @@ options:
|
||||
key."
|
||||
- "Private keys can be created with the
|
||||
M(community.crypto.openssl_privatekey) or M(community.crypto.openssl_privatekey_pipe)
|
||||
modules. If the requisites (pyOpenSSL or cryptography) are not available,
|
||||
modules. If the requisite (cryptography) is 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
|
||||
@@ -74,9 +74,9 @@ options:
|
||||
- "The ACME version of the endpoint."
|
||||
- "Must be C(1) for the classic Let's Encrypt and Buypass ACME endpoints,
|
||||
or C(2) for standardized ACME v2 endpoints."
|
||||
- "The default value is C(1). Note that in community.crypto 2.0.0, this
|
||||
option B(will be required) and will no longer have a default."
|
||||
- "Please also note that we will deprecate ACME v1 support eventually."
|
||||
- "The value C(1) is deprecated since community.crypto 2.0.0 and will be
|
||||
removed from community.crypto 3.0.0."
|
||||
required: true
|
||||
type: int
|
||||
choices: [ 1, 2 ]
|
||||
acme_directory:
|
||||
@@ -86,22 +86,12 @@ options:
|
||||
- "For safety reasons the default is set to the Let's Encrypt staging
|
||||
server (for the ACME v1 protocol). This will create technically correct,
|
||||
but untrusted certificates."
|
||||
- "The default value is C(https://acme-staging.api.letsencrypt.org/directory).
|
||||
Note that in community.crypto 2.0.0, this option B(will be required) and
|
||||
will no longer have a default. Note that the default is the Let's Encrypt
|
||||
staging server for the ACME v1 protocol, which is deprecated and will
|
||||
be disabled in May 2021 (see
|
||||
L(here,https://community.letsencrypt.org/t/end-of-life-plan-for-acmev1/88430/7)
|
||||
for details)."
|
||||
- "For Let's Encrypt, all staging endpoints can be found here:
|
||||
U(https://letsencrypt.org/docs/staging-environment/). For Buypass, all
|
||||
endpoints can be found here:
|
||||
U(https://community.buypass.com/t/63d4ay/buypass-go-ssl-endpoints)"
|
||||
- "For B(Let's Encrypt), the production directory URL for ACME v2 is
|
||||
U(https://acme-v02.api.letsencrypt.org/directory).
|
||||
(The production directory URL for ACME v1 is
|
||||
U(https://acme-v01.api.letsencrypt.org/directory) and will be
|
||||
disabled in July 2021.)"
|
||||
U(https://acme-v02.api.letsencrypt.org/directory)."
|
||||
- "For B(Buypass), the production directory URL for ACME v2 and v1 is
|
||||
U(https://api.buypass.com/acme/directory)."
|
||||
- "For B(ZeroSSL), the production directory URL for ACME v2 is
|
||||
@@ -113,6 +103,7 @@ options:
|
||||
L(create an issue,https://github.com/ansible-collections/community.crypto/issues/new/choose)
|
||||
to help us supporting it. Feedback that an ACME server not mentioned does work
|
||||
is also appreciated."
|
||||
required: true
|
||||
type: str
|
||||
validate_certs:
|
||||
description:
|
||||
|
||||
@@ -14,12 +14,9 @@ class ModuleDocFragment(object):
|
||||
DOCUMENTATION = r'''
|
||||
description:
|
||||
- This module allows one to (re)generate 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.
|
||||
- It uses the cryptography python library to interact with OpenSSL.
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.6 (if using C(selfsigned), C(ownca) or C(assertonly) provider)
|
||||
- cryptography >= 1.6 (if using C(selfsigned) or C(ownca) provider)
|
||||
options:
|
||||
force:
|
||||
description:
|
||||
@@ -55,17 +52,22 @@ options:
|
||||
- This is required if the private key is password protected.
|
||||
type: str
|
||||
|
||||
ignore_timestamps:
|
||||
description:
|
||||
- Whether the "not before" and "not after" timestamps should be ignored for idempotency checks.
|
||||
- It is better to keep the default value C(true) when using relative timestamps (like C(+0s) for now).
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 2.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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- 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 ]
|
||||
choices: [ auto, cryptography ]
|
||||
|
||||
notes:
|
||||
- All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern.
|
||||
@@ -119,201 +121,6 @@ options:
|
||||
default: https://acme-v02.api.letsencrypt.org/directory
|
||||
'''
|
||||
|
||||
BACKEND_ASSERTONLY_DOCUMENTATION = r'''
|
||||
description:
|
||||
- The C(assertonly) provider is intended for use cases where one is only interested in
|
||||
checking properties of a supplied certificate. Please note that this provider has been
|
||||
deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. See the examples on how
|
||||
to emulate C(assertonly) usage with M(community.crypto.x509_certificate_info),
|
||||
M(community.crypto.openssl_csr_info), M(community.crypto.openssl_privatekey_info) and
|
||||
M(ansible.builtin.assert). This also allows more flexible checks than
|
||||
the ones offered by the C(assertonly) provider.
|
||||
- Many properties that can be specified in this module are for validation of an
|
||||
existing or newly generated certificate. The proper place to specify them, if you
|
||||
want to receive a certificate with these properties is a CSR (Certificate Signing Request).
|
||||
options:
|
||||
csr_path:
|
||||
description:
|
||||
- This is not required for the C(assertonly) provider.
|
||||
|
||||
csr_content:
|
||||
description:
|
||||
- This is not required for the C(assertonly) provider.
|
||||
|
||||
signature_algorithms:
|
||||
description:
|
||||
- A list of algorithms that you would accept the certificate to be signed with
|
||||
(e.g. ['sha256WithRSAEncryption', 'sha512WithRSAEncryption']).
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
issuer:
|
||||
description:
|
||||
- The key/value pairs that must be present in the issuer name field of the certificate.
|
||||
- If you need to specify more than one value with the same key, use a list as value.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: dict
|
||||
|
||||
issuer_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(issuer) field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
subject:
|
||||
description:
|
||||
- The key/value pairs that must be present in the subject name field of the certificate.
|
||||
- If you need to specify more than one value with the same key, use a list as value.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: dict
|
||||
|
||||
subject_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(subject) field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
has_expired:
|
||||
description:
|
||||
- Checks if the certificate is expired/not expired at the time the module is executed.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
version:
|
||||
description:
|
||||
- The version of the certificate.
|
||||
- Nowadays it should almost always be 3.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: int
|
||||
|
||||
valid_at:
|
||||
description:
|
||||
- The certificate must be valid at this point in time.
|
||||
- The timestamp is formatted as an ASN.1 TIME.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
|
||||
invalid_at:
|
||||
description:
|
||||
- The certificate must be invalid at this point in time.
|
||||
- The timestamp is formatted as an ASN.1 TIME.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
|
||||
not_before:
|
||||
description:
|
||||
- The certificate must start to become valid at this point in time.
|
||||
- The timestamp is formatted as an ASN.1 TIME.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
aliases: [ notBefore ]
|
||||
|
||||
not_after:
|
||||
description:
|
||||
- The certificate must expire at this point in time.
|
||||
- The timestamp is formatted as an ASN.1 TIME.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
aliases: [ notAfter ]
|
||||
|
||||
valid_in:
|
||||
description:
|
||||
- The certificate must still be valid at this relative time offset from now.
|
||||
- Valid format is C([+-]timespec | number_of_seconds) where timespec can be an integer
|
||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||
- Note that if using this parameter, this module is NOT idempotent.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
|
||||
key_usage:
|
||||
description:
|
||||
- The I(key_usage) extension field must contain all these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: list
|
||||
elements: str
|
||||
aliases: [ keyUsage ]
|
||||
|
||||
key_usage_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(key_usage) extension field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
aliases: [ keyUsage_strict ]
|
||||
|
||||
extended_key_usage:
|
||||
description:
|
||||
- The I(extended_key_usage) extension field must contain all these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: list
|
||||
elements: str
|
||||
aliases: [ extendedKeyUsage ]
|
||||
|
||||
extended_key_usage_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(extended_key_usage) extension field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
aliases: [ extendedKeyUsage_strict ]
|
||||
|
||||
subject_alt_name:
|
||||
description:
|
||||
- The I(subject_alt_name) extension field must contain these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: list
|
||||
elements: str
|
||||
aliases: [ subjectAltName ]
|
||||
|
||||
subject_alt_name_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(subject_alt_name) extension field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
aliases: [ subjectAltName_strict ]
|
||||
'''
|
||||
|
||||
BACKEND_ENTRUST_DOCUMENTATION = r'''
|
||||
options:
|
||||
entrust_cert_type:
|
||||
@@ -386,6 +193,7 @@ options:
|
||||
- The minimum certificate lifetime is 90 days, and maximum is three years.
|
||||
- If this value is not specified, the certificate will stop being valid 365 days the date of issue.
|
||||
- This is only used by the C(entrust) provider.
|
||||
- Please note that this value is B(not) covered by the I(ignore_timestamps) option.
|
||||
type: str
|
||||
default: +365d
|
||||
|
||||
@@ -457,8 +265,10 @@ options:
|
||||
- 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).
|
||||
- Note that if using relative time this module is NOT idempotent.
|
||||
- If this value is not specified, the certificate will start being valid from now.
|
||||
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
||||
This can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should
|
||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
||||
- This is only used by the C(ownca) provider.
|
||||
type: str
|
||||
default: +0s
|
||||
@@ -470,8 +280,10 @@ options:
|
||||
- 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).
|
||||
- Note that if using relative time this module is NOT idempotent.
|
||||
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
||||
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
||||
This can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should
|
||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
||||
- This is only used by the C(ownca) provider.
|
||||
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
||||
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
||||
@@ -548,8 +360,10 @@ options:
|
||||
- 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).
|
||||
- Note that if using relative time this module is NOT idempotent.
|
||||
- If this value is not specified, the certificate will start being valid from now.
|
||||
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
||||
This can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should
|
||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
||||
- This is only used by the C(selfsigned) provider.
|
||||
type: str
|
||||
default: +0s
|
||||
@@ -562,8 +376,10 @@ options:
|
||||
- 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).
|
||||
- Note that if using relative time this module is NOT idempotent.
|
||||
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
||||
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
||||
This can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should
|
||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
||||
- This is only used by the C(selfsigned) provider.
|
||||
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
||||
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
||||
|
||||
@@ -15,13 +15,8 @@ description:
|
||||
- This module allows one to (re)generate OpenSSL certificate signing requests.
|
||||
- This module supports the subjectAltName, keyUsage, extendedKeyUsage, basicConstraints and OCSP Must Staple
|
||||
extensions.
|
||||
- "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.3
|
||||
- Or pyOpenSSL >= 0.15
|
||||
- cryptography >= 1.3
|
||||
options:
|
||||
digest:
|
||||
description:
|
||||
@@ -48,14 +43,29 @@ options:
|
||||
- The version of the certificate signing request.
|
||||
- "The only allowed value according to L(RFC 2986,https://tools.ietf.org/html/rfc2986#section-4.1)
|
||||
is 1."
|
||||
- This option will no longer accept unsupported values from community.crypto 2.0.0 on.
|
||||
- This option no longer accepts unsupported values since community.crypto 2.0.0.
|
||||
type: int
|
||||
default: 1
|
||||
choices:
|
||||
- 1
|
||||
subject:
|
||||
description:
|
||||
- Key/value pairs that will be present in the subject name field of the certificate signing request.
|
||||
- If you need to specify more than one value with the same key, use a list as value.
|
||||
- If the order of the components is important, use I(subject_ordered).
|
||||
- Mutually exclusive with I(subject_ordered).
|
||||
type: dict
|
||||
subject_ordered:
|
||||
description:
|
||||
- A list of dictionaries, where every dictionary must contain one key/value pair. This key/value pair
|
||||
will be present in the subject name field of the certificate signing request.
|
||||
- If you want to specify more than one value with the same key in a row, you can use a list as value.
|
||||
- Mutually exclusive with I(subject), and any other subject field option, such as I(country_name),
|
||||
I(state_or_province_name), I(locality_name), I(organization_name), I(organizational_unit_name),
|
||||
I(common_name), or I(email_address).
|
||||
type: list
|
||||
elements: dict
|
||||
version_added: 2.0.0
|
||||
country_name:
|
||||
description:
|
||||
- The countryName field of the certificate signing request subject.
|
||||
@@ -196,14 +206,11 @@ options:
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- 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 ]
|
||||
choices: [ auto, cryptography ]
|
||||
create_subject_key_identifier:
|
||||
description:
|
||||
- Create the Subject Key Identifier from the public key.
|
||||
|
||||
@@ -22,13 +22,8 @@ description:
|
||||
(or specify none), change the keysize, etc., the private key will be
|
||||
regenerated. If you are concerned that this could B(overwrite your private key),
|
||||
consider using the I(backup) option."
|
||||
- "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.2.3 (older versions might work as well)
|
||||
- Or pyOpenSSL
|
||||
- cryptography >= 1.2.3 (older versions might work as well)
|
||||
options:
|
||||
size:
|
||||
description:
|
||||
@@ -80,22 +75,16 @@ options:
|
||||
type: str
|
||||
cipher:
|
||||
description:
|
||||
- The cipher to encrypt the private key. (Valid values can be found by
|
||||
running `openssl list -cipher-algorithms` or `openssl list-cipher-algorithms`,
|
||||
depending on your OpenSSL version.)
|
||||
- When using the C(cryptography) backend, use C(auto).
|
||||
- The cipher to encrypt the private key. Must be C(auto).
|
||||
type: str
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- 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 ]
|
||||
choices: [ auto, cryptography ]
|
||||
format:
|
||||
description:
|
||||
- Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format)
|
||||
@@ -105,8 +94,6 @@ options:
|
||||
selected one for generation.
|
||||
- Note that if the format for an existing private key mismatches, the key is B(regenerated) by default.
|
||||
To change this behavior, use the I(format_mismatch) option.
|
||||
- The I(format) option is only supported by the C(cryptography) backend. The C(pyopenssl) backend will
|
||||
fail if a value different from C(auto_ignore) is used.
|
||||
type: str
|
||||
default: auto_ignore
|
||||
choices: [ pkcs1, pkcs8, raw, auto, auto_ignore ]
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright: (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# 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 base64
|
||||
import binascii
|
||||
import copy
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import locale
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
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.common.text.converters import to_native, to_text, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||
get_default_argspec,
|
||||
ACMEDirectory,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
|
||||
CryptographyBackend,
|
||||
CRYPTOGRAPHY_VERSION,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
|
||||
OpenSSLCLIBackend,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme._compatibility import (
|
||||
handle_standard_module_arguments,
|
||||
set_crypto_backend,
|
||||
HAS_CURRENT_CRYPTOGRAPHY,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme._compatibility import ACMELegacyAccount as ACMEAccount
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
|
||||
read_file,
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||
nopad_b64,
|
||||
pem_to_der,
|
||||
process_links,
|
||||
)
|
||||
|
||||
|
||||
def openssl_get_csr_identifiers(openssl_binary, module, csr_filename, csr_content=None):
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
return OpenSSLCLIBackend(module, openssl_binary=openssl_binary).get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)
|
||||
|
||||
|
||||
def cryptography_get_csr_identifiers(module, csr_filename, csr_content=None):
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
return CryptographyBackend(module).get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)
|
||||
|
||||
|
||||
def cryptography_get_cert_days(module, cert_file, now=None):
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
return CryptographyBackend(module).get_cert_days(cert_filename=cert_file, now=now)
|
||||
@@ -1,267 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright: (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# 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 locale
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import HAS_CURRENT_CRYPTOGRAPHY as _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
|
||||
CryptographyBackend,
|
||||
CRYPTOGRAPHY_VERSION,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
|
||||
OpenSSLCLIBackend,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||
ACMEClient,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.account import (
|
||||
ACMEAccount,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import (
|
||||
create_key_authorization,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
|
||||
KeyParsingError,
|
||||
)
|
||||
|
||||
|
||||
HAS_CURRENT_CRYPTOGRAPHY = _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY
|
||||
|
||||
|
||||
def set_crypto_backend(module):
|
||||
'''
|
||||
Sets which crypto backend to use (default: auto detection).
|
||||
|
||||
Does not care whether a new enough cryptoraphy is available or not. Must
|
||||
be called before any real stuff is done which might evaluate
|
||||
``HAS_CURRENT_CRYPTOGRAPHY``.
|
||||
'''
|
||||
global HAS_CURRENT_CRYPTOGRAPHY
|
||||
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
# Choose backend
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
pass
|
||||
elif backend == 'openssl':
|
||||
HAS_CURRENT_CRYPTOGRAPHY = False
|
||||
elif backend == 'cryptography':
|
||||
if not _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY:
|
||||
module.fail_json(msg=missing_required_lib('cryptography'))
|
||||
HAS_CURRENT_CRYPTOGRAPHY = True
|
||||
else:
|
||||
module.fail_json(msg='Unknown crypto backend "{0}"!'.format(backend))
|
||||
# Inform about choices
|
||||
if HAS_CURRENT_CRYPTOGRAPHY:
|
||||
module.debug('Using cryptography backend (library version {0})'.format(CRYPTOGRAPHY_VERSION))
|
||||
return 'cryptography'
|
||||
else:
|
||||
module.debug('Using OpenSSL binary backend')
|
||||
return 'openssl'
|
||||
|
||||
|
||||
def handle_standard_module_arguments(module, needs_acme_v2=False):
|
||||
'''
|
||||
Do standard module setup, argument handling and warning emitting.
|
||||
'''
|
||||
backend = set_crypto_backend(module)
|
||||
|
||||
if not module.params['validate_certs']:
|
||||
module.warn(
|
||||
'Disabling certificate validation for communications with ACME endpoint. '
|
||||
'This should only be done for testing against a local ACME server for '
|
||||
'development purposes, but *never* for production purposes.'
|
||||
)
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
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))
|
||||
|
||||
# AnsibleModule() changes the locale, so change it back to C because we rely on time.strptime() when parsing certificate dates.
|
||||
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
|
||||
return backend
|
||||
|
||||
|
||||
def get_compatibility_backend(module):
|
||||
if HAS_CURRENT_CRYPTOGRAPHY:
|
||||
return CryptographyBackend(module)
|
||||
else:
|
||||
return OpenSSLCLIBackend(module)
|
||||
|
||||
|
||||
class ACMELegacyAccount(object):
|
||||
'''
|
||||
ACME account object. Handles the authorized communication with the
|
||||
ACME server. Provides access to account bound information like
|
||||
the currently active authorizations and valid certificates
|
||||
'''
|
||||
|
||||
def __init__(self, module):
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
backend = get_compatibility_backend(module)
|
||||
self.client = ACMEClient(module, backend)
|
||||
self.account = ACMEAccount(self.client)
|
||||
self.key = self.client.account_key_file
|
||||
self.key_content = self.client.account_key_content
|
||||
self.uri = self.client.account_uri
|
||||
self.key_data = self.client.account_key_data
|
||||
self.jwk = self.client.account_jwk
|
||||
self.jws_header = self.client.account_jws_header
|
||||
self.directory = self.client.directory
|
||||
|
||||
def get_keyauthorization(self, token):
|
||||
'''
|
||||
Returns the key authorization for the given token
|
||||
https://tools.ietf.org/html/rfc8555#section-8.1
|
||||
'''
|
||||
return create_key_authorization(self.client, token)
|
||||
|
||||
def parse_key(self, key_file=None, key_content=None):
|
||||
'''
|
||||
Parses an RSA or Elliptic Curve key file in PEM format and returns a pair
|
||||
(error, key_data).
|
||||
'''
|
||||
try:
|
||||
return None, self.client.parse_key(key_file=key_file, key_content=key_content)
|
||||
except KeyParsingError as e:
|
||||
return e.msg, {}
|
||||
|
||||
def sign_request(self, protected, payload, key_data, encode_payload=True):
|
||||
return self.client.sign_request(protected, payload, key_data, encode_payload=encode_payload)
|
||||
|
||||
def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True, encode_payload=True):
|
||||
'''
|
||||
Sends a JWS signed HTTP POST request to the ACME server and returns
|
||||
the response as dictionary
|
||||
https://tools.ietf.org/html/rfc8555#section-6.2
|
||||
|
||||
If payload is None, a POST-as-GET is performed.
|
||||
(https://tools.ietf.org/html/rfc8555#section-6.3)
|
||||
'''
|
||||
return self.client.send_signed_request(
|
||||
url,
|
||||
payload,
|
||||
key_data=key_data,
|
||||
jws_header=jws_header,
|
||||
parse_json_result=parse_json_result,
|
||||
encode_payload=encode_payload,
|
||||
fail_on_error=False,
|
||||
)
|
||||
|
||||
def get_request(self, uri, parse_json_result=True, headers=None, get_only=False, fail_on_error=True):
|
||||
'''
|
||||
Perform a GET-like request. Will try POST-as-GET for ACMEv2, with fallback
|
||||
to GET if server replies with a status code of 405.
|
||||
'''
|
||||
return self.client.get_request(
|
||||
uri,
|
||||
parse_json_result=parse_json_result,
|
||||
headers=headers,
|
||||
get_only=get_only,
|
||||
fail_on_error=fail_on_error,
|
||||
)
|
||||
|
||||
def set_account_uri(self, uri):
|
||||
'''
|
||||
Set account URI. For ACME v2, it needs to be used to sending signed
|
||||
requests.
|
||||
'''
|
||||
self.client.set_account_uri(uri)
|
||||
self.uri = self.client.account_uri
|
||||
|
||||
def get_account_data(self):
|
||||
'''
|
||||
Retrieve account information. Can only be called when the account
|
||||
URI is already known (such as after calling setup_account).
|
||||
Return None if the account was deactivated, or a dict otherwise.
|
||||
'''
|
||||
return self.account.get_account_data()
|
||||
|
||||
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):
|
||||
'''
|
||||
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
|
||||
account exists is to try and create one with the provided account
|
||||
key, this method will always result in an account being present
|
||||
(except on error situations). For ACME v2, a new account will
|
||||
only be created if ``allow_creation`` is set to True.
|
||||
|
||||
For ACME v2, ``check_mode`` is fully respected. For ACME v1, the
|
||||
account might be created if it does not yet exist.
|
||||
|
||||
Return a pair ``(created, account_data)``. Here, ``created`` will
|
||||
be ``True`` in case the account was created or would be created
|
||||
(check mode). ``account_data`` will be the current account data,
|
||||
or ``None`` if the account does not exist.
|
||||
|
||||
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
|
||||
'''
|
||||
result = self.account.setup_account(
|
||||
contact=contact,
|
||||
agreement=agreement,
|
||||
terms_agreed=terms_agreed,
|
||||
allow_creation=allow_creation,
|
||||
remove_account_uri_if_not_exists=remove_account_uri_if_not_exists,
|
||||
external_account_binding=external_account_binding,
|
||||
)
|
||||
self.uri = self.client.account_uri
|
||||
return result
|
||||
|
||||
def update_account(self, account_data, contact=None):
|
||||
'''
|
||||
Update an account on the ACME server. Check mode is fully respected.
|
||||
|
||||
The current account data must be provided as ``account_data``.
|
||||
|
||||
Return a pair ``(updated, account_data)``, where ``updated`` is
|
||||
``True`` in case something changed (contact info updated) or
|
||||
would be changed (check mode), and ``account_data`` the updated
|
||||
account data.
|
||||
|
||||
https://tools.ietf.org/html/rfc8555#section-7.3.2
|
||||
'''
|
||||
return self.account.update_account(account_data, contact=contact)
|
||||
@@ -12,6 +12,7 @@ import copy
|
||||
import datetime
|
||||
import json
|
||||
import locale
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
@@ -38,6 +39,14 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
|
||||
nopad_b64,
|
||||
)
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
HAS_IPADDRESS = False
|
||||
IPADDRESS_IMPORT_ERROR = traceback.format_exc()
|
||||
else:
|
||||
HAS_IPADDRESS = True
|
||||
|
||||
|
||||
def _assert_fetch_url_success(module, response, info, allow_redirect=False, allow_client_error=True, allow_server_error=True):
|
||||
if info['status'] < 0:
|
||||
@@ -319,14 +328,17 @@ def get_default_argspec():
|
||||
account_key_content=dict(type='str', no_log=True),
|
||||
account_key_passphrase=dict(type='str', no_log=True),
|
||||
account_uri=dict(type='str'),
|
||||
acme_directory=dict(type='str'),
|
||||
acme_version=dict(type='int', choices=[1, 2]),
|
||||
acme_directory=dict(type='str', required=True),
|
||||
acme_version=dict(type='int', required=True, choices=[1, 2]),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
|
||||
)
|
||||
|
||||
|
||||
def create_backend(module, needs_acme_v2):
|
||||
if not HAS_IPADDRESS:
|
||||
module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMPORT_ERROR)
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
|
||||
# Backend autodetect
|
||||
@@ -353,19 +365,13 @@ def create_backend(module, needs_acme_v2):
|
||||
'development purposes, but *never* for production purposes.'
|
||||
)
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
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))
|
||||
|
||||
if module.params['acme_version'] == 1:
|
||||
module.deprecate("The value 1 for 'acme_version' is deprecated. Please switch to ACME v2",
|
||||
version='3.0.0', collection_name='community.crypto')
|
||||
|
||||
# AnsibleModule() changes the locale, so change it back to C because we rely
|
||||
# on datetime.datetime.strptime() when parsing certificate dates.
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
|
||||
@@ -118,11 +118,11 @@ class CryptographyChainMatcher(ChainMatcher):
|
||||
self.issuer = []
|
||||
if criterium.subject:
|
||||
self.subject = [
|
||||
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject)
|
||||
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject, 'subject')
|
||||
]
|
||||
if criterium.issuer:
|
||||
self.issuer = [
|
||||
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer)
|
||||
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer, 'issuer')
|
||||
]
|
||||
self.subject_key_identifier = CryptographyChainMatcher._parse_key_identifier(
|
||||
criterium.subject_key_identifier, 'subject_key_identifier', criterium.index, module)
|
||||
|
||||
@@ -29,7 +29,10 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||
@@ -216,7 +219,7 @@ class OpenSSLCLIBackend(CryptoBackend):
|
||||
@staticmethod
|
||||
def _normalize_ip(ip):
|
||||
try:
|
||||
return to_native(compat_ipaddress.ip_address(to_text(ip)).compressed)
|
||||
return to_native(ipaddress.ip_address(to_text(ip)).compressed)
|
||||
except ValueError:
|
||||
# We don't want to error out on something IPAddress() can't parse
|
||||
return ip
|
||||
|
||||
@@ -16,8 +16,6 @@ import time
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||
nopad_b64,
|
||||
)
|
||||
@@ -28,6 +26,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
ModuleFailException,
|
||||
)
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def create_key_authorization(client, token):
|
||||
'''
|
||||
@@ -110,7 +113,7 @@ class Challenge(object):
|
||||
# https://www.rfc-editor.org/rfc/rfc8737.html#section-3
|
||||
if identifier_type == 'ip':
|
||||
# IPv4/IPv6 address: use reverse mapping (RFC1034, RFC3596)
|
||||
resource = compat_ipaddress.ip_address(identifier).reverse_pointer
|
||||
resource = ipaddress.ip_address(identifier).reverse_pointer
|
||||
if not resource.endswith('.'):
|
||||
resource += '.'
|
||||
else:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 .pem 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,
|
||||
)
|
||||
@@ -20,6 +20,10 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
# WARNING: this function no longer works with cryptography 35.0.0 and newer!
|
||||
# It must **ONLY** be used in compatibility code for older
|
||||
# cryptography versions!
|
||||
|
||||
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
|
||||
|
||||
@@ -22,14 +22,6 @@ __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
|
||||
|
||||
@@ -22,6 +22,9 @@ __metaclass__ = type
|
||||
import base64
|
||||
import binascii
|
||||
import re
|
||||
import sys
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text, to_bytes
|
||||
from ._asn1 import serialize_asn1_string_as_der
|
||||
@@ -29,6 +32,7 @@ from ._asn1 import serialize_asn1_string_as_der
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
@@ -67,9 +71,19 @@ 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.
|
||||
backend = default_backend()
|
||||
try:
|
||||
# For certain old versions of cryptography, backend is a MultiBackend object,
|
||||
# which has no _lib attribute. In that case, revert to the old approach.
|
||||
backend._lib
|
||||
except AttributeError:
|
||||
backend = cert._backend
|
||||
|
||||
result = dict()
|
||||
backend = cert._backend
|
||||
x509_obj = cert._x509
|
||||
# With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
|
||||
# not allow to get the raw value of an extension, so we have to use this ugly hack:
|
||||
exts = list(cert.extensions)
|
||||
|
||||
for i in range(backend._lib.X509_get_ext_count(x509_obj)):
|
||||
ext = backend._lib.X509_get_ext(x509_obj, i)
|
||||
@@ -83,8 +97,12 @@ def cryptography_get_extensions_from_cert(cert):
|
||||
critical=(crit == 1),
|
||||
value=base64.b64encode(der),
|
||||
)
|
||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||
try:
|
||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||
except AttributeError:
|
||||
oid = exts[i].oid.dotted_string
|
||||
result[oid] = entry
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -93,7 +111,13 @@ def cryptography_get_extensions_from_csr(csr):
|
||||
# (that is only stored for unrecognized extensions), we have to re-do
|
||||
# the extension parsing outselves.
|
||||
result = dict()
|
||||
backend = csr._backend
|
||||
backend = default_backend()
|
||||
try:
|
||||
# For certain old versions of cryptography, backend is a MultiBackend object,
|
||||
# which has no _lib attribute. In that case, revert to the old approach.
|
||||
backend._lib
|
||||
except AttributeError:
|
||||
backend = csr._backend
|
||||
|
||||
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req)
|
||||
extensions = backend._ffi.gc(
|
||||
@@ -104,6 +128,10 @@ def cryptography_get_extensions_from_csr(csr):
|
||||
)
|
||||
)
|
||||
|
||||
# With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
|
||||
# not allow to get the raw value of an extension, so we have to use this ugly hack:
|
||||
exts = list(csr.extensions)
|
||||
|
||||
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:
|
||||
@@ -116,8 +144,12 @@ def cryptography_get_extensions_from_csr(csr):
|
||||
critical=(crit == 1),
|
||||
value=base64.b64encode(der),
|
||||
)
|
||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||
try:
|
||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||
except AttributeError:
|
||||
oid = exts[i].oid.dotted_string
|
||||
result[oid] = entry
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -161,34 +193,68 @@ def _parse_hex(bytesstr):
|
||||
return data
|
||||
|
||||
|
||||
DN_COMPONENT_START_RE = re.compile(r'^ *([a-zA-z0-9]+) *= *')
|
||||
DN_COMPONENT_START_RE = re.compile(b'^ *([a-zA-z0-9.]+) *= *')
|
||||
DN_HEX_LETTER = b'0123456789abcdef'
|
||||
|
||||
|
||||
def _parse_dn_component(name, sep=',', sep_str='\\', decode_remainder=True):
|
||||
if sys.version_info[0] < 3:
|
||||
_int_to_byte = chr
|
||||
else:
|
||||
def _int_to_byte(value):
|
||||
return bytes((value, ))
|
||||
|
||||
|
||||
def _parse_dn_component(name, sep=b',', decode_remainder=True):
|
||||
m = DN_COMPONENT_START_RE.match(name)
|
||||
if not m:
|
||||
raise OpenSSLObjectError('cannot start part in "{0}"'.format(name))
|
||||
oid = cryptography_name_to_oid(m.group(1))
|
||||
raise OpenSSLObjectError(u'cannot start part in "{0}"'.format(to_text(name)))
|
||||
oid = cryptography_name_to_oid(to_text(m.group(1)))
|
||||
idx = len(m.group(0))
|
||||
decoded_name = []
|
||||
sep_str = sep + b'\\'
|
||||
if decode_remainder:
|
||||
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])
|
||||
if length > idx and name[idx:idx + 1] == b'#':
|
||||
# Decoding a hex string
|
||||
idx += 1
|
||||
while idx + 1 < length:
|
||||
ch1 = name[idx:idx + 1]
|
||||
ch2 = name[idx + 1:idx + 2]
|
||||
idx1 = DN_HEX_LETTER.find(ch1.lower())
|
||||
idx2 = DN_HEX_LETTER.find(ch2.lower())
|
||||
if idx1 < 0 or idx2 < 0:
|
||||
raise OpenSSLObjectError(u'Invalid hex sequence entry "{0}"'.format(to_text(ch1 + ch2)))
|
||||
idx += 2
|
||||
if idx < length and name[idx] == sep:
|
||||
break
|
||||
decoded_name.append(_int_to_byte(idx1 * 16 + idx2))
|
||||
else:
|
||||
# Decoding a regular string
|
||||
while idx < length:
|
||||
i = idx
|
||||
while i < length and name[i:i + 1] not in sep_str:
|
||||
i += 1
|
||||
if i > idx:
|
||||
decoded_name.append(name[idx:i])
|
||||
idx = i
|
||||
while idx + 1 < length and name[idx:idx + 1] == b'\\':
|
||||
ch = name[idx + 1:idx + 2]
|
||||
idx1 = DN_HEX_LETTER.find(ch.lower())
|
||||
if idx1 >= 0:
|
||||
if idx + 2 >= length:
|
||||
raise OpenSSLObjectError(u'Hex escape sequence "\\{0}" incomplete at end of string'.format(to_text(ch)))
|
||||
ch2 = name[idx + 2:idx + 3]
|
||||
idx2 = DN_HEX_LETTER.find(ch2.lower())
|
||||
if idx2 < 0:
|
||||
raise OpenSSLObjectError(u'Hex escape sequence "\\{0}" has invalid second letter'.format(to_text(ch + ch2)))
|
||||
ch = _int_to_byte(idx1 * 16 + idx2)
|
||||
idx += 1
|
||||
idx += 2
|
||||
decoded_name.append(ch)
|
||||
if idx < length and name[idx:idx + 1] == sep:
|
||||
break
|
||||
else:
|
||||
decoded_name.append(name[idx:])
|
||||
idx = len(name)
|
||||
return x509.NameAttribute(oid, ''.join(decoded_name)), name[idx:]
|
||||
return x509.NameAttribute(oid, to_text(b''.join(decoded_name))), name[idx:]
|
||||
|
||||
|
||||
def _parse_dn(name):
|
||||
@@ -199,21 +265,20 @@ def _parse_dn(name):
|
||||
'''
|
||||
original_name = name
|
||||
name = name.lstrip()
|
||||
sep = ','
|
||||
if name.startswith('/'):
|
||||
sep = '/'
|
||||
sep = b','
|
||||
if name.startswith(b'/'):
|
||||
sep = b'/'
|
||||
name = name[1:]
|
||||
sep_str = sep + '\\'
|
||||
result = []
|
||||
while name:
|
||||
try:
|
||||
attribute, name = _parse_dn_component(name, sep=sep, sep_str=sep_str)
|
||||
attribute, name = _parse_dn_component(name, sep=sep)
|
||||
except OpenSSLObjectError as e:
|
||||
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": {1}'.format(original_name, e))
|
||||
raise OpenSSLObjectError(u'Error while parsing distinguished name "{0}": {1}'.format(to_text(original_name), e))
|
||||
result.append(attribute)
|
||||
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))
|
||||
if name[0:1] != sep or len(name) < 2:
|
||||
raise OpenSSLObjectError(u'Error while parsing distinguished name "{0}": unexpected end of string'.format(to_text(original_name)))
|
||||
name = name[1:]
|
||||
return result
|
||||
|
||||
@@ -222,9 +287,9 @@ def cryptography_parse_relative_distinguished_name(rdn):
|
||||
names = []
|
||||
for part in rdn:
|
||||
try:
|
||||
names.append(_parse_dn_component(to_text(part), decode_remainder=False)[0])
|
||||
names.append(_parse_dn_component(to_bytes(part), decode_remainder=False)[0])
|
||||
except OpenSSLObjectError as e:
|
||||
raise OpenSSLObjectError('Error while parsing relative distinguished name "{0}": {1}'.format(part, e))
|
||||
raise OpenSSLObjectError(u'Error while parsing relative distinguished name "{0}": {1}'.format(part, e))
|
||||
return cryptography.x509.RelativeDistinguishedName(names)
|
||||
|
||||
|
||||
@@ -268,7 +333,7 @@ def cryptography_get_name(name, what='Subject Alternative Name'):
|
||||
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:]))))
|
||||
return x509.DirectoryName(x509.Name(reversed(_parse_dn(to_bytes(name[8:])))))
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError('Cannot parse {what} "{name}": {error}'.format(name=name, what=what, error=e))
|
||||
if ':' not in name:
|
||||
@@ -280,11 +345,14 @@ 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:]
|
||||
value = value.replace(u'\\', u'\\\\')
|
||||
for ch in [u',', u'+', u'<', u'>', u';', u'"']:
|
||||
value = value.replace(ch, u'\\%s' % ch)
|
||||
value = value.replace(u'\0', u'\\00')
|
||||
if value.startswith((u' ', u'#')):
|
||||
value = u'\\%s' % value[0] + value[1:]
|
||||
if value.endswith(u' '):
|
||||
value = value[:-1] + u'\\ '
|
||||
return value
|
||||
|
||||
|
||||
@@ -294,24 +362,26 @@ def cryptography_decode_name(name):
|
||||
Raises an OpenSSLObjectError if the name is not supported.
|
||||
'''
|
||||
if isinstance(name, x509.DNSName):
|
||||
return 'DNS:{0}'.format(name.value)
|
||||
return u'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)
|
||||
return u'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
|
||||
return u'IP:{0}'.format(name.value.compressed)
|
||||
if isinstance(name, x509.RFC822Name):
|
||||
return 'email:{0}'.format(name.value)
|
||||
return u'email:{0}'.format(name.value)
|
||||
if isinstance(name, x509.UniformResourceIdentifier):
|
||||
return 'URI:{0}'.format(name.value)
|
||||
return u'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
|
||||
# According to https://datatracker.ietf.org/doc/html/rfc4514.html#section-2.1 the
|
||||
# list needs to be reversed, and joined by commas
|
||||
return u'dirName:' + ','.join([
|
||||
u'{0}={1}'.format(to_text(cryptography_oid_to_name(attribute.oid, short=True)), _dn_escape_value(attribute.value))
|
||||
for attribute in reversed(list(name.value))
|
||||
])
|
||||
if isinstance(name, x509.RegisteredID):
|
||||
return 'RID:{0}'.format(name.value.dotted_string)
|
||||
return u'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))
|
||||
return u'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value))
|
||||
raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name))
|
||||
|
||||
|
||||
@@ -450,9 +520,35 @@ def parse_pkcs12(pkcs12_bytes, passphrase=None):
|
||||
friendly_name = None
|
||||
if certificate:
|
||||
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
|
||||
maybe_name = certificate._backend._lib.X509_alias_get0(
|
||||
certificate._x509, certificate._backend._ffi.NULL)
|
||||
if maybe_name != certificate._backend._ffi.NULL:
|
||||
friendly_name = certificate._backend._ffi.string(maybe_name)
|
||||
backend = default_backend()
|
||||
try:
|
||||
# For certain old versions of cryptography, backend is a MultiBackend object,
|
||||
# which has no _lib attribute. In that case, revert to the old approach.
|
||||
backend._lib
|
||||
except AttributeError:
|
||||
backend = certificate._backend
|
||||
|
||||
if LooseVersion(cryptography.__version__) >= LooseVersion('35.0'):
|
||||
# This code basically does what load_key_and_certificates() does, but without error-checking.
|
||||
# Since load_key_and_certificates succeeded, it should not fail.
|
||||
pkcs12 = backend._ffi.gc(
|
||||
backend._lib.d2i_PKCS12_bio(backend._bytes_to_bio(pkcs12_bytes).bio, backend._ffi.NULL),
|
||||
backend._lib.PKCS12_free)
|
||||
certificate_x509_ptr = backend._ffi.new("X509 **")
|
||||
with backend._zeroed_null_terminated_buf(to_bytes(passphrase) if passphrase is not None else None) as passphrase_buffer:
|
||||
backend._lib.PKCS12_parse(
|
||||
pkcs12,
|
||||
passphrase_buffer,
|
||||
backend._ffi.new("EVP_PKEY **"),
|
||||
certificate_x509_ptr,
|
||||
backend._ffi.new("Cryptography_STACK_OF_X509 **"))
|
||||
if certificate_x509_ptr[0] != backend._ffi.NULL:
|
||||
maybe_name = backend._lib.X509_alias_get0(certificate_x509_ptr[0], backend._ffi.NULL)
|
||||
if maybe_name != backend._ffi.NULL:
|
||||
friendly_name = backend._ffi.string(maybe_name)
|
||||
else:
|
||||
# cryptography < 35.0.0
|
||||
maybe_name = backend._lib.X509_alias_get0(certificate._x509, backend._ffi.NULL)
|
||||
if maybe_name != backend._ffi.NULL:
|
||||
friendly_name = backend._ffi.string(maybe_name)
|
||||
return private_key, certificate, additional_certificates, friendly_name
|
||||
|
||||
@@ -38,18 +38,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
)
|
||||
|
||||
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__)
|
||||
except ImportError:
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
CRYPTOGRAPHY_VERSION = None
|
||||
@@ -75,6 +63,7 @@ class CertificateBackend(object):
|
||||
self.backend = backend
|
||||
|
||||
self.force = module.params['force']
|
||||
self.ignore_timestamps = module.params['ignore_timestamps']
|
||||
self.privatekey_path = module.params['privatekey_path']
|
||||
self.privatekey_content = module.params['privatekey_content']
|
||||
if self.privatekey_content is not None:
|
||||
@@ -173,43 +162,12 @@ class CertificateBackend(object):
|
||||
|
||||
def _check_privatekey(self):
|
||||
"""Check whether provided parameters match, assuming self.existing_certificate and self.privatekey have been populated."""
|
||||
if self.backend == 'pyopenssl':
|
||||
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)
|
||||
ctx.use_privatekey(self.privatekey)
|
||||
ctx.use_certificate(self.existing_certificate)
|
||||
try:
|
||||
ctx.check_privatekey()
|
||||
return True
|
||||
except OpenSSL.SSL.Error:
|
||||
return False
|
||||
elif self.backend == 'cryptography':
|
||||
if self.backend == 'cryptography':
|
||||
return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key())
|
||||
|
||||
def _check_csr(self):
|
||||
"""Check whether provided parameters match, assuming self.existing_certificate and self.csr have been populated."""
|
||||
if self.backend == 'pyopenssl':
|
||||
# Verify that CSR is signed by certificate's private key
|
||||
try:
|
||||
self.csr.verify(self.existing_certificate.get_pubkey())
|
||||
except OpenSSL.crypto.Error:
|
||||
return False
|
||||
# Check subject
|
||||
if self.check_csr_subject and self.csr.get_subject() != self.existing_certificate.get_subject():
|
||||
return False
|
||||
# Check extensions
|
||||
if not self.check_csr_extensions:
|
||||
return True
|
||||
csr_extensions = self.csr.get_extensions()
|
||||
cert_extension_count = self.existing_certificate.get_extension_count()
|
||||
if len(csr_extensions) != cert_extension_count:
|
||||
return False
|
||||
for extension_number in range(0, cert_extension_count):
|
||||
cert_extension = self.existing_certificate.get_extension(extension_number)
|
||||
csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions)
|
||||
if cert_extension.get_data() != list(csr_extension)[0].get_data():
|
||||
return False
|
||||
return True
|
||||
elif self.backend == 'cryptography':
|
||||
if self.backend == 'cryptography':
|
||||
# Verify that CSR is signed by certificate's private key
|
||||
if not self.csr.is_signature_valid:
|
||||
return False
|
||||
@@ -244,10 +202,6 @@ class CertificateBackend(object):
|
||||
|
||||
def _check_subject_key_identifier(self):
|
||||
"""Check whether Subject Key Identifier matches, assuming self.existing_certificate has been populated."""
|
||||
if self.backend != 'cryptography':
|
||||
# We do not support SKI with pyOpenSSL backend
|
||||
return True
|
||||
|
||||
# Get hold of certificate's SKI
|
||||
try:
|
||||
ext = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
|
||||
@@ -270,7 +224,7 @@ class CertificateBackend(object):
|
||||
return False
|
||||
return True
|
||||
|
||||
def needs_regeneration(self):
|
||||
def needs_regeneration(self, not_before=None, not_after=None):
|
||||
"""Check whether a regeneration is necessary."""
|
||||
if self.force or self.existing_certificate_bytes is None:
|
||||
return True
|
||||
@@ -294,6 +248,15 @@ class CertificateBackend(object):
|
||||
if self.create_subject_key_identifier != 'never_create' and not self._check_subject_key_identifier():
|
||||
return True
|
||||
|
||||
# Check not before
|
||||
if not_before is not None and not self.ignore_timestamps:
|
||||
if self.existing_certificate.not_valid_before != not_before:
|
||||
return True
|
||||
|
||||
# Check not after
|
||||
if not_after is not None and not self.ignore_timestamps:
|
||||
if self.existing_certificate.not_valid_after != not_after:
|
||||
return True
|
||||
return False
|
||||
|
||||
def dump(self, include_certificate):
|
||||
@@ -328,10 +291,6 @@ class CertificateProvider(object):
|
||||
def needs_version_two_certs(self, module):
|
||||
"""Whether the provider needs to create a version 2 certificate."""
|
||||
|
||||
def needs_pyopenssl_get_extensions(self, module):
|
||||
"""Whether the provider needs to use get_extensions() with pyOpenSSL."""
|
||||
return True
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_backend(self, module, backend):
|
||||
"""Create an implementation for a backend.
|
||||
@@ -352,45 +311,22 @@ def select_backend(module, backend, provider):
|
||||
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'
|
||||
|
||||
if provider.needs_version_two_certs(module):
|
||||
module.warn('crypto backend forced to pyopenssl. The cryptography library does not support v2 certificates')
|
||||
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))
|
||||
module.fail_json(msg=("Can't detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
if provider.needs_pyopenssl_get_extensions(module):
|
||||
try:
|
||||
getattr(crypto.X509Req, 'get_extensions')
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
|
||||
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
if provider.needs_version_two_certs(module):
|
||||
module.fail_json(msg='The cryptography backend does not support v2 certificates, '
|
||||
'use select_crypto_backend=pyopenssl for v2 certificates')
|
||||
module.fail_json(msg='The cryptography backend does not support v2 certificates')
|
||||
|
||||
return provider.create_backend(module, backend)
|
||||
|
||||
@@ -402,7 +338,8 @@ def get_certificate_argument_spec():
|
||||
force=dict(type='bool', default=False,),
|
||||
csr_path=dict(type='path'),
|
||||
csr_content=dict(type='str'),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
ignore_timestamps=dict(type='bool', default=True),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
|
||||
# General properties of a certificate
|
||||
privatekey_path=dict(type='path'),
|
||||
|
||||
@@ -1,664 +0,0 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
import abc
|
||||
import datetime
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
parse_name_field,
|
||||
get_relative_time_option,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_compare_public_keys,
|
||||
cryptography_get_name,
|
||||
cryptography_name_to_oid,
|
||||
cryptography_parse_key_usage_params,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_normalize_name_attribute,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
||||
CertificateBackend,
|
||||
CertificateProvider,
|
||||
)
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
from cryptography.x509 import NameAttribute, Name
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def compare_sets(subset, superset, equality=False):
|
||||
if equality:
|
||||
return set(subset) == set(superset)
|
||||
else:
|
||||
return all(x in superset for x in subset)
|
||||
|
||||
|
||||
def compare_dicts(subset, superset, equality=False):
|
||||
if equality:
|
||||
return subset == superset
|
||||
else:
|
||||
return all(superset.get(x) == v for x, v in subset.items())
|
||||
|
||||
|
||||
NO_EXTENSION = 'no extension'
|
||||
|
||||
|
||||
class AssertOnlyCertificateBackend(CertificateBackend):
|
||||
def __init__(self, module, backend):
|
||||
super(AssertOnlyCertificateBackend, self).__init__(module, backend)
|
||||
|
||||
self.signature_algorithms = module.params['signature_algorithms']
|
||||
if module.params['subject']:
|
||||
self.subject = parse_name_field(module.params['subject'])
|
||||
else:
|
||||
self.subject = []
|
||||
self.subject_strict = module.params['subject_strict']
|
||||
if module.params['issuer']:
|
||||
self.issuer = parse_name_field(module.params['issuer'])
|
||||
else:
|
||||
self.issuer = []
|
||||
self.issuer_strict = module.params['issuer_strict']
|
||||
self.has_expired = module.params['has_expired']
|
||||
self.version = module.params['version']
|
||||
self.key_usage = module.params['key_usage']
|
||||
self.key_usage_strict = module.params['key_usage_strict']
|
||||
self.extended_key_usage = module.params['extended_key_usage']
|
||||
self.extended_key_usage_strict = module.params['extended_key_usage_strict']
|
||||
self.subject_alt_name = module.params['subject_alt_name']
|
||||
self.subject_alt_name_strict = module.params['subject_alt_name_strict']
|
||||
self.not_before = module.params['not_before']
|
||||
self.not_after = module.params['not_after']
|
||||
self.valid_at = module.params['valid_at']
|
||||
self.invalid_at = module.params['invalid_at']
|
||||
self.valid_in = module.params['valid_in']
|
||||
if self.valid_in and not self.valid_in.startswith("+") and not self.valid_in.startswith("-"):
|
||||
try:
|
||||
int(self.valid_in)
|
||||
except ValueError:
|
||||
module.fail_json(msg='The supplied value for "valid_in" (%s) is not an integer or a valid timespec' % self.valid_in)
|
||||
self.valid_in = "+" + self.valid_in + "s"
|
||||
|
||||
# Load objects
|
||||
self._ensure_private_key_loaded()
|
||||
self._ensure_csr_loaded()
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_privatekey(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_csr_signature(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_csr_subject(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_csr_extensions(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_signature_algorithms(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_subject(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_issuer(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_has_expired(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_version(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_key_usage(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_extended_key_usage(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_subject_alt_name(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_not_before(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_not_after(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_valid_at(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_invalid_at(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_valid_in(self):
|
||||
pass
|
||||
|
||||
def assertonly(self):
|
||||
messages = []
|
||||
if self.privatekey_path is not None or self.privatekey_content is not None:
|
||||
if not self._validate_privatekey():
|
||||
messages.append(
|
||||
'Certificate and private key %s do not match' %
|
||||
(self.privatekey_path or '(provided in module options)')
|
||||
)
|
||||
|
||||
if self.csr_path is not None or self.csr_content is not None:
|
||||
if not self._validate_csr_signature():
|
||||
messages.append(
|
||||
'Certificate and CSR %s do not match: private key mismatch' %
|
||||
(self.csr_path or '(provided in module options)')
|
||||
)
|
||||
if not self._validate_csr_subject():
|
||||
messages.append(
|
||||
'Certificate and CSR %s do not match: subject mismatch' %
|
||||
(self.csr_path or '(provided in module options)')
|
||||
)
|
||||
if not self._validate_csr_extensions():
|
||||
messages.append(
|
||||
'Certificate and CSR %s do not match: extensions mismatch' %
|
||||
(self.csr_path or '(provided in module options)')
|
||||
)
|
||||
|
||||
if self.signature_algorithms is not None:
|
||||
wrong_alg = self._validate_signature_algorithms()
|
||||
if wrong_alg:
|
||||
messages.append(
|
||||
'Invalid signature algorithm (got %s, expected one of %s)' %
|
||||
(wrong_alg, self.signature_algorithms)
|
||||
)
|
||||
|
||||
if self.subject is not None:
|
||||
failure = self._validate_subject()
|
||||
if failure:
|
||||
dummy, cert_subject = failure
|
||||
messages.append(
|
||||
'Invalid subject component (got %s, expected all of %s to be present)' %
|
||||
(cert_subject, self.subject)
|
||||
)
|
||||
|
||||
if self.issuer is not None:
|
||||
failure = self._validate_issuer()
|
||||
if failure:
|
||||
dummy, cert_issuer = failure
|
||||
messages.append(
|
||||
'Invalid issuer component (got %s, expected all of %s to be present)' % (cert_issuer, self.issuer)
|
||||
)
|
||||
|
||||
if self.has_expired is not None:
|
||||
cert_expired = self._validate_has_expired()
|
||||
if cert_expired != self.has_expired:
|
||||
messages.append(
|
||||
'Certificate expiration check failed (certificate expiration is %s, expected %s)' %
|
||||
(cert_expired, self.has_expired)
|
||||
)
|
||||
|
||||
if self.version is not None:
|
||||
cert_version = self._validate_version()
|
||||
if cert_version != self.version:
|
||||
messages.append(
|
||||
'Invalid certificate version number (got %s, expected %s)' %
|
||||
(cert_version, self.version)
|
||||
)
|
||||
|
||||
if self.key_usage is not None:
|
||||
failure = self._validate_key_usage()
|
||||
if failure == NO_EXTENSION:
|
||||
messages.append('Found no keyUsage extension')
|
||||
elif failure:
|
||||
dummy, cert_key_usage = failure
|
||||
messages.append(
|
||||
'Invalid keyUsage components (got %s, expected all of %s to be present)' %
|
||||
(cert_key_usage, self.key_usage)
|
||||
)
|
||||
|
||||
if self.extended_key_usage is not None:
|
||||
failure = self._validate_extended_key_usage()
|
||||
if failure == NO_EXTENSION:
|
||||
messages.append('Found no extendedKeyUsage extension')
|
||||
elif failure:
|
||||
dummy, ext_cert_key_usage = failure
|
||||
messages.append(
|
||||
'Invalid extendedKeyUsage component (got %s, expected all of %s to be present)' % (ext_cert_key_usage, self.extended_key_usage)
|
||||
)
|
||||
|
||||
if self.subject_alt_name is not None:
|
||||
failure = self._validate_subject_alt_name()
|
||||
if failure == NO_EXTENSION:
|
||||
messages.append('Found no subjectAltName extension')
|
||||
elif failure:
|
||||
dummy, cert_san = failure
|
||||
messages.append(
|
||||
'Invalid subjectAltName component (got %s, expected all of %s to be present)' %
|
||||
(cert_san, self.subject_alt_name)
|
||||
)
|
||||
|
||||
if self.not_before is not None:
|
||||
cert_not_valid_before = self._validate_not_before()
|
||||
if cert_not_valid_before != get_relative_time_option(self.not_before, 'not_before', backend=self.backend):
|
||||
messages.append(
|
||||
'Invalid not_before component (got %s, expected %s to be present)' %
|
||||
(cert_not_valid_before, self.not_before)
|
||||
)
|
||||
|
||||
if self.not_after is not None:
|
||||
cert_not_valid_after = self._validate_not_after()
|
||||
if cert_not_valid_after != get_relative_time_option(self.not_after, 'not_after', backend=self.backend):
|
||||
messages.append(
|
||||
'Invalid not_after component (got %s, expected %s to be present)' %
|
||||
(cert_not_valid_after, self.not_after)
|
||||
)
|
||||
|
||||
if self.valid_at is not None:
|
||||
not_before, valid_at, not_after = self._validate_valid_at()
|
||||
if not (not_before <= valid_at <= not_after):
|
||||
messages.append(
|
||||
'Certificate is not valid for the specified date (%s) - not_before: %s - not_after: %s' %
|
||||
(self.valid_at, not_before, not_after)
|
||||
)
|
||||
|
||||
if self.invalid_at is not None:
|
||||
not_before, invalid_at, not_after = self._validate_invalid_at()
|
||||
if not_before <= invalid_at <= not_after:
|
||||
messages.append(
|
||||
'Certificate is not invalid for the specified date (%s) - not_before: %s - not_after: %s' %
|
||||
(self.invalid_at, not_before, not_after)
|
||||
)
|
||||
|
||||
if self.valid_in is not None:
|
||||
not_before, valid_in, not_after = self._validate_valid_in()
|
||||
if not not_before <= valid_in <= not_after:
|
||||
messages.append(
|
||||
'Certificate is not valid in %s from now (that would be %s) - not_before: %s - not_after: %s' %
|
||||
(self.valid_in, valid_in, not_before, not_after)
|
||||
)
|
||||
return messages
|
||||
|
||||
def needs_regeneration(self):
|
||||
self._ensure_existing_certificate_loaded()
|
||||
if self.existing_certificate is None:
|
||||
self.messages = ['Certificate not provided']
|
||||
else:
|
||||
self.messages = self.assertonly()
|
||||
|
||||
return len(self.messages) != 0
|
||||
|
||||
def generate_certificate(self):
|
||||
self.module.fail_json(msg=' | '.join(self.messages))
|
||||
|
||||
def get_certificate_data(self):
|
||||
return self.existing_certificate_bytes
|
||||
|
||||
|
||||
class AssertOnlyCertificateBackendCryptography(AssertOnlyCertificateBackend):
|
||||
"""Validate the supplied cert, using the cryptography backend"""
|
||||
def __init__(self, module):
|
||||
super(AssertOnlyCertificateBackendCryptography, self).__init__(module, 'cryptography')
|
||||
|
||||
def _validate_privatekey(self):
|
||||
return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key())
|
||||
|
||||
def _validate_csr_signature(self):
|
||||
if not self.csr.is_signature_valid:
|
||||
return False
|
||||
return cryptography_compare_public_keys(self.csr.public_key(), self.existing_certificate.public_key())
|
||||
|
||||
def _validate_csr_subject(self):
|
||||
return self.csr.subject == self.existing_certificate.subject
|
||||
|
||||
def _validate_csr_extensions(self):
|
||||
cert_exts = self.existing_certificate.extensions
|
||||
csr_exts = self.csr.extensions
|
||||
if len(cert_exts) != len(csr_exts):
|
||||
return False
|
||||
for cert_ext in cert_exts:
|
||||
try:
|
||||
csr_ext = csr_exts.get_extension_for_oid(cert_ext.oid)
|
||||
if cert_ext != csr_ext:
|
||||
return False
|
||||
except cryptography.x509.ExtensionNotFound as dummy:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_signature_algorithms(self):
|
||||
if self.existing_certificate.signature_algorithm_oid._name not in self.signature_algorithms:
|
||||
return self.existing_certificate.signature_algorithm_oid._name
|
||||
|
||||
def _validate_subject(self):
|
||||
expected_subject = Name([NameAttribute(oid=cryptography_name_to_oid(sub[0]), value=to_text(sub[1]))
|
||||
for sub in self.subject])
|
||||
cert_subject = self.existing_certificate.subject
|
||||
if not compare_sets(expected_subject, cert_subject, self.subject_strict):
|
||||
return expected_subject, cert_subject
|
||||
|
||||
def _validate_issuer(self):
|
||||
expected_issuer = Name([NameAttribute(oid=cryptography_name_to_oid(iss[0]), value=to_text(iss[1]))
|
||||
for iss in self.issuer])
|
||||
cert_issuer = self.existing_certificate.issuer
|
||||
if not compare_sets(expected_issuer, cert_issuer, self.issuer_strict):
|
||||
return self.issuer, cert_issuer
|
||||
|
||||
def _validate_has_expired(self):
|
||||
cert_not_after = self.existing_certificate.not_valid_after
|
||||
cert_expired = cert_not_after < datetime.datetime.utcnow()
|
||||
return cert_expired
|
||||
|
||||
def _validate_version(self):
|
||||
if self.existing_certificate.version == x509.Version.v1:
|
||||
return 1
|
||||
if self.existing_certificate.version == x509.Version.v3:
|
||||
return 3
|
||||
return "unknown"
|
||||
|
||||
def _validate_key_usage(self):
|
||||
try:
|
||||
current_key_usage = self.existing_certificate.extensions.get_extension_for_class(x509.KeyUsage).value
|
||||
test_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 test_key_usage['key_agreement']:
|
||||
test_key_usage.update(dict(
|
||||
encipher_only=current_key_usage.encipher_only,
|
||||
decipher_only=current_key_usage.decipher_only
|
||||
))
|
||||
|
||||
key_usages = cryptography_parse_key_usage_params(self.key_usage)
|
||||
if not compare_dicts(key_usages, test_key_usage, self.key_usage_strict):
|
||||
return self.key_usage, [k for k, v in test_key_usage.items() if v is True]
|
||||
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.key_usage:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_extended_key_usage(self):
|
||||
try:
|
||||
current_ext_keyusage = self.existing_certificate.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value
|
||||
usages = [cryptography_name_to_oid(usage) for usage in self.extended_key_usage]
|
||||
expected_ext_keyusage = x509.ExtendedKeyUsage(usages)
|
||||
if not compare_sets(expected_ext_keyusage, current_ext_keyusage, self.extended_key_usage_strict):
|
||||
return [eku.value for eku in expected_ext_keyusage], [eku.value for eku in current_ext_keyusage]
|
||||
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.extended_key_usage:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_subject_alt_name(self):
|
||||
try:
|
||||
current_san = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
|
||||
expected_san = [cryptography_get_name(san) for san in self.subject_alt_name]
|
||||
if not compare_sets(expected_san, current_san, self.subject_alt_name_strict):
|
||||
return self.subject_alt_name, current_san
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.subject_alt_name:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_not_before(self):
|
||||
return self.existing_certificate.not_valid_before
|
||||
|
||||
def _validate_not_after(self):
|
||||
return self.existing_certificate.not_valid_after
|
||||
|
||||
def _validate_valid_at(self):
|
||||
rt = get_relative_time_option(self.valid_at, 'valid_at', backend=self.backend)
|
||||
return self.existing_certificate.not_valid_before, rt, self.existing_certificate.not_valid_after
|
||||
|
||||
def _validate_invalid_at(self):
|
||||
rt = get_relative_time_option(self.invalid_at, 'invalid_at', backend=self.backend)
|
||||
return self.existing_certificate.not_valid_before, rt, self.existing_certificate.not_valid_after
|
||||
|
||||
def _validate_valid_in(self):
|
||||
valid_in_date = get_relative_time_option(self.valid_in, "valid_in", backend=self.backend)
|
||||
return self.existing_certificate.not_valid_before, valid_in_date, self.existing_certificate.not_valid_after
|
||||
|
||||
|
||||
class AssertOnlyCertificateBackendPyOpenSSL(AssertOnlyCertificateBackend):
|
||||
"""validate the supplied certificate."""
|
||||
|
||||
def __init__(self, module):
|
||||
super(AssertOnlyCertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
|
||||
# Ensure inputs are properly sanitized before comparison.
|
||||
for param in ['signature_algorithms', 'key_usage', 'extended_key_usage',
|
||||
'subject_alt_name', 'subject', 'issuer', 'not_before',
|
||||
'not_after', 'valid_at', 'invalid_at']:
|
||||
attr = getattr(self, param)
|
||||
if isinstance(attr, list) and attr:
|
||||
if isinstance(attr[0], str):
|
||||
setattr(self, param, [to_bytes(item) for item in attr])
|
||||
elif isinstance(attr[0], tuple):
|
||||
setattr(self, param, [(to_bytes(item[0]), to_bytes(item[1])) for item in attr])
|
||||
elif isinstance(attr, tuple):
|
||||
setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items()))
|
||||
elif isinstance(attr, dict):
|
||||
setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items()))
|
||||
elif isinstance(attr, str):
|
||||
setattr(self, param, to_bytes(attr))
|
||||
|
||||
def _validate_privatekey(self):
|
||||
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)
|
||||
ctx.use_privatekey(self.privatekey)
|
||||
ctx.use_certificate(self.existing_certificate)
|
||||
try:
|
||||
ctx.check_privatekey()
|
||||
return True
|
||||
except OpenSSL.SSL.Error:
|
||||
return False
|
||||
|
||||
def _validate_csr_signature(self):
|
||||
try:
|
||||
self.csr.verify(self.existing_certificate.get_pubkey())
|
||||
except OpenSSL.crypto.Error:
|
||||
return False
|
||||
|
||||
def _validate_csr_subject(self):
|
||||
if self.csr.get_subject() != self.existing_certificate.get_subject():
|
||||
return False
|
||||
|
||||
def _validate_csr_extensions(self):
|
||||
csr_extensions = self.csr.get_extensions()
|
||||
cert_extension_count = self.existing_certificate.get_extension_count()
|
||||
if len(csr_extensions) != cert_extension_count:
|
||||
return False
|
||||
for extension_number in range(0, cert_extension_count):
|
||||
cert_extension = self.existing_certificate.get_extension(extension_number)
|
||||
csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions)
|
||||
if cert_extension.get_data() != list(csr_extension)[0].get_data():
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_signature_algorithms(self):
|
||||
if self.existing_certificate.get_signature_algorithm() not in self.signature_algorithms:
|
||||
return self.existing_certificate.get_signature_algorithm()
|
||||
|
||||
def _validate_subject(self):
|
||||
expected_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in self.subject]
|
||||
cert_subject = self.existing_certificate.get_subject().get_components()
|
||||
current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in cert_subject]
|
||||
if not compare_sets(expected_subject, current_subject, self.subject_strict):
|
||||
return expected_subject, current_subject
|
||||
|
||||
def _validate_issuer(self):
|
||||
expected_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in self.issuer]
|
||||
cert_issuer = self.existing_certificate.get_issuer().get_components()
|
||||
current_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in cert_issuer]
|
||||
if not compare_sets(expected_issuer, current_issuer, self.issuer_strict):
|
||||
return self.issuer, cert_issuer
|
||||
|
||||
def _validate_has_expired(self):
|
||||
# The following 3 lines are the same as the current PyOpenSSL code for cert.has_expired().
|
||||
# Older version of PyOpenSSL have a buggy implementation,
|
||||
# to avoid issues with those we added the code from a more recent release here.
|
||||
|
||||
time_string = to_native(self.existing_certificate.get_notAfter())
|
||||
not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
cert_expired = not_after < datetime.datetime.utcnow()
|
||||
return cert_expired
|
||||
|
||||
def _validate_version(self):
|
||||
# Version numbers in certs are off by one:
|
||||
# v1: 0, v2: 1, v3: 2 ...
|
||||
return self.existing_certificate.get_version() + 1
|
||||
|
||||
def _validate_key_usage(self):
|
||||
found = False
|
||||
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
|
||||
extension = self.existing_certificate.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'keyUsage':
|
||||
found = True
|
||||
expected_extension = crypto.X509Extension(b"keyUsage", False, b', '.join(self.key_usage))
|
||||
key_usage = [usage.strip() for usage in to_text(expected_extension, errors='surrogate_or_strict').split(',')]
|
||||
current_ku = [usage.strip() for usage in to_text(extension, errors='surrogate_or_strict').split(',')]
|
||||
if not compare_sets(key_usage, current_ku, self.key_usage_strict):
|
||||
return self.key_usage, str(extension).split(', ')
|
||||
if not found:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.key_usage:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_extended_key_usage(self):
|
||||
found = False
|
||||
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
|
||||
extension = self.existing_certificate.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'extendedKeyUsage':
|
||||
found = True
|
||||
extKeyUsage = [OpenSSL._util.lib.OBJ_txt2nid(keyUsage) for keyUsage in self.extended_key_usage]
|
||||
current_xku = [OpenSSL._util.lib.OBJ_txt2nid(usage.strip()) for usage in
|
||||
to_bytes(extension, errors='surrogate_or_strict').split(b',')]
|
||||
if not compare_sets(extKeyUsage, current_xku, self.extended_key_usage_strict):
|
||||
return self.extended_key_usage, str(extension).split(', ')
|
||||
if not found:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.extended_key_usage:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_subject_alt_name(self):
|
||||
found = False
|
||||
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
|
||||
extension = self.existing_certificate.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'subjectAltName':
|
||||
found = True
|
||||
l_altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
to_text(extension, errors='surrogate_or_strict').split(', ')]
|
||||
sans = [pyopenssl_normalize_name_attribute(to_text(san, errors='surrogate_or_strict')) for san in self.subject_alt_name]
|
||||
if not compare_sets(sans, l_altnames, self.subject_alt_name_strict):
|
||||
return self.subject_alt_name, l_altnames
|
||||
if not found:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.subject_alt_name:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_not_before(self):
|
||||
return self.existing_certificate.get_notBefore()
|
||||
|
||||
def _validate_not_after(self):
|
||||
return self.existing_certificate.get_notAfter()
|
||||
|
||||
def _validate_valid_at(self):
|
||||
rt = get_relative_time_option(self.valid_at, "valid_at", backend=self.backend)
|
||||
rt = to_bytes(rt, errors='surrogate_or_strict')
|
||||
return self.existing_certificate.get_notBefore(), rt, self.existing_certificate.get_notAfter()
|
||||
|
||||
def _validate_invalid_at(self):
|
||||
rt = get_relative_time_option(self.invalid_at, "invalid_at", backend=self.backend)
|
||||
rt = to_bytes(rt, errors='surrogate_or_strict')
|
||||
return self.existing_certificate.get_notBefore(), rt, self.existing_certificate.get_notAfter()
|
||||
|
||||
def _validate_valid_in(self):
|
||||
valid_in_asn1 = get_relative_time_option(self.valid_in, "valid_in", backend=self.backend)
|
||||
valid_in_date = to_bytes(valid_in_asn1, errors='surrogate_or_strict')
|
||||
return self.existing_certificate.get_notBefore(), valid_in_date, self.existing_certificate.get_notAfter()
|
||||
|
||||
|
||||
class AssertOnlyCertificateProvider(CertificateProvider):
|
||||
def validate_module_args(self, module):
|
||||
module.deprecate("The 'assertonly' provider is deprecated; please see the examples of "
|
||||
"the 'x509_certificate' module on how to replace it with other modules",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
def needs_version_two_certs(self, module):
|
||||
return False
|
||||
|
||||
def create_backend(self, module, backend):
|
||||
if backend == 'cryptography':
|
||||
return AssertOnlyCertificateBackendCryptography(module)
|
||||
if backend == 'pyopenssl':
|
||||
return AssertOnlyCertificateBackendPyOpenSSL(module)
|
||||
|
||||
|
||||
def add_assertonly_provider_to_argument_spec(argument_spec):
|
||||
argument_spec.argument_spec['provider']['choices'].append('assertonly')
|
||||
argument_spec.argument_spec.update(dict(
|
||||
signature_algorithms=dict(type='list', elements='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
subject=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
subject_strict=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
issuer=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
issuer_strict=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
has_expired=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
version=dict(type='int', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
key_usage=dict(type='list', elements='str', aliases=['keyUsage'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
key_usage_strict=dict(type='bool', default=False, aliases=['keyUsage_strict'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
extended_key_usage=dict(type='list', elements='str', aliases=['extendedKeyUsage'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
extended_key_usage_strict=dict(type='bool', default=False, aliases=['extendedKeyUsage_strict'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
subject_alt_name=dict(type='list', elements='str', aliases=['subjectAltName'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
subject_alt_name_strict=dict(type='bool', default=False, aliases=['subjectAltName_strict'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
not_before=dict(type='str', aliases=['notBefore'], removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
not_after=dict(type='str', aliases=['notAfter'], removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
valid_at=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
invalid_at=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
valid_in=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
))
|
||||
@@ -58,18 +58,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
||||
# We want to always force behavior of trying to use the organization provided in the CSR.
|
||||
# To that end we need to parse out the organization from the CSR.
|
||||
self.csr_org = None
|
||||
if self.backend == 'pyopenssl':
|
||||
csr_subject = self.csr.get_subject()
|
||||
csr_subject_components = csr_subject.get_components()
|
||||
for k, v in csr_subject_components:
|
||||
if k.upper() == 'O':
|
||||
# Entrust does not support multiple validated organizations in a single certificate
|
||||
if self.csr_org is not None:
|
||||
self.module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations "
|
||||
"found in Subject DN: '{0}'. ".format(csr_subject)))
|
||||
else:
|
||||
self.csr_org = v
|
||||
elif self.backend == 'cryptography':
|
||||
if self.backend == 'cryptography':
|
||||
csr_subject_orgs = self.csr.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)
|
||||
if len(csr_subject_orgs) == 1:
|
||||
self.csr_org = csr_subject_orgs[0].value
|
||||
@@ -162,11 +151,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
||||
if self.existing_certificate:
|
||||
serial_number = None
|
||||
expiry = None
|
||||
if self.backend == 'pyopenssl':
|
||||
serial_number = "{0:X}".format(self.existing_certificate.get_serial_number())
|
||||
time_string = to_native(self.existing_certificate.get_notAfter())
|
||||
expiry = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
elif self.backend == 'cryptography':
|
||||
if self.backend == 'cryptography':
|
||||
serial_number = "{0:X}".format(cryptography_serial_number_of_cert(self.existing_certificate))
|
||||
expiry = self.existing_certificate.not_valid_after
|
||||
|
||||
|
||||
@@ -33,37 +33,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||
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,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||
get_publickey_info,
|
||||
)
|
||||
|
||||
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:
|
||||
@@ -209,20 +183,19 @@ class CertificateInfoRetrieval(object):
|
||||
result['fingerprints'] = get_fingerprint_of_bytes(
|
||||
self._get_der_bytes(), prefer_one=prefer_one_fingerprint)
|
||||
|
||||
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
|
||||
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
|
||||
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()
|
||||
@@ -392,136 +365,9 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
||||
return None
|
||||
|
||||
|
||||
class CertificateInfoRetrievalPyOpenSSL(CertificateInfoRetrieval):
|
||||
"""validate the supplied certificate."""
|
||||
|
||||
def __init__(self, module, content):
|
||||
super(CertificateInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content)
|
||||
|
||||
def _get_der_bytes(self):
|
||||
return crypto.dump_certificate(crypto.FILETYPE_ASN1, self.cert)
|
||||
|
||||
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_pem(self):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_PEM,
|
||||
self.cert.get_pubkey(),
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
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_public_key_object(self):
|
||||
return self.cert.get_pubkey()
|
||||
|
||||
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 get_certificate_info(module, backend, content, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = CertificateInfoRetrievalCryptography(module, content)
|
||||
elif backend == 'pyopenssl':
|
||||
info = CertificateInfoRetrievalPyOpenSSL(module, content)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
@@ -529,34 +375,17 @@ def select_backend(module, backend, content):
|
||||
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)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
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))
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_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 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')
|
||||
return backend, CertificateInfoRetrievalPyOpenSSL(module, content)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -169,7 +169,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
||||
return self.cert.public_bytes(Encoding.PEM)
|
||||
|
||||
def needs_regeneration(self):
|
||||
if super(OwnCACertificateBackendCryptography, self).needs_regeneration():
|
||||
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(not_before=self.notBefore, not_after=self.notAfter):
|
||||
return True
|
||||
|
||||
# Check AuthorityKeyIdentifier
|
||||
@@ -227,100 +227,6 @@ def generate_serial_number():
|
||||
return result
|
||||
|
||||
|
||||
class OwnCACertificateBackendPyOpenSSL(CertificateBackend):
|
||||
def __init__(self, module):
|
||||
super(OwnCACertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
|
||||
self.notBefore = get_relative_time_option(self.module.params['ownca_not_before'], 'ownca_not_before', backend=self.backend)
|
||||
self.notAfter = get_relative_time_option(self.module.params['ownca_not_after'], 'ownca_not_after', backend=self.backend)
|
||||
self.digest = self.module.params['ownca_digest']
|
||||
self.version = self.module.params['ownca_version']
|
||||
self.serial_number = generate_serial_number()
|
||||
if self.module.params['ownca_create_subject_key_identifier'] != 'create_if_not_provided':
|
||||
self.module.fail_json(msg='ownca_create_subject_key_identifier cannot be used with the pyOpenSSL backend!')
|
||||
if self.module.params['ownca_create_authority_key_identifier']:
|
||||
self.module.warn('ownca_create_authority_key_identifier is ignored by the pyOpenSSL backend!')
|
||||
self.ca_cert_path = self.module.params['ownca_path']
|
||||
self.ca_cert_content = self.module.params['ownca_content']
|
||||
if self.ca_cert_content is not None:
|
||||
self.ca_cert_content = self.ca_cert_content.encode('utf-8')
|
||||
self.ca_privatekey_path = self.module.params['ownca_privatekey_path']
|
||||
self.ca_privatekey_content = self.module.params['ownca_privatekey_content']
|
||||
if self.ca_privatekey_content is not None:
|
||||
self.ca_privatekey_content = self.ca_privatekey_content.encode('utf-8')
|
||||
self.ca_privatekey_passphrase = self.module.params['ownca_privatekey_passphrase']
|
||||
|
||||
if self.csr_content is None and not os.path.exists(self.csr_path):
|
||||
raise CertificateError(
|
||||
'The certificate signing request file {0} does not exist'.format(self.csr_path)
|
||||
)
|
||||
if self.ca_cert_content is None and not os.path.exists(self.ca_cert_path):
|
||||
raise CertificateError(
|
||||
'The CA certificate file {0} does not exist'.format(self.ca_cert_path)
|
||||
)
|
||||
if self.ca_privatekey_content is None and not os.path.exists(self.ca_privatekey_path):
|
||||
raise CertificateError(
|
||||
'The CA private key file {0} does not exist'.format(self.ca_privatekey_path)
|
||||
)
|
||||
|
||||
self._ensure_csr_loaded()
|
||||
self.ca_cert = load_certificate(
|
||||
path=self.ca_cert_path,
|
||||
content=self.ca_cert_content,
|
||||
)
|
||||
try:
|
||||
self.ca_privatekey = load_privatekey(
|
||||
path=self.ca_privatekey_path,
|
||||
content=self.ca_privatekey_content,
|
||||
passphrase=self.ca_privatekey_passphrase
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
self.module.fail_json(msg=str(exc))
|
||||
|
||||
def generate_certificate(self):
|
||||
"""(Re-)Generate certificate."""
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(self.serial_number)
|
||||
cert.set_notBefore(to_bytes(self.notBefore))
|
||||
cert.set_notAfter(to_bytes(self.notAfter))
|
||||
cert.set_subject(self.csr.get_subject())
|
||||
cert.set_issuer(self.ca_cert.get_subject())
|
||||
cert.set_version(self.version - 1)
|
||||
cert.set_pubkey(self.csr.get_pubkey())
|
||||
cert.add_extensions(self.csr.get_extensions())
|
||||
|
||||
cert.sign(self.ca_privatekey, self.digest)
|
||||
self.cert = cert
|
||||
|
||||
def get_certificate_data(self):
|
||||
"""Return bytes for self.cert."""
|
||||
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
|
||||
|
||||
def dump(self, include_certificate):
|
||||
result = super(OwnCACertificateBackendPyOpenSSL, self).dump(include_certificate)
|
||||
result.update({
|
||||
'ca_cert': self.ca_cert_path,
|
||||
'ca_privatekey': self.ca_privatekey_path,
|
||||
})
|
||||
|
||||
if self.module.check_mode:
|
||||
result.update({
|
||||
'notBefore': self.notBefore,
|
||||
'notAfter': self.notAfter,
|
||||
'serial_number': self.serial_number,
|
||||
})
|
||||
else:
|
||||
if self.cert is None:
|
||||
self.cert = self.existing_certificate
|
||||
result.update({
|
||||
'notBefore': self.cert.get_notBefore(),
|
||||
'notAfter': self.cert.get_notAfter(),
|
||||
'serial_number': self.cert.get_serial_number(),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class OwnCACertificateProvider(CertificateProvider):
|
||||
def validate_module_args(self, module):
|
||||
if module.params['ownca_path'] is None and module.params['ownca_content'] is None:
|
||||
@@ -334,8 +240,6 @@ class OwnCACertificateProvider(CertificateProvider):
|
||||
def create_backend(self, module, backend):
|
||||
if backend == 'cryptography':
|
||||
return OwnCACertificateBackendCryptography(module)
|
||||
if backend == 'pyopenssl':
|
||||
return OwnCACertificateBackendPyOpenSSL(module)
|
||||
|
||||
|
||||
def add_ownca_provider_to_argument_spec(argument_spec):
|
||||
|
||||
@@ -134,6 +134,10 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
||||
"""Return bytes for self.cert."""
|
||||
return self.cert.public_bytes(Encoding.PEM)
|
||||
|
||||
def needs_regeneration(self):
|
||||
return super(SelfSignedCertificateBackendCryptography, self).needs_regeneration(
|
||||
not_before=self.notBefore, not_after=self.notAfter)
|
||||
|
||||
def dump(self, include_certificate):
|
||||
result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate)
|
||||
|
||||
@@ -163,76 +167,6 @@ def generate_serial_number():
|
||||
return result
|
||||
|
||||
|
||||
class SelfSignedCertificateBackendPyOpenSSL(CertificateBackend):
|
||||
def __init__(self, module):
|
||||
super(SelfSignedCertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
|
||||
if module.params['selfsigned_create_subject_key_identifier'] != 'create_if_not_provided':
|
||||
module.fail_json(msg='selfsigned_create_subject_key_identifier cannot be used with the pyOpenSSL backend!')
|
||||
self.notBefore = get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before', backend=self.backend)
|
||||
self.notAfter = get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after', backend=self.backend)
|
||||
self.digest = module.params['selfsigned_digest']
|
||||
self.version = module.params['selfsigned_version']
|
||||
self.serial_number = generate_serial_number()
|
||||
|
||||
if self.csr_path is not None and not os.path.exists(self.csr_path):
|
||||
raise CertificateError(
|
||||
'The certificate signing request file {0} does not exist'.format(self.csr_path)
|
||||
)
|
||||
if self.privatekey_content is None and not os.path.exists(self.privatekey_path):
|
||||
raise CertificateError(
|
||||
'The private key file {0} does not exist'.format(self.privatekey_path)
|
||||
)
|
||||
|
||||
self._ensure_private_key_loaded()
|
||||
|
||||
self._ensure_csr_loaded()
|
||||
if self.csr is None:
|
||||
# Create empty CSR on the fly
|
||||
self.csr = crypto.X509Req()
|
||||
self.csr.set_pubkey(self.privatekey)
|
||||
self.csr.sign(self.privatekey, self.digest)
|
||||
|
||||
def generate_certificate(self):
|
||||
"""(Re-)Generate certificate."""
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(self.serial_number)
|
||||
cert.set_notBefore(to_bytes(self.notBefore))
|
||||
cert.set_notAfter(to_bytes(self.notAfter))
|
||||
cert.set_subject(self.csr.get_subject())
|
||||
cert.set_issuer(self.csr.get_subject())
|
||||
cert.set_version(self.version - 1)
|
||||
cert.set_pubkey(self.csr.get_pubkey())
|
||||
cert.add_extensions(self.csr.get_extensions())
|
||||
|
||||
cert.sign(self.privatekey, self.digest)
|
||||
self.cert = cert
|
||||
|
||||
def get_certificate_data(self):
|
||||
"""Return bytes for self.cert."""
|
||||
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
|
||||
|
||||
def dump(self, include_certificate):
|
||||
result = super(SelfSignedCertificateBackendPyOpenSSL, self).dump(include_certificate)
|
||||
|
||||
if self.module.check_mode:
|
||||
result.update({
|
||||
'notBefore': self.notBefore,
|
||||
'notAfter': self.notAfter,
|
||||
'serial_number': self.serial_number,
|
||||
})
|
||||
else:
|
||||
if self.cert is None:
|
||||
self.cert = self.existing_certificate
|
||||
result.update({
|
||||
'notBefore': self.cert.get_notBefore(),
|
||||
'notAfter': self.cert.get_notAfter(),
|
||||
'serial_number': self.cert.get_serial_number(),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class SelfSignedCertificateProvider(CertificateProvider):
|
||||
def validate_module_args(self, module):
|
||||
if module.params['privatekey_path'] is None and module.params['privatekey_content'] is None:
|
||||
@@ -244,8 +178,6 @@ class SelfSignedCertificateProvider(CertificateProvider):
|
||||
def create_backend(self, module, backend):
|
||||
if backend == 'cryptography':
|
||||
return SelfSignedCertificateBackendCryptography(module)
|
||||
if backend == 'pyopenssl':
|
||||
return SelfSignedCertificateBackendPyOpenSSL(module)
|
||||
|
||||
|
||||
def add_selfsigned_provider_to_argument_spec(argument_spec):
|
||||
|
||||
@@ -16,7 +16,7 @@ from distutils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils import six
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
@@ -27,6 +27,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
||||
load_privatekey,
|
||||
load_certificate_request,
|
||||
parse_name_field,
|
||||
parse_ordered_name_field,
|
||||
select_message_digest,
|
||||
)
|
||||
|
||||
@@ -43,11 +44,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||
REVOCATION_REASON_MAP,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_normalize_name_attribute,
|
||||
pyopenssl_parse_name_constraints,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.csr_info import (
|
||||
get_csr_info,
|
||||
)
|
||||
@@ -55,28 +51,8 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
||||
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
|
||||
|
||||
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
|
||||
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"
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
@@ -144,6 +120,7 @@ class CertificateSigningRequestBackend(object):
|
||||
if self.create_subject_key_identifier and self.subject_key_identifier is not None:
|
||||
module.fail_json(msg='subject_key_identifier cannot be specified if create_subject_key_identifier is true')
|
||||
|
||||
self.ordered_subject = False
|
||||
self.subject = [
|
||||
('C', module.params['country_name']),
|
||||
('ST', module.params['state_or_province_name']),
|
||||
@@ -153,11 +130,19 @@ class CertificateSigningRequestBackend(object):
|
||||
('CN', module.params['common_name']),
|
||||
('emailAddress', module.params['email_address']),
|
||||
]
|
||||
|
||||
if module.params['subject']:
|
||||
self.subject = self.subject + parse_name_field(module.params['subject'])
|
||||
self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]]
|
||||
|
||||
try:
|
||||
if module.params['subject']:
|
||||
self.subject = self.subject + parse_name_field(module.params['subject'], 'subject')
|
||||
if module.params['subject_ordered']:
|
||||
if self.subject:
|
||||
raise CertificateSigningRequestError('subject_ordered cannot be combined with any other subject field')
|
||||
self.subject = parse_ordered_name_field(module.params['subject_ordered'], 'subject_ordered')
|
||||
self.ordered_subject = True
|
||||
except ValueError as exc:
|
||||
raise CertificateSigningRequestError(to_native(exc))
|
||||
|
||||
self.using_common_name_for_san = False
|
||||
if not self.subjectAltName and module.params['use_common_name_for_san']:
|
||||
for sub in self.subject:
|
||||
@@ -273,174 +258,6 @@ class CertificateSigningRequestBackend(object):
|
||||
return result
|
||||
|
||||
|
||||
# Implementation with using pyOpenSSL
|
||||
class CertificateSigningRequestPyOpenSSLBackend(CertificateSigningRequestBackend):
|
||||
def __init__(self, module):
|
||||
for o in ('create_subject_key_identifier', ):
|
||||
if module.params[o]:
|
||||
module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o))
|
||||
for o in ('subject_key_identifier', 'authority_key_identifier', 'authority_cert_issuer', 'authority_cert_serial_number', 'crl_distribution_points'):
|
||||
if module.params[o] is not None:
|
||||
module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o))
|
||||
super(CertificateSigningRequestPyOpenSSLBackend, self).__init__(module, 'pyopenssl')
|
||||
|
||||
def generate_csr(self):
|
||||
"""(Re-)Generate CSR."""
|
||||
self._ensure_private_key_loaded()
|
||||
|
||||
req = crypto.X509Req()
|
||||
req.set_version(self.version - 1)
|
||||
subject = req.get_subject()
|
||||
for entry in self.subject:
|
||||
if entry[1] is not None:
|
||||
# Workaround for https://github.com/pyca/pyopenssl/issues/165
|
||||
nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(entry[0]))
|
||||
if nid == 0:
|
||||
raise CertificateSigningRequestError('Unknown subject field identifier "{0}"'.format(entry[0]))
|
||||
res = OpenSSL._util.lib.X509_NAME_add_entry_by_NID(subject._name, nid, OpenSSL._util.lib.MBSTRING_UTF8, to_bytes(entry[1]), -1, -1, 0)
|
||||
if res == 0:
|
||||
raise CertificateSigningRequestError('Invalid value for subject field identifier "{0}": {1}'.format(entry[0], entry[1]))
|
||||
|
||||
extensions = []
|
||||
if self.subjectAltName:
|
||||
altnames = ', '.join(self.subjectAltName)
|
||||
try:
|
||||
extensions.append(crypto.X509Extension(b"subjectAltName", self.subjectAltName_critical, altnames.encode('ascii')))
|
||||
except OpenSSL.crypto.Error as e:
|
||||
raise CertificateSigningRequestError(
|
||||
'Error while parsing Subject Alternative Names {0} (check for missing type prefix, such as "DNS:"!): {1}'.format(
|
||||
', '.join(["{0}".format(san) for san in self.subjectAltName]), str(e)
|
||||
)
|
||||
)
|
||||
|
||||
if self.keyUsage:
|
||||
usages = ', '.join(self.keyUsage)
|
||||
extensions.append(crypto.X509Extension(b"keyUsage", self.keyUsage_critical, usages.encode('ascii')))
|
||||
|
||||
if self.extendedKeyUsage:
|
||||
usages = ', '.join(self.extendedKeyUsage)
|
||||
extensions.append(crypto.X509Extension(b"extendedKeyUsage", self.extendedKeyUsage_critical, usages.encode('ascii')))
|
||||
|
||||
if self.basicConstraints:
|
||||
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))
|
||||
|
||||
if extensions:
|
||||
req.add_extensions(extensions)
|
||||
|
||||
req.set_pubkey(self.privatekey)
|
||||
req.sign(self.privatekey, self.digest)
|
||||
self.csr = req
|
||||
|
||||
def get_csr_data(self):
|
||||
"""Return bytes for self.csr."""
|
||||
return crypto.dump_certificate_request(crypto.FILETYPE_PEM, self.csr)
|
||||
|
||||
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]
|
||||
current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in csr.get_subject().get_components()]
|
||||
if not set(subject) == set(current_subject):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
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
|
||||
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
|
||||
altnames_ext.get_critical() != self.subjectAltName_critical):
|
||||
return False
|
||||
else:
|
||||
if altnames:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _check_keyUsage_(extensions, extName, expected, critical):
|
||||
usages_ext = [ext for ext in extensions if ext.get_short_name() == extName]
|
||||
if (not usages_ext and expected) or (usages_ext and not expected):
|
||||
return False
|
||||
elif not usages_ext and not expected:
|
||||
return True
|
||||
else:
|
||||
current = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage.strip())) for usage in str(usages_ext[0]).split(',')]
|
||||
expected = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage)) for usage in expected]
|
||||
return set(current) == set(expected) and usages_ext[0].get_critical() == critical
|
||||
|
||||
def _check_keyUsage(extensions):
|
||||
usages_ext = [ext for ext in extensions if ext.get_short_name() == b'keyUsage']
|
||||
if (not usages_ext and self.keyUsage) or (usages_ext and not self.keyUsage):
|
||||
return False
|
||||
elif not usages_ext and not self.keyUsage:
|
||||
return True
|
||||
else:
|
||||
# OpenSSL._util.lib.OBJ_txt2nid() always returns 0 for all keyUsage values
|
||||
# (since keyUsage has a fixed bitfield for these values and is not extensible).
|
||||
# Therefore, we create an extension for the wanted values, and compare the
|
||||
# data of the extensions (which is the serialized bitfield).
|
||||
expected_ext = crypto.X509Extension(b"keyUsage", False, ', '.join(self.keyUsage).encode('ascii'))
|
||||
return usages_ext[0].get_data() == expected_ext.get_data() and usages_ext[0].get_critical() == self.keyUsage_critical
|
||||
|
||||
def _check_extenededKeyUsage(extensions):
|
||||
return _check_keyUsage_(extensions, b'extendedKeyUsage', self.extendedKeyUsage, self.extendedKeyUsage_critical)
|
||||
|
||||
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:
|
||||
# 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 self.ocspMustStaple:
|
||||
return len(oms_ext) > 0 and oms_ext[0].get_critical() == self.ocspMustStaple_critical
|
||||
else:
|
||||
return len(oms_ext) == 0
|
||||
|
||||
def _check_extensions(csr):
|
||||
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))
|
||||
|
||||
def _check_signature(csr):
|
||||
try:
|
||||
return csr.verify(self.privatekey)
|
||||
except crypto.Error:
|
||||
return False
|
||||
|
||||
return _check_subject(self.existing_csr) and _check_extensions(self.existing_csr) and _check_signature(self.existing_csr)
|
||||
|
||||
|
||||
def parse_crl_distribution_points(module, crl_distribution_points):
|
||||
result = []
|
||||
for index, parse_crl_distribution_point in enumerate(crl_distribution_points):
|
||||
@@ -594,7 +411,10 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
def _check_subject(csr):
|
||||
subject = [(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject]
|
||||
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
|
||||
return set(subject) == set(current_subject)
|
||||
if self.ordered_subject:
|
||||
return subject == current_subject
|
||||
else:
|
||||
return set(subject) == set(current_subject)
|
||||
|
||||
def _find_extension(extensions, exttype):
|
||||
return next(
|
||||
@@ -754,42 +574,20 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
|
||||
|
||||
def select_backend(module, backend):
|
||||
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')
|
||||
|
||||
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)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
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))
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_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 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')
|
||||
return backend, CertificateSigningRequestPyOpenSSLBackend(module)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
@@ -805,8 +603,9 @@ def get_csr_argument_spec():
|
||||
privatekey_path=dict(type='path'),
|
||||
privatekey_content=dict(type='str', no_log=True),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
version=dict(type='int', default=1),
|
||||
version=dict(type='int', default=1, choices=[1]),
|
||||
subject=dict(type='dict'),
|
||||
subject_ordered=dict(type='list', elements='dict'),
|
||||
country_name=dict(type='str', aliases=['C', 'countryName']),
|
||||
state_or_province_name=dict(type='str', aliases=['ST', 'stateOrProvinceName']),
|
||||
locality_name=dict(type='str', aliases=['L', 'localityName']),
|
||||
@@ -853,13 +652,14 @@ def get_csr_argument_spec():
|
||||
),
|
||||
mutually_exclusive=[('full_name', 'relative_name')]
|
||||
),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_together=[
|
||||
['authority_cert_issuer', 'authority_cert_serial_number'],
|
||||
],
|
||||
mutually_exclusive=[
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
['subject', 'subject_ordered'],
|
||||
],
|
||||
required_one_of=[
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
|
||||
@@ -30,38 +30,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||
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.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||
get_publickey_info,
|
||||
)
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
|
||||
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:
|
||||
@@ -173,20 +146,19 @@ class CSRInfoRetrieval(object):
|
||||
'public_key_fingerprints': public_key_info['fingerprints'],
|
||||
})
|
||||
|
||||
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
|
||||
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
|
||||
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['extensions_by_oid'] = self._get_all_extensions()
|
||||
|
||||
@@ -332,112 +304,9 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
||||
return self.csr.is_signature_valid
|
||||
|
||||
|
||||
class CSRInfoRetrievalPyOpenSSL(CSRInfoRetrieval):
|
||||
"""validate the supplied CSR."""
|
||||
|
||||
def __init__(self, module, content, validate_signature):
|
||||
super(CSRInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content, validate_signature)
|
||||
|
||||
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.csr.get_subject())
|
||||
|
||||
def _get_extension(self, short_name):
|
||||
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(',')
|
||||
]
|
||||
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.csr.get_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:
|
||||
# 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 in self.csr.get_extensions():
|
||||
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_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_pem(self):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_PEM,
|
||||
self.csr.get_pubkey(),
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
bio = crypto._new_mem_buf()
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.csr.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_public_key_object(self):
|
||||
return self.csr.get_pubkey()
|
||||
|
||||
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_all_extensions(self):
|
||||
return pyopenssl_get_extensions_from_csr(self.csr)
|
||||
|
||||
def _is_signature_valid(self):
|
||||
try:
|
||||
return bool(self.csr.verify(self.csr.get_pubkey()))
|
||||
except crypto.Error:
|
||||
# OpenSSL error means that key is not consistent
|
||||
return False
|
||||
|
||||
|
||||
def get_csr_info(module, backend, content, validate_signature=True, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = CSRInfoRetrievalCryptography(module, content, validate_signature=validate_signature)
|
||||
elif backend == 'pyopenssl':
|
||||
info = CSRInfoRetrievalPyOpenSSL(module, content, validate_signature=validate_signature)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
@@ -445,34 +314,17 @@ def select_backend(module, backend, content, validate_signature=True):
|
||||
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)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
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))
|
||||
module.fail_json(msg=("Can't detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_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 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')
|
||||
return backend, CSRInfoRetrievalPyOpenSSL(module, content, validate_signature=validate_signature)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -46,20 +46,8 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
||||
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.6'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
|
||||
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
|
||||
@@ -263,61 +251,6 @@ class PrivateKeyBackend:
|
||||
return result
|
||||
|
||||
|
||||
# Implementation with using pyOpenSSL
|
||||
class PrivateKeyPyOpenSSLBackend(PrivateKeyBackend):
|
||||
|
||||
def __init__(self, module):
|
||||
super(PrivateKeyPyOpenSSLBackend, self).__init__(module=module, backend='pyopenssl')
|
||||
|
||||
if self.type == 'RSA':
|
||||
self.openssl_type = crypto.TYPE_RSA
|
||||
elif self.type == 'DSA':
|
||||
self.openssl_type = crypto.TYPE_DSA
|
||||
else:
|
||||
self.module.fail_json(msg="PyOpenSSL backend only supports RSA and DSA keys.")
|
||||
|
||||
if self.format != 'auto_ignore':
|
||||
self.module.fail_json(msg="PyOpenSSL backend only supports auto_ignore format.")
|
||||
|
||||
def generate_private_key(self):
|
||||
"""(Re-)Generate private key."""
|
||||
self.private_key = crypto.PKey()
|
||||
try:
|
||||
self.private_key.generate_key(self.openssl_type, self.size)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise PrivateKeyError(exc)
|
||||
|
||||
def _ensure_existing_private_key_loaded(self):
|
||||
if self.existing_private_key is None and self.has_existing():
|
||||
try:
|
||||
self.existing_private_key = load_privatekey(
|
||||
None, self.passphrase, content=self.existing_private_key_bytes, backend=self.backend)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
raise PrivateKeyError(exc)
|
||||
|
||||
def get_private_key_data(self):
|
||||
"""Return bytes for self.private_key"""
|
||||
if self.cipher and self.passphrase:
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.private_key,
|
||||
self.cipher, to_bytes(self.passphrase))
|
||||
else:
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.private_key)
|
||||
|
||||
def _check_passphrase(self):
|
||||
try:
|
||||
load_privatekey(None, self.passphrase, content=self.existing_private_key_bytes, backend=self.backend)
|
||||
return True
|
||||
except Exception as dummy:
|
||||
return False
|
||||
|
||||
def _check_size_and_type(self):
|
||||
return self.size == self.existing_private_key.bits() and self.openssl_type == self.existing_private_key.type()
|
||||
|
||||
def _check_format(self):
|
||||
# Not supported by this backend
|
||||
return True
|
||||
|
||||
|
||||
# Implementation with using cryptography
|
||||
class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
|
||||
@@ -550,36 +483,16 @@ def select_backend(module, 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 module.params['cipher'] and module.params['passphrase'] and module.params['cipher'] != 'auto':
|
||||
# First try pyOpenSSL, then cryptography
|
||||
if can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
elif can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
else:
|
||||
# First try cryptography, then pyOpenSSL
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
|
||||
# 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))
|
||||
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')
|
||||
return backend, PrivateKeyPyOpenSSLBackend(module)
|
||||
elif backend == 'cryptography':
|
||||
module.fail_json(msg=("Can't detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
@@ -605,7 +518,7 @@ def get_privatekey_argument_spec():
|
||||
cipher=dict(type='str'),
|
||||
format=dict(type='str', default='auto_ignore', choices=['pkcs1', 'pkcs8', 'raw', 'auto', 'auto_ignore']),
|
||||
format_mismatch=dict(type='str', default='regenerate', choices=['regenerate', 'convert']),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
regenerate=dict(
|
||||
type='str',
|
||||
default='full_idempotence',
|
||||
|
||||
@@ -36,24 +36,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.math impor
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||
_get_cryptography_public_key_info,
|
||||
_bigint_to_int,
|
||||
_get_pyopenssl_public_key_info,
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
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:
|
||||
@@ -69,20 +55,21 @@ else:
|
||||
SIGNATURE_TEST_DATA = b'1234'
|
||||
|
||||
|
||||
def _get_cryptography_private_key_info(key):
|
||||
def _get_cryptography_private_key_info(key, need_private_key_data=False):
|
||||
key_type, key_public_data = _get_cryptography_public_key_info(key.public_key())
|
||||
key_private_data = dict()
|
||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['p'] = private_numbers.p
|
||||
key_private_data['q'] = private_numbers.q
|
||||
key_private_data['exponent'] = private_numbers.d
|
||||
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['x'] = private_numbers.x
|
||||
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['multiplier'] = private_numbers.private_value
|
||||
if need_private_key_data:
|
||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['p'] = private_numbers.p
|
||||
key_private_data['q'] = private_numbers.q
|
||||
key_private_data['exponent'] = private_numbers.d
|
||||
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['x'] = private_numbers.x
|
||||
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['multiplier'] = private_numbers.private_value
|
||||
return key_type, key_public_data, key_private_data
|
||||
|
||||
|
||||
@@ -188,20 +175,21 @@ class PrivateKeyParseError(OpenSSLObjectError):
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class PrivateKeyInfoRetrieval(object):
|
||||
def __init__(self, module, backend, content, passphrase=None, return_private_key_data=False):
|
||||
def __init__(self, module, backend, content, passphrase=None, return_private_key_data=False, check_consistency=False):
|
||||
# content must be a bytes string
|
||||
self.module = module
|
||||
self.backend = backend
|
||||
self.content = content
|
||||
self.passphrase = passphrase
|
||||
self.return_private_key_data = return_private_key_data
|
||||
self.check_consistency = check_consistency
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_public_key(self, binary):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_key_info(self):
|
||||
def _get_key_info(self, need_private_key_data=False):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -230,20 +218,22 @@ class PrivateKeyInfoRetrieval(object):
|
||||
result['public_key_fingerprints'] = get_fingerprint_of_bytes(
|
||||
pk, prefer_one=prefer_one_fingerprint) if pk is not None else dict()
|
||||
|
||||
key_type, key_public_data, key_private_data = self._get_key_info()
|
||||
key_type, key_public_data, key_private_data = self._get_key_info(
|
||||
need_private_key_data=self.return_private_key_data or self.check_consistency)
|
||||
result['type'] = key_type
|
||||
result['public_data'] = key_public_data
|
||||
if self.return_private_key_data:
|
||||
result['private_data'] = key_private_data
|
||||
|
||||
result['key_is_consistent'] = self._is_key_consistent(key_public_data, key_private_data)
|
||||
if result['key_is_consistent'] is False:
|
||||
# Only fail when it is False, to avoid to fail on None (which means "we don't know")
|
||||
msg = (
|
||||
"Private key is not consistent! (See "
|
||||
"https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html)"
|
||||
)
|
||||
raise PrivateKeyConsistencyError(msg, result)
|
||||
if self.check_consistency:
|
||||
result['key_is_consistent'] = self._is_key_consistent(key_public_data, key_private_data)
|
||||
if result['key_is_consistent'] is False:
|
||||
# Only fail when it is False, to avoid to fail on None (which means "we don't know")
|
||||
msg = (
|
||||
"Private key is not consistent! (See "
|
||||
"https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html)"
|
||||
)
|
||||
raise PrivateKeyConsistencyError(msg, result)
|
||||
return result
|
||||
|
||||
|
||||
@@ -258,177 +248,39 @@ class PrivateKeyInfoRetrievalCryptography(PrivateKeyInfoRetrieval):
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
|
||||
def _get_key_info(self):
|
||||
return _get_cryptography_private_key_info(self.key)
|
||||
def _get_key_info(self, need_private_key_data=False):
|
||||
return _get_cryptography_private_key_info(self.key, need_private_key_data=need_private_key_data)
|
||||
|
||||
def _is_key_consistent(self, key_public_data, key_private_data):
|
||||
return _is_cryptography_key_consistent(self.key, key_public_data, key_private_data)
|
||||
|
||||
|
||||
class PrivateKeyInfoRetrievalPyOpenSSL(PrivateKeyInfoRetrieval):
|
||||
"""validate the supplied private key."""
|
||||
|
||||
def __init__(self, module, content, **kwargs):
|
||||
super(PrivateKeyInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content, **kwargs)
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM,
|
||||
self.key
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
if binary:
|
||||
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.key._pkey)
|
||||
else:
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.key._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_key_info(self):
|
||||
key_type, key_public_data, try_fallback = _get_pyopenssl_public_key_info(self.key)
|
||||
key_private_data = dict()
|
||||
openssl_key_type = self.key.type()
|
||||
if crypto.TYPE_RSA == openssl_key_type:
|
||||
try:
|
||||
# Use OpenSSL directly to extract key data
|
||||
key = OpenSSL._util.lib.EVP_PKEY_get1_RSA(self.key._pkey)
|
||||
key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.RSA_free)
|
||||
# OpenSSL 1.1 and newer have functions to extract the parameters
|
||||
# from the EVP PKEY data structures. Older versions didn't have
|
||||
# these getters, and it was common use to simply access the values
|
||||
# directly. Since there's no guarantee that these data structures
|
||||
# will still be accessible in the future, we use the getters for
|
||||
# 1.1 and later, and directly access the values for 1.0.x and
|
||||
# earlier.
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# Get modulus and exponents
|
||||
n = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
e = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
d = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.RSA_get0_key(key, n, e, d)
|
||||
key_private_data['exponent'] = _bigint_to_int(d[0])
|
||||
# Get factors
|
||||
p = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
q = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.RSA_get0_factors(key, p, q)
|
||||
key_private_data['p'] = _bigint_to_int(p[0])
|
||||
key_private_data['q'] = _bigint_to_int(q[0])
|
||||
else:
|
||||
# Get private exponent
|
||||
key_private_data['exponent'] = _bigint_to_int(key.d)
|
||||
# Get factors
|
||||
key_private_data['p'] = _bigint_to_int(key.p)
|
||||
key_private_data['q'] = _bigint_to_int(key.q)
|
||||
except AttributeError:
|
||||
try_fallback = True
|
||||
elif crypto.TYPE_DSA == openssl_key_type:
|
||||
try:
|
||||
# Use OpenSSL directly to extract key data
|
||||
key = OpenSSL._util.lib.EVP_PKEY_get1_DSA(self.key._pkey)
|
||||
key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.DSA_free)
|
||||
# OpenSSL 1.1 and newer have functions to extract the parameters
|
||||
# from the EVP PKEY data structures. Older versions didn't have
|
||||
# these getters, and it was common use to simply access the values
|
||||
# directly. Since there's no guarantee that these data structures
|
||||
# will still be accessible in the future, we use the getters for
|
||||
# 1.1 and later, and directly access the values for 1.0.x and
|
||||
# earlier.
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# Get private key exponents
|
||||
y = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
x = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.DSA_get0_key(key, y, x)
|
||||
key_private_data['x'] = _bigint_to_int(x[0])
|
||||
else:
|
||||
# Get private key exponents
|
||||
key_private_data['x'] = _bigint_to_int(key.priv_key)
|
||||
except AttributeError:
|
||||
try_fallback = True
|
||||
else:
|
||||
# Return 'unknown'
|
||||
key_type = 'unknown ({0})'.format(self.key.type())
|
||||
# If needed and if possible, fall back to cryptography
|
||||
if try_fallback and PYOPENSSL_VERSION >= LooseVersion('16.1.0') and CRYPTOGRAPHY_FOUND:
|
||||
return _get_cryptography_private_key_info(self.key.to_cryptography_key())
|
||||
return key_type, key_public_data, key_private_data
|
||||
|
||||
def _is_key_consistent(self, key_public_data, key_private_data):
|
||||
openssl_key_type = self.key.type()
|
||||
if crypto.TYPE_RSA == openssl_key_type:
|
||||
try:
|
||||
return self.key.check()
|
||||
except crypto.Error:
|
||||
# OpenSSL error means that key is not consistent
|
||||
return False
|
||||
if crypto.TYPE_DSA == openssl_key_type:
|
||||
result = _check_dsa_consistency(key_public_data, key_private_data)
|
||||
if result is not None:
|
||||
return result
|
||||
signature = crypto.sign(self.key, SIGNATURE_TEST_DATA, 'sha256')
|
||||
# Verify wants a cert (where it can get the public key from)
|
||||
cert = crypto.X509()
|
||||
cert.set_pubkey(self.key)
|
||||
try:
|
||||
crypto.verify(cert, signature, SIGNATURE_TEST_DATA, 'sha256')
|
||||
return True
|
||||
except crypto.Error:
|
||||
return False
|
||||
# If needed and if possible, fall back to cryptography
|
||||
if PYOPENSSL_VERSION >= LooseVersion('16.1.0') and CRYPTOGRAPHY_FOUND:
|
||||
return _is_cryptography_key_consistent(self.key.to_cryptography_key(), key_public_data, key_private_data)
|
||||
return None
|
||||
|
||||
|
||||
def get_privatekey_info(module, backend, content, passphrase=None, return_private_key_data=False, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = PrivateKeyInfoRetrievalCryptography(
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
elif backend == 'pyopenssl':
|
||||
info = PrivateKeyInfoRetrievalPyOpenSSL(
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
def select_backend(module, backend, content, passphrase=None, return_private_key_data=False):
|
||||
def select_backend(module, backend, content, passphrase=None, return_private_key_data=False, check_consistency=False):
|
||||
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)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
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))
|
||||
module.fail_json(msg=("Can't detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_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)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, PrivateKeyInfoRetrievalPyOpenSSL(
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
return backend, PrivateKeyInfoRetrievalCryptography(
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data, check_consistency=check_consistency)
|
||||
else:
|
||||
raise ValueError('Unsupported value for backend: {0}'.format(backend))
|
||||
|
||||
@@ -31,18 +31,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
MINIMAL_PYOPENSSL_VERSION = '16.0.0' # when working with public key objects, the minimal required version is 0.15
|
||||
|
||||
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:
|
||||
@@ -93,98 +81,6 @@ def _get_cryptography_public_key_info(key):
|
||||
return key_type, key_public_data
|
||||
|
||||
|
||||
def _bigint_to_int(bn):
|
||||
'''Convert OpenSSL BIGINT to Python integer'''
|
||||
if bn == OpenSSL._util.ffi.NULL:
|
||||
return None
|
||||
hexstr = OpenSSL._util.lib.BN_bn2hex(bn)
|
||||
try:
|
||||
return int(OpenSSL._util.ffi.string(hexstr), 16)
|
||||
finally:
|
||||
OpenSSL._util.lib.OPENSSL_free(hexstr)
|
||||
|
||||
|
||||
def _get_pyopenssl_public_key_info(key):
|
||||
key_public_data = dict()
|
||||
try_fallback = True
|
||||
openssl_key_type = key.type()
|
||||
if crypto.TYPE_RSA == openssl_key_type:
|
||||
key_type = 'RSA'
|
||||
key_public_data['size'] = key.bits()
|
||||
|
||||
try:
|
||||
# Use OpenSSL directly to extract key data
|
||||
key = OpenSSL._util.lib.EVP_PKEY_get1_RSA(key._pkey)
|
||||
key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.RSA_free)
|
||||
# OpenSSL 1.1 and newer have functions to extract the parameters
|
||||
# from the EVP PKEY data structures. Older versions didn't have
|
||||
# these getters, and it was common use to simply access the values
|
||||
# directly. Since there's no guarantee that these data structures
|
||||
# will still be accessible in the future, we use the getters for
|
||||
# 1.1 and later, and directly access the values for 1.0.x and
|
||||
# earlier.
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# Get modulus and exponents
|
||||
n = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
e = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
d = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.RSA_get0_key(key, n, e, d)
|
||||
key_public_data['modulus'] = _bigint_to_int(n[0])
|
||||
key_public_data['exponent'] = _bigint_to_int(e[0])
|
||||
else:
|
||||
# Get modulus and exponents
|
||||
key_public_data['modulus'] = _bigint_to_int(key.n)
|
||||
key_public_data['exponent'] = _bigint_to_int(key.e)
|
||||
try_fallback = False
|
||||
except AttributeError:
|
||||
# Use fallback if available
|
||||
pass
|
||||
elif crypto.TYPE_DSA == openssl_key_type:
|
||||
key_type = 'DSA'
|
||||
key_public_data['size'] = key.bits()
|
||||
|
||||
try:
|
||||
# Use OpenSSL directly to extract key data
|
||||
key = OpenSSL._util.lib.EVP_PKEY_get1_DSA(key._pkey)
|
||||
key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.DSA_free)
|
||||
# OpenSSL 1.1 and newer have functions to extract the parameters
|
||||
# from the EVP PKEY data structures. Older versions didn't have
|
||||
# these getters, and it was common use to simply access the values
|
||||
# directly. Since there's no guarantee that these data structures
|
||||
# will still be accessible in the future, we use the getters for
|
||||
# 1.1 and later, and directly access the values for 1.0.x and
|
||||
# earlier.
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# Get public parameters (primes and group element)
|
||||
p = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
q = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
g = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.DSA_get0_pqg(key, p, q, g)
|
||||
key_public_data['p'] = _bigint_to_int(p[0])
|
||||
key_public_data['q'] = _bigint_to_int(q[0])
|
||||
key_public_data['g'] = _bigint_to_int(g[0])
|
||||
# Get public key exponents
|
||||
y = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
x = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.DSA_get0_key(key, y, x)
|
||||
key_public_data['y'] = _bigint_to_int(y[0])
|
||||
else:
|
||||
# Get public parameters (primes and group element)
|
||||
key_public_data['p'] = _bigint_to_int(key.p)
|
||||
key_public_data['q'] = _bigint_to_int(key.q)
|
||||
key_public_data['g'] = _bigint_to_int(key.g)
|
||||
# Get public key exponents
|
||||
key_public_data['y'] = _bigint_to_int(key.pub_key)
|
||||
try_fallback = False
|
||||
except AttributeError:
|
||||
# Use fallback if available
|
||||
pass
|
||||
else:
|
||||
# Return 'unknown'
|
||||
key_type = 'unknown ({0})'.format(key.type())
|
||||
return key_type, key_public_data, try_fallback
|
||||
|
||||
|
||||
class PublicKeyParseError(OpenSSLObjectError):
|
||||
def __init__(self, msg, result):
|
||||
super(PublicKeyParseError, self).__init__(msg)
|
||||
@@ -242,46 +138,9 @@ class PublicKeyInfoRetrievalCryptography(PublicKeyInfoRetrieval):
|
||||
return _get_cryptography_public_key_info(self.key)
|
||||
|
||||
|
||||
class PublicKeyInfoRetrievalPyOpenSSL(PublicKeyInfoRetrieval):
|
||||
"""validate the supplied public key."""
|
||||
|
||||
def __init__(self, module, content=None, key=None):
|
||||
super(PublicKeyInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content=content, key=key)
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM,
|
||||
self.key
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
if binary:
|
||||
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.key._pkey)
|
||||
else:
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.key._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_key_info(self):
|
||||
key_type, key_public_data, try_fallback = _get_pyopenssl_public_key_info(self.key)
|
||||
# If needed and if possible, fall back to cryptography
|
||||
if try_fallback and PYOPENSSL_VERSION >= LooseVersion('16.1.0') and CRYPTOGRAPHY_FOUND:
|
||||
return _get_cryptography_public_key_info(self.key.to_cryptography_key())
|
||||
return key_type, key_public_data
|
||||
|
||||
|
||||
def get_publickey_info(module, backend, content=None, key=None, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = PublicKeyInfoRetrievalCryptography(module, content=content, key=key)
|
||||
elif backend == 'pyopenssl':
|
||||
info = PublicKeyInfoRetrievalPyOpenSSL(module, content=content, key=key)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
@@ -289,29 +148,17 @@ def select_backend(module, backend, content=None, key=None):
|
||||
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)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
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))
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_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)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, PublicKeyInfoRetrievalPyOpenSSL(module, content=content, key=key)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -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.common.text.converters 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
|
||||
@@ -98,25 +98,10 @@ def get_fingerprint_of_bytes(source, prefer_one=False):
|
||||
return fingerprint
|
||||
|
||||
|
||||
def get_fingerprint_of_privatekey(privatekey, backend='pyopenssl', prefer_one=False):
|
||||
def get_fingerprint_of_privatekey(privatekey, backend='cryptography', prefer_one=False):
|
||||
"""Generate the fingerprint of the public key. """
|
||||
|
||||
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':
|
||||
if backend == 'cryptography':
|
||||
publickey = privatekey.public_key().public_bytes(
|
||||
serialization.Encoding.DER,
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
@@ -125,7 +110,7 @@ def get_fingerprint_of_privatekey(privatekey, backend='pyopenssl', prefer_one=Fa
|
||||
return get_fingerprint_of_bytes(publickey, prefer_one=prefer_one)
|
||||
|
||||
|
||||
def get_fingerprint(path, passphrase=None, content=None, backend='pyopenssl', prefer_one=False):
|
||||
def get_fingerprint(path, passphrase=None, content=None, backend='cryptography', prefer_one=False):
|
||||
"""Generate the fingerprint of the public key. """
|
||||
|
||||
privatekey = load_privatekey(path, passphrase=passphrase, content=content, check_passphrase=False, backend=backend)
|
||||
@@ -133,7 +118,7 @@ def get_fingerprint(path, passphrase=None, content=None, backend='pyopenssl', pr
|
||||
return get_fingerprint_of_privatekey(privatekey, backend=backend, prefer_one=prefer_one)
|
||||
|
||||
|
||||
def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='pyopenssl'):
|
||||
def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='cryptography'):
|
||||
"""Load the specified OpenSSL private key.
|
||||
|
||||
The content can also be specified via content; in that case,
|
||||
@@ -213,14 +198,9 @@ def load_publickey(path=None, content=None, backend=None):
|
||||
return serialization.load_pem_public_key(content, backend=cryptography_backend())
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e))
|
||||
else:
|
||||
try:
|
||||
return crypto.load_publickey(crypto.FILETYPE_PEM, content)
|
||||
except crypto.Error as e:
|
||||
raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e))
|
||||
|
||||
|
||||
def load_certificate(path, content=None, backend='pyopenssl'):
|
||||
def load_certificate(path, content=None, backend='cryptography'):
|
||||
"""Load the specified certificate."""
|
||||
|
||||
try:
|
||||
@@ -240,7 +220,7 @@ def load_certificate(path, content=None, backend='pyopenssl'):
|
||||
raise OpenSSLObjectError(exc)
|
||||
|
||||
|
||||
def load_certificate_request(path, content=None, backend='pyopenssl'):
|
||||
def load_certificate_request(path, content=None, backend='cryptography'):
|
||||
"""Load the specified certificate signing request."""
|
||||
try:
|
||||
if content is None:
|
||||
@@ -250,25 +230,50 @@ def load_certificate_request(path, content=None, backend='pyopenssl'):
|
||||
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':
|
||||
if backend == 'cryptography':
|
||||
try:
|
||||
return x509.load_pem_x509_csr(csr_content, cryptography_backend())
|
||||
except ValueError as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
|
||||
|
||||
def parse_name_field(input_dict):
|
||||
def parse_name_field(input_dict, name_field_name=None):
|
||||
"""Take a dict with key: value or key: list_of_values mappings and return a list of tuples"""
|
||||
error_str = '{key}' if name_field_name is None else '{key} in {name}'
|
||||
|
||||
result = []
|
||||
for key, value in input_dict.items():
|
||||
if isinstance(value, list):
|
||||
for entry in value:
|
||||
if not isinstance(entry, six.string_types):
|
||||
raise TypeError(('Values %s must be strings' % error_str).format(key=key, name=name_field_name))
|
||||
if not entry:
|
||||
raise ValueError(('Values for %s must not be empty strings' % error_str).format(key=key))
|
||||
result.append((key, entry))
|
||||
elif isinstance(value, six.string_types):
|
||||
if not value:
|
||||
raise ValueError(('Value for %s must not be an empty string' % error_str).format(key=key))
|
||||
result.append((key, value))
|
||||
else:
|
||||
raise TypeError(('Value for %s must be either a string or a list of strings' % error_str).format(key=key))
|
||||
return result
|
||||
|
||||
|
||||
def parse_ordered_name_field(input_list, name_field_name):
|
||||
"""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]))
|
||||
for index, entry in enumerate(input_list):
|
||||
if len(entry) != 1:
|
||||
raise ValueError(
|
||||
'Entry #{index} in {name} must be a dictionary with exactly one key-value pair'.format(
|
||||
name=name_field_name, index=index + 1))
|
||||
try:
|
||||
result.extend(parse_name_field(entry, name_field_name=name_field_name))
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(
|
||||
'Error while processing entry #{index} in {name}: {error}'.format(
|
||||
name=name_field_name, index=index + 1, error=exc))
|
||||
return result
|
||||
|
||||
|
||||
@@ -322,9 +327,7 @@ def get_relative_time_option(input_string, input_name, backend='cryptography'):
|
||||
elif backend == 'cryptography':
|
||||
return result_datetime
|
||||
# Absolute time
|
||||
if backend == 'pyopenssl':
|
||||
return input_string
|
||||
elif backend == 'cryptography':
|
||||
if 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)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
acme_account_info.py
|
||||
@@ -31,9 +31,8 @@ options:
|
||||
by the ACME server."
|
||||
- "A value of C(ignore) will not fetch the list of orders."
|
||||
- "If the value is not C(ignore) and the ACME server supports orders, the C(order_uris)
|
||||
return value is always populated. The C(orders) return value currently depends on
|
||||
whether this option is set to C(url_list) or C(object_list). In community.crypto 2.0.0,
|
||||
it will only be returned if this option is set to C(object_list)."
|
||||
return value is always populated. The C(orders) return value is only returned
|
||||
if this option is set to C(object_list)."
|
||||
- "Currently, Let's Encrypt does not return orders, so the C(orders) result
|
||||
will always be empty."
|
||||
type: str
|
||||
@@ -125,12 +124,9 @@ account:
|
||||
orders:
|
||||
description:
|
||||
- "The list of orders."
|
||||
- "If I(retrieve_orders) is C(url_list), this will be a list of URLs. In community.crypto 2.0.0,
|
||||
this return value will no longer be returned for C(url_list)."
|
||||
- "If I(retrieve_orders) is C(object_list), this will be a list of objects."
|
||||
type: list
|
||||
#elements: ... depends on retrieve_orders
|
||||
returned: if account exists, I(retrieve_orders) is not C(ignore), and server supports order listing
|
||||
elements: dict
|
||||
returned: if account exists, I(retrieve_orders) is C(object_list), and server supports order listing
|
||||
contains:
|
||||
status:
|
||||
description: The order's status.
|
||||
@@ -282,9 +278,6 @@ 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')
|
||||
backend = create_backend(module, True)
|
||||
|
||||
try:
|
||||
@@ -313,13 +306,6 @@ def main():
|
||||
if account_data.get('orders') and module.params['retrieve_orders'] != 'ignore':
|
||||
orders = get_orders_list(module, client, account_data['orders'])
|
||||
result['order_uris'] = orders
|
||||
if module.params['retrieve_orders'] == 'url_list':
|
||||
module.deprecate(
|
||||
'retrieve_orders=url_list now returns the order URI list as `order_uris`.'
|
||||
' Right now it also returns this list as `orders` for backwards compatibility,'
|
||||
' but this will stop in community.crypto 2.0.0',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
result['orders'] = orders
|
||||
if module.params['retrieve_orders'] == 'object_list':
|
||||
result['orders'] = [get_order(client, order) for order in orders]
|
||||
module.exit_json(**result)
|
||||
|
||||
@@ -580,9 +580,12 @@ class ACMECertificateClient(object):
|
||||
|
||||
if self.module.params['select_chain']:
|
||||
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
|
||||
self.select_chain_matcher.append(
|
||||
self.client.backend.create_chain_matcher(
|
||||
Criterium(criterium, index=criterium_idx)))
|
||||
try:
|
||||
self.select_chain_matcher.append(
|
||||
self.client.backend.create_chain_matcher(
|
||||
Criterium(criterium, index=criterium_idx)))
|
||||
except ValueError as exc:
|
||||
self.module.warn('Error while parsing criterium: {error}. Ignoring criterium.'.format(error=exc))
|
||||
|
||||
# Make sure account exists
|
||||
modify_account = module.params['modify_account']
|
||||
|
||||
@@ -14,10 +14,7 @@ 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
|
||||
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."
|
||||
- The module uses the cryptography Python library.
|
||||
- Support SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) only with python >= 2.7.
|
||||
options:
|
||||
host:
|
||||
@@ -66,19 +63,18 @@ options:
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
choices: [ auto, cryptography ]
|
||||
|
||||
notes:
|
||||
- When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed.
|
||||
|
||||
requirements:
|
||||
- "python >= 2.7 when using C(proxy_host)"
|
||||
- "cryptography >= 1.6 or pyOpenSSL >= 0.15"
|
||||
- "cryptography >= 1.6"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -103,7 +99,13 @@ extensions:
|
||||
asn1_data:
|
||||
returned: success
|
||||
type: str
|
||||
description: The Base64 encoded ASN.1 content of the extnesion.
|
||||
description:
|
||||
- The Base64 encoded ASN.1 content of the extension.
|
||||
- B(Note) that depending on the C(cryptography) version used, it is
|
||||
not possible to extract the ASN.1 content of the extension, but only
|
||||
to provide the re-encoded content of the extension in case it was
|
||||
parsed by C(cryptography). This should usually result in exactly the
|
||||
same value, except if the original extension value was malformed.
|
||||
name:
|
||||
returned: success
|
||||
type: str
|
||||
@@ -180,7 +182,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||
cryptography_get_extensions_from_cert,
|
||||
)
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
|
||||
CREATE_DEFAULT_CONTEXT_IMP_ERR = None
|
||||
@@ -192,17 +193,6 @@ except ImportError:
|
||||
else:
|
||||
HAS_CREATE_DEFAULT_CONTEXT = True
|
||||
|
||||
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
|
||||
@@ -241,7 +231,7 @@ def main():
|
||||
proxy_port=dict(type='int', default=8080),
|
||||
server_name=dict(type='str'),
|
||||
timeout=dict(type='int', default=10),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
starttls=dict(type='str', choices=['mysql']),
|
||||
),
|
||||
)
|
||||
@@ -259,28 +249,17 @@ def main():
|
||||
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)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
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))
|
||||
module.fail_json(msg=("Can't detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_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)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
@@ -343,37 +322,7 @@ def main():
|
||||
|
||||
result['cert'] = cert
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
|
||||
result['subject'] = {}
|
||||
for component in x509.get_subject().get_components():
|
||||
result['subject'][component[0]] = component[1]
|
||||
|
||||
result['expired'] = x509.has_expired()
|
||||
|
||||
result['extensions'] = []
|
||||
extension_count = x509.get_extension_count()
|
||||
for index in range(0, extension_count):
|
||||
extension = x509.get_extension(index)
|
||||
result['extensions'].append({
|
||||
'critical': extension.get_critical(),
|
||||
'asn1_data': extension.get_data(),
|
||||
'name': extension.get_short_name(),
|
||||
})
|
||||
|
||||
result['issuer'] = {}
|
||||
for component in x509.get_issuer().get_components():
|
||||
result['issuer'][component[0]] = component[1]
|
||||
|
||||
result['not_after'] = x509.get_notAfter()
|
||||
result['not_before'] = x509.get_notBefore()
|
||||
|
||||
result['serial_number'] = x509.get_serial_number()
|
||||
result['signature_algorithm'] = x509.get_signature_algorithm()
|
||||
|
||||
result['version'] = x509.get_version()
|
||||
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography_backend())
|
||||
result['subject'] = {}
|
||||
for attribute in x509.subject:
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
x509_certificate.py
|
||||
@@ -1 +0,0 @@
|
||||
x509_certificate_info.py
|
||||
@@ -17,13 +17,9 @@ description:
|
||||
- This module allows one to query information on OpenSSL Certificate Signing Requests (CSR).
|
||||
- In case the CSR signature cannot be validated, the module will fail. In this case, all return
|
||||
variables are still returned.
|
||||
- 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.
|
||||
- It uses the cryptography python library to interact with OpenSSL.
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.3
|
||||
- cryptography >= 1.3
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
- Yanis Guenane (@Spredzy)
|
||||
@@ -42,14 +38,11 @@ options:
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- 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 ]
|
||||
choices: [ auto, cryptography ]
|
||||
|
||||
seealso:
|
||||
- module: community.crypto.openssl_csr
|
||||
@@ -110,7 +103,13 @@ extensions_by_oid:
|
||||
returned: success
|
||||
type: bool
|
||||
value:
|
||||
description: The Base64 encoded value (in DER format) of the extension
|
||||
description:
|
||||
- The Base64 encoded value (in DER format) of the extension.
|
||||
- B(Note) that depending on the C(cryptography) version used, it is
|
||||
not possible to extract the ASN.1 content of the extension, but only
|
||||
to provide the re-encoded content of the extension in case it was
|
||||
parsed by C(cryptography). This should usually result in exactly the
|
||||
same value, except if the original extension value was malformed.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "MAMCAQU="
|
||||
@@ -267,7 +266,7 @@ subject_key_identifier:
|
||||
- The CSR'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
|
||||
returned: success
|
||||
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:
|
||||
@@ -275,14 +274,14 @@ authority_key_identifier:
|
||||
- The CSR'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
|
||||
returned: success
|
||||
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 CSR'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
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
|
||||
@@ -290,7 +289,7 @@ authority_cert_serial_number:
|
||||
description:
|
||||
- The CSR'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
|
||||
returned: success
|
||||
type: int
|
||||
sample: '12345'
|
||||
'''
|
||||
@@ -313,7 +312,7 @@ def main():
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str'),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
|
||||
@@ -117,7 +117,6 @@ filename:
|
||||
fingerprint:
|
||||
description:
|
||||
- The fingerprint of the public key. Fingerprint will be generated for each C(hashlib.algorithms) available.
|
||||
- The PyOpenSSL backend requires PyOpenSSL >= 16.0 for meaningful output.
|
||||
returned: changed or success
|
||||
type: dict
|
||||
sample:
|
||||
|
||||
@@ -19,13 +19,9 @@ description:
|
||||
private key. In this case, all return variables are still returned. Note that key consistency
|
||||
checks are not available all key types; if none is available, C(none) is returned for
|
||||
C(key_is_consistent).
|
||||
- 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.
|
||||
- It uses the cryptography python library to interact with OpenSSL.
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.2.3
|
||||
- cryptography >= 1.2.3
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
- Yanis Guenane (@Spredzy)
|
||||
@@ -49,21 +45,29 @@ options:
|
||||
- Whether to return private key data.
|
||||
- Only set this to C(yes) when you want private information about this key to
|
||||
leave the remote machine.
|
||||
- "WARNING: you have to make sure that private key data isn't accidentally logged!"
|
||||
- "B(WARNING:) you have to make sure that private key data isn't accidentally logged!"
|
||||
type: bool
|
||||
default: no
|
||||
check_consistency:
|
||||
description:
|
||||
- Whether to check consistency of the private key.
|
||||
- In community.crypto < 2.0.0, consistency was always checked.
|
||||
- Since community.crypto 2.0.0, the consistency check has been disabled by default to
|
||||
avoid private key material to be transported around and computed with, and only do
|
||||
so when requested explicitly. This can potentially prevent
|
||||
L(side-channel attacks,https://en.wikipedia.org/wiki/Side-channel_attack).
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 2.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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- 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 ]
|
||||
choices: [ auto, cryptography ]
|
||||
|
||||
notes:
|
||||
- Supports C(check_mode).
|
||||
@@ -102,7 +106,7 @@ key_is_consistent:
|
||||
- Whether the key is consistent. Can also return C(none) next to C(yes) and
|
||||
C(no), to indicate that consistency could not be checked.
|
||||
- In case the check returns C(no), the module will fail.
|
||||
returned: always
|
||||
returned: when I(check_consistency=true)
|
||||
type: bool
|
||||
public_key:
|
||||
description: Private key's public key in PEM format.
|
||||
@@ -215,7 +219,8 @@ def main():
|
||||
content=dict(type='str', no_log=True),
|
||||
passphrase=dict(type='str', no_log=True),
|
||||
return_private_key_data=dict(type='bool', default=False),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
check_consistency=dict(type='bool', default=False),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
@@ -248,7 +253,8 @@ def main():
|
||||
module.params['select_crypto_backend'],
|
||||
data,
|
||||
passphrase=module.params['passphrase'],
|
||||
return_private_key_data=module.params['return_private_key_data'])
|
||||
return_private_key_data=module.params['return_private_key_data'],
|
||||
check_consistency=module.params['check_consistency'])
|
||||
|
||||
try:
|
||||
result.update(module_backend.get_info())
|
||||
|
||||
@@ -97,7 +97,6 @@ curve:
|
||||
fingerprint:
|
||||
description:
|
||||
- The fingerprint of the public key. Fingerprint will be generated for each C(hashlib.algorithms) available.
|
||||
- The PyOpenSSL backend requires PyOpenSSL >= 16.0 for meaningful output.
|
||||
returned: changed or success
|
||||
type: dict
|
||||
sample:
|
||||
|
||||
@@ -15,14 +15,9 @@ 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
|
||||
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."
|
||||
- The module uses the cryptography Python library.
|
||||
requirements:
|
||||
- Either cryptography >= 1.2.3 (older versions might work as well)
|
||||
- Or pyOpenSSL >= 16.0.0
|
||||
- cryptography >= 1.2.3 (older versions might work as well)
|
||||
- Needs cryptography >= 1.4 if I(format) is C(OpenSSH)
|
||||
author:
|
||||
- Yanis Guenane (@Spredzy)
|
||||
@@ -76,12 +71,11 @@ options:
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
choices: [ auto, cryptography ]
|
||||
return_content:
|
||||
description:
|
||||
- If set to C(yes), will return the (current or generated) public key's content as I(publickey).
|
||||
@@ -157,7 +151,6 @@ filename:
|
||||
fingerprint:
|
||||
description:
|
||||
- The fingerprint of the public key. Fingerprint will be generated for each hashlib.algorithms available.
|
||||
- Requires PyOpenSSL >= 16.0 for meaningful output.
|
||||
returned: changed or success
|
||||
type: dict
|
||||
sample:
|
||||
@@ -208,21 +201,9 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
get_publickey_info,
|
||||
)
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '16.0.0'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH = '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
|
||||
@@ -300,11 +281,6 @@ class PublicKey(OpenSSLObject):
|
||||
crypto_serialization.Encoding.PEM,
|
||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
else:
|
||||
try:
|
||||
return crypto.dump_publickey(crypto.FILETYPE_PEM, self.privatekey)
|
||||
except AttributeError as dummy:
|
||||
raise PublicKeyError('You need to have PyOpenSSL>=16.0.0 to generate public keys')
|
||||
|
||||
def generate(self, module):
|
||||
"""Generate the public key."""
|
||||
@@ -372,11 +348,6 @@ class PublicKey(OpenSSLObject):
|
||||
crypto_serialization.Encoding.PEM,
|
||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
else:
|
||||
publickey_content = crypto.dump_publickey(
|
||||
crypto.FILETYPE_PEM,
|
||||
crypto.load_publickey(crypto.FILETYPE_PEM, publickey_content)
|
||||
)
|
||||
except Exception as dummy:
|
||||
return False
|
||||
|
||||
@@ -434,7 +405,7 @@ def main():
|
||||
format=dict(type='str', default='PEM', choices=['OpenSSH', 'PEM']),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
backup=dict(type='bool', default=False),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
return_content=dict(type='bool', default=False),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
@@ -453,36 +424,20 @@ def main():
|
||||
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:
|
||||
if module.params['format'] == 'OpenSSH':
|
||||
module.fail_json(
|
||||
msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR
|
||||
)
|
||||
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))
|
||||
module.fail_json(msg=("Can't detect the required Python library "
|
||||
"cryptography (>= {0})").format(minimal_cryptography_version))
|
||||
|
||||
if module.params['format'] == 'OpenSSH' and backend != 'cryptography':
|
||||
module.fail_json(msg="Format OpenSSH requires the cryptography backend.")
|
||||
|
||||
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')
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(minimal_cryptography_version)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -14,14 +14,10 @@ module: openssl_publickey_info
|
||||
short_description: Provide information for OpenSSL public keys
|
||||
description:
|
||||
- This module allows one to query information on OpenSSL public keys.
|
||||
- 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.
|
||||
- It uses the cryptography python library to interact with OpenSSL.
|
||||
version_added: 1.7.0
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.2.3
|
||||
- cryptography >= 1.2.3
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
options:
|
||||
@@ -38,14 +34,11 @@ options:
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- 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 ]
|
||||
choices: [ auto, cryptography ]
|
||||
|
||||
notes:
|
||||
- Supports C(check_mode).
|
||||
@@ -174,7 +167,7 @@ def main():
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str', no_log=True),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
|
||||
@@ -15,13 +15,9 @@ 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.
|
||||
- The module uses the cryptography Python library.
|
||||
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)
|
||||
- cryptography >= 1.4 (some key types require newer versions)
|
||||
author:
|
||||
- Patrick Pichler (@aveexy)
|
||||
- Markus Teufelberger (@MarkusTeufelberger)
|
||||
@@ -50,12 +46,11 @@ options:
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
choices: [ auto, cryptography ]
|
||||
notes:
|
||||
- |
|
||||
When using the C(cryptography) backend, the following key types require at least the following C(cryptography) version:
|
||||
@@ -99,20 +94,8 @@ 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
|
||||
@@ -170,34 +153,6 @@ class SignatureBase(OpenSSLObject):
|
||||
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):
|
||||
|
||||
@@ -262,7 +217,7 @@ def main():
|
||||
privatekey_content=dict(type='str', no_log=True),
|
||||
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'),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
@@ -283,29 +238,17 @@ def main():
|
||||
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))
|
||||
module.fail_json(msg=("Can't detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_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 backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -15,13 +15,9 @@ version_added: 1.1.0
|
||||
short_description: Verify signatures with openssl
|
||||
description:
|
||||
- This module allows one to verify a signature for a file by 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.
|
||||
- The module uses the cryptography Python library.
|
||||
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)
|
||||
- cryptography >= 1.4 (some key types require newer versions)
|
||||
author:
|
||||
- Patrick Pichler (@aveexy)
|
||||
- Markus Teufelberger (@MarkusTeufelberger)
|
||||
@@ -49,12 +45,11 @@ options:
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
choices: [ auto, cryptography ]
|
||||
notes:
|
||||
- |
|
||||
When using the C(cryptography) backend, the following key types require at least the following C(cryptography) version:
|
||||
@@ -99,20 +94,8 @@ 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
|
||||
@@ -170,37 +153,6 @@ class SignatureInfoBase(OpenSSLObject):
|
||||
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):
|
||||
|
||||
@@ -295,7 +247,7 @@ def main():
|
||||
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'),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['certificate_path', 'certificate_content'],
|
||||
@@ -316,29 +268,17 @@ def main():
|
||||
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))
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_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 backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -14,7 +14,7 @@ DOCUMENTATION = r'''
|
||||
module: x509_certificate
|
||||
short_description: Generate and/or check OpenSSL certificates
|
||||
description:
|
||||
- It implements a notion of provider (ie. C(selfsigned), C(ownca), C(acme), C(assertonly), C(entrust))
|
||||
- It implements a notion of provider (one of C(selfsigned), C(ownca), C(acme), and C(entrust))
|
||||
for your certificate.
|
||||
- "Please note that the module regenerates existing certificate if it does not match the module's
|
||||
options, or if it seems to be corrupt. If you are concerned that this could overwrite
|
||||
@@ -47,8 +47,6 @@ options:
|
||||
provider:
|
||||
description:
|
||||
- Name of the provider to use to generate/retrieve the OpenSSL certificate.
|
||||
- The C(assertonly) provider will not generate files and fail if the certificate file is missing.
|
||||
- The C(assertonly) provider has been deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0.
|
||||
Please see the examples on how to emulate it with
|
||||
M(community.crypto.x509_certificate_info), M(community.crypto.openssl_csr_info),
|
||||
M(community.crypto.openssl_privatekey_info) and M(ansible.builtin.assert).
|
||||
@@ -56,7 +54,7 @@ options:
|
||||
L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API."
|
||||
- Required if I(state) is C(present).
|
||||
type: str
|
||||
choices: [ acme, assertonly, entrust, ownca, selfsigned ]
|
||||
choices: [ acme, entrust, ownca, selfsigned ]
|
||||
|
||||
return_content:
|
||||
description:
|
||||
@@ -69,9 +67,6 @@ options:
|
||||
description:
|
||||
- Create a backup file including a timestamp so you can get the original
|
||||
certificate back if you overwrote it with a new one by accident.
|
||||
- This is not used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
@@ -96,7 +91,6 @@ extends_documentation_fragment:
|
||||
- ansible.builtin.files
|
||||
- community.crypto.module_certificate
|
||||
- community.crypto.module_certificate.backend_acme_documentation
|
||||
- community.crypto.module_certificate.backend_assertonly_documentation
|
||||
- community.crypto.module_certificate.backend_entrust_documentation
|
||||
- community.crypto.module_certificate.backend_ownca_documentation
|
||||
- community.crypto.module_certificate.backend_selfsigned_documentation
|
||||
@@ -150,40 +144,9 @@ EXAMPLES = r'''
|
||||
entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-key.crt
|
||||
entrust_api_specification_path: /etc/ssl/entrust/api-docs/cms-api-2.1.0.yaml
|
||||
|
||||
# The following example shows one assertonly usage using all existing options for
|
||||
# assertonly, and shows how to emulate the behavior with the x509_certificate_info,
|
||||
# openssl_csr_info, openssl_privatekey_info and assert modules:
|
||||
- name: Usage of assertonly with all existing options
|
||||
community.crypto.x509_certificate:
|
||||
provider: assertonly
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
csr_path: /etc/ssl/csr/ansible.com.csr
|
||||
privatekey_path: /etc/ssl/csr/ansible.com.key
|
||||
signature_algorithms:
|
||||
- sha256WithRSAEncryption
|
||||
- sha512WithRSAEncryption
|
||||
subject:
|
||||
commonName: ansible.com
|
||||
subject_strict: yes
|
||||
issuer:
|
||||
commonName: ansible.com
|
||||
issuer_strict: yes
|
||||
has_expired: no
|
||||
version: 3
|
||||
key_usage:
|
||||
- Data Encipherment
|
||||
key_usage_strict: yes
|
||||
extended_key_usage:
|
||||
- DVCS
|
||||
extended_key_usage_strict: yes
|
||||
subject_alt_name:
|
||||
- dns:ansible.com
|
||||
subject_alt_name_strict: yes
|
||||
not_before: 20190331202428Z
|
||||
not_after: 20190413202428Z
|
||||
valid_at: "+1d10h"
|
||||
invalid_at: 20200331202428Z
|
||||
valid_in: 10 # in ten seconds
|
||||
# The following example shows how to emulate the behavior of the removed
|
||||
# "assertonly" provider with the x509_certificate_info, openssl_csr_info,
|
||||
# openssl_privatekey_info and assert modules:
|
||||
|
||||
- name: Get certificate information
|
||||
community.crypto.x509_certificate_info:
|
||||
@@ -208,9 +171,9 @@ EXAMPLES = r'''
|
||||
|
||||
- assert:
|
||||
that:
|
||||
# When private key is specified for assertonly, this will be checked:
|
||||
# When private key was specified for assertonly, this was checked:
|
||||
- result.public_key == result_privatekey.public_key
|
||||
# When CSR is specified for assertonly, this will be checked:
|
||||
# When CSR was specified for assertonly, this was checked:
|
||||
- result.public_key == result_csr.public_key
|
||||
- result.subject_ordered == result_csr.subject_ordered
|
||||
- result.extensions_by_oid == result_csr.extensions_by_oid
|
||||
@@ -242,103 +205,6 @@ EXAMPLES = r'''
|
||||
- "result.valid_at.one_day_ten_hours" # for valid_at
|
||||
- "not result.valid_at.fixed_timestamp" # for invalid_at
|
||||
- "result.valid_at.ten_seconds" # for valid_in
|
||||
|
||||
# Examples for some checks one could use the assertonly provider for:
|
||||
# (Please note that assertonly has been deprecated!)
|
||||
|
||||
# How to use the assertonly provider to implement and trigger your own custom certificate generation workflow:
|
||||
- name: Check if a certificate is currently still valid, ignoring failures
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
has_expired: no
|
||||
ignore_errors: yes
|
||||
register: validity_check
|
||||
|
||||
- name: Run custom task(s) to get a new, valid certificate in case the initial check failed
|
||||
command: superspecialSSL recreate /etc/ssl/crt/example.com.crt
|
||||
when: validity_check.failed
|
||||
|
||||
- name: Check the new certificate again for validity with the same parameters, this time failing the play if it is still invalid
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
has_expired: no
|
||||
when: validity_check.failed
|
||||
|
||||
# Some other checks that assertonly could be used for:
|
||||
- name: Verify that an existing certificate was issued by the Let's Encrypt CA and is currently still valid
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
issuer:
|
||||
O: Let's Encrypt
|
||||
has_expired: no
|
||||
|
||||
- name: Ensure that a certificate uses a modern signature algorithm (no SHA1, MD5 or DSA)
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
signature_algorithms:
|
||||
- sha224WithRSAEncryption
|
||||
- sha256WithRSAEncryption
|
||||
- sha384WithRSAEncryption
|
||||
- sha512WithRSAEncryption
|
||||
- sha224WithECDSAEncryption
|
||||
- sha256WithECDSAEncryption
|
||||
- sha384WithECDSAEncryption
|
||||
- sha512WithECDSAEncryption
|
||||
|
||||
- name: Ensure that the existing certificate belongs to the specified private key
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
privatekey_path: /etc/ssl/private/example.com.pem
|
||||
provider: assertonly
|
||||
|
||||
- name: Ensure that the existing certificate is still valid at the winter solstice 2017
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
valid_at: 20171221162800Z
|
||||
|
||||
- name: Ensure that the existing certificate is still valid 2 weeks (1209600 seconds) from now
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
valid_in: 1209600
|
||||
|
||||
- name: Ensure that the existing certificate is only used for digital signatures and encrypting other keys
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
key_usage:
|
||||
- digitalSignature
|
||||
- keyEncipherment
|
||||
key_usage_strict: true
|
||||
|
||||
- name: Ensure that the existing certificate can be used for client authentication
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
extended_key_usage:
|
||||
- clientAuth
|
||||
|
||||
- name: Ensure that the existing certificate can only be used for client authentication and time stamping
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
extended_key_usage:
|
||||
- clientAuth
|
||||
- 1.3.6.1.5.5.7.3.8
|
||||
extended_key_usage_strict: true
|
||||
|
||||
- name: Ensure that the existing certificate has a certain domain in its subjectAltName
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/example.com.crt
|
||||
provider: assertonly
|
||||
subject_alt_name:
|
||||
- www.example.com
|
||||
- test.example.com
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
@@ -374,11 +240,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
add_acme_provider_to_argument_spec,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_assertonly import (
|
||||
AssertOnlyCertificateProvider,
|
||||
add_assertonly_provider_to_argument_spec,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_entrust import (
|
||||
EntrustCertificateProvider,
|
||||
add_entrust_provider_to_argument_spec,
|
||||
@@ -495,7 +356,6 @@ class GenericCertificate(OpenSSLObject):
|
||||
def main():
|
||||
argument_spec = get_certificate_argument_spec()
|
||||
add_acme_provider_to_argument_spec(argument_spec)
|
||||
add_assertonly_provider_to_argument_spec(argument_spec)
|
||||
add_entrust_provider_to_argument_spec(argument_spec)
|
||||
add_ownca_provider_to_argument_spec(argument_spec)
|
||||
add_selfsigned_provider_to_argument_spec(argument_spec)
|
||||
@@ -511,10 +371,6 @@ def main():
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if module._name == 'community.crypto.openssl_certificate':
|
||||
module.deprecate("The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
try:
|
||||
if module.params['state'] == 'absent':
|
||||
certificate = CertificateAbsent(module)
|
||||
@@ -537,7 +393,6 @@ def main():
|
||||
provider = module.params['provider']
|
||||
provider_map = {
|
||||
'acme': AcmeCertificateProvider,
|
||||
'assertonly': AssertOnlyCertificateProvider,
|
||||
'entrust': EntrustCertificateProvider,
|
||||
'ownca': OwnCACertificateProvider,
|
||||
'selfsigned': SelfSignedCertificateProvider,
|
||||
|
||||
@@ -15,11 +15,7 @@ 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.
|
||||
- It uses the cryptography python library to interact with OpenSSL.
|
||||
- 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
|
||||
@@ -29,7 +25,7 @@ description:
|
||||
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
|
||||
- cryptography >= 1.6
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
- Yanis Guenane (@Spredzy)
|
||||
@@ -60,14 +56,11 @@ options:
|
||||
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.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- 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 ]
|
||||
choices: [ auto, cryptography ]
|
||||
|
||||
notes:
|
||||
- All timestamp values are provided in ASN.1 TIME format, in other words, following the C(YYYYMMDDHHMMSSZ) pattern.
|
||||
@@ -154,7 +147,13 @@ extensions_by_oid:
|
||||
returned: success
|
||||
type: bool
|
||||
value:
|
||||
description: The Base64 encoded value (in DER format) of the extension.
|
||||
description:
|
||||
- The Base64 encoded value (in DER format) of the extension.
|
||||
- B(Note) that depending on the C(cryptography) version used, it is
|
||||
not possible to extract the ASN.1 content of the extension, but only
|
||||
to provide the re-encoded content of the extension in case it was
|
||||
parsed by C(cryptography). This should usually result in exactly the
|
||||
same value, except if the original extension value was malformed.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "MAMCAQU="
|
||||
@@ -341,7 +340,7 @@ subject_key_identifier:
|
||||
- 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
|
||||
returned: success
|
||||
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:
|
||||
@@ -349,14 +348,14 @@ authority_key_identifier:
|
||||
- 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
|
||||
returned: success
|
||||
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
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
|
||||
@@ -364,7 +363,7 @@ 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
|
||||
returned: success
|
||||
type: int
|
||||
sample: '12345'
|
||||
ocsp_uri:
|
||||
@@ -398,7 +397,7 @@ def main():
|
||||
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']),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
@@ -408,9 +407,6 @@ def main():
|
||||
),
|
||||
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')
|
||||
|
||||
if module.params['content'] is not None:
|
||||
data = module.params['content'].encode('utf-8')
|
||||
|
||||
@@ -92,8 +92,21 @@ options:
|
||||
description:
|
||||
- Key/value pairs that will be present in the issuer name field of the CRL.
|
||||
- If you need to specify more than one value with the same key, use a list as value.
|
||||
- Required if I(state) is C(present).
|
||||
- If the order of the components is important, use I(issuer_ordered).
|
||||
- One of I(issuer) and I(issuer_ordered) is required if I(state) is C(present).
|
||||
- Mutually exclusive with I(issuer_ordered).
|
||||
type: dict
|
||||
issuer_ordered:
|
||||
description:
|
||||
- A list of dictionaries, where every dictionary must contain one key/value pair.
|
||||
This key/value pair will be present in the issuer name field of the CRL.
|
||||
- If you want to specify more than one value with the same key in a row, you can
|
||||
use a list as value.
|
||||
- One of I(issuer) and I(issuer_ordered) is required if I(state) is C(present).
|
||||
- Mutually exclusive with I(issuer).
|
||||
type: list
|
||||
elements: dict
|
||||
version_added: 2.0.0
|
||||
|
||||
last_update:
|
||||
description:
|
||||
@@ -386,6 +399,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
||||
load_privatekey,
|
||||
load_certificate,
|
||||
parse_name_field,
|
||||
parse_ordered_name_field,
|
||||
get_relative_time_option,
|
||||
select_message_digest,
|
||||
)
|
||||
@@ -462,8 +476,15 @@ 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 = [(entry[0], entry[1]) for entry in self.issuer if entry[1]]
|
||||
try:
|
||||
if module.params['issuer_ordered']:
|
||||
self.issuer_ordered = True
|
||||
self.issuer = parse_ordered_name_field(module.params['issuer_ordered'], 'issuer_ordered')
|
||||
else:
|
||||
self.issuer_ordered = False
|
||||
self.issuer = parse_name_field(module.params['issuer'], 'issuer')
|
||||
except (TypeError, ValueError) as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
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')
|
||||
@@ -616,7 +637,11 @@ class CRL(OpenSSLObject):
|
||||
return False
|
||||
|
||||
want_issuer = [(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]:
|
||||
is_issuer = [(sub.oid, sub.value) for sub in self.crl.issuer]
|
||||
if not self.issuer_ordered:
|
||||
want_issuer = set(want_issuer)
|
||||
is_issuer = set(is_issuer)
|
||||
if want_issuer != is_issuer:
|
||||
return False
|
||||
|
||||
old_entries = [self._compress_entry(cryptography_decode_revoked_certificate(cert)) for cert in self.crl]
|
||||
@@ -782,6 +807,7 @@ def main():
|
||||
privatekey_content=dict(type='str', no_log=True),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
issuer=dict(type='dict'),
|
||||
issuer_ordered=dict(type='list', elements='dict'),
|
||||
last_update=dict(type='str', default='+0s'),
|
||||
next_update=dict(type='str'),
|
||||
digest=dict(type='str', default='sha256'),
|
||||
@@ -815,10 +841,12 @@ def main():
|
||||
),
|
||||
required_if=[
|
||||
('state', 'present', ['privatekey_path', 'privatekey_content'], True),
|
||||
('state', 'present', ['issuer', 'next_update', 'revoked_certificates'], False),
|
||||
('state', 'present', ['issuer', 'issuer_ordered'], True),
|
||||
('state', 'present', ['next_update', 'revoked_certificates'], False),
|
||||
],
|
||||
mutually_exclusive=(
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
['issuer', 'issuer_ordered'],
|
||||
),
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
|
||||
@@ -167,21 +167,17 @@
|
||||
assert:
|
||||
that:
|
||||
- "'account' in account_orders_urls"
|
||||
- "'orders' in account_orders_urls"
|
||||
- "account_orders_urls.orders[0] is string"
|
||||
- "'orders' not in account_orders_urls"
|
||||
- "'order_uris' in account_orders_urls"
|
||||
- "account_orders_urls.order_uris[0] is string"
|
||||
- "account_orders_urls.order_uris == account_orders_urls.orders"
|
||||
|
||||
- name: Validate that orders were retrieved as list of URLs (2/2)
|
||||
assert:
|
||||
that:
|
||||
- "'account' in account_orders_urls2"
|
||||
- "'orders' in account_orders_urls2"
|
||||
- "account_orders_urls2.orders[0] is string"
|
||||
- "'orders' not in account_orders_urls2"
|
||||
- "'order_uris' in account_orders_urls2"
|
||||
- "account_orders_urls2.order_uris[0] is string"
|
||||
- "account_orders_urls2.order_uris == account_orders_urls2.orders"
|
||||
|
||||
- name: Validate that orders were retrieved as list of objects (1/2)
|
||||
assert:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- prepare_http_tests
|
||||
|
||||
@@ -30,17 +30,7 @@
|
||||
that:
|
||||
- result is success or skip_tests
|
||||
|
||||
when: |
|
||||
pyopenssl_version.stdout is version('0.15', '>=') or
|
||||
cryptography_version.stdout is version('1.6', '>=')
|
||||
|
||||
- block:
|
||||
|
||||
- include_tasks: ../tests/validate.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=') and not skip_tests
|
||||
when: cryptography_version.stdout is version('1.6', '>=')
|
||||
|
||||
- block:
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/csr.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
subject:
|
||||
commonName: www.ansible.com
|
||||
subject_ordered:
|
||||
- commonName: www.ansible.com
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
return_content: yes
|
||||
register: generate_csr_idempotent
|
||||
@@ -363,7 +363,6 @@
|
||||
commonName: www.ansible.com
|
||||
subject_key_identifier: "00:11:22:33"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: subject_key_identifier_1
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (idempotency)"
|
||||
@@ -374,7 +373,6 @@
|
||||
commonName: www.ansible.com
|
||||
subject_key_identifier: "00:11:22:33"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: subject_key_identifier_2
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (change)"
|
||||
@@ -385,7 +383,6 @@
|
||||
commonName: www.ansible.com
|
||||
subject_key_identifier: "44:55:66:77:88"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: subject_key_identifier_3
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (auto-create)"
|
||||
@@ -396,7 +393,6 @@
|
||||
commonName: www.ansible.com
|
||||
create_subject_key_identifier: yes
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: subject_key_identifier_4
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (auto-create idempotency)"
|
||||
@@ -407,7 +403,6 @@
|
||||
commonName: www.ansible.com
|
||||
create_subject_key_identifier: yes
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: subject_key_identifier_5
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (remove)"
|
||||
@@ -417,7 +412,6 @@
|
||||
subject:
|
||||
commonName: www.ansible.com
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: subject_key_identifier_6
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority key identifier"
|
||||
@@ -428,7 +422,6 @@
|
||||
commonName: www.ansible.com
|
||||
authority_key_identifier: "00:11:22:33"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_key_identifier_1
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority key identifier (idempotency)"
|
||||
@@ -439,7 +432,6 @@
|
||||
commonName: www.ansible.com
|
||||
authority_key_identifier: "00:11:22:33"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_key_identifier_2
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority key identifier (change)"
|
||||
@@ -450,7 +442,6 @@
|
||||
commonName: www.ansible.com
|
||||
authority_key_identifier: "44:55:66:77:88"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_key_identifier_3
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority key identifier (remove)"
|
||||
@@ -460,7 +451,6 @@
|
||||
subject:
|
||||
commonName: www.ansible.com
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_key_identifier_4
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number"
|
||||
@@ -474,7 +464,6 @@
|
||||
- "IP:1.2.3.4"
|
||||
authority_cert_serial_number: 12345
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_cert_issuer_sn_1
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number (idempotency)"
|
||||
@@ -488,7 +477,6 @@
|
||||
- "IP:1.2.3.4"
|
||||
authority_cert_serial_number: 12345
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_cert_issuer_sn_2
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number (change issuer)"
|
||||
@@ -502,7 +490,6 @@
|
||||
- "DNS:ca.example.org"
|
||||
authority_cert_serial_number: 12345
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_cert_issuer_sn_3
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number (change serial number)"
|
||||
@@ -516,7 +503,6 @@
|
||||
- "DNS:ca.example.org"
|
||||
authority_cert_serial_number: 54321
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_cert_issuer_sn_4
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number (remove)"
|
||||
@@ -525,30 +511,29 @@
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
subject:
|
||||
commonName: www.ansible.com
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: authority_cert_issuer_sn_5
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with everything"
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/csr_everything.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
subject:
|
||||
commonName: www.example.com
|
||||
C: de
|
||||
L: Somewhere
|
||||
ST: Zürich
|
||||
streetAddress: Welcome Street N° 5
|
||||
O: Ansiblé
|
||||
organizationalUnitName: Crÿpto Depârtment ☺
|
||||
serialNumber: "1234"
|
||||
SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr.
|
||||
GN: First Name
|
||||
title: Chïeff
|
||||
pseudonym: test
|
||||
UID: asdf
|
||||
emailAddress: test@example.com
|
||||
postalAddress: 1234 Somewhere
|
||||
postalCode: "1234"
|
||||
subject_ordered:
|
||||
- commonName: www.example.com
|
||||
- C: de
|
||||
- L: Somewhere
|
||||
- ST: Zürich
|
||||
- streetAddress: Welcome Street N° 5
|
||||
- O: Ansiblé
|
||||
- organizationalUnitName: Crÿpto Depârtment ☺
|
||||
- serialNumber: "1234"
|
||||
- SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr.
|
||||
- GN: First Name
|
||||
- title: Chïeff
|
||||
- pseudonym: test
|
||||
- UID: asdf
|
||||
- emailAddress: test@example.com
|
||||
- postalAddress: 1234 Somewhere
|
||||
- postalCode: "1234"
|
||||
useCommonNameForSAN: no
|
||||
key_usage:
|
||||
- digitalSignature
|
||||
@@ -561,37 +546,24 @@
|
||||
- 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: '{{ value_for_extended_key_usage }}'
|
||||
subject_alt_name: '{{ value_for_san }}'
|
||||
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_permitted: '{{ value_for_name_constraints_permitted }}'
|
||||
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 }}'
|
||||
authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
subject_key_identifier: 00:11:22:33
|
||||
authority_key_identifier: 44:55:66:77
|
||||
authority_cert_issuer: '{{ value_for_authority_cert_issuer }}'
|
||||
authority_cert_serial_number: 12345
|
||||
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
|
||||
@@ -609,13 +581,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"
|
||||
@@ -625,38 +590,35 @@
|
||||
- "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"
|
||||
- "dirName:CN = example.net, O = Example Net"
|
||||
- "dirName:CN=example.com,O=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: "({{ select_crypto_backend }}) Generate CSR with everything (idempotent, check mode)"
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/csr_everything.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
subject:
|
||||
CN: www.example.com
|
||||
countryName: de
|
||||
L: Somewhere
|
||||
ST: Zürich
|
||||
streetAddress: Welcome Street N° 5
|
||||
organizationName: Ansiblé
|
||||
organizationalUnitName: Crÿpto Depârtment ☺
|
||||
serialNumber: "1234"
|
||||
SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr.
|
||||
GN: First Name
|
||||
title: Chïeff
|
||||
pseudonym: test
|
||||
UID: asdf
|
||||
emailAddress: test@example.com
|
||||
postalAddress: 1234 Somewhere
|
||||
postalCode: "1234"
|
||||
subject_ordered:
|
||||
- CN: www.example.com
|
||||
- countryName: de
|
||||
- L: Somewhere
|
||||
- ST: Zürich
|
||||
- streetAddress: Welcome Street N° 5
|
||||
- organizationName: Ansiblé
|
||||
- organizationalUnitName: Crÿpto Depârtment ☺
|
||||
- serialNumber: "1234"
|
||||
- SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr.
|
||||
- GN: First Name
|
||||
- title: Chïeff
|
||||
- pseudonym: test
|
||||
- UID: asdf
|
||||
- emailAddress: test@example.com
|
||||
- postalAddress: 1234 Somewhere
|
||||
- postalCode: "1234"
|
||||
useCommonNameForSAN: no
|
||||
key_usage:
|
||||
- digitalSignature
|
||||
@@ -669,37 +631,24 @@
|
||||
- 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: '{{ value_for_extended_key_usage }}'
|
||||
subject_alt_name: '{{ value_for_san }}'
|
||||
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_permitted: '{{ value_for_name_constraints_permitted }}'
|
||||
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 }}'
|
||||
authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
subject_key_identifier: 00:11:22:33
|
||||
authority_key_identifier: 44:55:66:77
|
||||
authority_cert_issuer: '{{ value_for_authority_cert_issuer }}'
|
||||
authority_cert_serial_number: 12345
|
||||
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
|
||||
@@ -717,13 +666,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"
|
||||
@@ -733,15 +675,12 @@
|
||||
- "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"
|
||||
- "dirName:CN=example.net,O=Example Net"
|
||||
- "dirName:CN = example.com,O = 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
|
||||
|
||||
@@ -750,18 +689,19 @@
|
||||
path: '{{ remote_tmp_dir }}/csr_everything.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
subject:
|
||||
# Subject has been reordered, but is inside 'subject' and not 'subject_ordered'
|
||||
CN: www.example.com
|
||||
countryName: de
|
||||
L: Somewhere
|
||||
countryName: de
|
||||
ST: Zürich
|
||||
streetAddress: Welcome Street N° 5
|
||||
organizationName: Ansiblé
|
||||
organizationalUnitName: Crÿpto Depârtment ☺
|
||||
organizationName: Ansiblé
|
||||
serialNumber: "1234"
|
||||
SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr.
|
||||
GN: First Name
|
||||
title: Chïeff
|
||||
pseudonym: test
|
||||
title: Chïeff
|
||||
UID: asdf
|
||||
emailAddress: test@example.com
|
||||
postalAddress: 1234 Somewhere
|
||||
@@ -778,37 +718,24 @@
|
||||
- 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: '{{ value_for_extended_key_usage }}'
|
||||
subject_alt_name: '{{ value_for_san }}'
|
||||
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_permitted: '{{ value_for_name_constraints_permitted }}'
|
||||
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 }}'
|
||||
authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
subject_key_identifier: 00:11:22:33
|
||||
authority_key_identifier: 44:55:66:77
|
||||
authority_cert_issuer: '{{ value_for_authority_cert_issuer }}'
|
||||
authority_cert_serial_number: 12345
|
||||
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
|
||||
@@ -826,13 +753,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"
|
||||
@@ -842,16 +762,100 @@
|
||||
- "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"
|
||||
- "dirName:CN= example.net, O =Example Net"
|
||||
- "dirName:/CN= example.com/O =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:
|
||||
register: everything_3
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate CSR with everything (not idempotent, check mode)"
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/csr_everything.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
subject_ordered:
|
||||
# Subject has been reordered, this should force a change
|
||||
- CN: www.example.com
|
||||
- L: Somewhere
|
||||
- countryName: de
|
||||
- ST: Zürich
|
||||
- streetAddress: Welcome Street N° 5
|
||||
- organizationalUnitName: Crÿpto Depârtment ☺
|
||||
- organizationName: Ansiblé
|
||||
- serialNumber: "1234"
|
||||
- SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr.
|
||||
- GN: First Name
|
||||
- pseudonym: test
|
||||
- title: Chïeff
|
||||
- UID: asdf
|
||||
- emailAddress: test@example.com
|
||||
- postalAddress: 1234 Somewhere
|
||||
- postalCode: "1234"
|
||||
useCommonNameForSAN: no
|
||||
key_usage:
|
||||
- digitalSignature
|
||||
- keyAgreement
|
||||
- Non Repudiation
|
||||
- Key Encipherment
|
||||
- dataEncipherment
|
||||
- Certificate Sign
|
||||
- cRLSign
|
||||
- Encipher Only
|
||||
- decipherOnly
|
||||
key_usage_critical: yes
|
||||
extended_key_usage: '{{ value_for_extended_key_usage }}'
|
||||
subject_alt_name: '{{ value_for_san }}'
|
||||
basic_constraints:
|
||||
- "CA:TRUE"
|
||||
- "pathlen:23"
|
||||
basic_constraints_critical: yes
|
||||
name_constraints_permitted: '{{ value_for_name_constraints_permitted }}'
|
||||
name_constraints_excluded:
|
||||
- "DNS:.org"
|
||||
- "DNS:.example.com"
|
||||
name_constraints_critical: yes
|
||||
ocsp_must_staple: yes
|
||||
subject_key_identifier: 00:11:22:33
|
||||
authority_key_identifier: 44:55:66:77
|
||||
authority_cert_issuer: '{{ value_for_authority_cert_issuer }}'
|
||||
authority_cert_serial_number: 12345
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
vars:
|
||||
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:
|
||||
- "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:CN= example.net, O =Example Net"
|
||||
- "dirName:/CN= example.com/O =Example Com"
|
||||
value_for_name_constraints_permitted:
|
||||
- "DNS:www.example.com"
|
||||
- "IP:1.2.3.0/255.255.255.0"
|
||||
register: everything_3
|
||||
- "IP:0::0:1:0:0/112"
|
||||
register: everything_4
|
||||
check_mode: true
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Get info from CSR with everything"
|
||||
community.crypto.openssl_csr_info:
|
||||
|
||||
@@ -4,40 +4,18 @@
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
- name: Prepare private key for backend autodetection test
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekey_backend_selection.pem'
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
- name: Run module with backend autodetection
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/csr_backend_selection.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey_backend_selection.pem'
|
||||
subject:
|
||||
commonName: www.ansible.com
|
||||
|
||||
- block:
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
- name: Prepare private key for backend autodetection test
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekey_backend_selection.pem'
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
- name: Run module with backend autodetection
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/csr_backend_selection.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey_backend_selection.pem'
|
||||
subject:
|
||||
commonName: www.ansible.com
|
||||
|
||||
- import_tasks: ../tests/validate.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=')
|
||||
|
||||
- name: Remove output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Re-create output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: directory
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
|
||||
@@ -74,13 +74,13 @@
|
||||
- "'Subject Alternative Name' in generate_csr_invalid_san.msg"
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Validate invalid SAN (2/2)"
|
||||
# Note that pyOpenSSL simply accepts this name, and modern cryptography versions do so as well.
|
||||
# Note that modern cryptography versions simply accept this name.
|
||||
# The error has been observed with cryptography 1.7.2 and 1.9, but not with 2.3 and newer.
|
||||
assert:
|
||||
that:
|
||||
- generate_csr_invalid_san_2 is failed
|
||||
- "'The label system:kube-controller-manager is not a valid A-label' in generate_csr_invalid_san_2.msg"
|
||||
when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.0', '<')
|
||||
when: cryptography_version.stdout is version('2.0', '<')
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Validate OCSP Must Staple CSR (test - everything)"
|
||||
shell: "{{ openssl_binary }} req -noout -in {{ remote_tmp_dir }}/csr_ocsp.csr -text"
|
||||
@@ -156,7 +156,6 @@
|
||||
- subject_key_identifier_4 is changed
|
||||
- subject_key_identifier_5 is not changed
|
||||
- subject_key_identifier_6 is changed
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Verify that authority key identifier handling works"
|
||||
assert:
|
||||
@@ -165,7 +164,6 @@
|
||||
- authority_key_identifier_2 is not changed
|
||||
- authority_key_identifier_3 is changed
|
||||
- authority_key_identifier_4 is changed
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Verify that authority cert issuer / serial number handling works"
|
||||
assert:
|
||||
@@ -175,7 +173,6 @@
|
||||
- authority_cert_issuer_sn_3 is changed
|
||||
- authority_cert_issuer_sn_4 is changed
|
||||
- authority_cert_issuer_sn_5 is changed
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Check backup"
|
||||
assert:
|
||||
@@ -198,6 +195,7 @@
|
||||
- everything_1 is changed
|
||||
- everything_2 is not changed
|
||||
- everything_3 is not changed
|
||||
- everything_4 is changed
|
||||
- everything_info.basic_constraints == [
|
||||
"CA:TRUE",
|
||||
"pathlen:23",
|
||||
@@ -236,6 +234,25 @@
|
||||
- everything_info.subject.title == "Chïeff"
|
||||
- everything_info.subject.userId == "asdf"
|
||||
- everything_info.subject | length == 16
|
||||
- >
|
||||
everything_info.subject_ordered == [
|
||||
["commonName", "www.example.com"],
|
||||
["countryName", "de"],
|
||||
["localityName", "Somewhere"],
|
||||
["stateOrProvinceName", "Zürich"],
|
||||
["streetAddress", "Welcome Street N° 5"],
|
||||
["organizationName", "Ansiblé"],
|
||||
["organizationalUnitName", "Crÿpto Depârtment ☺"],
|
||||
["serialNumber", "1234"],
|
||||
["surname", "Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr."],
|
||||
["givenName", "First Name"],
|
||||
["title", "Chïeff"],
|
||||
["pseudonym", "test"],
|
||||
["userId", "asdf"],
|
||||
["emailAddress", "test@example.com"],
|
||||
["postalAddress", "1234 Somewhere"],
|
||||
["postalCode", "1234"],
|
||||
]
|
||||
- everything_info.subject_alt_name_critical == false
|
||||
- everything_info.name_constraints_excluded == [
|
||||
"DNS:.example.com",
|
||||
@@ -243,38 +260,7 @@
|
||||
]
|
||||
- everything_info.name_constraints_critical == true
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Check CSR with everything (pyOpenSSL specific)"
|
||||
assert:
|
||||
that:
|
||||
- everything_info.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",
|
||||
"RID:1.2.3.4",
|
||||
]
|
||||
- everything_info.extended_key_usage == [
|
||||
"Any Extended Key Usage",
|
||||
"Biometric Info",
|
||||
"Code Signing",
|
||||
"E-mail Protection",
|
||||
"IPSec User",
|
||||
"OCSP Signing",
|
||||
"TLS Web Client Authentication",
|
||||
"TLS Web Server Authentication",
|
||||
"TLS Web Server Authentication",
|
||||
"Time Stamping",
|
||||
"dvcs",
|
||||
"qcStatements",
|
||||
]
|
||||
- everything_info.name_constraints_permitted == [
|
||||
"DNS:www.example.com",
|
||||
"IP:1.2.3.0/24",
|
||||
]
|
||||
when: select_crypto_backend == 'pyopenssl'
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Check CSR with everything (non-pyOpenSSL specific)"
|
||||
- name: "({{ select_crypto_backend }}) Check CSR with everything"
|
||||
assert:
|
||||
that:
|
||||
- everything_info.authority_cert_issuer == [
|
||||
@@ -292,8 +278,8 @@
|
||||
"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;0c:0d:62:6f:62:40:6c:6f:63:61:6c:68:6f:73:74",
|
||||
"dirName:/O=Example Net/CN=example.net",
|
||||
"dirName:/O=Example Com/CN=example.com"
|
||||
"dirName:CN=example.net,O=Example Net",
|
||||
"dirName:CN=example.com,O=Example Com"
|
||||
]
|
||||
- everything_info.subject_key_identifier == "00:11:22:33"
|
||||
- everything_info.extended_key_usage == [
|
||||
@@ -316,7 +302,6 @@
|
||||
"IP:1.2.3.0/24",
|
||||
"IP:::1:0:0/112",
|
||||
]
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.6, < 2.8)"
|
||||
assert:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
- prepare_jinja2_compat
|
||||
|
||||
@@ -28,11 +28,7 @@
|
||||
expected_authority_cert_issuer:
|
||||
- "DNS:ca.example.org"
|
||||
- "IP:1.2.3.4"
|
||||
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Update result list"
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
when: cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Read CSR"
|
||||
slurp:
|
||||
@@ -56,10 +52,6 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Update result list"
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Get CSR info"
|
||||
openssl_csr_info:
|
||||
path: '{{ remote_tmp_dir }}/csr_3.csr'
|
||||
@@ -76,11 +68,7 @@
|
||||
expected_authority_cert_issuer:
|
||||
- "DNS:ca.example.org"
|
||||
- "IP:1.2.3.4"
|
||||
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Update result list"
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
when: cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Get CSR info"
|
||||
openssl_csr_info:
|
||||
@@ -94,8 +82,4 @@
|
||||
- result.authority_key_identifier == "44:55:66:77"
|
||||
- result.authority_cert_issuer is none
|
||||
- result.authority_cert_serial_number is none
|
||||
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Update result list"
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
when: cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
@@ -119,50 +119,8 @@
|
||||
useCommonNameForSAN: no
|
||||
authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
info_results: []
|
||||
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=')
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
pyopenssl_info_results: "{{ info_results }}"
|
||||
info_results: []
|
||||
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: cryptography
|
||||
when: cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
cryptography_info_results: "{{ info_results }}"
|
||||
|
||||
- block:
|
||||
- name: Dump pyOpenSSL results
|
||||
debug:
|
||||
var: pyopenssl_info_results
|
||||
- name: Dump cryptography results
|
||||
debug:
|
||||
var: cryptography_info_results
|
||||
- name: Compare results
|
||||
assert:
|
||||
that:
|
||||
- ' (item.0 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)
|
||||
== (item.1 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)'
|
||||
quiet: yes
|
||||
loop: "{{ pyopenssl_info_results | zip(cryptography_info_results) | list }}"
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=') and cryptography_version.stdout is version('1.3', '>=')
|
||||
vars:
|
||||
keys_to_ignore:
|
||||
- deprecations
|
||||
- subject_key_identifier
|
||||
- authority_key_identifier
|
||||
- authority_cert_issuer
|
||||
- authority_cert_serial_number
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -14,24 +14,6 @@
|
||||
subject:
|
||||
commonName: www.ansible.com
|
||||
|
||||
- block:
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=')
|
||||
|
||||
- name: Remove output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Re-create output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: directory
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekey5.pem'
|
||||
passphrase: ansible
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekey5.pem'
|
||||
passphrase: ansible
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: privatekey5_idempotence
|
||||
@@ -84,13 +84,10 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekey6.pem'
|
||||
passphrase: ànsïblé
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- set_fact:
|
||||
ecc_types: []
|
||||
when: select_crypto_backend == 'pyopenssl'
|
||||
- set_fact:
|
||||
ecc_types:
|
||||
- curve: secp384r1
|
||||
@@ -150,7 +147,6 @@
|
||||
- curve: sect163r2
|
||||
openssl_name: sect163r2
|
||||
min_cryptography_version: "0.5"
|
||||
when: select_crypto_backend == 'cryptography'
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Test ECC key generation"
|
||||
openssl_privatekey:
|
||||
@@ -221,7 +217,7 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
@@ -231,7 +227,7 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
@@ -257,7 +253,7 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
@@ -278,7 +274,7 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
@@ -289,7 +285,7 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
@@ -551,7 +547,7 @@
|
||||
type: RSA
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
cipher: auto
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
loop: "{{ regenerate_values }}"
|
||||
- name: "({{ select_crypto_backend }}) Regenerate - setup broken keys"
|
||||
|
||||
@@ -36,29 +36,6 @@
|
||||
path: '{{ remote_tmp_dir }}/privatekey_backend_selection.pem'
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
|
||||
- block:
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
- import_tasks: ../tests/validate.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
# FIXME: minimal pyOpenSSL version?!
|
||||
when: pyopenssl_version.stdout is version('0.6', '>=')
|
||||
|
||||
- name: Remove output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Re-create output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: directory
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
@@ -70,45 +47,3 @@
|
||||
select_crypto_backend: cryptography
|
||||
|
||||
when: cryptography_version.stdout is version('0.5', '>=')
|
||||
|
||||
- name: Check that fingerprints do not depend on the backend
|
||||
block:
|
||||
- name: "Fingerprint comparison: pyOpenSSL"
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/fingerprint-{{ item }}.pem'
|
||||
type: "{{ item }}"
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: pyopenssl
|
||||
loop:
|
||||
- RSA
|
||||
- DSA
|
||||
register: fingerprint_pyopenssl
|
||||
|
||||
- name: "Fingerprint comparison: cryptography"
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/fingerprint-{{ item }}.pem'
|
||||
type: "{{ item }}"
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
select_crypto_backend: cryptography
|
||||
loop:
|
||||
- RSA
|
||||
- DSA
|
||||
register: fingerprint_cryptography
|
||||
|
||||
- name: Verify that keys were not regenerated
|
||||
assert:
|
||||
that:
|
||||
- fingerprint_cryptography is not changed
|
||||
|
||||
- name: Verify that fingerprints match
|
||||
assert:
|
||||
that: item.0.fingerprint[item.2] == item.1.fingerprint[item.2]
|
||||
when: item.0 is not skipped and item.1 is not skipped
|
||||
loop: |
|
||||
{{ query('nested',
|
||||
fingerprint_pyopenssl.results | zip(fingerprint_cryptography.results),
|
||||
fingerprint_pyopenssl.results[0].fingerprint.keys()
|
||||
) if fingerprint_pyopenssl.results[0].fingerprint else [] }}
|
||||
loop_control:
|
||||
label: "{{ [item.0.item, item.2] }}"
|
||||
when: pyopenssl_version.stdout is version('0.6', '>=') and cryptography_version.stdout is version('0.5', '>=')
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
- prepare_jinja2_compat
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
- "result.public_data.exponent > 5"
|
||||
- "'private_data' not in result"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key1': result}) }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Read private key
|
||||
slurp:
|
||||
src: '{{ remote_tmp_dir }}/privatekey_1.pem'
|
||||
@@ -62,10 +58,6 @@
|
||||
- "result.public_data.modulus == result.private_data.p * result.private_data.q"
|
||||
- "result.private_data.exponent > 5"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key2': result}) }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get key 3 info (without passphrase)
|
||||
openssl_privatekey_info:
|
||||
path: '{{ remote_tmp_dir }}/privatekey_3.pem'
|
||||
@@ -113,10 +105,6 @@
|
||||
- "result.public_data.modulus == result.private_data.p * result.private_data.q"
|
||||
- "result.private_data.exponent > 5"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key3': result}) }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get key 4 info
|
||||
openssl_privatekey_info:
|
||||
path: '{{ remote_tmp_dir }}/privatekey_4.pem'
|
||||
@@ -124,37 +112,20 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
|
||||
- block:
|
||||
- name: Check that ECC key info is ok
|
||||
assert:
|
||||
that:
|
||||
- "'public_key' in result"
|
||||
- "'public_key_fingerprints' in result"
|
||||
- "'type' in result"
|
||||
- "result.type == 'ECC'"
|
||||
- "'public_data' in result"
|
||||
- "result.public_data.curve is string"
|
||||
- "result.public_data.x != 0"
|
||||
- "result.public_data.y != 0"
|
||||
- "result.public_data.exponent_size == (521 if (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') else 256)"
|
||||
- "'private_data' in result"
|
||||
- "result.private_data.multiplier > 1024"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key4': result}) }}"
|
||||
when: select_crypto_backend != 'pyopenssl' or (pyopenssl_version.stdout is version('16.1.0', '>=') and cryptography_version.stdout is version('0.0', '>'))
|
||||
|
||||
- name: Check that ECC key info is ok
|
||||
assert:
|
||||
that:
|
||||
- "'public_key' in result"
|
||||
- "'public_key_fingerprints' in result"
|
||||
- "'type' in result"
|
||||
- "result.type.startswith('unknown ')"
|
||||
- "result.type == 'ECC'"
|
||||
- "'public_data' in result"
|
||||
- "result.public_data.curve is string"
|
||||
- "result.public_data.x != 0"
|
||||
- "result.public_data.y != 0"
|
||||
- "result.public_data.exponent_size == (521 if (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') else 256)"
|
||||
- "'private_data' in result"
|
||||
when: select_crypto_backend == 'pyopenssl' and not (pyopenssl_version.stdout is version('16.1.0', '>=') and cryptography_version.stdout is version('0.0', '>'))
|
||||
- "result.private_data.multiplier > 1024"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get key 5 info
|
||||
openssl_privatekey_info:
|
||||
@@ -177,7 +148,3 @@
|
||||
- "result.public_data.y > 2"
|
||||
- "'private_data' in result"
|
||||
- "result.private_data.x > 2"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key5': result}) }}"
|
||||
|
||||
@@ -36,42 +36,8 @@
|
||||
type: DSA
|
||||
size: 1024
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
info_results: {}
|
||||
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=')
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
pyopenssl_info_results: "{{ info_results }}"
|
||||
info_results: {}
|
||||
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: cryptography
|
||||
when: cryptography_version.stdout is version('1.2.3', '>=')
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
cryptography_info_results: "{{ info_results }}"
|
||||
|
||||
- block:
|
||||
- name: Dump pyOpenSSL results
|
||||
debug:
|
||||
var: pyopenssl_info_results
|
||||
- name: Dump cryptography results
|
||||
debug:
|
||||
var: cryptography_info_results
|
||||
- name: Compare results
|
||||
assert:
|
||||
that:
|
||||
- ' (pyopenssl_info_results[item] | dict2items | rejectattr("key", "equalto", "deprecations") | list | items2dict)
|
||||
== (cryptography_info_results[item] | dict2items | rejectattr("key", "equalto", "deprecations") | list | items2dict)'
|
||||
loop: "{{ pyopenssl_info_results.keys() | intersect(cryptography_info_results.keys()) | list }}"
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=') and cryptography_version.stdout is version('1.2.3', '>=')
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -8,25 +8,6 @@
|
||||
openssl_privatekey_pipe:
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
|
||||
- block:
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
# FIXME: minimal pyOpenSSL version?!
|
||||
when: pyopenssl_version.stdout is version('0.6', '>=')
|
||||
|
||||
- name: Remove output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Re-create output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: directory
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekey3.pem'
|
||||
passphrase: ansible
|
||||
cipher: aes256
|
||||
cipher: auto
|
||||
size: '{{ default_rsa_key_size }}'
|
||||
|
||||
- name: "({{ select_crypto_backend }}) Generate publickey3 - with passphrase protected privatekey"
|
||||
|
||||
@@ -15,33 +15,6 @@
|
||||
path: '{{ remote_tmp_dir }}/privatekey_autodetect_public.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey_autodetect.pem'
|
||||
|
||||
when: |
|
||||
pyopenssl_version.stdout is version('16.0.0', '>=') or
|
||||
cryptography_version.stdout is version('1.2.3', '>=')
|
||||
|
||||
- block:
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
- import_tasks: ../tests/validate.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
when: pyopenssl_version.stdout is version('16.0.0', '>=')
|
||||
|
||||
- name: Remove output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Re-create output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: directory
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
- prepare_jinja2_compat
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
- "2 ** (result.public_data.size - 1) < result.public_data.modulus < 2 ** result.public_data.size"
|
||||
- "result.public_data.exponent > 5"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key1': result}) }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Read file
|
||||
slurp:
|
||||
src: '{{ remote_tmp_dir }}/publickey_1.pem'
|
||||
@@ -55,42 +51,23 @@
|
||||
- "2 ** (result.public_data.size - 1) < result.public_data.modulus < 2 ** result.public_data.size"
|
||||
- "result.public_data.exponent > 5"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key2': result}) }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get key 3 info
|
||||
openssl_publickey_info:
|
||||
path: '{{ remote_tmp_dir }}/publickey_3.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
|
||||
- block:
|
||||
- name: Check that ECC key info is ok
|
||||
assert:
|
||||
that:
|
||||
- "'fingerprints' in result"
|
||||
- "'type' in result"
|
||||
- "result.type == 'ECC'"
|
||||
- "'public_data' in result"
|
||||
- "result.public_data.curve is string"
|
||||
- "result.public_data.x != 0"
|
||||
- "result.public_data.y != 0"
|
||||
- "result.public_data.exponent_size == (521 if (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') else 256)"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key3': result}) }}"
|
||||
when: select_crypto_backend != 'pyopenssl' or (pyopenssl_version.stdout is version('16.1.0', '>=') and cryptography_version.stdout is version('0.0', '>'))
|
||||
|
||||
- name: Check that ECC key info is ok
|
||||
assert:
|
||||
that:
|
||||
- "'fingerprints' in result"
|
||||
- "'type' in result"
|
||||
- "result.type.startswith('unknown ')"
|
||||
- "result.type == 'ECC'"
|
||||
- "'public_data' in result"
|
||||
when: select_crypto_backend == 'pyopenssl' and not (pyopenssl_version.stdout is version('16.1.0', '>=') and cryptography_version.stdout is version('0.0', '>'))
|
||||
- "result.public_data.curve is string"
|
||||
- "result.public_data.x != 0"
|
||||
- "result.public_data.y != 0"
|
||||
- "result.public_data.exponent_size == (521 if (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') else 256)"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get key 4 info
|
||||
openssl_publickey_info:
|
||||
@@ -109,7 +86,3 @@
|
||||
- "result.public_data.q > 2"
|
||||
- "result.public_data.g >= 2"
|
||||
- "result.public_data.y > 2"
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results | combine({'key4': result}) }}"
|
||||
|
||||
@@ -42,38 +42,8 @@
|
||||
set_fact:
|
||||
info_results: {}
|
||||
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
when: pyopenssl_version.stdout is version('16.0.0', '>=')
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
pyopenssl_info_results: "{{ info_results }}"
|
||||
info_results: {}
|
||||
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: cryptography
|
||||
when: cryptography_version.stdout is version('1.2.3', '>=')
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
cryptography_info_results: "{{ info_results }}"
|
||||
|
||||
- block:
|
||||
- name: Dump pyOpenSSL results
|
||||
debug:
|
||||
var: pyopenssl_info_results
|
||||
- name: Dump cryptography results
|
||||
debug:
|
||||
var: cryptography_info_results
|
||||
- name: Compare results
|
||||
assert:
|
||||
that:
|
||||
- ' (pyopenssl_info_results[item] | dict2items | rejectattr("key", "equalto", "deprecations") | list | items2dict)
|
||||
== (cryptography_info_results[item] | dict2items | rejectattr("key", "equalto", "deprecations") | list | items2dict)'
|
||||
loop: "{{ pyopenssl_info_results.keys() | intersect(cryptography_info_results.keys()) | list }}"
|
||||
when: pyopenssl_version.stdout is version('16.0.0', '>=') and cryptography_version.stdout is version('1.2.3', '>=')
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
####################################################################
|
||||
|
||||
# Test matrix:
|
||||
# * pyopenssl or cryptography
|
||||
# * cryptography
|
||||
# * DSA or ECC or ...
|
||||
# * password protected private key or not
|
||||
|
||||
@@ -25,11 +25,6 @@
|
||||
backends: "{{ backends + [ { 'backend': 'cryptography' } ] }}"
|
||||
when: cryptography_version.stdout is version('1.4', '>=')
|
||||
|
||||
- name: Add pyopenssl backend
|
||||
set_fact:
|
||||
backends: "{{ backends + [ { 'backend': 'pyopenssl' } ] }}"
|
||||
when: pyopenssl_version.stdout is version('0.11', '>=')
|
||||
|
||||
- name: Add RSA tests
|
||||
set_fact:
|
||||
key_types: "{{ key_types + [ { 'type': 'RSA', 'size': default_rsa_key_size } ] }}"
|
||||
@@ -58,14 +53,11 @@
|
||||
all_tests: >-
|
||||
[
|
||||
{% for b in backends %}
|
||||
{% for kt in key_types %}
|
||||
{% for kp in key_password %}
|
||||
{# Exclude Ed25519 and Ed448 tests on pyopenssl #}
|
||||
{% if not (b.backend == 'pyopenssl' and (kt.type == 'Ed25519' or kt.type == 'Ed448')) %}
|
||||
{{ b | combine (kt) | combine(kp) }},
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% for kt in key_types %}
|
||||
{% for kp in key_password %}
|
||||
{{ b | combine (kt) | combine(kp) }},
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
---
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Generate privatekey
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
size: '{{ default_rsa_key_size_certifiates }}'
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Generate privatekey with password
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
passphrase: hunter2
|
||||
cipher: auto
|
||||
select_crypto_backend: cryptography
|
||||
size: '{{ default_rsa_key_size_certifiates }}'
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Generate CSR (no extensions)
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/csr_noext.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
subject:
|
||||
commonName: www.example.com
|
||||
useCommonNameForSAN: no
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Generate CSR (with SANs)
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/csr_sans.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
subject:
|
||||
commonName: www.example.com
|
||||
subject_alt_name:
|
||||
- "DNS:ansible.com"
|
||||
- "IP:127.0.0.1"
|
||||
- "IP:::1"
|
||||
useCommonNameForSAN: no
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (no extensions)
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_noext.pem'
|
||||
csr_path: '{{ remote_tmp_dir }}/csr_noext.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
provider: selfsigned
|
||||
selfsigned_digest: sha256
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (with SANs)
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_sans.pem'
|
||||
csr_path: '{{ remote_tmp_dir }}/csr_sans.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
provider: selfsigned
|
||||
selfsigned_digest: sha256
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (should fail)
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_noext.pem'
|
||||
provider: assertonly
|
||||
subject_alt_name:
|
||||
- "DNS:example.com"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
ignore_errors: yes
|
||||
register: extension_missing_san
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_sans.pem'
|
||||
provider: assertonly
|
||||
subject_alt_name:
|
||||
- "DNS:ansible.com"
|
||||
- "IP:127.0.0.1"
|
||||
- "IP:::1"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: extension_san
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (strict)
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_sans.pem'
|
||||
provider: assertonly
|
||||
subject_alt_name:
|
||||
- "DNS:ansible.com"
|
||||
- "IP:127.0.0.1"
|
||||
- "IP:::1"
|
||||
subject_alt_name_strict: yes
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: extension_san_strict
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that key_usage is there (should fail)
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_noext.pem'
|
||||
provider: assertonly
|
||||
key_usage:
|
||||
- digitalSignature
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
ignore_errors: yes
|
||||
register: extension_missing_ku
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that extended_key_usage is there (should fail)
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_noext.pem'
|
||||
provider: assertonly
|
||||
extended_key_usage:
|
||||
- biometricInfo
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
ignore_errors: yes
|
||||
register: extension_missing_eku
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- extension_missing_san is failed
|
||||
- "'Found no subjectAltName extension' in extension_missing_san.msg"
|
||||
- extension_san is succeeded
|
||||
- extension_san_strict is succeeded
|
||||
- extension_missing_ku is failed
|
||||
- "'Found no keyUsage extension' in extension_missing_ku.msg"
|
||||
- extension_missing_eku is failed
|
||||
- "'Found no extendedKeyUsage extension' in extension_missing_eku.msg"
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Check wrong key fail
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_noext.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
privatekey_passphrase: hunter2
|
||||
provider: assertonly
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
ignore_errors: yes
|
||||
register: private_key_error
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 1
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_noext.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
privatekey_passphrase: hunter2
|
||||
provider: assertonly
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
ignore_errors: yes
|
||||
register: passphrase_error_1
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 2
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_noext.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
privatekey_passphrase: wrong_password
|
||||
provider: assertonly
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
ignore_errors: yes
|
||||
register: passphrase_error_2
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 3
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/cert_noext.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekeypw.pem'
|
||||
provider: assertonly
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
ignore_errors: yes
|
||||
register: passphrase_error_3
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) -
|
||||
assert:
|
||||
that:
|
||||
- private_key_error is failed
|
||||
- "'Certificate and private key ' in private_key_error.msg and ' do not match' in private_key_error.msg"
|
||||
- passphrase_error_1 is failed
|
||||
- "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_1.msg"
|
||||
- passphrase_error_2 is failed
|
||||
- "'assphrase' in passphrase_error_2.msg or 'assword' in passphrase_error_2.msg or 'serializ' in passphrase_error_2.msg"
|
||||
- passphrase_error_3 is failed
|
||||
- "'assphrase' in passphrase_error_3.msg or 'assword' in passphrase_error_3.msg or 'serializ' in passphrase_error_3.msg"
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
- name: (Expired, {{select_crypto_backend}}) Generate privatekey
|
||||
openssl_privatekey:
|
||||
path: '{{ remote_tmp_dir }}/has_expired_privatekey.pem'
|
||||
size: '{{ default_rsa_key_size_certifiates }}'
|
||||
|
||||
- name: (Expired, {{select_crypto_backend}}) Generate CSR
|
||||
openssl_csr:
|
||||
path: '{{ remote_tmp_dir }}/has_expired_csr.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/has_expired_privatekey.pem'
|
||||
subject:
|
||||
commonName: www.example.com
|
||||
|
||||
- name: (Expired, {{select_crypto_backend}}) Generate expired selfsigned certificate
|
||||
x509_certificate:
|
||||
path: '{{ remote_tmp_dir }}/has_expired_cert.pem'
|
||||
csr_path: '{{ remote_tmp_dir }}/has_expired_csr.csr'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/has_expired_privatekey.pem'
|
||||
provider: selfsigned
|
||||
selfsigned_digest: sha256
|
||||
selfsigned_not_after: "-1s"
|
||||
selfsigned_not_before: "-100s"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend == 'pyopenssl' # cryptography won't allow creating expired certificates
|
||||
|
||||
- name: (Expired, {{select_crypto_backend}}) Generate expired selfsigned certificate
|
||||
command: "{{ openssl_binary }} x509 -req -days -1 -in {{ remote_tmp_dir }}/has_expired_csr.csr -signkey {{ remote_tmp_dir }}/has_expired_privatekey.pem -out {{ remote_tmp_dir }}/has_expired_cert.pem"
|
||||
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:
|
||||
provider: assertonly
|
||||
path: "{{ remote_tmp_dir }}/has_expired_cert.pem"
|
||||
has_expired: false
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
ignore_errors: true
|
||||
register: expired_cert_check
|
||||
|
||||
- name: (Expired, {{select_crypto_backend}}) Ensure previous task failed
|
||||
assert:
|
||||
that: expired_cert_check is failed
|
||||
|
||||
- name: "(Expired) Check expired cert check is ignored (has_expired: true)"
|
||||
x509_certificate:
|
||||
provider: assertonly
|
||||
path: "{{ remote_tmp_dir }}/has_expired_cert.pem"
|
||||
has_expired: true
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: expired_cert_skip
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "Executing tests with backend {{ select_crypto_backend }}"
|
||||
- import_tasks: assertonly.yml
|
||||
- import_tasks: expired.yml
|
||||
- import_tasks: selfsigned.yml
|
||||
- import_tasks: ownca.yml
|
||||
- import_tasks: removal.yml
|
||||
|
||||
@@ -4,22 +4,6 @@
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=')
|
||||
|
||||
- name: Remove output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Re-create output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: directory
|
||||
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
|
||||
@@ -110,21 +110,27 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
check_mode: yes
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate
|
||||
x509_certificate:
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Get certificate information
|
||||
community.crypto.x509_certificate_info:
|
||||
path: '{{ remote_tmp_dir }}/ownca_cert.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
provider: assertonly
|
||||
has_expired: False
|
||||
version: 3
|
||||
signature_algorithms:
|
||||
- sha256WithRSAEncryption
|
||||
- sha256WithECDSAEncryption
|
||||
subject:
|
||||
commonName: www.example.com
|
||||
issuer:
|
||||
commonName: Example CA
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Get private key information
|
||||
community.crypto.openssl_privatekey_info:
|
||||
path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result_privatekey
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate
|
||||
assert:
|
||||
that:
|
||||
- result.public_key == result_privatekey.public_key
|
||||
- "result.signature_algorithm == 'sha256WithRSAEncryption' or result.signature_algorithm == 'sha256WithECDSAEncryption'"
|
||||
- "result.subject.commonName == 'www.example.com'"
|
||||
- "result.issuer.commonName == 'Example CA'"
|
||||
- not result.expired
|
||||
- result.version == 3
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca v2 certificate
|
||||
x509_certificate:
|
||||
@@ -151,33 +157,35 @@
|
||||
ownca_digest: sha256
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate2
|
||||
x509_certificate:
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Get certificate information
|
||||
community.crypto.x509_certificate_info:
|
||||
path: '{{ remote_tmp_dir }}/ownca_cert2.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey2.pem'
|
||||
provider: assertonly
|
||||
has_expired: False
|
||||
version: 3
|
||||
signature_algorithms:
|
||||
- sha256WithRSAEncryption
|
||||
- sha256WithECDSAEncryption
|
||||
subject:
|
||||
commonName: www.example.com
|
||||
C: US
|
||||
ST: California
|
||||
L: Los Angeles
|
||||
O: ACME Inc.
|
||||
OU:
|
||||
- Roadrunner pest control
|
||||
- Pyrotechnics
|
||||
keyUsage:
|
||||
- digitalSignature
|
||||
extendedKeyUsage:
|
||||
- ipsecUser
|
||||
- biometricInfo
|
||||
issuer:
|
||||
commonName: Example CA
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Get private key information
|
||||
community.crypto.openssl_privatekey_info:
|
||||
path: '{{ remote_tmp_dir }}/privatekey2.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result_privatekey
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate2
|
||||
assert:
|
||||
that:
|
||||
- result.public_key == result_privatekey.public_key
|
||||
- "result.signature_algorithm == 'sha256WithRSAEncryption' or result.signature_algorithm == 'sha256WithECDSAEncryption'"
|
||||
- "result.subject.commonName == 'www.example.com'"
|
||||
- "result.subject.countryName == 'US'"
|
||||
- "result.subject.localityName == 'Los Angeles'" # L
|
||||
- "result.subject.organizationName == 'ACME Inc.'"
|
||||
- "['organizationalUnitName', 'Pyrotechnics'] in result.subject_ordered"
|
||||
- "['organizationalUnitName', 'Roadrunner pest control'] in result.subject_ordered"
|
||||
- "result.issuer.commonName == 'Example CA'"
|
||||
- not result.expired
|
||||
- result.version == 3
|
||||
- "'Digital Signature' in result.key_usage"
|
||||
- "'IPSec User' in result.extended_key_usage"
|
||||
- "'Biometric Info' in result.extended_key_usage"
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with notBefore and notAfter
|
||||
x509_certificate:
|
||||
@@ -340,7 +348,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_subject_key_identifier: always_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_subject_key_identifier_1
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (idempotency)
|
||||
@@ -353,7 +360,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_subject_key_identifier: always_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_subject_key_identifier_2
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove)
|
||||
@@ -366,7 +372,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_subject_key_identifier: never_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_subject_key_identifier_3
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove idempotency)
|
||||
@@ -379,7 +384,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_subject_key_identifier: never_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_subject_key_identifier_4
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (re-enable)
|
||||
@@ -392,7 +396,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_subject_key_identifier: always_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_subject_key_identifier_5
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier
|
||||
@@ -405,7 +408,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_authority_key_identifier: yes
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_authority_key_identifier_1
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (idempotency)
|
||||
@@ -418,7 +420,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_authority_key_identifier: yes
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_authority_key_identifier_2
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove)
|
||||
@@ -431,7 +432,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_authority_key_identifier: no
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_authority_key_identifier_3
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove idempotency)
|
||||
@@ -444,7 +444,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_authority_key_identifier: no
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_authority_key_identifier_4
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (re-add)
|
||||
@@ -457,7 +456,6 @@
|
||||
ownca_digest: sha256
|
||||
ownca_create_authority_key_identifier: yes
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: ownca_authority_key_identifier_5
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Ed25519 and Ed448 tests (for cryptography >= 2.6)
|
||||
|
||||
@@ -99,19 +99,26 @@
|
||||
check_mode: yes
|
||||
register: selfsigned_certificate_csr_minimal_change
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate
|
||||
x509_certificate:
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Get certificate information
|
||||
community.crypto.x509_certificate_info:
|
||||
path: '{{ remote_tmp_dir }}/cert.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
provider: assertonly
|
||||
has_expired: False
|
||||
version: 3
|
||||
signature_algorithms:
|
||||
- sha256WithRSAEncryption
|
||||
- sha256WithECDSAEncryption
|
||||
subject:
|
||||
commonName: www.example.com
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Get private key information
|
||||
community.crypto.openssl_privatekey_info:
|
||||
path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result_privatekey
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate
|
||||
assert:
|
||||
that:
|
||||
- result.public_key == result_privatekey.public_key
|
||||
- "result.signature_algorithm == 'sha256WithRSAEncryption' or result.signature_algorithm == 'sha256WithECDSAEncryption'"
|
||||
- "result.subject.commonName == 'www.example.com'"
|
||||
- not result.expired
|
||||
- result.version == 3
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned v2 certificate
|
||||
x509_certificate:
|
||||
@@ -158,31 +165,34 @@
|
||||
selfsigned_digest: sha256
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate2
|
||||
x509_certificate:
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Get certificate information
|
||||
community.crypto.x509_certificate_info:
|
||||
path: '{{ remote_tmp_dir }}/cert2.pem'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey2.pem'
|
||||
provider: assertonly
|
||||
has_expired: False
|
||||
version: 3
|
||||
signature_algorithms:
|
||||
- sha256WithRSAEncryption
|
||||
- sha256WithECDSAEncryption
|
||||
subject:
|
||||
commonName: www.example.com
|
||||
C: US
|
||||
ST: California
|
||||
L: Los Angeles
|
||||
O: ACME Inc.
|
||||
OU:
|
||||
- Roadrunner pest control
|
||||
- Pyrotechnics
|
||||
keyUsage:
|
||||
- digitalSignature
|
||||
extendedKeyUsage:
|
||||
- ipsecUser
|
||||
- biometricInfo
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Get private key information
|
||||
community.crypto.openssl_privatekey_info:
|
||||
path: '{{ remote_tmp_dir }}/privatekey2.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result_privatekey
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate2
|
||||
assert:
|
||||
that:
|
||||
- result.public_key == result_privatekey.public_key
|
||||
- "result.signature_algorithm == 'sha256WithRSAEncryption' or result.signature_algorithm == 'sha256WithECDSAEncryption'"
|
||||
- "result.subject.commonName == 'www.example.com'"
|
||||
- "result.subject.countryName == 'US'"
|
||||
- "result.subject.localityName == 'Los Angeles'" # L
|
||||
- "result.subject.organizationName == 'ACME Inc.'"
|
||||
- "['organizationalUnitName', 'Pyrotechnics'] in result.subject_ordered"
|
||||
- "['organizationalUnitName', 'Roadrunner pest control'] in result.subject_ordered"
|
||||
- not result.expired
|
||||
- result.version == 3
|
||||
- "'Digital Signature' in result.key_usage"
|
||||
- "'IPSec User' in result.extended_key_usage"
|
||||
- "'Biometric Info' in result.extended_key_usage"
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create private key 3
|
||||
openssl_privatekey:
|
||||
@@ -353,7 +363,6 @@
|
||||
selfsigned_digest: sha256
|
||||
selfsigned_create_subject_key_identifier: always_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: selfsigned_subject_key_identifier_1
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (idempotency)
|
||||
@@ -365,7 +374,6 @@
|
||||
selfsigned_digest: sha256
|
||||
selfsigned_create_subject_key_identifier: always_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: selfsigned_subject_key_identifier_2
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove)
|
||||
@@ -377,7 +385,6 @@
|
||||
selfsigned_digest: sha256
|
||||
selfsigned_create_subject_key_identifier: never_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: selfsigned_subject_key_identifier_3
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove idempotency)
|
||||
@@ -389,7 +396,6 @@
|
||||
selfsigned_digest: sha256
|
||||
selfsigned_create_subject_key_identifier: never_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: selfsigned_subject_key_identifier_4
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (re-enable)
|
||||
@@ -401,7 +407,6 @@
|
||||
selfsigned_digest: sha256
|
||||
selfsigned_create_subject_key_identifier: always_create
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
register: selfsigned_subject_key_identifier_5
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Ed25519 and Ed448 tests (for cryptography >= 2.6)
|
||||
|
||||
@@ -140,7 +140,6 @@
|
||||
- ownca_subject_key_identifier_3 is changed
|
||||
- ownca_subject_key_identifier_4 is not changed
|
||||
- ownca_subject_key_identifier_5 is changed
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
|
||||
- name: (OwnCA validation, {{select_crypto_backend}}) Check create authority key identifier
|
||||
assert:
|
||||
@@ -150,7 +149,6 @@
|
||||
- ownca_authority_key_identifier_3 is changed
|
||||
- ownca_authority_key_identifier_4 is not changed
|
||||
- ownca_authority_key_identifier_5 is changed
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
|
||||
- name: (OwnCA validation, {{select_crypto_backend}}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.6, < 2.8)
|
||||
assert:
|
||||
|
||||
@@ -185,7 +185,6 @@
|
||||
- selfsigned_subject_key_identifier_3 is changed
|
||||
- selfsigned_subject_key_identifier_4 is not changed
|
||||
- selfsigned_subject_key_identifier_5 is changed
|
||||
when: select_crypto_backend != 'pyopenssl'
|
||||
|
||||
- name: (Selfsigned validation, {{select_crypto_backend}}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.6, < 2.8)
|
||||
assert:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
- prepare_jinja2_compat
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
|
||||
- result.public_key_type == 'RSA'
|
||||
- result.public_key_data.size == (default_rsa_key_size_certifiates | int)
|
||||
- "result.subject_alt_name == [
|
||||
'DNS:www.ansible.com',
|
||||
'DNS:' ~ ('öç' if cryptography_version.stdout is version('2.1', '<') else 'xn--74h') ~ '.com',
|
||||
'IP:1.2.3.4',
|
||||
'IP:::1',
|
||||
'email:test@example.org',
|
||||
'URI:https://example.org/test/index.html'
|
||||
]"
|
||||
|
||||
- name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier
|
||||
assert:
|
||||
@@ -31,11 +39,7 @@
|
||||
expected_authority_cert_issuer:
|
||||
- "DNS:ca.example.org"
|
||||
- "IP:1.2.3.4"
|
||||
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
when: cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: ({{select_crypto_backend}}) Read file
|
||||
slurp:
|
||||
@@ -68,10 +72,6 @@
|
||||
- not result.valid_at.past
|
||||
- not result.valid_at.twentydays
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get certificate info
|
||||
x509_certificate_info:
|
||||
path: '{{ remote_tmp_dir }}/cert_3.pem'
|
||||
@@ -88,11 +88,7 @@
|
||||
expected_authority_cert_issuer:
|
||||
- "DNS:ca.example.org"
|
||||
- "IP:1.2.3.4"
|
||||
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
when: cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get certificate info
|
||||
x509_certificate_info:
|
||||
@@ -106,11 +102,7 @@
|
||||
- result.authority_key_identifier == "44:55:66:77"
|
||||
- result.authority_cert_issuer is none
|
||||
- result.authority_cert_serial_number is none
|
||||
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
when: cryptography_version.stdout is version('1.3', '>=')
|
||||
|
||||
- name: Copy packed cert 1 to remote
|
||||
copy:
|
||||
@@ -131,7 +123,3 @@
|
||||
that:
|
||||
- (result.fingerprints.sha256 == '57:7c:f1:f5:dd:cc:6e:e9:f3:17:28:73:17:e4:25:c7:69:74:3e:f7:9a:df:58:20:7a:5a:e4:aa:de:bf:24:5b' if result.fingerprints.sha256 is defined else true)
|
||||
- (result.fingerprints.sha1 == 'b7:79:64:f4:2b:e0:ae:45:74:d4:f3:08:f6:53:cb:39:26:fa:52:6b' if result.fingerprints.sha1 is defined else true)
|
||||
|
||||
- name: Update result list
|
||||
set_fact:
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
- biometricInfo
|
||||
subject_alt_name:
|
||||
- "DNS:www.ansible.com"
|
||||
- "DNS:{{ 'öç' if cryptography_version.stdout is version('2.1', '<') else 'xn--74h' }}.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
@@ -134,50 +135,8 @@
|
||||
- 3
|
||||
- 4
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
info_results: []
|
||||
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=')
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
pyopenssl_info_results: "{{ info_results }}"
|
||||
info_results: []
|
||||
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: cryptography
|
||||
when: cryptography_version.stdout is version('1.6', '>=')
|
||||
|
||||
- name: Prepare result list
|
||||
set_fact:
|
||||
cryptography_info_results: "{{ info_results }}"
|
||||
|
||||
- block:
|
||||
- name: Dump pyOpenSSL results
|
||||
debug:
|
||||
var: pyopenssl_info_results
|
||||
- name: Dump cryptography results
|
||||
debug:
|
||||
var: cryptography_info_results
|
||||
- name: Compare results
|
||||
assert:
|
||||
that:
|
||||
- ' (item.0 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)
|
||||
== (item.1 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)'
|
||||
quiet: yes
|
||||
loop: "{{ pyopenssl_info_results | zip(cryptography_info_results) | list }}"
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=') and cryptography_version.stdout is version('1.6', '>=')
|
||||
vars:
|
||||
keys_to_ignore:
|
||||
- deprecations
|
||||
- subject_key_identifier
|
||||
- authority_key_identifier
|
||||
- authority_cert_issuer
|
||||
- authority_cert_serial_number
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -13,24 +13,6 @@
|
||||
provider: selfsigned
|
||||
privatekey_path: '{{ remote_tmp_dir }}/privatekey_backend_selection.pem'
|
||||
|
||||
- block:
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
when: pyopenssl_version.stdout is version('0.15', '>=')
|
||||
|
||||
- name: Remove output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Re-create output directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: directory
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl # the x509_crl* modules don't need this, but the other modules using during the tests do in some situations
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -1,25 +1,4 @@
|
||||
---
|
||||
- name: Create CRL 1 (check mode)
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl1.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: 20191013000000Z
|
||||
next_update: 20191113000000Z
|
||||
revoked_certificates:
|
||||
- path: '{{ remote_tmp_dir }}/cert-1.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
- path: '{{ remote_tmp_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 (check mode)
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl1.crl'
|
||||
@@ -283,8 +262,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -301,8 +283,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -318,8 +303,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- C: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -337,8 +325,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -355,8 +346,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -370,8 +364,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -384,8 +381,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -402,8 +402,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -419,8 +422,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -437,8 +443,11 @@
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- CN: CRL
|
||||
- countryName: US
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
@@ -451,8 +460,67 @@
|
||||
return_content: yes
|
||||
register: crl_2_change
|
||||
|
||||
- name: Read ca-crl2.crl
|
||||
slurp:
|
||||
src: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
register: slurp_crl2_1
|
||||
|
||||
- name: Retrieve CRL 2 infos
|
||||
x509_crl_info:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
list_revoked_certificates: false
|
||||
register: crl_2_info_1
|
||||
|
||||
- name: Create CRL 2 (changed order, should be ignored)
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer:
|
||||
countryName: US
|
||||
CN:
|
||||
- Ansible
|
||||
- CRL
|
||||
- Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ remote_tmp_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
ignore_timestamps: true
|
||||
mode: update
|
||||
return_content: yes
|
||||
register: crl_2_change_order_ignore
|
||||
|
||||
- name: Create CRL 2 (changed order)
|
||||
x509_crl:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
|
||||
issuer_ordered:
|
||||
- CN: Ansible
|
||||
- countryName: US
|
||||
- CN: CRL
|
||||
- CN: Test
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ remote_tmp_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
ignore_timestamps: true
|
||||
mode: update
|
||||
return_content: yes
|
||||
register: crl_2_change_order
|
||||
|
||||
- name: Read ca-crl2.crl
|
||||
slurp:
|
||||
src: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
register: slurp_crl2_2
|
||||
|
||||
- name: Retrieve CRL 2 infos again
|
||||
x509_crl_info:
|
||||
path: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
list_revoked_certificates: false
|
||||
register: crl_2_info_2
|
||||
|
||||
@@ -66,11 +66,6 @@
|
||||
that:
|
||||
- crl_1_format_idem.crl | b64decode == content.content | b64decode
|
||||
|
||||
- name: Read ca-crl2.crl
|
||||
slurp:
|
||||
src: '{{ remote_tmp_dir }}/ca-crl2.crl'
|
||||
register: slurp
|
||||
|
||||
- name: Validate CRL 2
|
||||
assert:
|
||||
that:
|
||||
@@ -84,9 +79,26 @@
|
||||
- crl_2_idem_update is not changed
|
||||
- crl_2_change_check is changed
|
||||
- crl_2_change is changed
|
||||
- crl_2_change.crl == (slurp.content | b64decode)
|
||||
- crl_2_change.crl == (slurp_crl2_1.content | b64decode)
|
||||
- crl_2_change_order_ignore is not changed
|
||||
- crl_2_change_order is changed
|
||||
- crl_2_change_order.crl == (slurp_crl2_2.content | b64decode)
|
||||
|
||||
- name: Validate CRL 2 info
|
||||
assert:
|
||||
that:
|
||||
- "'revoked_certificates' not in crl_2_info_1"
|
||||
- >
|
||||
crl_2_info_1.issuer_ordered == [
|
||||
['commonName', 'Ansible'],
|
||||
['commonName', 'CRL'],
|
||||
['countryName', 'US'],
|
||||
['commonName', 'Test'],
|
||||
]
|
||||
- >
|
||||
crl_2_info_2.issuer_ordered == [
|
||||
['commonName', 'Ansible'],
|
||||
['countryName', 'US'],
|
||||
['commonName', 'CRL'],
|
||||
['commonName', 'Test'],
|
||||
]
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"disabled": true,
|
||||
"all_targets": true,
|
||||
"ignore_self": true,
|
||||
"extensions": [
|
||||
".py"
|
||||
],
|
||||
"prefixes": [
|
||||
"plugins/module_utils/compat/"
|
||||
],
|
||||
"output": "path-message",
|
||||
"requirements": [
|
||||
"requests"
|
||||
]
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
This test checks whether the libraries we're bundling are out of date and need to be synced with
|
||||
a newer upstream release.
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
import packaging.specifiers
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
BUNDLED_RE = re.compile(b'\\b_BUNDLED_METADATA\\b')
|
||||
|
||||
|
||||
def get_bundled_libs(paths):
|
||||
"""
|
||||
Return the set of known bundled libraries
|
||||
|
||||
:arg paths: The paths which the test has been instructed to check
|
||||
:returns: The list of all files which we know to contain bundled libraries. If a bundled
|
||||
library consists of multiple files, this should be the file which has metadata included.
|
||||
"""
|
||||
bundled_libs = set()
|
||||
bundled_libs.add('plugins/module_utils/compat/ipaddress.py')
|
||||
|
||||
return bundled_libs
|
||||
|
||||
|
||||
def get_files_with_bundled_metadata(paths):
|
||||
"""
|
||||
Search for any files which have bundled metadata inside of them
|
||||
|
||||
:arg paths: Iterable of filenames to search for metadata inside of
|
||||
:returns: A set of pathnames which contained metadata
|
||||
"""
|
||||
|
||||
with_metadata = set()
|
||||
for path in paths:
|
||||
with open(path, 'rb') as f:
|
||||
body = f.read()
|
||||
|
||||
if BUNDLED_RE.search(body):
|
||||
with_metadata.add(path)
|
||||
|
||||
return with_metadata
|
||||
|
||||
|
||||
def get_bundled_metadata(filename):
|
||||
"""
|
||||
Retrieve the metadata about a bundled library from a python file
|
||||
|
||||
:arg filename: The filename to look inside for the metadata
|
||||
:raises ValueError: If we're unable to extract metadata from the file
|
||||
:returns: The metadata from the python file
|
||||
"""
|
||||
with open(filename, 'r') as module:
|
||||
for line in module:
|
||||
if line.strip().startswith('_BUNDLED_METADATA'):
|
||||
data = line[line.index('{'):].strip()
|
||||
break
|
||||
else:
|
||||
raise ValueError('Unable to check bundled library for update. Please add'
|
||||
' _BUNDLED_METADATA dictionary to the library file with'
|
||||
' information on pypi name and bundled version.')
|
||||
metadata = json.loads(data)
|
||||
return metadata
|
||||
|
||||
|
||||
def get_latest_applicable_version(pypi_data, constraints=None):
|
||||
"""Get the latest pypi version of the package that we allow
|
||||
|
||||
:arg pypi_data: Pypi information about the data as returned by
|
||||
``https://pypi.org/pypi/{pkg_name}/json``
|
||||
:kwarg constraints: version constraints on what we're allowed to use as specified by
|
||||
the bundled metadata
|
||||
:returns: The most recent version on pypi that are allowed by ``constraints``
|
||||
"""
|
||||
latest_version = "0"
|
||||
if constraints:
|
||||
version_specification = packaging.specifiers.SpecifierSet(constraints)
|
||||
for version in pypi_data['releases']:
|
||||
if version in version_specification:
|
||||
if LooseVersion(version) > LooseVersion(latest_version):
|
||||
latest_version = version
|
||||
else:
|
||||
latest_version = pypi_data['info']['version']
|
||||
|
||||
return latest_version
|
||||
|
||||
|
||||
def main():
|
||||
"""Entrypoint to the script"""
|
||||
|
||||
paths = sys.argv[1:] or sys.stdin.read().splitlines()
|
||||
|
||||
bundled_libs = get_bundled_libs(paths)
|
||||
files_with_bundled_metadata = get_files_with_bundled_metadata(paths)
|
||||
|
||||
for filename in files_with_bundled_metadata.difference(bundled_libs):
|
||||
print('{0}: ERROR: File contains _BUNDLED_METADATA but needs to be added to'
|
||||
' test/sanity/code-smell/update-bundled.py'.format(filename))
|
||||
|
||||
for filename in bundled_libs:
|
||||
try:
|
||||
metadata = get_bundled_metadata(filename)
|
||||
except ValueError as e:
|
||||
print('{0}: ERROR: {1}'.format(filename, e))
|
||||
continue
|
||||
except (IOError, OSError) as e:
|
||||
if e.errno == 2:
|
||||
print('{0}: ERROR: {1}. Perhaps the bundled library has been removed'
|
||||
' or moved and the bundled library test needs to be modified as'
|
||||
' well?'.format(filename, e))
|
||||
|
||||
pypi_r = requests.get('https://pypi.org/pypi/{0}/json'.format(metadata['pypi_name']))
|
||||
pypi_data = pypi_r.json()
|
||||
|
||||
constraints = metadata.get('version_constraints', None)
|
||||
latest_version = get_latest_applicable_version(pypi_data, constraints)
|
||||
|
||||
if LooseVersion(metadata['version']) < LooseVersion(latest_version):
|
||||
print('{0}: UPDATE {1} from {2} to {3} {4}'.format(
|
||||
filename,
|
||||
metadata['pypi_name'],
|
||||
metadata['version'],
|
||||
latest_version,
|
||||
'https://pypi.org/pypi/{0}/json'.format(metadata['pypi_name'])))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,7 +1 @@
|
||||
plugins/module_utils/acme/__init__.py empty-init
|
||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
||||
plugins/module_utils/compat/ipaddress.py no-assert
|
||||
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
||||
plugins/module_utils/crypto/__init__.py empty-init
|
||||
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
plugins/module_utils/acme/__init__.py empty-init
|
||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
||||
plugins/module_utils/compat/ipaddress.py no-assert
|
||||
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
||||
plugins/module_utils/crypto/__init__.py empty-init
|
||||
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user