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:
Yanis Guenane 2017-09-14 18:03:00 +02:00 committed by Sam Doran
parent 1950bcc14e
commit 0648e339a7
6 changed files with 63 additions and 40 deletions

View file

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

View file

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

View file

@ -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()

View file

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

View file

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

View file

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