Class: Msf::Trace::CertificateTracePresenter
- Inherits:
-
Object
- Object
- Msf::Trace::CertificateTracePresenter
- Defined in:
- lib/msf/core/trace/certificate_trace_presenter.rb
Overview
Presenter for X.509 certificates.
Follows the same responsibility split as Rex::Proto::Kerberos::CredentialCache::Krb5CCachePresenter: this class constructs formatted strings only. The caller (module instance) is responsible for invoking its own print methods so output is correctly associated with the running module.
Usage:
presenter = Msf::Trace::CertificateTracePresenter.new(cert)
mod.print_line(presenter.)
mod.print_line(presenter.to_s_full)
# CSR mode - present a certificate signing request before it is submitted
# (e.g. the AD CS / MS-ICPR enrollment flow). +attributes+ is the enrollment
# attribute hash returned alongside the CSR by CertRequest#create_csr.
presenter = Msf::Trace::CertificateTracePresenter.new
mod.print_line(presenter.(csr))
mod.print_line(presenter.to_s_csr_full(csr, attributes))
Constant Summary collapse
- SEPARATOR_WIDTH =
‘[CertificateTrace] ’ (19) + 38 dashes, preserved
57- NAMED_OIDS =
OIDs surfaced as named fields in to_s_full; excluded from the raw extension dump.
%w[subjectAltName extendedKeyUsage keyUsage].freeze
- CERT_TEMPLATE_NAME_OID =
Microsoft AD CS enrollment extensions whose content carries the template name / template version. OpenSSL has no friendly decoder for these, so we decode them ourselves below.
'1.3.6.1.4.1.311.20.2'- CERT_TEMPLATE_INFO_OID =
'1.3.6.1.4.1.311.21.7'- APPLICATION_POLICIES_OID =
Microsoft Application Policies extension. Its content is a CertificatePolicies SEQUENCE OF PolicyInformation, which the framework already decodes for the icpr_cert workflow; we resolve each policy OID to its friendly label rather than dumping the raw bytes (the policy OIDs - e.g. Client Authentication - are central to ESC attack triage).
'1.3.6.1.4.1.311.21.10'- EXTENSION_OID_NAMES =
Friendly labels for extension OIDs that OpenSSL leaves as raw numeric strings (predominantly Microsoft enrollment OIDs on AD CS certificates).
{ CERT_TEMPLATE_NAME_OID => 'Certificate Template Name', CERT_TEMPLATE_INFO_OID => 'Certificate Template Information', APPLICATION_POLICIES_OID => 'Application Policies', '1.3.6.1.4.1.311.25.2' => 'AD DS Security Extension (SID)' }.freeze
- OPENSSL_READABLE_EXTENSIONS =
Standard PKIX extensions OpenSSL renders as clean, human-readable text. For any other extension - notably the Microsoft AD CS enrollment OIDs - OpenSSL emits a lossy byte dump (raw bytes on OpenSSL, non-printables collapsed to ‘.’ on LibreSSL), so we hex-encode the raw extnValue instead of printing mojibake. Matched against the short name OpenSSL reports for recognised OIDs.
%w[ basicConstraints authorityKeyIdentifier subjectKeyIdentifier crlDistributionPoints authorityInfoAccess certificatePolicies issuerAltName nameConstraints nsComment nsCertType ].freeze
- IDENTITY_SOURCES =
Priority order for resolving a single auth identity from the cert. UPN is the primary AD identity; email and CN are fallbacks.
[ [:upn, 'UPN'], [:email, 'Email SAN'], [:cn, 'Subject CN'] ].freeze
Class Method Summary collapse
-
.coerce(cert) ⇒ OpenSSL::X509::Certificate?
Attempt to coerce input into an OpenSSL::X509::Certificate.
-
.coerce_csr(csr) ⇒ OpenSSL::X509::Request?
Attempt to coerce input into an OpenSSL::X509::Request (CSR).
Instance Method Summary collapse
-
#initialize(cert = nil) ⇒ CertificateTracePresenter
constructor
A new instance of CertificateTracePresenter.
-
#to_s_csr_full(csr, attributes = {}) ⇒ String?
Returns a formatted full CSR string: metadata plus the requested template and SAN (taken from the enrollment
attributeshash) and any extensions carried in the CSR’s PKCS#9 extensionRequest attribute. -
#to_s_csr_metadata(csr) ⇒ String?
Returns a formatted CSR metadata string: subject, public key, signature algorithm.
-
#to_s_full(label: 'x.509') ⇒ String?
Returns a formatted full string: metadata + serial, version, public key algorithm, SAN / EKU / Key Usage as named fields, then remaining extensions.
-
#to_s_metadata(label: 'x.509') ⇒ String?
Returns a formatted metadata string: subject, issuer, validity, SHA-256 fingerprint.
Constructor Details
#initialize(cert = nil) ⇒ CertificateTracePresenter
Returns a new instance of CertificateTracePresenter.
117 118 119 |
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 117 def initialize(cert = nil) @cert = self.class.coerce(cert) end |
Class Method Details
.coerce(cert) ⇒ OpenSSL::X509::Certificate?
Attempt to coerce input into an OpenSSL::X509::Certificate. Accepts a live certificate object, an OpenSSL::PKCS12 bundle (extracts the leaf certificate), or raw DER/PEM bytes.
88 89 90 91 92 93 94 95 96 |
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 88 def self.coerce(cert) return cert if cert.is_a?(OpenSSL::X509::Certificate) return cert.certificate if cert.is_a?(OpenSSL::PKCS12) return OpenSSL::X509::Certificate.new(cert) if cert.is_a?(String) nil rescue OpenSSL::X509::CertificateError, OpenSSL::PKCS12::PKCS12Error nil end |
.coerce_csr(csr) ⇒ OpenSSL::X509::Request?
Attempt to coerce input into an OpenSSL::X509::Request (CSR). Accepts a live request object (the type CertRequest#create_csr returns is a plain OpenSSL::X509::Request) or raw DER/PEM bytes. Anything else - for example the CMC ContentInfo wrapper produced for on-behalf-of enrollments - is returned as nil so the caller silently skips the trace.
106 107 108 109 110 111 112 113 114 |
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 106 def self.coerce_csr(csr) return csr if csr.is_a?(OpenSSL::X509::Request) return OpenSSL::X509::Request.new(csr) if csr.is_a?(String) return OpenSSL::X509::Request.new(csr.to_der) if csr.respond_to?(:to_der) nil rescue OpenSSL::X509::RequestError, OpenSSL::ASN1::ASN1Error nil end |
Instance Method Details
#to_s_csr_full(csr, attributes = {}) ⇒ String?
Returns a formatted full CSR string: metadata plus the requested template and SAN (taken from the enrollment attributes hash) and any extensions carried in the CSR’s PKCS#9 extensionRequest attribute.
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 213 def to_s_csr_full(csr, attributes = {}) csr = self.class.coerce_csr(csr) base = (csr) return nil unless base attributes ||= {} lines = [base] template = attributes['CertificateTemplate'] || attributes[:CertificateTemplate] lines << " Req Template : #{template}" if template san = attributes['SAN'] || attributes[:SAN] lines << " Req SAN : #{san}" if san # subjectAltName is already surfaced above as the friendly "Req SAN" line; # drop it from the raw dump so we don't repeat it as an opaque hex blob. extensions = csr_extensions(csr).reject { |e| e.oid == 'subjectAltName' } if extensions.any? lines << ' Req Extns :' extensions.each do |e| label = EXTENSION_OID_NAMES[normalize_oid(e.oid)] || e.oid lines << " #{label} : #{format_extension(e)}" end end lines.join("\n") end |
#to_s_csr_metadata(csr) ⇒ String?
Returns a formatted CSR metadata string: subject, public key, signature algorithm. Mirrors #to_s_metadata for the request side of an enrollment.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 188 def (csr) csr = self.class.coerce_csr(csr) return nil unless csr lines = [trace_separator('CSR'), " Subject : #{csr.subject}"] public_key = csr_public_key(csr) lines << " Public Key : #{format_public_key(public_key)}" if public_key sig_alg = csr_signature_algorithm(csr) lines << " Sig Alg : #{sig_alg}" if sig_alg lines.join("\n") rescue StandardError nil end |
#to_s_full(label: 'x.509') ⇒ String?
Returns a formatted full string: metadata + serial, version, public key algorithm, SAN / EKU / Key Usage as named fields, then remaining extensions.
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 146 def to_s_full(label: 'x.509') base = (label: label) return nil unless base lines = [base] lines << " Serial : #{@cert.serial}" # OpenSSL exposes the zero-based encoded X.509 version (v3 == 2). lines << " Version : v#{@cert.version + 1}" lines << " Public Key : #{format_public_key(@cert.public_key)}" identities = parse_san_identities identity_key, identity_label = IDENTITY_SOURCES.find { |key, _| identities[key] } identity_value = identity_key ? identities[identity_key] : subject_cn identity_source = identity_key ? identity_label : 'Subject CN' lines << " Identity : #{identity_value} (#{identity_source})" if identity_value san = extension_value('subjectAltName') lines << " SAN : #{san}" if san eku = extension_value('extendedKeyUsage') lines << " EKU : #{eku}" if eku ku = extension_value('keyUsage') lines << " Key Usage : #{ku}" if ku other = @cert.extensions.reject { |e| NAMED_OIDS.include?(e.oid) } if other.any? lines << ' Extensions :' other.each do |e| label = EXTENSION_OID_NAMES[normalize_oid(e.oid)] || e.oid lines << " #{label} : #{format_extension(e)}" end end lines.join("\n") end |
#to_s_metadata(label: 'x.509') ⇒ String?
Returns a formatted metadata string: subject, issuer, validity, SHA-256 fingerprint.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 126 def (label: 'x.509') return nil unless @cert fingerprint = OpenSSL::Digest::SHA256.hexdigest(@cert.to_der) [ trace_separator(label), " Subject : #{@cert.subject}", " Issuer : #{@cert.issuer}", " Not Before : #{@cert.not_before}", " Not After : #{@cert.not_after}", " SHA-256 : #{fingerprint}" ].join("\n") end |