Detecting Device Trust Certificate Exports: How to Build Custom SIEM/XDR Rules

Introduction

In modern Zero‑Trust environments a Device Trust certificate is the linchpin that provides endpoints the ability to authenticate independently and extends an additional layer of actionable controls. These certificates are typically combined into PKCS #12 (*.p12) bundles and imported into the Windows Certificate Store or macOS Keychain with the non‑exportable private key attribute set. The intention is clear: users should never be able to pull the private key out of the local store.

In practice, however, attackers (or careless insiders) can still attempt to export the private key using native utilities such as certutil.exe on Windows or the security CLI on macOS. Because the export operation leaves an audit trail in system logs, detecting Device Trust certificate exports becomes a realistic and valuable detection use‑case for any SIEM or XDR platform.

This blog walks you through:

  • Why non‑exportable flags are not always enough
  • The exact commands used to attempt to export on Windows and macOS
  • The log events generated by each those commands
  • How to translate that knowledge into reusable Sigma detection rules

Device Trust Certificates – The Security Goal and the Reality

A Device Trust certificate forums an endpoint’s identity for an additional layer of authentication and authorization in your environment . When a service receives a TLS client‑certificate handshake it can verify that the device its identity, managed, and security posture.

Administrators usually elect to set the NoExport option when importing with certutil in Windows and/or rely on the -x (non-extractable) option when using security import in macOS. Setting these flags tell the OS to keep the private key inside a protected store and not to write them to disk in clear text.

Why it still matters:

  • The protection is enforced at the API level, not at the file‑system level.
  • A user with administrative rights can invoke privileged utilities that request the private key from the CryptoAPI (Windows) or Security.framework (macOS).
  • Attackers who have already compromised an account often have the same ability to run those utilities or advanced forensics tools

Note: If certificates are not set as non-exportable when loaded into the users certificate store. The user will be able to extract the private key and full key bundle without any additional permissions.

Therefore, detection must focus on the act of attempting an export, not just the presence of a non‑exportable flag.

Threat Scenario – Exporting a Private Key for Lateral Movement

Consider an attacker who has gained local admin rights on a laptop that is enrolled in Device Trust. The attacker’s objectives may include:

  1. Steal the private key to impersonate the device when communicating with internal services.
  2. Reuse the certificate on another machine to bypass device compliance checks.
  3. Exfiltrate the key for later use in a authentication attacks or user impersonation .

Even if the attacker cannot directly read the private key from the store, they attempt to call certutil -exportpfx (Windows) or security export (macOS) to generate a new PKCS #12 bundle. The resulting file can be copied to a USB drive, uploaded to cloud storage, or transmitted over an encrypted tunnel—each step leaving observable footprints.

Export Techniques on Windows

Using certutil.exe

The most common native tool is certutil.exe. Users can attempt export from their local store like:

certutil -exportpfx -p "" MY <Thumbprint> C:\Temp\device-trust.p12
  • -p "" supplies an empty password (or a user‑chosen one).
  • My is the personal store where Device Trust certificates live.
  • <Thumbprint> identifies the exact certificate to export.

If the key was not marked non‑exportable or it was specifically marked as exportable, certutil will still succeed when run under a privileged context because it uses the Cryptographic Service Provider (CSP) API that can request the private key material for an authorized user.

PowerShell Alternative

PowerShell’s Export-PfxCertificate cmdlet also works:

$cert = Get-ChildItem Cert:\CurrentUser\My\<Thumbprint>
Export-PfxCertificate -Cert $cert -FilePath C:\Temp\device-trust.p12 -Password (ConvertTo-SecureString -String "" -AsPlainText -Force)

Both commands generate a *.p12 file that can be moved off the host.

What Gets Logged?

  • Windows Security Auditing – Event ID 4688 (A new process has been created) logs the full command line when audit policy Process Creation is enabled.
  • Sysmon (if installed) – Event ID 1 captures the same data with additional hashes for the executable.
  • Application Logscertutil may emit an informational entry in the System or Application log, but process creation events are the most reliable source.

Note: The windows certificateservicesclient lifecycle system now also natively creates an event with id 1007 when any certificate is exported from the local certificate store (including Public-only and CA Chain certificates). This means it can commonly be triggered by various applications and can’t be scoped to specific certificates, but there is already a community approved Sigma rule for the event.

Export Techniques on macOS

Using the security CLI

On macOS the security command can read from the login keychain and write a PKCS #12 bundle:

security export -k ~/Library/Keychains/login.keychain-db -t priv -p "" -o /tmp/device-trust.p12
  • -k points to the keychain file.
  • -t priv requests private keys only.
  • -p "" supplies an empty password for the output file (or a user‑chosen one).

If the certificate’s private key is stored in the Secure Enclave, the command may prompt for Touch ID or the user’s password. However, an attacker with a compromised admin session can bypass that prompt by using sudo.

Note: In newer versions of MacOSX the native tools do still respect the non-extractable attribute set when the certificate is imported, even with elevated privileges. But given its just an encrypted db, you can still utilize thrid-party tools like chainbreaker to export the keys with a password and hexdump.

What Gets Logged?

  • Unified Logging (os_log) – The subsystem com.apple.security.keychain logs messages such as “Exported private key”.
  • Auditd – If audit is enabled (audit -s), an execve record for /usr/bin/security with the export arguments appears.
  • Console.app – Shows the same entries, but for automated detection we rely on the log files under /var/log/system.log or the structured logging API.

Note: It may also be possible, even with non-exportable option set, to utilize Mimikatz like mimikatz log "crypto::certificates /export /systemstore:my" exit or Chainbreaker like python -m chainbreaker --export-x509-certificates to extract certificates from the user-space during post exploitable; but that feels like a whole different topic entirely.

Building Detection Rules in Sigma

Sigma is a vendor‑agnostic rule format that can be translated into SPL (Splunk), KQL (Sentinel), Lucene DSL (Elastic) and many others. Below are two examples—one for Windows, one for macOS—targeting the export commands discussed earlier.

Windows Sigma Rule – Detect CertUtil Export

title: Detection of Device Trust Certificate Export via certutil
id: e7c9b1a4-3d6f-4eaa-b5c8-0c2f6a9c1234
status: stable
description: |
  Detects execution of certutil.exe with the -exportpfx flag which is commonly used to extract a Device Trust private key even when it is marked non‑exportable.
author: Michael Contino
date: 2025-11-06
logsource:
  product: windows
  service: sysmon
detection:
  selection_process:
    Image|endswith: '\\certutil.exe'
  selection_export:
    CommandLine|contains: '-exportpfx'
  condition: all of selection_*
fields:
  - CommandLine
  - ParentImage
  - User
level: high
tags:
  - attack.t1552.006   # Unsecured Credentials: Private Keys
  - detection.DeviceTrustExport

If you prefer the native Windows Security log, change service to security and use Event ID 4688 in the detection block.

macOS Sigma Rule – Detect security Export

title: Detection of Device Trust Certificate Export via security CLI
id: a1f4c9e2-7b2a-44d5-a6f3-58c9f2d8b765
status: stable
description: |
  Flags execution of the macOS `security` command with arguments that request export of private keys, indicating an attempt to extract a Device Trust certificate.
author: Michael Contino
date: 2025-11-06
logsource:
  product: macos
  service: auditd
detection:
  selection_process:
    exe|endswith: '/usr/bin/security'
  selection_export:
    cmdline|contains: 'export'
  selection_privkey:
    cmdline|contains: '-t priv'
  condition: all of selection_*
fields:
  - exe
  - cmdline
  - auid
level: high
tags:
  - attack.t1552.006   # Unsecured Credentials: Private Keys
  - detection.DeviceTrustExport

For environments that rely on Unified Logging, replace service with osquery or a custom parser that extracts the com.apple.security.keychain messages.

Converting Sigma to Platform Queries

Most SIEMs provide an online converter (e.g., Sigma Converter at sigmahq.io). Below are quick examples for three popular platforms.

Splunk SPL

index=windows sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational"
(Image="*\\certutil.exe" AND CommandLine="* -exportpfx*")

Elastic Lucene DSL

{
  "query": {
    "bool": {
      "must": [
        { "wildcard": { "process.executable": "*certutil.exe" }},
        { "wildcard": { "process.command_line": "*-exportpfx*" }}
      ]
    }
  }
}

Azure Sentinel KQL

Sysmon
| where Image endswith @"\certutil.exe"
| where CommandLine contains "-exportpfx"

Apply the same conversion logic to the macOS rule, swapping process_name for exe and adjusting the field names accordingly.

Attack Flow Diagram – Exporting a Device Trust Certificate

Visualizing the steps helps analysts understand the context of an alert.

graph TD
    A[Compromised Endpoint] --> B[Locate Device Trust Cert in Store]
    B --> C{Export Attempt}
    C -->|Windows| D[certutil -exportpfx]
    C -->|macOS| E[security export -t priv]
    D --> F[PKCS12 file written to TEMP file]
    E --> F
    F --> G[Copy to Staging Location USB, Share, Cloud]
    G --> H[Exfiltration over Network or Physical Media]
    H --> I[Attacker Reuses Private Key on Remote Service]
    style A fill:#ffcccc,stroke:#c00
    style I fill:#ccffcc,stroke:#090

Explanation of the flow

  1. Compromised Endpoint – The attacker already has local admin or system privileges.
  2. Locate Device Trust Cert – Queries the certificate store (certutil -store My or security find‑identity).
  3. Export Attempt – Executes a native export tool (Windows or macOS).
  4. PKCS12 file written – The private key is now in clear text inside the .p12.
  5. Copy to Staging Location – Moves the file to a place where it can be exfiltrated.
  6. Exfiltration – Could be a cloud upload, SMB share copy, or USB drop.
  7. Attacker Reuses Private Key – The stolen key is used for impersonation, lateral movement, or credential stuffing against services that trust the Device Trust certificate.

Deploying and Tuning Your Rules

Enriching Alerts

When an export is detected, enrich the event with:

  • Certificate Thumbprint – Extracted from the command line to correlate with asset inventory.
  • Process Hash – Compare against known good binaries (e.g., Microsoft‑signed certutil.exe).
  • Endpoint Context – OS version, posture level, logged in user(s), and serial number/UUIDs.

Enrichment enables faster triage: if the exporter is a legitimate admin running from a hardened workstation, you may downgrade the alert. Otherwise, trigger an automated response.

Preventive Controls

Detection is only half of the story. Harden the environment to reduce the chance that an attacker can run the export commands:

ControlWindowsmacOS
Set Non-Exportable OptionsEnsure you still utilize export blocking options when importing certutil -importPFX [PFXfile] NoExportLikewise specific the key as non-extractable with the -x when importing security import <p12_path> -x
AppLocker / SRPBlock certutil.exe except for signed admin scriptsUse /usr/sbin/launchd policies to restrict security binary execution
Group PolicySet Do not allow private key export in PKI templates (doesn’t fully prevent certutil)Enable Secure Enclave only keys (-T -s) which refuse export without Touch ID
Audit PoliciesEnable “Process Creation” and “Credential Access” sub‑categoriesTurn on auditd with execve monitoring for /usr/bin/security

Why Detection Complements Non‑Exportable Keys

  • Non‑exportable flags protect against casual dumping but do not stop a privileged user from invoking OS APIs that return the private key material.
  • Native utilities (certutil.exe, security) are widely available, leaving an audit trail that can be detected by any modern SIEM/XDR platform.
  • By crafting Sigma rules focused on command‑line arguments and process creation events, you gain a vendor‑agnostic detection layer that works across Windows and macOS fleets.

Implementing the examples in this post will give your organization an early warning system for detecting Device Trust certificate exports, reducing the risk of credential theft, lateral movement, and unauthorized device impersonation.

Taking Action

  1. Deploy the SIEM/XDR rules above into your rule repository.
  2. Enable detailed process creation auditing on all Windows endpoints (Event ID 4688) and auditd logging on macOS.
  3. Test the detection by intentionally exporting a test certificate in a lab environment – verify that alerts fire with the expected context.
  4. Harden your endpoint policies to restrict certutil and security usage to approved admin accounts only.

Stay ahead of attackers and resistant users alike, but understand the “non‑exportable” flag is not always enough. Detecting Device Trust certificate exports gives you the visibility you need to protect the cryptographic foundation of your Zero‑Trust architecture.

Enforcing Device Trust with Certificate‑Based Client Authentication – A Practical Guide for Modern Identity Providers

Introduction

In today’s zero‑trust world, granting access solely based on a user’s password or even an OTP is no longer sufficient. Threat actors are increasingly targeting compromised credentials, and the security community has responded with identity provider empowered device trust, a model that ensures only verified devices can obtain tokens from an identity provider (IdP).

When you combine device trust with TLS client‑certificate authentication, you create a powerful barrier: before any session token is issued for a sensitive application or service, the IdP validates a device‑bound certificate presented by the client. This approach works equally well for on‑premises workloads, cloud native services, and containerized applications.

In this post we’ll walk through how to configure popular IdPs like Keycloak, Authentik, Okta, OneLogin, and Auth0 to require Certificate ClientAuth using device trust certificates. We’ll also discuss best‑practice considerations for certificate issuance, rotation, and revocation.

Why Certificate‑Based Device Trust?

BenefitExplanation
Strong mutual TLS (mTLS)The client proves possession of a private key bound to a trusted device, eliminating credential stuffing attacks.
Device bindingCertificates are issued per device (or per container/compute) and can be tied to hardware TPMs or secure enclaves, making them hard to steal.
Zero‑trust enforcementEven if a user’s password is compromised, an attacker cannot obtain a token without the correct device certificate.
Fine‑grained policy controlIdPs can apply separate authentication policies based on certificate attributes (e.g., OU=Mobile, OU=Workstation).
Auditable provenanceEvery successful login includes the certificate fingerprint, simplifying forensic investigations.

Device Trust Certificate Lifecycle

  1. Enrollment – During first‑boot or via automated provisioning, the device presents its hardware-bound public certificate to an enrollment endpoint (often part of the IdP or Third-party service like ACME Attestation service).
  2. Provisioning – A trusted CA (internal PKI or managed service) issues a short‑lived X.509 certificate to each device. The private key is bound by the vTPM, HSM, or container runtime.
  3. Authentication – When a user attempts to access a protected app, the client initiates TLS with client‑certificate request. The presented certificate is validated against Device Trust certificate chain.
  4. Renewal / Rotation – Before expiry (typically 30–90 days), the device automatically requests a new cert via the provisioner. Old certificates are revoked or marked as expired in the CRL database.

Identify Provider Empowered Device Trust Authentication Flow

identity provider empowered with device trust Session flow diagram

The diagram illustrates that no token is ever issued unless the device presents a valid certificate that matches the IdP’s trusted CA list. This is the essence of an identity provider empowered device trust.

Preparing Your PKI

All IdPs discussed support the use of external CA integration. The steps are similar across platforms:

  1. Create a dedicated CA hierarchy – Root CA (offline) → Intermediate “Device‑Trust” CA (online).
  2. Define certificate profile – Include extensions such as subjectAltName for device ID, extendedKeyUsage = clientAuth, and optionally certificatePolicies to tag the trust domain.
  3. Deploy an automated provisioning service – For example, cert‑manager in Kubernetes, ACME with device attestation, or a simple SCEP service.

Note: Alternatively you can use smallstep step-ca as outlined in my practical cloud-native guide.

Sample OpenSSL config for the Device‑Trust CA:

[ req ]
distinguished_name = req_distinguished_name
prompt = no

[ req_distinguished_name ]
C  = US
ST = WA
L  = Seattle
O  = AcmeCorp
OU = DeviceTrustCA
CN = device-trust.acme.local

[ v3_intermediate_ca ]
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer

Use this to generate the intermediate CA and then sign device CSRs with -extensions client_auth:

openssl req -new -nodes -newkey rsa:2048 \
  -keyout device.key -out device.csr \
  -subj "/C=US/ST=WA/L=Seattle/O=AcmeCorp/OU=Workstation/CN=device01"

openssl ca -config openssl.cnf -extensions client_auth \
  -days 30 -notext -md sha256 \
  -in device.csr -out device.crt

Configuring Identity Providers

Below we present the minimal configuration needed to enable Certificate ClientAuth for each IdP. The examples assume you already have a running instance of the IdP and that your PKI’s intermediate certificate is available as device-trust-ca.pem.

Keycloak

Keycloak provides TLS client‑certificate authentication via the X509 Authentication flow.

# keycloak-realm.yaml – add a new authentication flow called "DeviceTrust"
realm: myrealm
authenticationFlows:
  - alias: DeviceTrust
    providerId: basic-flow
    topLevel: true
    builtIn: false
    authenticationExecutions:
      - authenticator: x509-browser-authenticator
        requirement: REQUIRED
        priority: 10
        config:
          # Truststore containing device‑trust CA
          trustStoreFile: /opt/keycloak/conf/device-trust-ca.p12
          trustStorePassword: changeit
          # Map certificate subject DN to user attribute "deviceId"
          principalAttribute: cn
      - authenticator: auth-cookie
        requirement: REQUIRED
        priority: 20
  1. Upload the CA – Convert device-trust-ca.pem into a PKCS‑12 keystore (keytool -importcert).
  2. Create the flow via the Admin Console or import the YAML above.
  3. Set the flow as default for the desired client (application) under Authentication → Flows.

Now, any request to /auth/realms/myrealm/protocol/openid-connect/auth will trigger a TLS handshake that expects a device‑trust certificate.

For More information about Keycloak clientAuth review the x509 documentation.

Authentik

Authentik’s Certificate provider can be combined with the Device stage.

# authentik.yaml – DeviceTrustProvider definition
providers:
  - name: device-trust-mtls
    kind: cert
    config:
      ca_file: /etc/authentik/certs/device-trust-ca.pem
      allowed_usages:
        - clientAuth
      map_subject_to_user_attribute: "device_id"
stages:
  - name: DeviceTrustStage
    kind: authentication
    flow: default-authentication-flow
    providers:
      - device-trust-mtls
  1. Place device-trust-ca.pem under /etc/authentik/certs.
  2. Restart Authentik; the stage will now reject any TLS handshake lacking a valid client cert.

For more information about configuring Authentik authentication review the stag flow documentation.

Okta

Okta’s Certificate Authentication is configured via API Access Management.

{
  "type": "CERTIFICATE",
  "name": "DeviceTrustPolicy",
  "settings": {
    "trustedCertificates": [
      { "x5c": ["MIID..."] }   // Base64‑encoded device‑trust CA cert
    ],
    "subjectMatchPattern": "CN=*.device.acme.com"
  }
}

Steps:

  1. In the Okta Admin Console, navigate to Security → Authenticators → Certificate and add a new authenticator with the above JSON.
  2. Attach this authenticator to an Authentication Policy that protects your sensitive app (e.g., “Sensitive‑App‑Policy”).

Okta will now enforce mTLS for any request hitting the OIDC /authorize endpoint of that app.

For more information on configuring certificate-based authentication for Okta review the official docs.

OneLogin

OneLogin supports X.509 certificate authentication through its MFA configuration.

<!-- one-login-mfa-config.xml -->
<CertificateAuthenticator>
    <TrustedCA>$CA_Chain_URL</TrustedCA>
    <SubjectRegex>CN=([a-z0-9\-]+)</SubjectRegex>
    <MapToUserAttribute>device_id</MapToUserAttribute>
</CertificateAuthenticator>

Upload the XML via Settings → Security → Multifactor Authentication → Certificate. Then enable this factor for the Security Policy that guards your high‑value applications.

For more information about how Onelogin handles third-party certificate authentication to validate a trusted device, review the official documentation.

Auth0

Auth0 uses Custom Database Connections with a pre‑login hook to verify client certificates.

// auth0-pre-login.js – Deploy as an Action (Pre‑Login)
exports.onExecutePostLogin = async (event, api) => {
  const certHeader = event.request.headers['x-client-cert'];
  if (!certHeader) {
    return api.access.deny('client_certificate_missing');
  }

  // Decode PEM and verify against trusted CA
  const forge = require('node-forge');
  const pki = forge.pki;
  const caPem = `-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----`;
  const caCert = pki.certificateFromPem(caPem);
  const clientCert = pki.certificateFromPem(certHeader);

  // Basic chain validation
  const verified = pki.verifyCertificateChain(pki.createCaStore([caCert]), [clientCert]);
  if (!verified) {
    return api.access.deny('invalid_device_certificate');
  }

  // Optional: map CN to user metadata
  const deviceId = clientCert.subject.getField('CN').value;
  event.user.app_metadata = { ...event.user.app_metadata, device_id: deviceId };
};

Deploy this Action and enable TLS termination with client‑certificate forwarding on your reverse proxy (e.g., Nginx proxy_set_header X-Client-Cert $ssl_client_cert;). Auth0 will reject any login that lacks a valid device‑trust certificate.

For more information about how to use Auth0’s mTLS authentication flow to validate device trust certificates, review their official mTLS docs.

Note: For services that may not directly support your Identity provider or may not be exposed externally, you can utilize a simple nginx proxy to validate a device trust certificate before allowing users to login.

Best Practices for Production Deployments

AreaRecommendation
Certificate LifetimeUse short lifetimes (30 days) and automate renewal via a provisioner like ACME or SCEP
Key ProtectionStore private keys in credential manager, KMS, Key Vault, or secrets store. Never write them to disk unencrypted.
RevocationPublish CRLs or use OCSP stapling; IdPs should query the revocation endpoint on each login.
Logging & AuditingInclude tls.client.subject_dn and certificate fingerprint in SIEM logs. Enable audit‑log retention for at least 90 days.
Fail‑Open vs Fail‑CloseDefault to fail‑close: if the client cert cannot be validated, deny access.
Device InventoryKeep a synchronized inventory service (e.g., CMDB) that tracks active device fingerprints; automate de‑provisioning when devices leave the fleet.

