Skip to content

Internal certificate scanners

Preferred method: CertIntel Agent

The CertIntel Agent provides internal TLS monitoring, certificate-file inventory and reporting with built-in scheduling. The Windows Agent also monitors certificate stores and AD CS templates. It makes these tasks easier to configure and maintain. Start with the Windows Agent or Linux Agent, then configure Agent monitors.

The scripts below are API reporting examples that can submit similar information when you need a custom integration or standalone script. They are optional alternatives; the Agent does not require them.

For certificates on hosts inside an organization's network that CertIntel can't reach itself. A script running inside that network reports each result inward with a write-scoped key scoped to internal_certs. Everything POSTs to one endpoint:

POST /api/v1/internal-certs
X-Api-Key: cik_<prefix>.<secret>
Content-Type: application/json; charset=utf-8

Returns 201. Rows are upserted server-side (not appended). A field over its size limit (for example notes above 8,192 characters) makes the request fail with 422 Request validation failed. and nothing is stored.

Reported certificates land in the unified inventory at Certificates › source: Internal scan; each certificate's detail dialog lists every scanner that has seen it.

Fields

Field Type Notes
source_method string https (live probe) or local_store (certificate store read) are the values the dashboard understands; any lowercase identifier (^[a-z][a-z0-9_.-]*$, up to 64 characters) is accepted
discovery_kind enum optional, local_store only - see below
template_name string required when discovery_kind is adcs_template; rejected (422) otherwise
thumbprint string required
target_host, target_port string / integer for https scans
store_path string for local_store scans, e.g. Cert:\LocalMachine\My
subject_cn, issuer_cn string optional
not_before, not_after string (ISO-8601) optional
friendly_name, owner string optional technician guidance, up to 200 characters
notes string optional notes, up to 8,192 characters
serial_number, certificate_sha256, spki_sha256 string optional additional identifiers
certificate_b64, chain_b64 base64 DER (string / string[]) optional observed certificate and presented chain (up to ten certificates, each up to 32,768 characters), used for certification-path display and validation
x509_extensions object[] optional, at most 32 {oid, critical, value} entries with base64 DER values up to 1,024 bytes each; normally supplied only by the Agent
run_as string optional; the Windows identity that ran the scan
computer_name string the reporting machine

The upsert identity is target_host:target_port | thumbprint for an https scan and store_path | thumbprint for a local_store scan, combined with the reporting key - so re-running the same scan updates its rows while a second scanning device stays distinct. discovery_kind is not part of that identity, but template_name is: an adcs_template report is keyed store_path | template_name | thumbprint, so the same certificate reported by both scanners appears as two rows (one per label). Two reports that both omit template_name share one row and the most recent report's discovery_kind wins.

discovery_kind

An optional string on local_store reports only - the API rejects it (422) on an https report. One of:

Value Meaning
certificate_file Read from a file path rather than a Windows store
windows_cert_store Read from a Windows certificate store, e.g. LocalMachine\My
adcs_template Matched to a monitored AD CS certificate template (see below); requires template_name

A local_store report that omits discovery_kind still works - there's no migration deadline - but the dashboard labels it "Windows Certificate Store - Agent (legacy)" as a visible signal the reporting script predates this field.

AD CS certificate template monitoring

The native Windows Agent performs this scan automatically for active platform assignments in LocalMachine\My; a separate scheduled scanner is not required. It fetches every assignment page, respects pauses, and skips the feature on Linux or when an older server does not expose it. Use the standalone script below when you need script-owned scanning instead of the native Agent.

Beyond scanning a fixed list of stores, an agent can ask CertIntel which AD CS certificate template names your organization wants watched, then report only the certificates matched to one of those templates:

GET /api/v1/adcs-templates/agent/all?offset=0&limit=500
X-Api-Key: cik_<prefix>.<secret>

