Krikri::X509CertInfo
Shared X.509 certificate parsing for the community.crypto info
modules (x509_certificate_info, get_certificate). Both plugins
need the same field set - subject/issuer dicts, validity, extensions,
fingerprints, public key data - so it lives here once, driven by the
openssl CLI (the same backend choice every other crypto plugin in
this tree makes; there is no Python cryptography library to lean on
on the target host).
Field vocabularies were matched against the Ansible module
(community.crypto 3.1.1, ansible-core 2.19.4), whose own output comes
from Python's cryptography library mapped through OpenSSL's
objects.txt long names - which is exactly the vocabulary openssl x509 -text itself prints, so most fields read straight off that
output. The module sorts every list-valued extension it returns
(key usage, extended key usage, basic constraints), which openssl
does not do, so parsed entries are sorted here.
extensions_by_oid (the DER-encoded value of every extension) comes
from the same asn1parse walk for certificates and CSRs - see
parse_extensions_by_oid.
Integers wider than Int64 (RSA moduli, EC coordinates, most serial numbers) go out as JSON numbers too - see json_int.
Constants
openssl's ASN1 OID short names where cryptography names the curve differently (the X9.62 prime curves); every other curve, secp384r1/secp521r1/brainpool* included, agrees.
The real csr_info get_info()'s own key order (community.crypto's module_backends/csr_info.py CSRInfoRetrieval.get_info) - the controller backfills failed/changed after these.
The Ansible module's extensions_by_oid:
cryptography_get_extensions_from_csr - {dotted_oid: {"critical":
bool, "value": base64(DER of the extension value)}}. The extension
value DER is exactly the content of each extension entry's
extnValue OCTET STRING, which openssl asn1parse prints verbatim
as a [HEX DUMP]. asn1parse only names known OIDs symbolically, so a
name table covers the standard X509v3 extensions and an already
dotted OBJECT passes through; names outside the table are skipped
(an unknown key would be wrong, a missing one merely incomplete).
Non-extension PKCS#9 request attributes (challengePassword etc.)
have the same SEQUENCE{OBJECT,OCTET STRING} shape - their OIDs are
excluded explicitly.
hashlib.algorithms_guaranteed - ALL of it, including the two SHAKE
XOFs: the Ansible module emits them too (its bare hexdigest() call
raises TypeError on the XOFs and falls back to hexdigest(32), i.e.
32 BYTES for both shake_128 and shake_256). Crystal's
OpenSSL::Digest cannot finalize an XOF (EVP_DigestFinal_ex has no
XOF length knob - live-verified: it errors on both), so the SHAKE
pair goes through the openssl dgst -shakeN -xoflen 32 CLI
instead (OpenSSL 3.0+, jammy's 3.0.2 included). On an older
openssl the CLI call fails and the two entries are omitted - the
same graceful degradation the hash loop's rescue already applies.
Everything else is emitted under the Ansible module's Python
algorithm names.
Request attributes that share the extension shape but are not X.509 extensions.
The two SHAKE XOFs under their real-module (hashlib) names. The
digest length is fixed at 32 bytes - the Ansible module's
hexdigest(32) fallback, not the XOF's nominal block size.
Class methods
Hex string -> decimal string, for values far wider than Int64 (an RSA modulus is 2048+ bits). Base-10 school multiplication over the hex nibbles - no BigInt shard in this tree.
Reads the same openssl -text shapes the Ansible module's Python backend reads from cryptography objects: RSA gives size/modulus/ exponent, ECC gives curve/x/y/exponent_size, Ed25519/X25519 etc. give an empty public_data dict.
parse_csr's output reordered into the real get_info() order, with extensions_by_oid added - the shape both openssl_csr_info (as the task result) and openssl_csr (as its diff's before/after payload) must reproduce.
A value that fits Int64 stays a JSON number (matching the real module's Python ints); anything wider - RSA moduli, ECC coordinates, big serial numbers - goes out as the BIGINT_SENTINEL_PREFIX-marked decimal string, because this engine's result world is JSON::Any (Int64 at widest) and truncating digits would be worse than a string. BasePlugin#run_and_capture rewrites the marked string to its bare digits on the JSON wire, and the controller's bigint-aware parse (Krikri::BigJson) turns those bytes back into the marked string - so every renderer downstream prints the value exactly like Ansible's Python int.
Public wrapper for the private-key modules: the decimal value of a labeled hex block ("prime1:", "priv:", ...) as a JSON int-or-string.
The main entry point: cert_pem is the PEM text of one certificate. Returns the module's result fields as a hash, or nil if openssl cannot parse it at all.
The openssl_csr_info half of the family: parses a PKCS#10
certificate request and returns the fields the Ansible module
(community.crypto 3.1.1, csr_info.py's get_info) returns that this
openssl-CLI backend can produce: subject, subject_ordered,
key_usage/extended_key_usage/basic_constraints/ocsp_must_staple/
subject_alt_name (each with its _critical flag, absent exactly
when the extension is absent - matching the real backend's None),
public_key, public_key_type, public_key_data,
public_key_fingerprints, and signature_valid (openssl's own
req -verify - the Ansible module asks cryptography's
is_signature_valid).
Same deliberate divergence as the certificate half above: extensions_by_oid, subject_key_identifier, authority_key_identifier and the name_constraints_* fields are not returned (they need an ASN.1 decoder this tree does not carry).