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:
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:
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.
- Scan-InternalCerts.ps1 - live TLS probe
- Scan-LocalCertStore.ps1 - local certificate store
- Scan-AdcsTemplates.ps1 - AD CS certificate template monitoring
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.