Same auth as /internal-certs, and paginated the same way as /api/v1/internal-endpoints/agent/all - increase offset by the page size until a page comes back smaller than limit. Each item is { template_name, label, is_paused }; skip anything with is_paused true.

For each active template name, match it against a certificate's Certificate Template extension - OID 1.3.6.1.4.1.311.20.2 (v1, name form) or 1.3.6.1.4.1.311.21.7 (v2, OID form) - case-insensitively, and report a match the same way as any other local_store report, with discovery_kind: "adcs_template" and template_name set to the matched name. A template with nothing matching it on a given machine produces no report for it.

CRL distribution point monitoring

In a tiered PKI the root CA is normally powered off, so its CRL is renewed by hand: somebody has to notice that the published CRL's nextUpdate is approaching, power the root on, sign a new CRL and copy it to every distribution point. CertIntel tracks that deadline for you. Open CRL Distribution Points in the dashboard and add the URL (http://, https://, ldap://) or absolute file path of each published .crl, with an optional label, an optional warn days lead time for the next update (otherwise the organization's regular expiry thresholds apply, scaled to the CRL's own validity window), and - for a file path that only exists on one host, such as C:\Windows\System32\CertSrv\CertEnroll\Corp Root CA.crl on the issuing CA - the computer name of the one agent that should read it.

Agents in the organization then read each source on their next scan and report what they found. The dashboard shows the issuer, CRL number, revoked-certificate count, signature algorithm, when the CRL last changed, when its next update is due, and whether the distribution point could be read at all; the alert digest carries an overdue, nearly-due or unreadable CRL under the crl.expiry event. A CRL an agent watches from its own local configuration, with no entry on the platform, is listed too as CRL Distribution Point - Agent.

The agent-facing API is two calls, with the same key and pagination as /adcs-templates/agent/all:

GET /api/v1/crl-monitors/agent/all?offset=0&limit=500
X-Api-Key: cik_<prefix>.<secret>

Each item is { id, source, source_kind, label, agent_computer_name, origin, is_paused }. Skip anything with is_paused true, and skip a source whose agent_computer_name is set and is not this machine's name (case-insensitive). source_kind is url or file. A file path this machine does not have is simply skipped - there is no "not found" report for it - unless the entry is pinned to this machine, in which case report the failure. Read a URL with a plain GET (no credentials, normal redirects), an ldap:// URL from the directory's certificateRevocationList attribute, and a file path from disk.

POST /api/v1/crl-monitors/report
X-Api-Key: cik_<prefix>.<secret>
Content-Type: application/json; charset=utf-8
Field Type Notes
source string the URL or path exactly as listed (matched case-insensitively); a source nobody registered on the platform is recorded as an agent-configured monitor
computer_name string the reporting machine
status success / failed
error string required on failed, up to 2,000 characters; not allowed on success
crl_b64 base64 string the CRL itself, DER or PEM, up to 512 KiB decoded - the platform parses it
facts object the extracted facts instead of the bytes, for a CRL too large to upload: issuer_dn (RFC 4514), issuer_cn, this_update, next_update (ISO-8601), crl_number, is_delta, base_crl_number, revoked_count, signature_algorithm, authority_key_id (hex), sha256 (hex of the DER), size_bytes

A successful report carries crl_b64 or facts; when both are present the uploaded CRL wins. Returns 200 with { id, status: "recorded", origin, crl_changed, revoked_wanted } - crl_changed is true when the CRL differs from the last one read (a new publication). Only the latest read is kept per distribution point.

Revoked certificates

The platform also stores every certificate the CRL lists as revoked: serial number, revocation date, reason and invalidity date, plus when each entry was first listed (and by which CRL number). An entry a later full CRL no longer lists - normally because the certificate expired and the CA pruned it - is kept and marked no longer listed. Click a distribution point to see the latest revocations and how many were added or dropped by the current CRL, then View all revoked certificates to search the list by serial number or reason.

