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

Add openssl_privatekey_info module #54845

Merged
merged 28 commits into from
Apr 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
096b075
Add openssl_privatekey_info module.
felixfontein Apr 4, 2019
e082950
Addressing review feedback.
felixfontein Apr 4, 2019
513ec2e
Update docs.
felixfontein Apr 4, 2019
3c7525f
Update tests.
felixfontein Apr 4, 2019
f2df410
Work around too broad sanity checks.
felixfontein Apr 4, 2019
775316d
...
felixfontein Apr 4, 2019
2337342
Don't die when None is returned.
felixfontein Apr 4, 2019
93ca8d7
Use OpenSSL to extract RSA and DSA key data.
felixfontein Apr 4, 2019
8c5e6ad
Extend tests.
felixfontein Apr 4, 2019
6386acc
Make OpenSSL code compatible to OpenSSL < 1.1.
felixfontein Apr 4, 2019
c59102c
Rewrite tests to use result dicts instead of result lists.
felixfontein Apr 4, 2019
8940bcf
Skip ECC for too old PyOpenSSL.
felixfontein Apr 4, 2019
75d3600
Reformulate.
felixfontein Apr 5, 2019
ffb8a55
Improve return_private_key_data docs.
felixfontein Apr 5, 2019
d0ee6d7
Rename path_content -> content.
felixfontein Apr 5, 2019
6000b35
Add sample.
felixfontein Apr 5, 2019
418e53a
Cleanup.
felixfontein Apr 5, 2019
ac76235
Add key consistency check.
felixfontein Apr 5, 2019
142e494
Improve description.
felixfontein Apr 5, 2019
47d9680
Adjust minimal version.
felixfontein Apr 5, 2019
7e5e218
Fallback code for some pyOpenSSL < 16.0 versions.
felixfontein Apr 5, 2019
75bf93d
Also support Ed25519 and Ed448 keys (or not).
felixfontein Apr 6, 2019
f5233f5
Add more consistency checks.
felixfontein Apr 6, 2019
e9a07e9
Verify DSA keys manually.
felixfontein Apr 6, 2019
7f0c133
Improve DSA key validation.
felixfontein Apr 7, 2019
8db429a
Forgot one condition.
felixfontein Apr 7, 2019
710cd7d
Make validation more robust.
felixfontein Apr 7, 2019
913e0d2
Move generic arithmetic code to module_utils/crypto.py.
felixfontein Apr 7, 2019
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
70 changes: 62 additions & 8 deletions lib/ansible/module_utils/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,34 @@ def get_fingerprint(path, passphrase=None):
privatekey = load_privatekey(path, passphrase, check_passphrase=False)
try:
publickey = crypto.dump_publickey(crypto.FILETYPE_ASN1, privatekey)
return get_fingerprint_of_bytes(publickey)
except AttributeError:
# If PyOpenSSL < 16.0 crypto.dump_publickey() will fail.
# By doing this we prevent the code from raising an error
# yet we return no value in the fingerprint hash.
return None
try:
bio = crypto._new_mem_buf()
rc = crypto._lib.i2d_PUBKEY_bio(bio, privatekey._pkey)
if rc != 1:
crypto._raise_current_error()
publickey = crypto._bio_to_string(bio)
except AttributeError:
# By doing this we prevent the code from raising an error
# yet we return no value in the fingerprint hash.
return None
return get_fingerprint_of_bytes(publickey)


def load_privatekey(path, passphrase=None, check_passphrase=True, backend='pyopenssl'):
"""Load the specified OpenSSL private key."""
def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='pyopenssl'):
"""Load the specified OpenSSL private key.

The content can also be specified via content; in that case,
this function will not load the key from disk.
"""

try:
with open(path, 'rb') as b_priv_key_fh:
priv_key_detail = b_priv_key_fh.read()
if content is None:
with open(path, 'rb') as b_priv_key_fh:
priv_key_detail = b_priv_key_fh.read()
else:
priv_key_detail = content

if backend == 'pyopenssl':

Expand Down Expand Up @@ -773,3 +787,43 @@ def cryptography_get_basic_constraints(constraints):
else:
raise OpenSSLObjectError('Unknown basic constraint "{0}"'.format(constraint))
return ca, path_length


def binary_exp_mod(f, e, m):
'''Computes f^e mod m in O(log e) multiplications modulo m.'''
# Compute len_e = floor(log_2(e))
len_e = -1
x = e
while x > 0:
x >>= 1
len_e += 1
# Compute f**e mod m
result = 1
for k in range(len_e, -1, -1):
result = (result * result) % m
if ((e >> k) & 1) != 0:
result = (result * f) % m
return result


