diff --git a/changelogs/fragments/233-public-key-info.yml b/changelogs/fragments/233-public-key-info.yml new file mode 100644 index 00000000..42c36a6f --- /dev/null +++ b/changelogs/fragments/233-public-key-info.yml @@ -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)." diff --git a/plugins/module_utils/crypto/module_backends/certificate_info.py b/plugins/module_utils/crypto/module_backends/certificate_info.py index 42c136d1..f329d168 100644 --- a/plugins/module_utils/crypto/module_backends/certificate_info.py +++ b/plugins/module_utils/crypto/module_backends/certificate_info.py @@ -39,6 +39,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_ 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_PYOPENSSL_VERSION = '0.15' @@ -134,7 +138,11 @@ class CertificateInfoRetrieval(object): pass @abc.abstractmethod - def _get_public_key(self, binary): + def _get_public_key_pem(self): + pass + + @abc.abstractmethod + def _get_public_key_object(self): pass @abc.abstractmethod @@ -185,9 +193,14 @@ class CertificateInfoRetrieval(object): result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT) result['expired'] = not_after < datetime.datetime.utcnow() - result['public_key'] = self._get_public_key(binary=False) - pk = self._get_public_key(binary=True) - result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict() + result['public_key'] = self._get_public_key_pem() + + 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()) @@ -330,12 +343,15 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval): def get_not_after(self): 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( - serialization.Encoding.DER if binary else serialization.Encoding.PEM, - serialization.PublicFormat.SubjectPublicKeyInfo + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo, ) + def _get_public_key_object(self): + return self.cert.public_key() + def _get_subject_key_identifier(self): try: 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()) 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: return crypto.dump_publickey( - crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM, - self.cert.get_pubkey() + crypto.FILETYPE_PEM, + self.cert.get_pubkey(), ) except AttributeError: try: # pyOpenSSL < 16.0: 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: crypto._raise_current_error() 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. ' '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): # Won't be implemented return None diff --git a/plugins/module_utils/crypto/module_backends/csr_info.py b/plugins/module_utils/crypto/module_backends/csr_info.py index 6d27acf2..7fc8fcef 100644 --- a/plugins/module_utils/crypto/module_backends/csr_info.py +++ b/plugins/module_utils/crypto/module_backends/csr_info.py @@ -37,6 +37,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_ 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_PYOPENSSL_VERSION = '0.15' @@ -113,7 +117,11 @@ class CSRInfoRetrieval(object): pass @abc.abstractmethod - def _get_public_key(self, binary): + def _get_public_key_pem(self): + pass + + @abc.abstractmethod + def _get_public_key_object(self): pass @abc.abstractmethod @@ -152,9 +160,14 @@ class CSRInfoRetrieval(object): result['name_constraints_critical'], ) = self._get_name_constraints() - result['public_key'] = self._get_public_key(binary=False) - pk = self._get_public_key(binary=True) - result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict() + result['public_key'] = self._get_public_key_pem() + + 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': ski = self._get_subject_key_identifier() @@ -282,12 +295,15 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval): except cryptography.x509.ExtensionNotFound: return None, None, False - def _get_public_key(self, binary): + def _get_public_key_pem(self): return self.csr.public_key().public_bytes( - serialization.Encoding.DER if binary else serialization.Encoding.PEM, - serialization.PublicFormat.SubjectPublicKeyInfo + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo, ) + def _get_public_key_object(self): + return self.csr.public_key() + def _get_subject_key_identifier(self): try: 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 None, None, False - def _get_public_key(self, binary): + def _get_public_key_pem(self): try: return crypto.dump_publickey( - crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM, - self.csr.get_pubkey() + crypto.FILETYPE_PEM, + self.csr.get_pubkey(), ) except AttributeError: try: 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: crypto._raise_current_error() 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. ' '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): # Won't be implemented return None diff --git a/plugins/modules/openssl_csr_info.py b/plugins/modules/openssl_csr_info.py index f744a60c..103ef2a2 100644 --- a/plugins/modules/openssl_csr_info.py +++ b/plugins/modules/openssl_csr_info.py @@ -183,6 +183,77 @@ public_key: returned: success type: str 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: description: - Fingerprints of CSR's public key. diff --git a/plugins/modules/x509_certificate_info.py b/plugins/modules/x509_certificate_info.py index 18aaf4c0..9c93039b 100644 --- a/plugins/modules/x509_certificate_info.py +++ b/plugins/modules/x509_certificate_info.py @@ -227,6 +227,77 @@ public_key: returned: success type: str 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: description: - Fingerprints of certificate's public key. diff --git a/tests/integration/targets/openssl_csr_info/tasks/impl.yml b/tests/integration/targets/openssl_csr_info/tasks/impl.yml index bc9037eb..dc8d694e 100644 --- a/tests/integration/targets/openssl_csr_info/tasks/impl.yml +++ b/tests/integration/targets/openssl_csr_info/tasks/impl.yml @@ -14,6 +14,8 @@ - result.subject.organizationalUnitName == 'ACME Department' - "['organizationalUnitName', 'Crypto 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" assert: diff --git a/tests/integration/targets/x509_certificate_info/tasks/impl.yml b/tests/integration/targets/x509_certificate_info/tasks/impl.yml index 91838bd4..90f8e70f 100644 --- a/tests/integration/targets/x509_certificate_info/tasks/impl.yml +++ b/tests/integration/targets/x509_certificate_info/tasks/impl.yml @@ -17,6 +17,8 @@ - result.subject.organizationalUnitName == 'ACME Department' - "['organizationalUnitName', 'Crypto 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 assert: