Automatic Certificate Revocation
If you're using Active Directory for workstation authentication with HYPR, deregistering a user's mobile device will remove the certificate information from the HYPR platform. However, the certificate is still present in AD CS and must be removed separately.
To help AD Administrators manage certificate revocation, HYPR internally tracks the various deregistration events and makes a list of deregistered certificate serial numbers available via the Bulk API. Use POST /rp/api/bulk/{rpAppId}/fetch with the deleted_certificates entity to retrieve this information. For more details, see the Bulk API documentation and API reference.
This document demonstrates how to create a PowerShell script that uses this API call to automate the certificate revocation workflow.
The following HYPR deregistration events are tracked:
-
The device or workstation has been deregistered
-
The user has been deleted in the HYPR Control Center, triggering a deregister
-
The workstation has renewed the certificate
-
The Smart Key has been unenrolled
Script Code
This example script demonstrates how to retrieve and process deregistered certificates. Then, the certificates are output to the screen and can be optionally revoked using the standard Microsoft Certificate Authority COM object.
Note that the script is provided as an example only. Depending on your production environment, you may need to implement the workflow differently.
This example provides no error handling, logging, or other facilities essential for a production-quality script. Please thoroughly test any script based on this example before using it in your production environment!
To use the Bulk API for deleted certificates, the ENROLLMENT_SERVICE feature flag must be enabled. To enable this feature, contact HYPR Support through the support portal.
# Usage examples ...
# .\revoke-dereg-certs-example.ps1 -CC <xxx.com> -RpApp <yyy> -Token '<xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx>' -From "11/22/2020 12:00:00 AM"
# .\revoke-dereg-certs-example.ps1 -CC <xxx.com> -RpApp <yyy> -Token '<xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx>' -From 1623876047
# .\revoke-dereg-certs-example.ps1 -CC <xxx.com> -RpApp <yyy> -Token '<xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx>' -From 1623876047 -Revoke $true
#
param(
[Parameter(Mandatory=$true)]
[string]$CC, # (required) Domain name of Control Center
[Parameter(Mandatory=$true)]
[string]$RpApp, # (required) RpApp
[Parameter(Mandatory=$true)]
[string]$Token, # (required) Bearer token (required)
[Parameter(Mandatory=$true)]
$From, # (required) UTC DateTime as regular DateTime string or Unix time (as number of seconds from Jan 1, 1970)
[bool]$Revoke = $false # (optional) If true, deregistered certificates will be revoked
)
# Script Entry Point
$Main = {
$filter = ConvertToUnixMillisec $From
$deregCerts = GetDeregisteredCertificates $CC $RpApp $Token $filter
# Show certificates
ShowCertificates $deregCerts
# Optionally revoke certificates
if ($Revoke) {
RevokeCertificates $deregCerts
}
}
# ------------------------------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------------------
#
# G e t D e r e g i s t e r e d C e r t i f i c a t e s
#
# This is a workhorse/core function, which makes a REST API call to the Bulk API endpoint to get a
# list of deregistered certificates, including certificate serial number, device ID, and machine ID.
# Note: The $timeFilter parameter is accepted for backward compatibility but is not currently used
# as date field names vary by environment. The function returns all available deleted certificates
# (typically the last 30 days). More details, including example of JSON "shape" are provided below.
#
function GetDeregisteredCertificates([string]$server, [string]$app, [string]$bearer, [int64]$timeFilter) {
# Alternative way to retrieve/test JSON via command line
# Note: Date filtering not included as field names vary by environment
# curl --location --request POST 'https://<$CC>/rp/api/bulk/<$RpApp>/fetch' \
# --header 'Authorization: Bearer xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx' \
# --header 'Content-Type: application/json' \
# --data-raw '{"target":{"subject":"deleted_certificates","fields":["certificateSerialNumber","deviceId","machineId"],"filter":null},"pagination":{"page":0,"rows":1000}}'
# This is an example of possible JSON output from the Bulk API fetch endpoint
# Note: The Bulk API fetch endpoint returns data in a "results" array, unlike the deprecated
# endpoint which returned data in a "certificates" array.
# Note: Field names and availability may vary by environment.
#
# {
# "status": "ok",
# "count": null,
# "cost": 3000,
# "results": [
# {
# "certificateSerialNumber": "172E255F0000000062DE",
# "deviceId": "20C3F05D-368D-4C4F-BA24-17E6577FB014",
# "machineId": "29910810-5183-40f0-7bfd-5456b1ae5776"
# },
# {
# "certificateSerialNumber": "1749B3D200000000437E1",
# "deviceId": "72A16C90-F2CF-481F-BD3B-3A7330EC0FE1",
# "machineId": "95745D81-64BD-4374-B2EB-11ADA729F04E"
# }
# ]
# }
#
# Build the Bulk API fetch request body
# Note: Date filtering is not included as the date field name varies by environment.
# Use RP-scoped endpoint which automatically filters by the specified RP App ID
# This returns all available deleted certificates (typically the last 30 days)
$uri = "https://$($server)/rp/api/bulk/$($app)/fetch"
# Build the request body for Bulk API fetch
# Fields available may vary by environment; use minimal set that works across environments
$requestBody = @{
"target" = @{
"subject" = "deleted_certificates"
"fields" = @("certificateSerialNumber", "deviceId", "machineId")
"filter" = $null
}
"pagination" = @{
"page" = 0
"rows" = 1000
}
} | ConvertTo-Json -Depth 10
$params = @{
Uri = $uri
Headers = @{ 'Authorization' = "Bearer $bearer"; 'Content-Type' = 'application/json' }
Method = 'POST'
Body = $requestBody
}
# Get JSON object from Bulk API fetch endpoint
$webData = Invoke-RestMethod @params
# Response structure from Bulk API fetch: results are in the "results" array
return $webData.results
}
# ------------------------------------------------------------------------------------------------------------
#
# S h o w C e r t i f i c a t e s
#
# Example of what can be done with the JSON of deregistered certificates. In this case they
# are simply displayed on the screen. In a production script, you can adapt to save them to a log
# file, or save to a network share for external application consumption, or do nothing and proceed
# immediately to their revocation.
#
function ShowCertificates($certs) {
# We can show nicely formatted output using PS one-liner. It is commented out to show
# more procedural column access/manipulation further down
#
# $certs | Format-Table -Property machineUserName,@{Label="Deleted(UTC)"; Expression={ConvertFromUnixTime($_.deleteDate/1000)}},certificateSerialNumber
#
# If you uncomment the line above your output should be simular to this
#
# machineUserName Deleted(UTC) certificateSerialNumber
# --------------- ------------ -----------------------
# DESKTOP-ghopper 6/16/2021 8:40:47 PM 172E255F0000000062DE
# DESKTOP-jclarke 6/16/2021 9:04:04 PM 1749B3D200000000437E1
#
# ... but if you want to adopt a more flexible output or other actions:
$counter = 0
foreach($cert in $certs) {
$counter = $counter + 1
write-output "$($counter)."
write-output "Machine: $($cert.machineUserName)"
write-output "Deleted(UTC): $(ConvertFromUnixTime($cert.deleteDate/1000))"
write-output "Serial#: $($cert.certificateSerialNumber)"
write-output ""
}
}
# ------------------------------------------------------------------------------------------------------------
#
# R e v o k e C e r t i f i c a t e s
#
# This is an example of a certificate revocation using two built-in (on Windows Server) COM
# objects created using their ProgIDs.
#
# "CertificateAuthority.Config" ProgID represents the ICertConfig interface and this script uses its
# ICertConfig::GetConfig() method documented here:
# https://docs.microsoft.com/en-us/windows/win32/api/certcli/nf-certcli-icertconfig-getconfig
# It's used to get CertificateAuthority configuration, which is a parameter for the other COM object's
# RevokeCertificate() method. This is a "COMPUTERNAME\CANAME" string, where COMPUTERNAME is the network
# name of the Certificate Services server and CANAME is the common name of the certification authority,
# as entered during Certificate Services setup.
#
# "CertificateAuthority.Admin" ProgID represents the ICertAdmin interface and this script uses its
# ICertAdmin::RevokeCertificate() and ICertAdmin::PublishCRL() methods documented here respectively:
# https://docs.microsoft.com/en-us/windows/win32/api/certadm/nf-certadm-icertadmin-revokecertificate
# https://docs.microsoft.com/en-us/windows/win32/api/certadm/nf-certadm-icertadmin-publishcrl
#
function RevokeCertificates($certs) {
# Get COM object with ProgID "CertificateAuthority.Config"
$CertConfig = New-Object -ComObject CertificateAuthority.Config
# Get COM object with ProgID "CertificateAuthority.Admin"
$CertAdmin = New-Object -ComObject CertificateAuthority.Admin
# Get "COMPUTERNAME\CANAME" configuration
$CC_LOCALACTIVECONFIG = 4
$caConfig = $CertConfig.GetConfig($CC_LOCALACTIVECONFIG)
# Loop through the certificates list
$CRL_REASON_UNSPECIFIED = 0
$REVOCATION_DATE_TIME = 0
foreach($cert in $certs) {
# Revoke certificate
$CertAdmin.RevokeCertificate($caConfig, $cert.certificateSerialNumber, $CRL_REASON_UNSPECIFIED, $REVOCATION_DATE_TIME)
}
# Publish CRL
$CertAdmin.PublishCRL($caConfig, $REVOCATION_DATE_TIME)
}
# -----------------------------------
#
# H e l p e r s
#
# Convert from Unix time to DateTime
function ConvertFromUnixTime ([int64]$unixTime) {
[datetime]$zeroUnixTime = '1970-01-01 00:00:00'
[datetime]$result = $zeroUnixTime.AddSeconds($unixTime)
return $result
}
# Convert from DateTime to Unix time
function ConvertToUnixTime ([datetime]$dateTime) {
[datetime]$zeroDateTime = '1970-01-01 00:00:00'
[int64]$unixTime = 0
$unixTime = (New-TimeSpan -Start $zeroDateTime -End $dateTime).TotalSeconds
return $unixTime
}
# Convert to Unix time (millisecond resolution) parameter which is either
# Unix time as seconds or DateTime as a string
function ConvertToUnixMillisec($dt) {
[int64]$unixTimeMillisec = 0
$isNum = $dt -is [int]
if ($isNum) {
# Unix time as seconds provided
$unixTimeMillisec = [int64]$dt
# Scale up to milliseconds (because endpoint API requires that)
$unixTimeMillisec *= 1000
}
else {
# DateTime as string provided
[datetime]$dateTime = [datetime]::Parse($dt)
$unixTimeMillisec = ConvertToUnixTime $dateTime
# Scale up to milliseconds (because endpoint API requires that)
$unixTimeMillisec *= 1000
}
return $unixTimeMillisec
}
# Execute Main function (entry point)
& $Main
Script Parameters
Run the script in a PowerShell window as follows:
.\revoke-dereg-certs-example.ps1 -CC <domain> -RpApp <appID> -Token
<xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx> -From <date> -Revoke $True
CC (string, required)
The base domain of your Control Center instance.
RpApp (string, required)
The ID of your Relying Party application in the Control Center.
Token (string, required)
A valid Control Center access token. For more information on generating a token, please see Access Tokens.
From (date, required)
UTC date and time, either as regular DateTime string or a Unix time (number of seconds from Jan 1, 1970).
Revoke (boolean, optional)
If $true, deregistered certificates will be revoked in ADCS. Note that if you set the Revoke parameter, the script must be executed on a Windows Server machine with elevated privileges. The required COM objects aren't available on non-server Windows installs.
Usage Examples
.\revoke-dereg-certs-example.ps1 -CC highlandsbank.com -RpApp WorkstationAccess -Token
110f7826-d95e-12d2-42ce-8bd67fb20bb9 -From "07/01/2021 12:00:00 AM"
.\revoke-dereg-certs-example.ps1 -CC highlandsbank.com -RpApp WorkstationAccess -Token
110f7826-d95e-12d2-42ce-8bd67fb20bb9 -From 1625097600
.\revoke-dereg-certs-example.ps1 -CC highlandsbank.com -RpApp WorkstationAccess -Token
110f7826-d95e-12d2-42ce-8bd67fb20bb9 -From 1625097600 -Revoke $true
Sample Output:
1.
Machine: DESKTOP-ghopper
Deleted(UTC): 07/01/2021 12:34:18
Serial#: 172E255F0000000062DE
2.
Machine: DESKTOP-jclarke
Deleted(UTC): 07/01/2021 12:42:30
Serial#: 1749B3D200000000437E1