mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Implement Ed25519, Ed448, X25519 and X448 support (cryptography backend). (#54947)
This commit is contained in:
parent
7a16703dff
commit
221da3e8b1
3 changed files with 82 additions and 17 deletions
|
@ -19,9 +19,9 @@ short_description: Generate OpenSSL private keys
|
||||||
description:
|
description:
|
||||||
- This module allows one to (re)generate OpenSSL private keys.
|
- This module allows one to (re)generate OpenSSL private keys.
|
||||||
- One can generate L(RSA,https://en.wikipedia.org/wiki/RSA_(cryptosystem)),
|
- One can generate L(RSA,https://en.wikipedia.org/wiki/RSA_(cryptosystem)),
|
||||||
L(DSA,https://en.wikipedia.org/wiki/Digital_Signature_Algorithm) or
|
L(DSA,https://en.wikipedia.org/wiki/Digital_Signature_Algorithm),
|
||||||
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)
|
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography) or
|
||||||
private keys.
|
L(EdDSA,https://en.wikipedia.org/wiki/EdDSA) private keys.
|
||||||
- Keys are generated in PEM format.
|
- Keys are generated in PEM format.
|
||||||
- "Please note that the module regenerates private keys if they don't match
|
- "Please note that the module regenerates private keys if they don't match
|
||||||
the module's options. In particular, if you provide another passphrase
|
the module's options. In particular, if you provide another passphrase
|
||||||
|
@ -52,12 +52,13 @@ options:
|
||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- The algorithm used to generate the TLS/SSL private key.
|
- The algorithm used to generate the TLS/SSL private key.
|
||||||
- Note that C(ECC) requires the C(cryptography) backend.
|
- Note that C(ECC), C(X25519), C(X448), C(Ed25519) and C(Ed448) require the C(cryptography) backend.
|
||||||
- Depending on the curve, you need a newer version of the 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
|
type: str
|
||||||
default: RSA
|
default: RSA
|
||||||
#choices: [ DSA, ECC, RSA, X448, X25519, Ed448, Ed25519 ]
|
choices: [ DSA, ECC, Ed25519, Ed448, RSA, X25519, X448 ]
|
||||||
choices: [ DSA, ECC, RSA ]
|
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- Note that not all curves are supported by all versions of C(cryptography).
|
- Note that not all curves are supported by all versions of C(cryptography).
|
||||||
|
@ -239,8 +240,14 @@ else:
|
||||||
try:
|
try:
|
||||||
import cryptography.hazmat.primitives.asymmetric.x25519
|
import cryptography.hazmat.primitives.asymmetric.x25519
|
||||||
CRYPTOGRAPHY_HAS_X25519 = True
|
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:
|
except ImportError:
|
||||||
CRYPTOGRAPHY_HAS_X25519 = False
|
CRYPTOGRAPHY_HAS_X25519 = False
|
||||||
|
CRYPTOGRAPHY_HAS_X25519_FULL = False
|
||||||
try:
|
try:
|
||||||
import cryptography.hazmat.primitives.asymmetric.x448
|
import cryptography.hazmat.primitives.asymmetric.x448
|
||||||
CRYPTOGRAPHY_HAS_X448 = True
|
CRYPTOGRAPHY_HAS_X448 = True
|
||||||
|
@ -467,6 +474,8 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||||
self.curve = module.params['curve']
|
self.curve = module.params['curve']
|
||||||
if not CRYPTOGRAPHY_HAS_X25519 and self.type == 'X25519':
|
if not CRYPTOGRAPHY_HAS_X25519 and self.type == 'X25519':
|
||||||
self.module.fail_json(msg='Your cryptography version does not support 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':
|
if not CRYPTOGRAPHY_HAS_X448 and self.type == 'X448':
|
||||||
self.module.fail_json(msg='Your cryptography version does not support X448')
|
self.module.fail_json(msg='Your cryptography version does not support X448')
|
||||||
if not CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
|
if not CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
|
||||||
|
@ -475,6 +484,7 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||||
self.module.fail_json(msg='Your cryptography version does not support Ed448')
|
self.module.fail_json(msg='Your cryptography version does not support Ed448')
|
||||||
|
|
||||||
def _generate_private_key_data(self):
|
def _generate_private_key_data(self):
|
||||||
|
format = cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL
|
||||||
try:
|
try:
|
||||||
if self.type == 'RSA':
|
if self.type == 'RSA':
|
||||||
self.privatekey = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
|
self.privatekey = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
|
||||||
|
@ -487,14 +497,18 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||||
key_size=self.size,
|
key_size=self.size,
|
||||||
backend=self.cryptography_backend
|
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()
|
self.privatekey = cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.generate()
|
||||||
|
format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
|
||||||
if CRYPTOGRAPHY_HAS_X448 and self.type == 'X448':
|
if CRYPTOGRAPHY_HAS_X448 and self.type == 'X448':
|
||||||
self.privatekey = cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.generate()
|
self.privatekey = cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.generate()
|
||||||
|
format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
|
||||||
if CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
|
if CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
|
||||||
self.privatekey = cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.generate()
|
self.privatekey = cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.generate()
|
||||||
|
format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
|
||||||
if CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448':
|
if CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448':
|
||||||
self.privatekey = cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.generate()
|
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.type == 'ECC' and self.curve in self.curves:
|
||||||
if self.curves[self.curve]['deprecated']:
|
if self.curves[self.curve]['deprecated']:
|
||||||
self.module.warn('Elliptic curves of type {0} should not be used for new keys!'.format(self.curve))
|
self.module.warn('Elliptic curves of type {0} should not be used for new keys!'.format(self.curve))
|
||||||
|
@ -516,7 +530,7 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||||
# Serialize key
|
# Serialize key
|
||||||
return self.privatekey.private_bytes(
|
return self.privatekey.private_bytes(
|
||||||
encoding=cryptography.hazmat.primitives.serialization.Encoding.PEM,
|
encoding=cryptography.hazmat.primitives.serialization.Encoding.PEM,
|
||||||
format=cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL,
|
format=format,
|
||||||
encryption_algorithm=encryption_algorithm
|
encryption_algorithm=encryption_algorithm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -565,6 +579,10 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||||
return self.type == 'X25519'
|
return self.type == 'X25519'
|
||||||
if CRYPTOGRAPHY_HAS_X448 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey):
|
if CRYPTOGRAPHY_HAS_X448 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey):
|
||||||
return self.type == 'X448'
|
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 isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
||||||
if self.type != 'ECC':
|
if self.type != 'ECC':
|
||||||
return False
|
return False
|
||||||
|
@ -590,10 +608,7 @@ def main():
|
||||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
size=dict(type='int', default=4096),
|
size=dict(type='int', default=4096),
|
||||||
type=dict(type='str', default='RSA', choices=[
|
type=dict(type='str', default='RSA', choices=[
|
||||||
'RSA', 'DSA', 'ECC',
|
'DSA', 'ECC', 'Ed25519', 'Ed448', 'RSA', 'X25519', 'X448'
|
||||||
# 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'
|
|
||||||
]),
|
]),
|
||||||
curve=dict(type='str', choices=[
|
curve=dict(type='str', choices=[
|
||||||
'secp384r1', 'secp521r1', 'secp224r1', 'secp192r1', 'secp256k1',
|
'secp384r1', 'secp521r1', 'secp224r1', 'secp192r1', 'secp256k1',
|
||||||
|
|
|
@ -55,10 +55,6 @@
|
||||||
when: select_crypto_backend == 'pyopenssl'
|
when: select_crypto_backend == 'pyopenssl'
|
||||||
- set_fact:
|
- set_fact:
|
||||||
ecc_types:
|
ecc_types:
|
||||||
# - curve: X448
|
|
||||||
# min_cryptography_version: "2.5"
|
|
||||||
# - curve: X25519
|
|
||||||
# min_cryptography_version: "2.0"
|
|
||||||
- curve: secp384r1
|
- curve: secp384r1
|
||||||
openssl_name: secp384r1
|
openssl_name: secp384r1
|
||||||
min_cryptography_version: "0.5"
|
min_cryptography_version: "0.5"
|
||||||
|
@ -143,6 +139,41 @@
|
||||||
label: "{{ item.curve }}"
|
label: "{{ item.curve }}"
|
||||||
register: privatekey_ecc_idempotency
|
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
|
- name: Generate privatekey with passphrase
|
||||||
openssl_privatekey:
|
openssl_privatekey:
|
||||||
path: '{{ output_dir }}/privatekeypw.pem'
|
path: '{{ output_dir }}/privatekeypw.pem'
|
||||||
|
|
|
@ -105,6 +105,25 @@
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.item.curve }}"
|
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
|
- name: Validate passphrase changing
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
|
|
Loading…
Reference in a new issue