Class: Msf::Trace::CertificateTracePresenter

Inherits:
Object
  • Object
show all
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)

Constant Summary collapse

SEPARATOR =
('[CertificateTrace] ' + ('-' * 38)).freeze
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

Instance Method Summary collapse

Constructor Details

#initialize(cert) ⇒ CertificateTracePresenter

Returns a new instance of CertificateTracePresenter.

Parameters:

  • cert (OpenSSL::X509::Certificate, OpenSSL::PKCS12, String)


92
93
94
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 92

def initialize(cert)
  @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.

Parameters:

  • cert (OpenSSL::X509::Certificate, OpenSSL::PKCS12, String)

Returns:

  • (OpenSSL::X509::Certificate, nil)


81
82
83
84
85
86
87
88
89
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 81

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

Instance Method Details

#to_s_fullString?

Returns a formatted full string: metadata + serial, version, public key algorithm, SAN / EKU / Key Usage as named fields, then remaining extensions.

Returns:

  • (String, nil)

    nil if certificate could not be parsed



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 118

def to_s_full
  base = 
  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_metadataString?

Returns a formatted metadata string: subject, issuer, validity, SHA-256 fingerprint.

Returns:

  • (String, nil)

    nil if certificate could not be parsed



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/msf/core/trace/certificate_trace_presenter.rb', line 99

def 
  return nil unless @cert

  fingerprint = OpenSSL::Digest::SHA256.hexdigest(@cert.to_der)

  [
    SEPARATOR,
    "  Subject    : #{@cert.subject}",
    "  Issuer     : #{@cert.issuer}",
    "  Not Before : #{@cert.not_before}",
    "  Not After  : #{@cert.not_after}",
    "  SHA-256    : #{fingerprint}"
  ].join("\n")
end