Revoked entries are matched to the certificates CertIntel already tracks in the same organization (serial number plus the issuing CA, by authority key identifier when the certificate's bytes are known, by issuer name otherwise). A matching certificate is flagged revoked in the inventory and its detail shows the revocation date, reason and CRL; a certificate reported after the CRL was read is flagged on arrival. A released certificate hold (removeFromCRL, or a hold entry dropped from the CRL) clears the flag again; other dropped entries stay revoked. Each new publication that adds or drops entries is logged in the activity log.

A CRL uploaded as crl_b64 is parsed and its entries stored straight away. For a CRL sent as facts, the acknowledgement sets revoked_wanted to true while the platform does not yet have that CRL's entries, and the agent uploads them in chunks:

POST /api/v1/crl-monitors/report/revoked
X-Api-Key: cik_<prefix>.<secret>
Content-Type: application/json; charset=utf-8
Field Type Notes
source string as in the report
crl_sha256 hex string facts.sha256 of the CRL the entries come from; 409 once a different CRL is recorded for the source
total integer distinct entries across all chunks, the same on every chunk; at most revoked_count
final boolean set on the last chunk; rejected with 422 unless total entries have arrived
entries array up to 5,000 per request: { serial, revoked_at, reason, invalidity_date } - serial in hex (stored lowercase without leading zeros), reason one of key_compromise, ca_compromise, affiliation_changed, superseded, cessation_of_operation, certificate_hold, remove_from_crl, privilege_withdrawn, aa_compromise or null

Each chunk returns { status: "recorded", accepted, synced }; synced turns true with the final chunk. Chunks are idempotent, so an interrupted upload is simply repeated on the next scan. Agents up to the release before this feature never upload entries; their large CRLs show only the count.

Read the stored entries with GET /api/v1/crl-monitors/{id}/revoked (dashboard credentials; serial, include_removed, offset and limit query parameters).

Scan-InternalCerts.ps1 - live TLS probe

Probes a list of internal hostname:port targets for whatever certificate they serve. No trust, hostname or expiry validation is performed - that is the point; this is a monitor, not a client. A single unreachable target logs a warning and is skipped; the script exits non-zero only if every target failed. Edit the $Targets array at the top of the file.

Scan-LocalCertStore.ps1 - local certificate store

Run on a specific machine, this reports every certificate in that machine's own Windows certificate store - the one case the live probe can't cover: a certificate installed but never served on any port, such as an internal root or subordinate CA on your CA server. Edit $StoreSelections to pick stores and (optionally) restrict to exact subject common names. Defaults to LocalMachine\My and LocalMachine\CA; Root is present but commented out. Reports discovery_kind: "windows_cert_store".

Scan-AdcsTemplates.ps1 - AD CS certificate template monitoring

Fetches your organization's monitored AD CS template names from CertIntel, scans $StorePaths (defaults to LocalMachine\My) for certificates matched to one of those templates by their Certificate Template extension, and reports only the matches with discovery_kind: "adcs_template" and template_name set. Matching the newer v2 extension form to a name requires this machine to already know that template's OID (typically true on your CA server, or any domain-joined host that's requested/renewed against it) - run it there if v2-templated certificates aren't showing up. If you also run Scan-LocalCertStore.ps1 against the same store, the template-matched certificates appear twice - once as a plain store report and once with the template label - because template_name is part of the report identity. Skip the My store in Scan-LocalCertStore.ps1 if you want one row per certificate.

Scan-InternalCerts.ps1 also claims any Internal Endpoints assigned to the organization before it scans its own $Targets list, and reports each result back under a short lease. Set CERTINTEL_MANAGED_CHECKS=false to skip that phase.

Download the scripts

Warning

These are example scripts. Review them and test in a non-production environment before relying on them.

All three read CERTINTEL_INTERNAL_CERTS_URL and CERTINTEL_API_KEY from the environment, falling back to values you edit at the top of the file. Set them as a scheduled task, e.g. daily.