def simple_gcd(a, b):
'''Compute GCD of its two inputs.'''
while b != 0:
a, b = b, a % b
return a


def quick_is_not_prime(n):
'''Does some quick checks to see if we can poke a hole into the primality of n.

A result of `False` does **not** mean that the number is prime; it just means
that we couldn't detect quickly whether it is not prime.
'''
if n <= 2:
return True
# The constant in the next line is the product of all primes < 200
if simple_gcd(n, 7799922041683461553249199106329813876687996789903550945093032474868511536164700810) > 1:
return True
# TODO: maybe do some iterations of Miller-Rabin to increase confidence
# (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test)
return False
2 changes: 1 addition & 1 deletion lib/ansible/modules/crypto/openssl_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1777,7 +1777,7 @@ def main():

# Fail if no backend has been found
if backend == 'auto':
module.fail_json(msg=("Can't detect none of the required Python libraries "
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
Expand Down
15 changes: 13 additions & 2 deletions lib/ansible/modules/crypto/openssl_certificate_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,19 @@ def _get_public_key(self, binary):
self.cert.get_pubkey()
)
except AttributeError:
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
try:
# pyOpenSSL < 16.0:
bio = crypto._new_mem_buf()
if binary:
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.cert.get_pubkey()._pkey)
else:
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
if rc != 1:
crypto._raise_current_error()
return crypto._bio_to_string(bio)
except AttributeError:
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')

def _get_serial_number(self):
return self.cert.get_serial_number()
Expand Down
4 changes: 2 additions & 2 deletions lib/ansible/modules/crypto/openssl_csr.py
Original file line number Diff line number Diff line change
Expand Up @@ -881,8 +881,8 @@ def main():

# Success?
if backend == 'auto':
module.fail_json(msg=('Can detect none of the Python libraries '
'cryptography (>= {0}) and pyOpenSSL (>= {1})').format(
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
try:
Expand Down
30 changes: 24 additions & 6 deletions lib/ansible/modules/crypto/openssl_privatekey.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
- Depending on the curve, you need a newer version of the cryptography backend.
type: str
default: RSA
#choices: [ DSA, ECC, RSA, X448, X25519 ]
#choices: [ DSA, ECC, RSA, X448, X25519, Ed448, Ed25519 ]
choices: [ DSA, ECC, RSA ]
curve:
description:
Expand Down Expand Up @@ -246,6 +246,16 @@
CRYPTOGRAPHY_HAS_X448 = True
except ImportError:
CRYPTOGRAPHY_HAS_X448 = False
try:
import cryptography.hazmat.primitives.asymmetric.ed25519
CRYPTOGRAPHY_HAS_ED25519 = True
except ImportError:
CRYPTOGRAPHY_HAS_ED25519 = False
try:
import cryptography.hazmat.primitives.asymmetric.ed448
CRYPTOGRAPHY_HAS_ED448 = True
except ImportError:
CRYPTOGRAPHY_HAS_ED448 = False

from ansible.module_utils import crypto as crypto_utils
from ansible.module_utils._text import to_native, to_bytes
Expand Down Expand Up @@ -459,6 +469,10 @@ def __init__(self, module):
self.module.fail_json(msg='Your cryptography version does not support X25519')
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':
self.module.fail_json(msg='Your cryptography version does not support Ed25519')
if not CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448':
self.module.fail_json(msg='Your cryptography version does not support Ed448')

def _generate_private_key_data(self):
try:
Expand All @@ -477,6 +491,10 @@ def _generate_private_key_data(self):
self.privatekey = cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.generate()
if CRYPTOGRAPHY_HAS_X448 and self.type == 'X448':
self.privatekey = cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.generate()
if CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
self.privatekey = cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.generate()
if CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448':
self.privatekey = cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.generate()
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 Down Expand Up @@ -573,9 +591,9 @@ def main():
size=dict(type='int', default=4096),
type=dict(type='str', default='RSA', choices=[
'RSA', 'DSA', 'ECC',
# x25519 is missing serialization functions: https://github.com/pyca/cryptography/issues/4386
# x448 is also missing it: https://github.com/pyca/cryptography/pull/4580#issuecomment-437913340
# 'X448', 'X25519',
# 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=[
'secp384r1', 'secp521r1', 'secp224r1', 'secp192r1', 'secp256k1',
Expand Down Expand Up @@ -629,8 +647,8 @@ def main():

# Success?
if backend == 'auto':
module.fail_json(msg=('Can detect none of the Python libraries '
'cryptography (>= {0}) and pyOpenSSL (>= {1})').format(
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
try:
Expand Down