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
parent
3293b77f18
commit
0a0d0f2bdf
|
@ -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)."
|
|
@ -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,19 +466,16 @@ 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.i2d_PUBKEY_bio(bio, self.cert.get_pubkey()._pkey)
|
|
||||||
else:
|
|
||||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
|
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()
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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,18 +390,15 @@ 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.i2d_PUBKEY_bio(bio, self.csr.get_pubkey()._pkey)
|
|
||||||
else:
|
|
||||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.csr.get_pubkey()._pkey)
|
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()
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue