Every certificate discovery tool I've used works one of two ways. It opens a TCP connection, finishes a handshake, and reads what came back. Or it authenticates to a cloud provider and lists what's sitting in ACM, Key Vault, or Certificate Manager.
Both work. Both are the right first thing to build. And both share a blind spot: they only find certificates attached to something you can address.
A SAML signing certificate isn't attached to anything addressable. It sits base64-encoded inside a metadata XML document, and its job is signing assertions, not terminating connections. A JWKS signing key sits inside a JSON document served over HTTPS. Yes, the transport has a certificate. The key you actually care about is payload. A code signing certificate lives on a hardware token in a rack somewhere, or behind a cloud signing API, and never touches a listener at all.
All X.509. All carrying a notAfter. All capable of breaking something loud on the day they hit it. And none of them will ever show up in an inventory built around port 443.
Why nobody noticed
There's a second reason these stay invisible, and it's structural. They're exempt from the lifetime cuts everyone is currently panicking about.
CA/Browser Forum ballot SC-081v3 covers publicly trusted TLS server certificates. The 200-day phase went live in March 2026, 100 days lands in March 2027, and 47 days with 10-day domain validation reuse arrives in March 2029. That pressure is what's forcing teams to automate their web-facing lifecycle, which is the whole point of the ballot.
It doesn't touch SAML signing certs, which are usually self-signed by the IdP and trusted through an out-of-band metadata exchange rather than a chain. It doesn't touch JWKS keys, which frequently aren't certificates at all, just a modulus and an exponent in a JSON object. Code signing plays by its own baseline requirements and a separate 39-month ceiling.
So while your web certs get renewed every few weeks by automation somebody forced you to build, these keep their multi-year lifetimes and get rotated approximately never. Entra ID hands out three-year SAML signing certificates by default. Okta's app-level signing certs run for years. Whoever clicked generate has probably changed teams. Possibly changed companies. The renewal notice on the app registration goes to an individual mailbox that bounces.
The certificates with the longest lifetimes get the least attention. Nobody has a runbook for the day they run out.
SAML: the outage that doesn't mention expiry
When a web server cert expires, the browser spells it out. Error page, date, name on the cert. Anyone diagnoses it in ten seconds. When a SAML signing cert expires, the service provider rejects the assertion with "signature validation failed" or "unable to verify SAML response signature." That describes a cryptographic failure, not a calendar one, and it sends whoever's on call hunting for a broken integration or a tampered assertion.
It gets worse. The SAML 2.0 metadata profile treats the X.509 element in a KeyDescriptor as a key carrier. Trust comes from the metadata exchange, not from validating a chain. So SP implementations disagree about whether to check notAfter at all. Some enforce it strictly. Some ignore it completely and keep happily verifying signatures with a cert that expired two years ago.
Which means expiry day isn't an outage. It's a partial outage across a semi-random subset of your applications, decided by whichever SAML library each vendor happened to pick up. Finance breaks. HR doesn't. The incident channel burns forty minutes arguing about whether this is even a certificate problem.
The two kinds of service provider
Every SP integration you own is one of two things, and you need to know which.
Dynamic SPs point at your IdP's federation metadata URL and refresh on a schedule. They fix themselves during a rollover, as long as the refresh interval is shorter than the gap between publishing a new key and signing with it.
Static SPs are the ones where somebody opened an admin console in 2023, pasted a base64 blob into a text field, and hit save. No refresh. No URL. The only record that the integration exists lives in the SP's config and maybe a ticket that closed two years ago.
Static is the whole problem, and in every estate I've looked at it's the majority, because plenty of SaaS vendors don't offer a metadata URL field at all. They offer a textarea.
Rollover done right
The SAML metadata schema lets you publish multiple KeyDescriptor elements with the signing use. That's the mechanism. Almost nobody uses it.
Publish the next signing certificate alongside the current one and leave both in metadata for longer than your slowest SP's refresh interval. Dynamic SPs pick up the new key while the old one is still live, so when you flip the active signing key they already trust it. Drop the old cert once the switch has settled.
Which is exactly what goes wrong with ADFS. AutoCertificateRollover is on by default: it generates a new token-signing certificate 20 days out from expiry and promotes it 5 days out, entirely on its own. For federation partners consuming metadata, that's invisible and correct. For every partner running a pasted static copy, the promotion is the outage. The automation did its job perfectly. The blast radius was made of SPs that were never wired to benefit from it.
If you run ADFS, or any IdP with automatic rollover, you want the static SP list before the rollover fires. After is just incident response.
JWKS: the pattern that mostly works
OIDC got this right, and SAML should have copied it.
A JWKS document publishes a set of keys, each with a key ID. Every JWT carries that key ID in its header. A relying party that receives a token it can't match to a cached key refetches the JWKS and finds the new one. The IdP can publish a key days before it starts signing with it, and retire an old one only after every token it signed has expired. Rotation stops being a cutover and becomes a gradual state change.
Plenty of providers, Entra ID included, ship full X.509 chains in their JWKS entries, notAfter and all, which almost nothing enforces. The certificate is decoration. The key is the payload.
Where it still breaks, roughly in order of how often I've run into it:
Hardcoded public keys. Someone pulled the PEM out of the JWKS once and pasted it into a service config or a Kubernetes secret, because that was faster than writing a fetch-and-cache path. That service is pinned to a key that will eventually be retired, and it will fail at rotation with no relationship to any deploy anybody made.
Fetch-once-at-boot. The JWKS is retrieved during startup and cached in a process variable for the life of the pod. Rotation breaks every instance that hasn't restarted since, so the failure looks like it correlates with pod age. Debugging that at 3am is genuinely miserable.
The refetch storm, which is the opposite failure. A client refetches on every unknown key ID with no rate limiting and no negative caching. One malformed token from some scanner and you're hammering your IdP's JWKS endpoint. Cache the negative result too.
Retiring a key too early. Access tokens live an hour, refresh flows can present older material, and pulling a key from the JWKS the moment you stop signing with it kills tokens that are still legitimately in flight. Keep retired keys published for at least the maximum token lifetime.
Code signing: hardware, and the delayed detonation
Since June 2023, CA/Browser Forum code signing requirements have mandated that private keys be generated and stored on hardware meeting FIPS 140-2 Level 2, Common Criteria EAL 4+, or equivalent. All code signing certificates, not just EV. The PFX-in-a-CI-secret era ended for new issuance.
That changed build pipelines for real. Most teams handled it by moving signing out of the build and into a service: Azure Trusted Signing, DigiCert KeyLocker, SSL.com's eSigner, or a self-hosted networked HSM with a signing runner. Signing became a dependency with its own credentials, quotas, failure modes, and its own certificate expiry, sitting entirely outside whatever inventory you built for TLS.
The trap is timestamping. If you check one thing after reading this, check that.
An Authenticode signature with no RFC 3161 timestamp is valid only while the signing certificate is valid. Add the timestamp and the signature stays verifiable after the cert expires, because the timestamp proves the signing happened inside the validity window. In signtool that's /tr and /td sha256. Two flags. Forgetting them costs nothing at build time and everything fourteen months later, when an installer that shipped clean starts throwing Unknown Publisher and tripping SmartScreen for people who downloaded it last week.
That's the worst failure in this whole post. An artifact that was fine when it was built, fine through test, fine on release day, and then goes bad on a calendar date long after the pipeline that produced it stopped running. CI will never tell you. Your support queue will.
Check published artifacts for timestamp presence as a standing job, not a one-time cleanup.
Building an inventory that actually covers these
Stop enumerating by port. Enumerate by source.
For SAML, keep the list of IdP metadata endpoints (for Entra, the per-application federation metadata URL) and parse the signing KeyDescriptor elements for notAfter on a schedule. That covers your side, and it's the easy half. The hard half is the SP register: for every integration, record whether it pulls metadata dynamically or holds a pasted copy, and who at the vendor you email to change it. The expiry date is the trivial field. The coupling and the contact decide whether you can act on the alert or just watch it fire.
For JWKS, poll each jwks_uri from the discovery document and store the set of key IDs, plus chain expiry where there's an x5c. Diff across polls. A new key ID appearing is worth knowing about. A key ID disappearing is the one that breaks token validation. You get a security signal out of it for free, too: an unannounced key change at an identity provider is something you want to see the same day.
For code signing, export expiry from the signing service API or your HSM inventory, then separately verify signature and timestamp presence on published artifacts. Two checks, two different questions.
Then set thresholds by rotation cost instead of by days. Thirty days is fine for a certificate you renew with an API call. It's useless for a SAML signing cert where rotation means opening a vendor ticket, negotiating a change window, and getting someone in a different timezone to paste a new blob into a form during their business hours. Give that ninety days, minimum. Code signing reissuance with hardware attestation, sixty. Automated JWKS rotation doesn't want a threshold at all, it wants an event notification.
The number should reflect how long a human takes to finish the change, including the part where you sit waiting on somebody else.
CertPulse will probe the TLS leg of your IdP's metadata endpoint and tell you when that transport certificate is expiring. That's a different certificate from the one embedded in the XML the endpoint serves, and the embedded one is what breaks SSO. Parsing it is a check you own, whether that's a scheduled job hitting your metadata URLs or a row in a spreadsheet with a real name in the owner column.
Something has to own it. The certificates that expire quietly are the ones with three-year lifetimes and no listener.
This is why we built CertPulse
CertPulse connects to your AWS, Azure, and GCP accounts, enumerates every certificate, monitors your external endpoints, and watches Certificate Transparency logs. One dashboard for every cert. Alerts when auto-renewal fails. Alerts when certs approach expiry. Alerts when someone issues a cert for your domain that you didn't request.
If you're looking for complete certificate visibility without maintaining scripts, we can get you there in about 5 minutes.