1
0
Fork 0
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:
Felix Fontein 2019-04-08 10:30:05 +02:00 committed by Martin Krizek
parent 7a16703dff
commit 221da3e8b1
3 changed files with 82 additions and 17 deletions

View file

@ -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',

View file

@ -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'

View file

@ -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: