mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-07 13:53:06 +00:00
acme_* modules: support private key passprases (#207)
* Support private key passprases. * Use c.c modules for key generation, add first passphrase tests. * Some more passphrase tests.
This commit is contained in:
@@ -57,6 +57,12 @@ options:
|
||||
Ansible in the process of moving the module with its argument to
|
||||
the node where it is executed."
|
||||
type: str
|
||||
account_key_passphrase:
|
||||
description:
|
||||
- Phassphrase to use to decode the account key.
|
||||
- "B(Note:) this is not supported by the C(openssl) backend, only by the C(cryptography) backend."
|
||||
type: str
|
||||
version_added: 1.6.0
|
||||
account_uri:
|
||||
description:
|
||||
- "If specified, assumes that the account URI is as given. If the
|
||||
|
||||
@@ -112,6 +112,7 @@ class ACMEClient(object):
|
||||
# account_key path and content are mutually exclusive
|
||||
self.account_key_file = module.params['account_key_src']
|
||||
self.account_key_content = module.params['account_key_content']
|
||||
self.account_key_passphrase = module.params['account_key_passphrase']
|
||||
|
||||
# Grab account URI from module parameters.
|
||||
# Make sure empty string is treated as None.
|
||||
@@ -122,7 +123,10 @@ class ACMEClient(object):
|
||||
self.account_jws_header = None
|
||||
if self.account_key_file is not None or self.account_key_content is not None:
|
||||
try:
|
||||
self.account_key_data = self.parse_key(key_file=self.account_key_file, key_content=self.account_key_content)
|
||||
self.account_key_data = self.parse_key(
|
||||
key_file=self.account_key_file,
|
||||
key_content=self.account_key_content,
|
||||
passphrase=self.account_key_passphrase)
|
||||
except KeyParsingError as e:
|
||||
raise ModuleFailException("Error while parsing account key: {msg}".format(msg=e.msg))
|
||||
self.account_jwk = self.account_key_data['jwk']
|
||||
@@ -146,14 +150,14 @@ class ACMEClient(object):
|
||||
self.account_jws_header.pop('jwk')
|
||||
self.account_jws_header['kid'] = self.account_uri
|
||||
|
||||
def parse_key(self, key_file=None, key_content=None):
|
||||
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
||||
'''
|
||||
Parses an RSA or Elliptic Curve key file in PEM format and returns key_data.
|
||||
In case of an error, raises KeyParsingError.
|
||||
'''
|
||||
if key_file is None and key_content is None:
|
||||
raise AssertionError('One of key_file and key_content must be specified!')
|
||||
error, key_data = self.backend.parse_key(key_file, key_content)
|
||||
error, key_data = self.backend.parse_key(key_file, key_content, passphrase=passphrase)
|
||||
if error:
|
||||
raise KeyParsingError(error)
|
||||
return key_data
|
||||
@@ -311,6 +315,7 @@ def get_default_argspec():
|
||||
return dict(
|
||||
account_key_src=dict(type='path', aliases=['account_key']),
|
||||
account_key_content=dict(type='str', no_log=True),
|
||||
account_key_passphrase=dict(type='str', no_log=True),
|
||||
account_uri=dict(type='str'),
|
||||
acme_directory=dict(type='str'),
|
||||
acme_version=dict(type='int', choices=[1, 2]),
|
||||
|
||||
@@ -179,7 +179,7 @@ class CryptographyBackend(CryptoBackend):
|
||||
def __init__(self, module):
|
||||
super(CryptographyBackend, self).__init__(module)
|
||||
|
||||
def parse_key(self, key_file=None, key_content=None):
|
||||
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
||||
'''
|
||||
Parses an RSA or Elliptic Curve key file in PEM format and returns a pair
|
||||
(error, key_data).
|
||||
@@ -191,7 +191,10 @@ class CryptographyBackend(CryptoBackend):
|
||||
key_content = to_bytes(key_content)
|
||||
# Parse key
|
||||
try:
|
||||
key = cryptography.hazmat.primitives.serialization.load_pem_private_key(key_content, password=None, backend=_cryptography_backend)
|
||||
key = cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
key_content,
|
||||
password=to_bytes(passphrase) if passphrase is not None else None,
|
||||
backend=_cryptography_backend)
|
||||
except Exception as e:
|
||||
return 'error while loading key: {0}'.format(e), None
|
||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
|
||||
@@ -41,11 +41,13 @@ class OpenSSLCLIBackend(CryptoBackend):
|
||||
openssl_binary = module.get_bin_path('openssl', True)
|
||||
self.openssl_binary = openssl_binary
|
||||
|
||||
def parse_key(self, key_file=None, key_content=None):
|
||||
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
||||
'''
|
||||
Parses an RSA or Elliptic Curve key file in PEM format and returns a pair
|
||||
(error, key_data).
|
||||
'''
|
||||
if passphrase is not None:
|
||||
return 'openssl backend does not support key passphrases', {}
|
||||
# If key_file isn't given, but key_content, write that to a temporary file
|
||||
if key_file is None:
|
||||
fd, tmpsrc = tempfile.mkstemp()
|
||||
|
||||
@@ -19,7 +19,7 @@ class CryptoBackend(object):
|
||||
self.module = module
|
||||
|
||||
@abc.abstractmethod
|
||||
def parse_key(self, key_file=None, key_content=None):
|
||||
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
||||
'''
|
||||
Parses an RSA or Elliptic Curve key file in PEM format and returns a pair
|
||||
(error, key_data).
|
||||
|
||||
@@ -88,6 +88,12 @@ options:
|
||||
- "Mutually exclusive with C(new_account_key_src)."
|
||||
- "Required if C(new_account_key_src) is not used and state is C(changed_key)."
|
||||
type: str
|
||||
new_account_key_passphrase:
|
||||
description:
|
||||
- Phassphrase to use to decode the new account key.
|
||||
- "B(Note:) this is not supported by the C(openssl) backend, only by the C(cryptography) backend."
|
||||
type: str
|
||||
version_added: 1.6.0
|
||||
external_account_binding:
|
||||
description:
|
||||
- Allows to provide external account binding data during account creation.
|
||||
@@ -183,6 +189,7 @@ def main():
|
||||
contact=dict(type='list', elements='str', default=[]),
|
||||
new_account_key_src=dict(type='path'),
|
||||
new_account_key_content=dict(type='str', no_log=True),
|
||||
new_account_key_passphrase=dict(type='str', no_log=True),
|
||||
external_account_binding=dict(type='dict', options=dict(
|
||||
kid=dict(type='str', required=True),
|
||||
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
|
||||
@@ -272,7 +279,8 @@ def main():
|
||||
try:
|
||||
new_key_data = client.parse_key(
|
||||
module.params.get('new_account_key_src'),
|
||||
module.params.get('new_account_key_content')
|
||||
module.params.get('new_account_key_content'),
|
||||
passphrase=module.params.get('new_account_key_passphrase'),
|
||||
)
|
||||
except KeyParsingError as e:
|
||||
raise ModuleFailException("Error while parsing new account key: {msg}".format(msg=e.msg))
|
||||
|
||||
@@ -54,7 +54,6 @@ options:
|
||||
private keys in PEM format can be used as well."
|
||||
- "Mutually exclusive with C(account_key_content)."
|
||||
- "Required if C(account_key_content) is not used."
|
||||
type: path
|
||||
account_key_content:
|
||||
description:
|
||||
- "Content of the ACME account RSA or Elliptic Curve key."
|
||||
@@ -69,7 +68,6 @@ options:
|
||||
temporary file. It can still happen that it is written to disk by
|
||||
Ansible in the process of moving the module with its argument to
|
||||
the node where it is executed."
|
||||
type: str
|
||||
private_key_src:
|
||||
description:
|
||||
- "Path to the certificate's private key."
|
||||
@@ -91,6 +89,12 @@ options:
|
||||
Ansible in the process of moving the module with its argument to
|
||||
the node where it is executed."
|
||||
type: str
|
||||
private_key_passphrase:
|
||||
description:
|
||||
- Phassphrase to use to decode the certificate's private key.
|
||||
- "B(Note:) this is not supported by the C(openssl) backend, only by the C(cryptography) backend."
|
||||
type: str
|
||||
version_added: 1.6.0
|
||||
revoke_reason:
|
||||
description:
|
||||
- "One of the revocation reasonCodes defined in
|
||||
@@ -146,6 +150,7 @@ def main():
|
||||
argument_spec.update(dict(
|
||||
private_key_src=dict(type='path'),
|
||||
private_key_content=dict(type='str', no_log=True),
|
||||
private_key_passphrase=dict(type='str', no_log=True),
|
||||
certificate=dict(type='path', required=True),
|
||||
revoke_reason=dict(type='int'),
|
||||
))
|
||||
@@ -184,9 +189,10 @@ def main():
|
||||
private_key_content = module.params.get('private_key_content')
|
||||
# Revoke certificate
|
||||
if private_key or private_key_content:
|
||||
passphrase = module.params['private_key_passphrase']
|
||||
# Step 1: load and parse private key
|
||||
try:
|
||||
private_key_data = client.parse_key(private_key, private_key_content)
|
||||
private_key_data = client.parse_key(private_key, private_key_content, passphrase=passphrase)
|
||||
except KeyParsingError as e:
|
||||
raise ModuleFailException("Error while parsing private key: {msg}".format(msg=e.msg))
|
||||
# Step 2: sign revokation request with private key
|
||||
|
||||
@@ -52,6 +52,11 @@ options:
|
||||
- "Content of the private key to use for this challenge certificate."
|
||||
- "Mutually exclusive with C(private_key_src)."
|
||||
type: str
|
||||
private_key_passphrase:
|
||||
description:
|
||||
- Phassphrase to use to decode the private key.
|
||||
type: str
|
||||
version_added: 1.6.0
|
||||
notes:
|
||||
- Does not support C(check_mode).
|
||||
'''
|
||||
@@ -187,6 +192,7 @@ def main():
|
||||
challenge_data=dict(type='dict', required=True),
|
||||
private_key_src=dict(type='path'),
|
||||
private_key_content=dict(type='str', no_log=True),
|
||||
private_key_passphrase=dict(type='str', no_log=True),
|
||||
),
|
||||
required_one_of=(
|
||||
['private_key_src', 'private_key_content'],
|
||||
@@ -205,12 +211,16 @@ def main():
|
||||
|
||||
# Get hold of private key
|
||||
private_key_content = module.params.get('private_key_content')
|
||||
private_key_passphrase = module.params.get('private_key_passphrase')
|
||||
if private_key_content is None:
|
||||
private_key_content = read_file(module.params['private_key_src'])
|
||||
else:
|
||||
private_key_content = to_bytes(private_key_content)
|
||||
try:
|
||||
private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key(private_key_content, password=None, backend=_cryptography_backend)
|
||||
private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
private_key_content,
|
||||
password=to_bytes(private_key_passphrase) if private_key_passphrase is not None else None,
|
||||
backend=_cryptography_backend)
|
||||
except Exception as e:
|
||||
raise ModuleFailException('Error while loading private key: {0}'.format(e))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user