openssl_*: improve passphrase handling for private keys in PyOpenSSL (#53489)

* Raise OpenSSLBadPassphraseError if passphrase is wrong.

* Improve handling of passphrase errors.

Current behavior for modules is: if passphrase is wrong (or wrongly specified), fail.
Current behavior for openssl_privatekey is: if passphrase is worng (or wrongly specified), regenerate.

* Add changelog.

* Add tests.

* Adjustments for some versions of PyOpenSSL.

* Update lib/ansible/modules/crypto/openssl_certificate.py

Improve text.

Co-Authored-By: felixfontein <felix@fontein.de>
This commit is contained in:
Felix Fontein 2019-03-08 17:21:18 +01:00 committed by John R Barker
commit caf7fd2245
20 changed files with 427 additions and 36 deletions

View file

@ -3,6 +3,13 @@
openssl_privatekey:
path: '{{ output_dir }}/privatekey.pem'
- name: Generate privatekey with password
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
passphrase: hunter2
cipher: auto
select_crypto_backend: cryptography
- name: Generate CSR (no extensions)
openssl_csr:
path: '{{ output_dir }}/csr_noext.csr'
@ -54,3 +61,39 @@
- "'Found no keyUsage extension' in extension_missing_ku.msg"
- extension_missing_eku is failed
- "'Found no extendedKeyUsage extension' in extension_missing_eku.msg"
- name: Check private key passphrase fail 1
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
privatekey_path: '{{ output_dir }}/privatekey.pem'
privatekey_passphrase: hunter2
provider: assertonly
ignore_errors: yes
register: passphrase_error_1
- name: Check private key passphrase fail 2
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
privatekey_passphrase: wrong_password
provider: assertonly
ignore_errors: yes
register: passphrase_error_2
- name: Check private key passphrase fail 3
openssl_certificate:
path: '{{ output_dir }}/cert_noext.pem'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
provider: assertonly
ignore_errors: yes
register: passphrase_error_3
- name:
assert:
that:
- 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_1.msg or 'assword' in passphrase_error_2.msg"
- passphrase_error_3 is failed
- "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_3.msg"

View file

@ -151,4 +151,39 @@
ownca_digest: sha256
register: ownca_certificate_ecc
- name: Generate ownca certificate (failed passphrase 1)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_pw1.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem'
ownca_privatekey_passphrase: hunter2
provider: ownca
ownca_digest: sha256
ignore_errors: yes
register: passphrase_error_1
- name: Generate ownca certificate (failed passphrase 2)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_pw1.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekeypw.pem'
ownca_privatekey_passphrase: wrong_password
provider: ownca
ownca_digest: sha256
ignore_errors: yes
register: passphrase_error_2
- name: Generate ownca certificate (failed passphrase 3)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_pw3.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekeypw.pem'
provider: ownca
ownca_digest: sha256
ignore_errors: yes
register: passphrase_error_3
- import_tasks: ../tests/validate_ownca.yml

View file

@ -3,6 +3,13 @@
openssl_privatekey:
path: '{{ output_dir }}/privatekey.pem'
- name: Generate privatekey with password
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
passphrase: hunter2
cipher: auto
select_crypto_backend: cryptography
- name: Generate CSR
openssl_csr:
path: '{{ output_dir }}/csr.csr'
@ -157,4 +164,36 @@
selfsigned_digest: sha256
register: selfsigned_certificate_ecc
- name: Generate selfsigned certificate (failed passphrase 1)
openssl_certificate:
path: '{{ output_dir }}/cert_pw1.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
privatekey_passphrase: hunter2
provider: selfsigned
selfsigned_digest: sha256
ignore_errors: yes
register: passphrase_error_1
- name: Generate selfsigned certificate (failed passphrase 2)
openssl_certificate:
path: '{{ output_dir }}/cert_pw2.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
privatekey_passphrase: wrong_password
provider: selfsigned
selfsigned_digest: sha256
ignore_errors: yes
register: passphrase_error_2
- name: Generate selfsigned certificate (failed passphrase 3)
openssl_certificate:
path: '{{ output_dir }}/cert_pw3.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
provider: selfsigned
selfsigned_digest: sha256
ignore_errors: yes
register: passphrase_error_3
- import_tasks: ../tests/validate_selfsigned.yml

View file

@ -81,3 +81,13 @@
- ownca_cert_ecc_pubkey.stdout == privatekey_ecc_pubkey.stdout
# openssl 1.1.x adds a space between the output
- ownca_cert_ecc_issuer.stdout in ['CN=Example CA', 'CN = Example CA']
- name:
assert:
that:
- 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_1.msg or 'assword' in passphrase_error_2.msg"
- passphrase_error_3 is failed
- "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_3.msg"

View file

@ -82,3 +82,13 @@
assert:
that:
- cert_ecc_pubkey.stdout == privatekey_ecc_pubkey.stdout
- name:
assert:
that:
- 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_1.msg or 'assword' in passphrase_error_2.msg"
- passphrase_error_3 is failed
- "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_3.msg"

View file

@ -241,3 +241,36 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: country_fail_4
ignore_errors: yes
- name: Generate privatekey with password
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
passphrase: hunter2
cipher: auto
select_crypto_backend: cryptography
- name: Generate publickey - PEM format
openssl_csr:
path: '{{ output_dir }}/csr_pw1.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
privatekey_passphrase: hunter2
select_crypto_backend: '{{ select_crypto_backend }}'
ignore_errors: yes
register: passphrase_error_1
- name: Generate publickey - PEM format
openssl_csr:
path: '{{ output_dir }}/csr_pw2.csr'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
privatekey_passphrase: wrong_password
select_crypto_backend: '{{ select_crypto_backend }}'
ignore_errors: yes
register: passphrase_error_2
- name: Generate publickey - PEM format
openssl_csr:
path: '{{ output_dir }}/csr_pw3.csr'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
ignore_errors: yes
register: passphrase_error_3

View file

@ -109,3 +109,13 @@
- country_idempotent_2 is not changed
- country_idempotent_3 is not changed
- country_fail_4 is failed
- name:
assert:
that:
- 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_1.msg or 'assword' in passphrase_error_2.msg"
- passphrase_error_3 is failed
- "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_3.msg"

View file

@ -53,6 +53,45 @@
action: 'parse'
state: 'present'
- name: Generate privatekey with password
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
passphrase: hunter2
cipher: auto
select_crypto_backend: cryptography
- name: 'Generate PKCS#12 file (password fail 1)'
openssl_pkcs12:
path: "{{ output_dir }}/ansible_pw1.p12"
friendly_name: 'abracadabra'
privatekey_path: "{{ output_dir }}/ansible_pkey.pem"
privatekey_passphrase: hunter2
certificate_path: "{{ output_dir }}/ansible.crt"
state: present
ignore_errors: yes
register: passphrase_error_1
- name: 'Generate PKCS#12 file (password fail 2)'
openssl_pkcs12:
path: "{{ output_dir }}/ansible_pw2.p12"
friendly_name: 'abracadabra'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
privatekey_passphrase: wrong_password
certificate_path: "{{ output_dir }}/ansible.crt"
state: present
ignore_errors: yes
register: passphrase_error_2
- name: 'Generate PKCS#12 file (password fail 3)'
openssl_pkcs12:
path: "{{ output_dir }}/ansible_pw3.p12"
friendly_name: 'abracadabra'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
certificate_path: "{{ output_dir }}/ansible.crt"
state: present
ignore_errors: yes
register: passphrase_error_3
- import_tasks: ../tests/validate.yml
always:

View file

@ -14,3 +14,13 @@
- p12_standard.mode == '0400'
- p12_force.changed
- p12_force_and_mode.mode == '0644' and p12_force_and_mode.changed
- name:
assert:
that:
- 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_1.msg or 'assword' in passphrase_error_2.msg"
- passphrase_error_3 is failed
- "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_3.msg"

View file

@ -142,3 +142,39 @@
loop_control:
label: "{{ item.curve }}"
register: privatekey_ecc_idempotency
- name: Generate privatekey with passphrase
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
passphrase: hunter2
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
select_crypto_backend: '{{ select_crypto_backend }}'
register: passphrase_1
- name: Generate privatekey with passphrase (idempotent)
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
passphrase: hunter2
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
select_crypto_backend: '{{ select_crypto_backend }}'
register: passphrase_2
- name: Regenerate privatekey without passphrase
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
register: passphrase_3
- name: Regenerate privatekey without passphrase (idempotent)
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
register: passphrase_4
- name: Regenerate privatekey with passphrase
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
passphrase: hunter2
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
select_crypto_backend: '{{ select_crypto_backend }}'
register: passphrase_5

View file

@ -104,3 +104,12 @@
when: "'skip_reason' not in item"
loop_control:
label: "{{ item.item.curve }}"
- name: Validate passphrase changing
assert:
that:
- passphrase_1 is changed
- passphrase_2 is not changed
- passphrase_3 is changed
- passphrase_4 is not changed
- passphrase_5 is changed

View file

@ -78,6 +78,36 @@
path: '{{ output_dir }}/publickey5.pub'
privatekey_path: '{{ output_dir }}/privatekey5.pem'
- name: Generate privatekey with password
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
passphrase: hunter2
cipher: auto
select_crypto_backend: cryptography
- name: Generate publickey - PEM format (failed passphrase 1)
openssl_publickey:
path: '{{ output_dir }}/publickey_pw1.pub'
privatekey_path: '{{ output_dir }}/privatekey.pem'
privatekey_passphrase: hunter2
ignore_errors: yes
register: passphrase_error_1
- name: Generate publickey - PEM format (failed passphrase 2)
openssl_publickey:
path: '{{ output_dir }}/publickey_pw2.pub'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
privatekey_passphrase: wrong_password
ignore_errors: yes
register: passphrase_error_2
- name: Generate publickey - PEM format (failed passphrase 3)
openssl_publickey:
path: '{{ output_dir }}/publickey_pw3.pub'
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
ignore_errors: yes
register: passphrase_error_3
- import_tasks: ../tests/validate.yml
when: pyopenssl_version.stdout is version('16.0.0', '>=')

View file

@ -96,3 +96,13 @@
assert:
that:
- publickey5_pubkey.stdout == privatekey5_pubkey.stdout
- name:
assert:
that:
- 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_1.msg or 'assword' in passphrase_error_2.msg"
- passphrase_error_3 is failed
- "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_3.msg"