openssl: remove static dict for keyUsage (#30339)
keyUsage and extendedKeyUsage are currently statically limited via a static dict defined in modules_utils/crypto.py. If one specify a value that isn't in there, idempotency won't work. Instead of having static dict, we uses keyUsage and extendedKyeUsage values OpenSSL NID and compare those rather than comparing strings. Fixes: https://github.com/ansible/ansible/issues/30316
This commit is contained in:
parent
1950bcc14e
commit
0648e339a7
6 changed files with 63 additions and 40 deletions
|
@ -96,34 +96,6 @@ def load_certificate_request(path):
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc)
|
||||||
|
|
||||||
|
|
||||||
keyUsageLong = {
|
|
||||||
"digitalSignature": "Digital Signature",
|
|
||||||
"nonRepudiation": "Non Repudiation",
|
|
||||||
"keyEncipherment": "Key Encipherment",
|
|
||||||
"dataEncipherment": "Data Encipherment",
|
|
||||||
"keyAgreement": "Key Agreement",
|
|
||||||
"keyCertSign": "Certificate Sign",
|
|
||||||
"cRLSign": "CRL Sign",
|
|
||||||
"encipherOnly": "Encipher Only",
|
|
||||||
"decipherOnly": "Decipher Only",
|
|
||||||
}
|
|
||||||
|
|
||||||
extendedKeyUsageLong = {
|
|
||||||
"anyExtendedKeyUsage": "Any Extended Key Usage",
|
|
||||||
"ipsecEndSystem": "IPSec End System",
|
|
||||||
"ipsecTunnel": "IPSec Tunnel",
|
|
||||||
"ipsecUser": "IPSec User",
|
|
||||||
"msSGC": "Microsoft Server Gated Crypto",
|
|
||||||
"nsSGC": "Netscape Server Gated Crypto",
|
|
||||||
"serverAuth": "TLS Web Server Authentication",
|
|
||||||
"clientAuth": "TLS Web Client Authentication",
|
|
||||||
"codeSigning": "Code Signing",
|
|
||||||
"emailProtection": "E-mail Protection",
|
|
||||||
"timeStamping": "Time Stamping",
|
|
||||||
"OCSPSigning": "OCSP Signing",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class OpenSSLObject(object):
|
class OpenSSLObject(object):
|
||||||
|
|
||||||
|
|
|
@ -538,9 +538,10 @@ class AssertOnlyCertificate(Certificate):
|
||||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||||
extension = self.cert.get_extension(extension_idx)
|
extension = self.cert.get_extension(extension_idx)
|
||||||
if extension.get_short_name() == 'keyUsage':
|
if extension.get_short_name() == 'keyUsage':
|
||||||
keyUsage = [crypto_utils.keyUsageLong.get(keyUsage, keyUsage) for keyUsage in self.keyUsage]
|
keyUsage = [OpenSSL._util.lib.OBJ_txt2nid(keyUsage) for keyUsage in self.keyUsage]
|
||||||
if (not self.keyUsage_strict and not all(x in str(extension).split(', ') for x in keyUsage)) or \
|
current_ku = [OpenSSL._util.lib.OBJ_txt2nid(usage.strip()) for usage in str(extension).split(',')]
|
||||||
(self.keyUsage_strict and not set(keyUsage) == set(str(extension).split(', '))):
|
if (not self.keyUsage_strict and not all(x in current_ku for x in keyUsage)) or \
|
||||||
|
(self.keyUsage_strict and not set(keyUsage) == set(current_ku)):
|
||||||
self.message.append(
|
self.message.append(
|
||||||
'Invalid keyUsage component (got %s, expected all of %s to be present)' % (str(extension).split(', '), keyUsage)
|
'Invalid keyUsage component (got %s, expected all of %s to be present)' % (str(extension).split(', '), keyUsage)
|
||||||
)
|
)
|
||||||
|
@ -550,9 +551,10 @@ class AssertOnlyCertificate(Certificate):
|
||||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||||
extension = self.cert.get_extension(extension_idx)
|
extension = self.cert.get_extension(extension_idx)
|
||||||
if extension.get_short_name() == 'extendedKeyUsage':
|
if extension.get_short_name() == 'extendedKeyUsage':
|
||||||
extKeyUsage = [crypto_utils.extendedKeyUsageLong.get(keyUsage, keyUsage) for keyUsage in self.extendedKeyUsage]
|
extKeyUsage = [OpenSSL._util.lib.OBJ_txt2nid(keyUsage) for keyUsage in self.extendedKeyUsage]
|
||||||
if (not self.extendedKeyUsage_strict and not all(x in str(extension).split(', ') for x in extKeyUsage)) or \
|
current_xku = [OpenSSL._util.lib.OBJ_txt2nid(usage.strip()) for usage in str(extension).split(',')]
|
||||||
(self.extendedKeyUsage_strict and not set(extKeyUsage) == set(str(extension).split(', '))):
|
if (not self.extendedKeyUsage_strict and not all(x in current_xku for x in extKeyUsage)) or \
|
||||||
|
(self.extendedKeyUsage_strict and not set(extKeyUsage) == set(current_xku)):
|
||||||
self.message.append(
|
self.message.append(
|
||||||
'Invalid extendedKeyUsage component (got %s, expected all of %s to be present)' % (str(extension).split(', '), extKeyUsage)
|
'Invalid extendedKeyUsage component (got %s, expected all of %s to be present)' % (str(extension).split(', '), extKeyUsage)
|
||||||
)
|
)
|
||||||
|
|
|
@ -227,9 +227,10 @@ import os
|
||||||
|
|
||||||
from ansible.module_utils import crypto as crypto_utils
|
from ansible.module_utils import crypto as crypto_utils
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native, to_bytes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
import OpenSSL
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pyopenssl_found = False
|
pyopenssl_found = False
|
||||||
|
@ -348,22 +349,22 @@ class CertificateSigningRequest(crypto_utils.OpenSSLObject):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _check_keyUsage_(extensions, extName, expected, critical, long):
|
def _check_keyUsage_(extensions, extName, expected, critical):
|
||||||
usages_ext = [ext for ext in extensions if ext.get_short_name() == extName]
|
usages_ext = [ext for ext in extensions if ext.get_short_name() == extName]
|
||||||
if (not usages_ext and expected) or (usages_ext and not expected):
|
if (not usages_ext and expected) or (usages_ext and not expected):
|
||||||
return False
|
return False
|
||||||
elif not usages_ext and not expected:
|
elif not usages_ext and not expected:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
current = [usage.strip() for usage in str(usages_ext[0]).split(',')]
|
current = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage.strip())) for usage in str(usages_ext[0]).split(',')]
|
||||||
expected = [long[usage] if usage in long else usage for usage in expected]
|
expected = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage)) for usage in expected]
|
||||||
return set(current) == set(expected) and usages_ext[0].get_critical() == critical
|
return set(current) == set(expected) and usages_ext[0].get_critical() == critical
|
||||||
|
|
||||||
def _check_keyUsage(extensions):
|
def _check_keyUsage(extensions):
|
||||||
return _check_keyUsage_(extensions, b'keyUsage', self.keyUsage, self.keyUsage_critical, crypto_utils.keyUsageLong)
|
return _check_keyUsage_(extensions, b'keyUsage', self.keyUsage, self.keyUsage_critical)
|
||||||
|
|
||||||
def _check_extenededKeyUsage(extensions):
|
def _check_extenededKeyUsage(extensions):
|
||||||
return _check_keyUsage_(extensions, b'extendedKeyUsage', self.extendedKeyUsage, self.extendedKeyUsage_critical, crypto_utils.extendedKeyUsageLong)
|
return _check_keyUsage_(extensions, b'extendedKeyUsage', self.extendedKeyUsage, self.extendedKeyUsage_critical)
|
||||||
|
|
||||||
def _check_extensions(csr):
|
def _check_extensions(csr):
|
||||||
extensions = csr.get_extensions()
|
extensions = csr.get_extensions()
|
||||||
|
|
|
@ -51,6 +51,11 @@
|
||||||
path: '{{ output_dir }}/csr2.csr'
|
path: '{{ output_dir }}/csr2.csr'
|
||||||
privatekey_path: '{{ output_dir }}/privatekey2.pem'
|
privatekey_path: '{{ output_dir }}/privatekey2.pem'
|
||||||
CN: 'www.example.com'
|
CN: 'www.example.com'
|
||||||
|
keyUsage:
|
||||||
|
- digitalSignature
|
||||||
|
extendedKeyUsage:
|
||||||
|
- ipsecUser
|
||||||
|
- biometricInfo
|
||||||
|
|
||||||
- name: Generate selfsigned certificate2
|
- name: Generate selfsigned certificate2
|
||||||
openssl_certificate:
|
openssl_certificate:
|
||||||
|
@ -77,6 +82,11 @@
|
||||||
L: Los Angeles
|
L: Los Angeles
|
||||||
O: ACME Inc.
|
O: ACME Inc.
|
||||||
OU: Roadrunner pest control
|
OU: Roadrunner pest control
|
||||||
|
keyUsage:
|
||||||
|
- digitalSignature
|
||||||
|
extendedKeyUsage:
|
||||||
|
- ipsecUser
|
||||||
|
- biometricInfo
|
||||||
|
|
||||||
- import_tasks: ../tests/validate.yml
|
- import_tasks: ../tests/validate.yml
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,39 @@
|
||||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||||
commonName: 'www.ansible.com'
|
commonName: 'www.ansible.com'
|
||||||
|
|
||||||
|
# keyUsage longname and shortname should be able to be used
|
||||||
|
# interchangeably. Hence the long name is specified here
|
||||||
|
# but the short name is used to test idempotency for ipsecuser
|
||||||
|
# and vice-versa for biometricInfo
|
||||||
|
- name: Generate CSR with KU and XKU
|
||||||
|
openssl_csr:
|
||||||
|
path: '{{ output_dir }}/csr_ku_xku.csr'
|
||||||
|
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||||
|
commonName: 'www.ansible.com'
|
||||||
|
keyUsage:
|
||||||
|
- digitalSignature
|
||||||
|
- keyAgreement
|
||||||
|
extendedKeyUsage:
|
||||||
|
- qcStatements
|
||||||
|
- DVCS
|
||||||
|
- IPSec User
|
||||||
|
- biometricInfo
|
||||||
|
|
||||||
|
- name: Generate CSR with KU and XKU (test idempotency)
|
||||||
|
openssl_csr:
|
||||||
|
path: '{{ output_dir }}/csr_ku_xku.csr'
|
||||||
|
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||||
|
commonName: 'www.ansible.com'
|
||||||
|
keyUsage:
|
||||||
|
- digitalSignature
|
||||||
|
- keyAgreement
|
||||||
|
extendedKeyUsage:
|
||||||
|
- ipsecUser
|
||||||
|
- qcStatements
|
||||||
|
- DVCS
|
||||||
|
- Biometric Info
|
||||||
|
register: csr_ku_xku
|
||||||
|
|
||||||
- import_tasks: ../tests/validate.yml
|
- import_tasks: ../tests/validate.yml
|
||||||
|
|
||||||
when: pyopenssl_version.stdout|version_compare('0.15', '>=')
|
when: pyopenssl_version.stdout|version_compare('0.15', '>=')
|
||||||
|
|
|
@ -15,3 +15,8 @@
|
||||||
that:
|
that:
|
||||||
- csr_cn.stdout.split('=')[-1] == 'www.ansible.com'
|
- csr_cn.stdout.split('=')[-1] == 'www.ansible.com'
|
||||||
- csr_modulus.stdout == privatekey_modulus.stdout
|
- csr_modulus.stdout == privatekey_modulus.stdout
|
||||||
|
|
||||||
|
- name: Validate CSR_KU_XKU (assert idempotency)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- csr_ku_xku.changed == False
|
||||||
|
|
Loading…
Reference in a new issue