Testing & Validation

  1. OpenSSL verification – From a client machine with the device cert:
   openssl s_client -connect idp.acme.local:443 \
     -cert device.crt -key device.key -CAfile device-trust-ca.pem

You should see Verify return code: 0 (ok) and the TLS handshake succeed.

  1. Token request – Use curl with the client cert:
   curl -k https://idp.acme.local/auth/realms/myrealm/protocol/openid-connect/token \
     -E device.crt --key device.key \
     -d "grant_type=client_credentials&client_id=myapp"

If the certificate is invalid or missing, the response will be 401 Unauthorized.

  1. Audit log check – In Keycloak’s admin console go to Events → Config and enable Login events. Verify that each successful login entry contains client_certificate_fingerprint.

Common Pitfalls & How to Avoid Them

SymptomRoot CauseFix
Handshake fails with “unknown ca”Device‑trust CA not added to truststore or wrong file format (PEM vs PKCS12)Convert to PKCS12 and import correctly.
Token issued despite missing certReverse proxy terminates TLS before forwarding to IdP, losing client‑cert headerEnable proxy_ssl_verify and forward X-Client-Cert or use end‑to‑end mTLS (no TLS termination at proxy).
Frequent revocation failuresCRL/OCSP endpoint unreachable from IdP and user deviceHost a OCSP responder or cache CRLs; ensure network connectivity.
Certificate renewal breaks sessionsApplications cache the old cert and do not reload new filesUse cronjob or sidecar containers that check the certificate and automate renewal

Conclusion

By integrating device‑bound certificates with your identity provider, you transform authentication from a just a 2 factor authentication model and combine it into a robust multi factor with verifiable trust paradigm. The configurations shown for Keycloak, Authentik, Okta, OneLogin, and Auth0 prove that enabling Certificate ClientAuth is straightforward, often just a few lines of YAML or JSON plus the import of a trusted CA.

When you adopt this pattern:

  • Security posture improves dramatically – compromised passwords no longer grant access.
  • Compliance becomes easier – many regulations (e.g., NIST 800‑63B, PCI DSS) encourage strong mutual authentication.
  • Operational overhead stays low – automated short‑lived cert issuance and rotation eliminate manual key management.

If you’re looking to future‑proof your applications against credential‑theft attacks, make the shift today: empower your identity provider with device trust and let mTLS do the heavy lifting.

Device Trust with step-ca, Google Cloud CAS, and SCEP: a Practical, Cloud-Ready Device Trust Build Out

I’m often asked how to stand up a device trust layer that scales from homelab to enterprise without the additional complexity of Hardware security modules. In this post, I’ll show you how to wire up step-ca (Smallstep’s open-source CA), Google Cloud Certificate Authority Service (CAS) as a managed signing backend, and SCEP for mass device enrollment. The result is a modern, automatable PKI that issues device certificates for clientAuth, mTLS, Wi-Fi, VPN, Access Gateways, and beyond.

Why this stack?

  • step-ca: A lightweight CA with batteries included—ACME, OIDC, SCEP, SSH CA, templates, audit logs, and more. It’s ideal as your “front-door” RA/CA service and policy brain.
  • Google Cloud CAS: A managed, audited CA that signs certificates on your behalf. Offload availability and compliance to Google while keeping your issuance logic under your control.
  • SCEP: A well-supported protocol for bulk device enrollment—especially useful for legacy or embedded systems, printers, network gear, and certain MDM agents.

This combo gives you:

  • Cloud scalability + low ops: Managed CA with step-ca’s simple deployment.
  • Security: Keep the signing CAs inside Google CAS; expose only step-ca to your fleet.
  • Automation: Scriptable bootstraps, ephemeral certs, and policy controls.

Architecture at a glance

Flow summary

  1. Devices talk SCEP to step-ca.
  2. step-ca acts as a Registration Authority, relaying/signing requests via CAS.
  3. SCEP returns a signed device certificate.
  4. Devices use certs for clientAuth challenges from the IdP/SSO provider or an access gateway before reaching protected apps

Prerequisites

  • A Google Cloud project with CAS enabled and an active CA in an appropriate CA Pool.
  • A Linux host/VM/container to run step-ca (front-door RA/CA and SCEP endpoint).
  • DNS for your step-ca endpoint (public or private, as needed).
  • Firewall rules allowing inbound 443 to step-ca.
  • gcloud CLI and step CLI.

Installation docs for step-ca: https://smallstep.com/docs/step-ca/installation/

Step 1 — Create a service account for step-ca → CAS access

Create a service account that step-ca will use to interact with CAS:

gcloud iam service-accounts create step-cas-sa \
    --description "Step-CA Service Account" \
    --display-name "Step-CA Service Account"

Grant this SA appropriate CAS roles (for certificate issuance). In many deployments that’s at least roles/privateca.certificateRequester on the CA Pool. (Use the principle of least privilege.)

Tip: You can utilize a certificate template within GCP CAS to further restrict the usage and options available to the service account when an certificate is requested.

Step 2 — Install step-ca and initialize with Cloud CAS as the RA

Create a working directory and initialize step-ca’s config to use Cloud CAS as the Registration Authority:

mkdir /etc/step-ca
export STEPPATH=/etc/step-ca
step ca init --name="CasPoC" --deployment-type standalone --remote-management --provisioner="admin@example.com" --ra=CloudCAS --issuer=projects/<project>/locations/<us-central1>/caPools/<Ca Pool>/certificateAuthorities/<ca ID> --dns="<FQDN>" --address=":443"

What this does:

  • Creates /etc/step-ca/config/ca.json and related directories.
  • Sets CloudCAS as the RA, pointing at your CA (–issuer=projects/…/certificateAuthorities/<ca ID>).
  • Binds HTTPS on :443 (we’ll allow a non-root user to bind shortly).
  • Registers a default OIDC or local provisioner (here, admin@example.com) for management.

Tip: Authenticate gcloud as the service account, or run step-ca compute with that service account identity, so Cloud CAS requests succeed.

Step 3 — Create a restricted system user and grant low-port bind

Run step-ca as a non-root user:

useradd step
passwd -l step
chown -R step:step /etc/step-ca

Allow binding to port 443 without root:

setcap CAP_NET_BIND_SERVICE=+eip /usr/bin/step-ca

This capability approach is safer than running as root.

Step 4 — (Optional) Prepare an intermediate CA template

 (CSR → CAS-signed)

We’ll generate an intermediate key and CSR locally, then have CAS sign it to establish our operational intermediate for issuance:

cat <<EOF >  /etc/step-ca/templates/rsa_intermediate_ca.tpl
{
  "subject": {{ toJson .Subject }},
  "issuer": {{ toJson .Subject }},
  "keyUsage": ["certSign", "crlSign"],
  "basicConstraints": {
    "isCA": true,
    "maxPathLen": 0
  }
  {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
    , "signatureAlgorithm": "SHA256-RSAPSS"
  {{- end }}
}
EOF

Generate the CSR and key:

step certificate create "SCEP Intermediate CA" \
    /etc/step-ca/certs/intermediate_ca.csr \
    /etc/step-ca/secrets/intermediate_ca_key \
    --template /etc/step-ca/templates/rsa_intermediate_ca.tpl \
    --kty RSA \
    --size 3072 --csr

Ask Cloud CAS to sign the CSR:

gcloud privateca certificates create CERT_ID \
    --issuer-pool <Pool> \
    --issuer-location <Location> \
    --csr /etc/step-ca/certs/intermediate_ca.csr \
    --cert-output-file /etc/step-ca/certs/intermediate_ca.crt \
    --validity "P1Y"

Update your ca.json so step-ca uses the newly minted intermediate:

        "root": "/etc/step-ca/certs/root_ca.crt",
        "federatedRoots": null,
        "crt": "/etc/step-ca/certs/intermediate_ca.crt",
        "key": "/etc/step-ca/secrets/intermediate_ca_key",

Notes
• Store /etc/step-ca/secrets on encrypted disk or a secrets-managed volume.
• Rotate the intermediate on a schedule (e.g., annually) and keep a CRL/OCSP strategy in place.

Step 5 — Dry run the CA

Before opening the floodgates, do a dry run:

sudo -u step step-ca /etc/step-ca/config/ca.json

If it boots cleanly, you should see the HTTP listener and provisioners registered in logs.

Step 6 — Enable SCEP  for device enrollment

Add a SCEP provisioner to your existing CA service. We’ll set challenge credentials and certificate lifetimes:

step ca provisioner add poc_devicetrust \
  --type SCEP --challenge "<redacted>" \
   --x509-min-dur=24h \
   --x509-max-dur=8760h \
   --x509-default-dur=1080h \
  --encryption-algorithm-identifier 2 --admin-name step

A few practical notes:

  • Challenge: Keep it secret (vault, KMS, or MDM payloads). Consider migrating to SCEP with client-side RA or EST for stronger auth if your device ecosystem supports it.
  • Durations: Default shown is 45 days (1080h). Short-lived certs reduce revocation surface.
  • Algorithm Identifier 2: This sets the SCEP Encryption Algorithm Identifier (commonly RSA/3DES/AES variations). Keep this consistent with your device agents.

Step 7 — (Workaround) Clean up ca.json authorities section if needed

Some versions/paths require removing the cloudcas entry from the authorities section. If you see startup errors or odd RA behavior, adjust and restart:

sudo vi /etc/step-ca/config/ca.json

Then relaunch:

sudo -u step step-ca /etc/step-ca/config/ca.json

Note: SCEP can operate with just the local Intermediate CA being used to sign device cert requests. We don’t need direct access to the Root CA or subordinate CAs, so they can be isolated within CAS.

Step 8 — Bootstrap devices and request a cert via SCEP

Once your root CA is trusted on the device (via MDM, config management, or manual import), request a certificate with a SCEP client. Example:

scepclient -private-key client.key -server-url=https://<Domain/IP>/scep/poc_devicetrust -challenge=<Redacted> -dnsname "Lab-PC.local" -cn "Lab-PC" -country "US" -organization "Lab" -ou "Device Trust"

This will:

  • Pull the CA chain from GetCACert
  • Submit a PKCSReq containing your CSR and challenge
  • Receive a signed certificate in CertRep

SCEP Enrollment Sequence

SCEP Enrollment Sequence

Hardening, Operations, and Best Practices

1) TLS and network posture

  • Put step-ca behind a reverse proxy or L7 load balancer for WAF/DoS controls.
  • Consider utilizing a URL Map and/or HTTP targets to restrict acces only to your provisioners
  • Use mTLS for internal admin APIs and restrict management endpoints by IP/VPN.

2) Identity for step-ca

  • Run as the dedicated step user (as above) and consider systemd hardening (ProtectSystem, NoNewPrivileges, PrivateTmp, AmbientCapabilities=CAP_NET_BIND_SERVICE).
  • Keep /etc/step-ca on a read-only or append-only partition where possible; separate secrets onto encrypted volumes.

3) Secrets management

  • Store SCEP challenge in a secret manager and template it into device configs at enrollment time.
  • If using multiple SCEP realms (e.g., printers vs. laptops), separate provisioners with distinct challenges and policies.

4) Short-lived certs + automation

  • Favor short lifetimes (7–45 days) and auto-renew via SCEP or ACME where supported.
  • For services (ingress gateways, sidecars), consider ACME provisioners in step-ca instead of SCEP.

5) Revocation and status

  • Enable OCSP and/or regularly published CRLs. Some gear only understands CRLs; others can do OCSP.
  • Document how to revoke by CN/serial and how MDM/CM tooling redistributes CRL/OCSP endpoints.

6) Names and OIDs

  • Standardize Subject and SANs. For devices, prefer DNS SANs and URNs (e.g., urn:device:asset:1234) over stuffing identifiers in CN.
  • Use policy OIDs or Extended Key Usages (EKUs) that match your relying parties (ClientAuth, ServerAuth, Wi-Fi EAP-TLS, IPsec, etc.).

7) Auditing

  • Step-ca logs each issuance; forward logs to a SIEM with context (device inventory ID, enrollment workflow ID).
  • Cloud CAS has control plane logs—monitor for volume spikes or unusual issuers.

Validating the build (quick checks)

  • Health: curl -ik https://<FQDN>/health (if you expose a health endpoint or use LB health checks).
  • SCEP reachability: curl -I https://<FQDN>/scep/poc_devicetrust should not 404.
  • CAS connectivity: Attempt a test cert request; if it fails, check service account auth and CAS IAM.
  • Chain trust: On a device, ensure the root (and intermediate, if needed) are in the appropriate trust store. Many SCEP clients install the chain automatically after GetCACert.

Troubleshooting tips

  • Challenge mismatch: 401/failed enrollment—verify SCEP challenge and transport (hidden in MDM payloads).
  • CAS quota or IAM: CAS may rate-limit or block by IAM; review Google Cloud audit logs.
  • Template oddities: If you need custom subject or SAN logic per device group, use step templates and multiple SCEP provisioners.

Where to go from here

  • Add ACME for servers and gateways while keeping SCEP for legacy endpoints.
  • Introduce device attestation checks before issuance (e.g., callbacks/webhooks from step-ca to your SCEP/ACME services).
  • Build up resilience by added device posture checks with existing VPN, EDR/XDR. and other security agents
  • Automate intermediate rotation with change windows and controlled CRL/OCSP updates.