CVE-2020-25646: Improper Encoding or Escaping of Output (CWE-116)
value: A flaw was found in Ansible Collection community.crypto. openssl_privatekey_info exposes private key in logs. This directly impacts confidentiality
CWE description: The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.
CVSS v3 base score: 7.5
Published: 2020-10-29T20:15Z
Repository: https://github.com/ansible-collections/community.crypto
Language: Python
Files changed: changelogs/fragments/cve-2020-25646.yml, plugins/modules/openssl_csr.py, plugins/modules/openssl_privatekey.py, plugins/modules/openssl_privatekey_info.py, plugins/modules/openssl_publickey.py, plugins/modules/openssl_signature.py, plugins/modules/x509_certificate.py, plugins/modules/x509_crl.py
Commit date: 2020-10-13 14:14:05 +0200
Commit message: CVE-2020-25646: no_log=True missing for private key content options (#125)

* Mark private key content options as no_log (CVE-2020-25646.)

* Mention no_log for openssl_privatekey's return_content option.

* Add change PR's URL.

* Plural.
Diff: diff --git a/changelogs/fragments/cve-2020-25646.yml b/changelogs/fragments/cve-2020-25646.yml
new file mode 100644
index 0000000..50e2a6d
--- /dev/null
+++ b/changelogs/fragments/cve-2020-25646.yml
@@ -0,0 +1,7 @@
+security_fixes:
+- "openssl_csr - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
+- "openssl_privatekey_info - the option ``content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
+- "openssl_publickey - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
+- "openssl_signature - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
+- "x509_certificate - the options ``privatekey_content`` and ``ownca_privatekey_content`` were not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
+- "x509_crl - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
diff --git a/plugins/modules/openssl_csr.py b/plugins/modules/openssl_csr.py
index dc16b3d..dcbc520 100644
--- a/plugins/modules/openssl_csr.py
+++ b/plugins/modules/openssl_csr.py
@@ -1161,11 +1161,11 @@ def main():
     module = AnsibleModule(
         argument_spec=dict(
             state=dict(type='str', default='present', choices=['absent', 'present']),
             digest=dict(type='str', default='sha256'),
             privatekey_path=dict(type='path'),
-            privatekey_content=dict(type='str'),
+            privatekey_content=dict(type='str', no_log=True),
             privatekey_passphrase=dict(type='str', no_log=True),
             version=dict(type='int', default=1),
             force=dict(type='bool', default=False),
             path=dict(type='path', required=True),
             subject=dict(type='dict'),
diff --git a/plugins/modules/openssl_privatekey.py b/plugins/modules/openssl_privatekey.py
index 47e3afd..25b9eaa 100644
--- a/plugins/modules/openssl_privatekey.py
+++ b/plugins/modules/openssl_privatekey.py
@@ -150,10 +150,12 @@ options:
     return_content:
         description:
             - If set to C(yes), will return the (current or generated) private key's content as I(privatekey).
             - Note that especially if the private key is not encrypted, you have to make sure that the returned
               value is treated appropriately and not accidentally written to logs etc.! Use with care!
+            - Use Ansible's I(no_log) task option to avoid the output being shown. See also
+              U(https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-keep-secret-data-in-my-playbook).
         type: bool
         default: no
         version_added: '1.0.0'
     regenerate:
         description:
diff --git a/plugins/modules/openssl_privatekey_info.py b/plugins/modules/openssl_privatekey_info.py
index f59be86..c4401c8 100644
--- a/plugins/modules/openssl_privatekey_info.py
+++ b/plugins/modules/openssl_privatekey_info.py
@@ -581,11 +581,11 @@ class PrivateKeyInfoPyOpenSSL(PrivateKeyInfo):
 
 def main():
     module = AnsibleModule(
         argument_spec=dict(
             path=dict(type='path'),
-            content=dict(type='str'),
+            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']),
         ),
         required_one_of=(
diff --git a/plugins/modules/openssl_publickey.py b/plugins/modules/openssl_publickey.py
index b3c5568..daa27a8 100644
--- a/plugins/modules/openssl_publickey.py
+++ b/plugins/modules/openssl_publickey.py
@@ -393,11 +393,11 @@ def main():
         argument_spec=dict(
             state=dict(type='str', default='present', choices=['present', 'absent']),
             force=dict(type='bool', default=False),
             path=dict(type='path', required=True),
             privatekey_path=dict(type='path'),
-            privatekey_content=dict(type='str'),
+            privatekey_content=dict(type='str', no_log=True),
             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'),
             return_content=dict(type='bool', default=False),
diff --git a/plugins/modules/openssl_signature.py b/plugins/modules/openssl_signature.py
index 5994dc8..57bbdc5 100644
--- a/plugins/modules/openssl_signature.py
+++ b/plugins/modules/openssl_signature.py
@@ -257,11 +257,11 @@ class SignatureCryptography(SignatureBase):
 
 def main():
     module = AnsibleModule(
         argument_spec=dict(
             privatekey_path=dict(type='path'),
-            privatekey_content=dict(type='str'),
+            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'),
         ),
         mutually_exclusive=(
diff --git a/plugins/modules/x509_certificate.py b/plugins/modules/x509_certificate.py
index 950e84c..6d82bca 100644
--- a/plugins/modules/x509_certificate.py
+++ b/plugins/modules/x509_certificate.py
@@ -2563,11 +2563,11 @@ def main():
             select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
             return_content=dict(type='bool', default=False),
 
             # General properties of a certificate
             privatekey_path=dict(type='path'),
-            privatekey_content=dict(type='str'),
+            privatekey_content=dict(type='str', no_log=True),
             privatekey_passphrase=dict(type='str', no_log=True),
 
             # provider: assertonly
             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'),
@@ -2607,11 +2607,11 @@ def main():
 
             # provider: ownca
             ownca_path=dict(type='path'),
             ownca_content=dict(type='str'),
             ownca_privatekey_path=dict(type='path'),
-            ownca_privatekey_content=dict(type='str'),
+            ownca_privatekey_content=dict(type='str', no_log=True),
             ownca_privatekey_passphrase=dict(type='str', no_log=True),
             ownca_digest=dict(type='str', default='sha256'),
             ownca_version=dict(type='int', default=3),
             ownca_not_before=dict(type='str', default='+0s'),
             ownca_not_after=dict(type='str', default='+3650d'),
diff --git a/plugins/modules/x509_crl.py b/plugins/modules/x509_crl.py
index 2e71995..54adb4f 100644
--- a/plugins/modules/x509_crl.py
+++ b/plugins/modules/x509_crl.py
@@ -752,11 +752,11 @@ def main():
             force=dict(type='bool', default=False),
             backup=dict(type='bool', default=False),
             path=dict(type='path', required=True),
             format=dict(type='str', default='pem', choices=['pem', 'der']),
             privatekey_path=dict(type='path'),
-            privatekey_content=dict(type='str'),
+            privatekey_content=dict(type='str', no_log=True),
             privatekey_passphrase=dict(type='str', no_log=True),
             issuer=dict(type='dict'),
             last_update=dict(type='str', default='+0s'),
             next_update=dict(type='str'),
             digest=dict(type='str', default='sha256'),

Vulnerable code:             privatekey_content=dict(type='str'),
            content=dict(type='str'),
            privatekey_content=dict(type='str'),
            privatekey_content=dict(type='str'),
            privatekey_content=dict(type='str'),
            ownca_privatekey_content=dict(type='str'),
            privatekey_content=dict(type='str'),
Fixed code: security_fixes:
- "openssl_csr - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
- "openssl_privatekey_info - the option ``content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
- "openssl_publickey - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
- "openssl_signature - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
- "x509_certificate - the options ``privatekey_content`` and ``ownca_privatekey_content`` were not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
- "x509_crl - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125)."
            privatekey_content=dict(type='str', no_log=True),
            - Use Ansible's I(no_log) task option to avoid the output being shown. See also
              U(https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-keep-secret-data-in-my-playbook).
            content=dict(type='str', no_log=True),
            privatekey_content=dict(type='str', no_log=True),
            privatekey_content=dict(type='str', no_log=True),
            privatekey_content=dict(type='str', no_log=True),
            ownca_privatekey_content=dict(type='str', no_log=True),
            privatekey_content=dict(type='str', no_log=True),