Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openssl_privatekey: add support for Ed22519, Ed448, X25519 and X448 keys #54947

Merged
merged 1 commit into from
Apr 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 28 additions & 13 deletions lib/ansible/modules/crypto/openssl_privatekey.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
description:
- This module allows one to (re)generate OpenSSL private keys.
- One can generate L(RSA,https://en.wikipedia.org/wiki/RSA_(cryptosystem)),
L(DSA,https://en.wikipedia.org/wiki/Digital_Signature_Algorithm) or
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)
private keys.
L(DSA,https://en.wikipedia.org/wiki/Digital_Signature_Algorithm),
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography) or
L(EdDSA,https://en.wikipedia.org/wiki/EdDSA) private keys.
- Keys are generated in PEM format.
- "Please note that the module regenerates private keys if they don't match
the module's options. In particular, if you provide another passphrase
Expand Down Expand Up @@ -52,12 +52,13 @@
type:
description:
- The algorithm used to generate the TLS/SSL private key.
- Note that C(ECC) requires the C(cryptography) backend.
- Depending on the curve, you need a newer version of the cryptography backend.
- Note that C(ECC), C(X25519), C(X448), C(Ed25519) and C(Ed448) require the C(cryptography) backend.
C(X25519) needs cryptography 2.5 or newer, while C(X448), C(Ed25519) and C(Ed448) require
cryptography 2.6 or newer. For C(ECC), the minimal cryptography version required depends on the
I(curve) option.
type: str
default: RSA
#choices: [ DSA, ECC, RSA, X448, X25519, Ed448, Ed25519 ]
choices: [ DSA, ECC, RSA ]
choices: [ DSA, ECC, Ed25519, Ed448, RSA, X25519, X448 ]
curve:
description:
- Note that not all curves are supported by all versions of C(cryptography).
Expand Down Expand Up @@ -239,8 +240,14 @@
try:
import cryptography.hazmat.primitives.asymmetric.x25519
CRYPTOGRAPHY_HAS_X25519 = True
try:
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.private_bytes
CRYPTOGRAPHY_HAS_X25519_FULL = True
except AttributeError:
CRYPTOGRAPHY_HAS_X25519_FULL = False
except ImportError:
CRYPTOGRAPHY_HAS_X25519 = False
CRYPTOGRAPHY_HAS_X25519_FULL = False
try:
import cryptography.hazmat.primitives.asymmetric.x448
CRYPTOGRAPHY_HAS_X448 = True
Expand Down Expand Up @@ -467,6 +474,8 @@ def __init__(self, module):
self.curve = module.params['curve']
if not CRYPTOGRAPHY_HAS_X25519 and self.type == 'X25519':
self.module.fail_json(msg='Your cryptography version does not support X25519')
if not CRYPTOGRAPHY_HAS_X25519_FULL and self.type == 'X25519':
self.module.fail_json(msg='Your cryptography version does not support X25519 serialization')
if not CRYPTOGRAPHY_HAS_X448 and self.type == 'X448':
self.module.fail_json(msg='Your cryptography version does not support X448')
if not CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
Expand All @@ -475,6 +484,7 @@ def __init__(self, module):
self.module.fail_json(msg='Your cryptography version does not support Ed448')

def _generate_private_key_data(self):
format = cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL
try:
if self.type == 'RSA':
self.privatekey = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
Expand All @@ -487,14 +497,18 @@ def _generate_private_key_data(self):
key_size=self.size,
backend=self.cryptography_backend
)
if CRYPTOGRAPHY_HAS_X25519 and self.type == 'X25519':
if CRYPTOGRAPHY_HAS_X25519_FULL and self.type == 'X25519':
self.privatekey = cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.generate()
format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
if CRYPTOGRAPHY_HAS_X448 and self.type == 'X448':
self.privatekey = cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.generate()
format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
if CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
self.privatekey = cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.generate()
format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
if CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448':
self.privatekey = cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.generate()
format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
if self.type == 'ECC' and self.curve in self.curves:
if self.curves[self.curve]['deprecated']:
self.module.warn('Elliptic curves of type {0} should not be used for new keys!'.format(self.curve))
Expand All @@ -516,7 +530,7 @@ def _generate_private_key_data(self):
# Serialize key
return self.privatekey.private_bytes(
encoding=cryptography.hazmat.primitives.serialization.Encoding.PEM,
format=cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL,
format=format,
encryption_algorithm=encryption_algorithm
)

Expand Down Expand Up @@ -565,6 +579,10 @@ def _check_size_and_type(self):
return self.type == 'X25519'
if CRYPTOGRAPHY_HAS_X448 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey):
return self.type == 'X448'
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
return self.type == 'Ed25519'
if CRYPTOGRAPHY_HAS_ED448 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
return self.type == 'Ed448'
if isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
if self.type != 'ECC':
return False
Expand All @@ -590,10 +608,7 @@ def main():
state=dict(type='str', default='present', choices=['present', 'absent']),
size=dict(type='int', default=4096),
type=dict(type='str', default='RSA', choices=[
'RSA', 'DSA', 'ECC',
# FIXME: NO LONGER TRUE: x25519 is missing serialization functions: https://github.com/pyca/cryptography/issues/4386
# FIXME: NO LONGER TRUE: x448 is also missing it: https://github.com/pyca/cryptography/pull/4580#issuecomment-437913340
# 'X448', 'X25519', 'Ed448', 'Ed25519'
'DSA', 'ECC', 'Ed25519', 'Ed448', 'RSA', 'X25519', 'X448'
]),
curve=dict(type='str', choices=[
'secp384r1', 'secp521r1', 'secp224r1', 'secp192r1', 'secp256k1',
Expand Down
39 changes: 35 additions & 4 deletions test/integration/targets/openssl_privatekey/tasks/impl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@
when: select_crypto_backend == 'pyopenssl'
- set_fact:
ecc_types:
# - curve: X448
# min_cryptography_version: "2.5"
# - curve: X25519
# min_cryptography_version: "2.0"
- curve: secp384r1
openssl_name: secp384r1
min_cryptography_version: "0.5"
Expand Down Expand Up @@ -143,6 +139,41 @@
label: "{{ item.curve }}"
register: privatekey_ecc_idempotency

- block:
- name: Test other type generation
openssl_privatekey:
path: '{{ output_dir }}/privatekey-{{ item.type }}.pem'
type: "{{ item.type }}"
select_crypto_backend: '{{ select_crypto_backend }}'
when: cryptography_version.stdout is version(item.min_version, '>=')
loop: "{{ types }}"
loop_control:
label: "{{ item.type }}"
register: privatekey_t1_generate

- name: Test other type generation (idempotency)
openssl_privatekey:
path: '{{ output_dir }}/privatekey-{{ item.type }}.pem'
type: "{{ item.type }}"
select_crypto_backend: '{{ select_crypto_backend }}'
when: cryptography_version.stdout is version(item.min_version, '>=')
loop: "{{ types }}"
loop_control:
label: "{{ item.type }}"
register: privatekey_t1_idempotency

when: select_crypto_backend == 'cryptography'
vars:
types:
- type: X25519
min_version: '2.5'
- type: Ed25519
min_version: '2.6'
- type: Ed448
min_version: '2.6'
- type: X448
min_version: '2.6'

- name: Generate privatekey with passphrase
openssl_privatekey:
path: '{{ output_dir }}/privatekeypw.pem'
Expand Down
19 changes: 19 additions & 0 deletions test/integration/targets/openssl_privatekey/tests/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,25 @@
loop_control:
label: "{{ item.item.curve }}"

- name: Validate other type generation (just check changed)
assert:
that:
- item is changed
loop: "{{ privatekey_t1_generate.results }}"
when: "'skip_reason' not in item"
loop_control:
label: "{{ item.item.type }}"


- name: Validate other type generation idempotency
assert:
that:
- item is not changed
loop: "{{ privatekey_t1_idempotency.results }}"
when: "'skip_reason' not in item"
loop_control:
label: "{{ item.item.type }}"

- name: Validate passphrase changing
assert:
that:
Expand Down