openssl_csr_info and x509_certificate_info: return more public key information (#233)

* Return more public key information.

* Make sure bit size is converted to int first.

* Apply suggestions from code review

Co-authored-by: Ajpantuso <ajpantuso@gmail.com>

* Remove no longer necessary code.

* Use correct return value's name.

* Add trailing commas.

Co-authored-by: Ajpantuso <ajpantuso@gmail.com>
pull/239/head
Felix Fontein 2021-05-19 14:02:45 +02:00 committed by GitHub
parent 3293b77f18
commit 0a0d0f2bdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 209 additions and 28 deletions

View File

@ -0,0 +1,3 @@
minor_changes:
- "openssl_csr_info - now returns ``public_key_type`` and ``public_key_data`` (https://github.com/ansible-collections/community.crypto/pull/233)."
- "x509_certificate_info - now returns ``public_key_type`` and ``public_key_data`` (https://github.com/ansible-collections/community.crypto/pull/233)."

View File

@ -39,6 +39,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_
pyopenssl_normalize_name_attribute, pyopenssl_normalize_name_attribute,
) )
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
get_publickey_info,
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
MINIMAL_PYOPENSSL_VERSION = '0.15' MINIMAL_PYOPENSSL_VERSION = '0.15'
@ -134,7 +138,11 @@ class CertificateInfoRetrieval(object):
pass pass
@abc.abstractmethod @abc.abstractmethod
def _get_public_key(self, binary): def _get_public_key_pem(self):
pass
@abc.abstractmethod
def _get_public_key_object(self):
pass pass
@abc.abstractmethod @abc.abstractmethod
@ -185,9 +193,14 @@ class CertificateInfoRetrieval(object):
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT) result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
result['expired'] = not_after < datetime.datetime.utcnow() result['expired'] = not_after < datetime.datetime.utcnow()
result['public_key'] = self._get_public_key(binary=False) result['public_key'] = self._get_public_key_pem()
pk = self._get_public_key(binary=True)
result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict() public_key_info = get_publickey_info(self.module, self.backend, key=self._get_public_key_object())
result.update({
'public_key_type': public_key_info['type'],
'public_key_data': public_key_info['public_data'],
'public_key_fingerprints': public_key_info['fingerprints'],
})
result['fingerprints'] = get_fingerprint_of_bytes(self._get_der_bytes()) result['fingerprints'] = get_fingerprint_of_bytes(self._get_der_bytes())
@ -330,12 +343,15 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
def get_not_after(self): def get_not_after(self):
return self.cert.not_valid_after return self.cert.not_valid_after
def _get_public_key(self, binary): def _get_public_key_pem(self):
return self.cert.public_key().public_bytes( return self.cert.public_key().public_bytes(
serialization.Encoding.DER if binary else serialization.Encoding.PEM, serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo serialization.PublicFormat.SubjectPublicKeyInfo,
) )
def _get_public_key_object(self):
return self.cert.public_key()
def _get_subject_key_identifier(self): def _get_subject_key_identifier(self):
try: try:
ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
@ -450,20 +466,17 @@ class CertificateInfoRetrievalPyOpenSSL(CertificateInfoRetrieval):
time_string = to_native(self.cert.get_notAfter()) time_string = to_native(self.cert.get_notAfter())
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
def _get_public_key(self, binary): def _get_public_key_pem(self):
try: try:
return crypto.dump_publickey( return crypto.dump_publickey(
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM, crypto.FILETYPE_PEM,
self.cert.get_pubkey() self.cert.get_pubkey(),
) )
except AttributeError: except AttributeError:
try: try:
# pyOpenSSL < 16.0: # pyOpenSSL < 16.0:
bio = crypto._new_mem_buf() bio = crypto._new_mem_buf()
if binary: rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
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: if rc != 1:
crypto._raise_current_error() crypto._raise_current_error()
return crypto._bio_to_string(bio) return crypto._bio_to_string(bio)
@ -471,6 +484,9 @@ class CertificateInfoRetrievalPyOpenSSL(CertificateInfoRetrieval):
self.module.warn('Your pyOpenSSL version does not support dumping public keys. ' 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.') 'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
def _get_public_key_object(self):
return self.cert.get_pubkey()
def _get_subject_key_identifier(self): def _get_subject_key_identifier(self):
# Won't be implemented # Won't be implemented
return None return None

View File

@ -37,6 +37,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_
pyopenssl_parse_name_constraints, pyopenssl_parse_name_constraints,
) )
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
get_publickey_info,
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3' MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
MINIMAL_PYOPENSSL_VERSION = '0.15' MINIMAL_PYOPENSSL_VERSION = '0.15'
@ -113,7 +117,11 @@ class CSRInfoRetrieval(object):
pass pass
@abc.abstractmethod @abc.abstractmethod
def _get_public_key(self, binary): def _get_public_key_pem(self):
pass
@abc.abstractmethod
def _get_public_key_object(self):
pass pass
@abc.abstractmethod @abc.abstractmethod
@ -152,9 +160,14 @@ class CSRInfoRetrieval(object):
result['name_constraints_critical'], result['name_constraints_critical'],
) = self._get_name_constraints() ) = self._get_name_constraints()
result['public_key'] = self._get_public_key(binary=False) result['public_key'] = self._get_public_key_pem()
pk = self._get_public_key(binary=True)
result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict() public_key_info = get_publickey_info(self.module, self.backend, key=self._get_public_key_object())
result.update({
'public_key_type': public_key_info['type'],
'public_key_data': public_key_info['public_data'],
'public_key_fingerprints': public_key_info['fingerprints'],
})
if self.backend != 'pyopenssl': if self.backend != 'pyopenssl':
ski = self._get_subject_key_identifier() ski = self._get_subject_key_identifier()
@ -282,12 +295,15 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
except cryptography.x509.ExtensionNotFound: except cryptography.x509.ExtensionNotFound:
return None, None, False return None, None, False
def _get_public_key(self, binary): def _get_public_key_pem(self):
return self.csr.public_key().public_bytes( return self.csr.public_key().public_bytes(
serialization.Encoding.DER if binary else serialization.Encoding.PEM, serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo serialization.PublicFormat.SubjectPublicKeyInfo,
) )
def _get_public_key_object(self):
return self.csr.public_key()
def _get_subject_key_identifier(self): def _get_subject_key_identifier(self):
try: try:
ext = self.csr.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) ext = self.csr.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
@ -374,19 +390,16 @@ class CSRInfoRetrievalPyOpenSSL(CSRInfoRetrieval):
return permitted, excluded, bool(extension.get_critical()) return permitted, excluded, bool(extension.get_critical())
return None, None, False return None, None, False
def _get_public_key(self, binary): def _get_public_key_pem(self):
try: try:
return crypto.dump_publickey( return crypto.dump_publickey(
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM, crypto.FILETYPE_PEM,
self.csr.get_pubkey() self.csr.get_pubkey(),
) )
except AttributeError: except AttributeError:
try: try:
bio = crypto._new_mem_buf() bio = crypto._new_mem_buf()
if binary: rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.csr.get_pubkey()._pkey)
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.csr.get_pubkey()._pkey)
else:
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.csr.get_pubkey()._pkey)
if rc != 1: if rc != 1:
crypto._raise_current_error() crypto._raise_current_error()
return crypto._bio_to_string(bio) return crypto._bio_to_string(bio)
@ -394,6 +407,9 @@ class CSRInfoRetrievalPyOpenSSL(CSRInfoRetrieval):
self.module.warn('Your pyOpenSSL version does not support dumping public keys. ' 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.') 'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
def _get_public_key_object(self):
return self.csr.get_pubkey()
def _get_subject_key_identifier(self): def _get_subject_key_identifier(self):
# Won't be implemented # Won't be implemented
return None return None

View File

@ -183,6 +183,77 @@ public_key:
returned: success returned: success
type: str type: str
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..." sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
public_key_type:
description:
- The CSR's public key's type.
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
- Will start with C(unknown) if the key type cannot be determined.
returned: success
type: str
version_added: 1.7.0
sample: RSA
public_key_data:
description:
- Public key data. Depends on the public key's type.
returned: success
type: dict
version_added: 1.7.0
contains:
size:
description:
- Bit size of modulus (RSA) or prime number (DSA).
type: int
returned: When C(public_key_type=RSA) or C(public_key_type=DSA)
modulus:
description:
- The RSA key's modulus.
type: int
returned: When C(public_key_type=RSA)
exponent:
description:
- The RSA key's public exponent.
type: int
returned: When C(public_key_type=RSA)
p:
description:
- The C(p) value for DSA.
- This is the prime modulus upon which arithmetic takes place.
type: int
returned: When C(public_key_type=DSA)
q:
description:
- The C(q) value for DSA.
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
multiplicative group of the prime field used.
type: int
returned: When C(public_key_type=DSA)
g:
description:
- The C(g) value for DSA.
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
type: int
returned: When C(public_key_type=DSA)
curve:
description:
- The curve's name for ECC.
type: str
returned: When C(public_key_type=ECC)
exponent_size:
description:
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
type: int
returned: When C(public_key_type=ECC)
x:
description:
- The C(x) coordinate for the public point on the elliptic curve.
type: int
returned: When C(public_key_type=ECC)
y:
description:
- For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
- For C(public_key_type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key.
type: int
returned: When C(public_key_type=DSA) or C(public_key_type=ECC)
public_key_fingerprints: public_key_fingerprints:
description: description:
- Fingerprints of CSR's public key. - Fingerprints of CSR's public key.

View File

@ -227,6 +227,77 @@ public_key:
returned: success returned: success
type: str type: str
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..." sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
public_key_type:
description:
- The certificate's public key's type.
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
- Will start with C(unknown) if the key type cannot be determined.
returned: success
type: str
version_added: 1.7.0
sample: RSA
public_key_data:
description:
- Public key data. Depends on the public key's type.
returned: success
type: dict
version_added: 1.7.0
contains:
size:
description:
- Bit size of modulus (RSA) or prime number (DSA).
type: int
returned: When C(public_key_type=RSA) or C(public_key_type=DSA)
modulus:
description:
- The RSA key's modulus.
type: int
returned: When C(public_key_type=RSA)
exponent:
description:
- The RSA key's public exponent.
type: int
returned: When C(public_key_type=RSA)
p:
description:
- The C(p) value for DSA.
- This is the prime modulus upon which arithmetic takes place.
type: int
returned: When C(public_key_type=DSA)
q:
description:
- The C(q) value for DSA.
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
multiplicative group of the prime field used.
type: int
returned: When C(public_key_type=DSA)
g:
description:
- The C(g) value for DSA.
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
type: int
returned: When C(public_key_type=DSA)
curve:
description:
- The curve's name for ECC.
type: str
returned: When C(public_key_type=ECC)
exponent_size:
description:
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
type: int
returned: When C(public_key_type=ECC)
x:
description:
- The C(x) coordinate for the public point on the elliptic curve.
type: int
returned: When C(public_key_type=ECC)
y:
description:
- For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
- For C(public_key_type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key.
type: int
returned: When C(public_key_type=DSA) or C(public_key_type=ECC)
public_key_fingerprints: public_key_fingerprints:
description: description:
- Fingerprints of certificate's public key. - Fingerprints of certificate's public key.

View File

@ -14,6 +14,8 @@
- result.subject.organizationalUnitName == 'ACME Department' - result.subject.organizationalUnitName == 'ACME Department'
- "['organizationalUnitName', 'Crypto Department'] in result.subject_ordered" - "['organizationalUnitName', 'Crypto Department'] in result.subject_ordered"
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered" - "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
- result.public_key_type == 'RSA'
- result.public_key_data.size == default_rsa_key_size
- name: "({{ select_crypto_backend }}) Check SubjectKeyIdentifier and AuthorityKeyIdentifier" - name: "({{ select_crypto_backend }}) Check SubjectKeyIdentifier and AuthorityKeyIdentifier"
assert: assert:

View File

@ -17,6 +17,8 @@
- result.subject.organizationalUnitName == 'ACME Department' - result.subject.organizationalUnitName == 'ACME Department'
- "['organizationalUnitName', 'Crypto Department'] in result.subject_ordered" - "['organizationalUnitName', 'Crypto Department'] in result.subject_ordered"
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered" - "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
- result.public_key_type == 'RSA'
- result.public_key_data.size == (default_rsa_key_size_certifiates | int)
- name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier - name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier
assert: assert: