Keyfactor Command is a highly flexible platform that provides end-to-end visibility and automation for digital certificates and keys across an organization's IT infrastructure. Command ACME enables workloads using the ACME protocol to seamlessly request certificates from any certificate authority (CA) integrated with Keyfactor Command. Command ACME uses a claim-based mapping, and this guide enables the following workflow.
- An admin user creates a claim in Command ACME using KeyfactorACMEConfig.exe. This claim will map the scope claim in JWT access tokens to a certificate template in Command.
- A user authenticates to the Command ACME Key Management API to fetch an EAB key using an access token containing the claim.
- The user configures the ACME client to use the EAB key when creating an account in Command ACME.
- Certificate orders created under the account are enrolled in Command using the certificate template mapped by the EAB key of the account used.
The following diagram depicts the architecture, protocols involved, and the steps above.
This guide walks you through the steps to identify and configure a certificate template in Command, then set up Command ACME to map an OAuth scope to that template. Next, you'll use provided scripts that use the OAuth 2.0 Client Credentials grant to fetch an EAB key for use by an ACME client like cert-manager or Certbot.
Outcomes
In this guide, you will:
- Identify and configure a certificate template in Command
- Create a claim in Command ACME that maps a JWT claim to the certificate template
- Fetch an EAB key using the Key Management API
Prerequisites
Before you continue, you must ensure that you have the following prerequisites.
- OAuth 2.0 provider. This guide expects that you have an OAuth 2.0 authorization server that is configured to issue JWT access tokens. You should have permission to create confidential client applications and configure the scopes that the client is allowed to request.
- Command ACME 25.1. This guide expects that Command ACME 25.1 is installed on a suitable Windows server. The Command ACME server must be configured with credentials to an upstream Keyfactor Command server and with an OAuth 2.0 provider for authorization to the Key Management API. Refer to the Command ACME documentation to learn more.
- Command 11 or greater. This guide expects that you have access to Command 11 or greater and that it's deployed per the documentation.
Step 1: Identify and configure a certificate template
Certificate templates in Command define the properties and constraints of issued certificates, including key usage, validity period, allowed algorithms, and required subject information. Refer to the Command documentation to learn more about certificate templates. This section provides the steps to configure a certificate template for use by Command ACME.
- Sign in to the Command Web Portal.
- Toggle the Locations dropdown, and then click Certificate Templates.
- Identify a certificate template suitable for use by your ACME use case. The template that you select cannot be targeted by an approval workflow. Double-click the certificate template that you have identified.
- On the Details tab, make a note of the Template Short Name. Then, under Allowed Enrollment Types, ensure that CSR Enrollment is toggled.
- On the Authorization Methods tab, ensure that Restrict Allowed Requestors is toggled.
- Click Add.
- In the Name dropdown, select a suitable security role that the identity used by the Command ACME server has access to.
- Click Save.
- Click Save again.
Step 2: Prepare an OAuth client to authenticate a user to the Key Management API
Users authenticate to the Command ACME server API for EAB key retrieval using an OAuth 2.0 access token. Claims in the JWT access token are mapped to a permission in the Command ACME server and a certificate template in Command. This guide details how to fetch access tokens for the ACME server API using the Client Credentials grant.
- Identify a suitable scope value representative of the certificate template identified earlier. Any EAB key requested with this scope will be tied to that template, so it's best to use a scope that clearly reflects the template or its intended use case. For example, acme:template:<template-name> or acme:<use case>.
- In your OAuth 2.0 provider, create or identify a confidential client application that supports the Client Credentials grant. Ensure that this client is authorized to request the scope identified earlier and that, when requested, the scope is included in access tokens issued by the authorization server. Ensure that the following information is readily available.
- Token URL: The endpoint provided by the OAuth 2.0 authorization server where you will exchange client credentials for a JWT access token to authenticate to the Key Management API.
- Client ID: The unique identifier assigned to a client application by the OAuth provider.
- Client Secret: A confidential key used alongside the client ID.
- Scopes: The scope value identified earlier in addition to any other scopes required by your OAuth provider.
- Audience: The intended recipient of the access token or a specific value required by your OAuth provider. This guide doesn't configure Command ACME to have any opinion on the contents of the audience claim in access tokens, and it's purely up to your OAuth authorization server to determine what audience is requested. In most cases, the audience will be blank.
Step 3: Create a claim
In Command ACME 25.1, a claim maps a particular JWT claim and value to a certificate template in Command. In this section, you will use the claims command to map the scope claim identified earlier to the certificate template identified and configured earlier. Each client that requests an EAB key using this scope will get a unique key that maps to the same certificate template. If you require multiple certificate templates, you'll need to create multiple claims.
Note: If Keyfactor hosts Command ACME for you, please reach out to your Keyfactor Support representative to create a claim on your behalf.
- On the Command ACME Windows server, open a PowerShell using the "Run as administrator" option.
- Change to the directory where the ACME server was installed. If you used the default installation location, you can run the following:
cd 'C:\Program Files\Keyfactor\ACME\Configuration'
- Use the claims command to set the initial configuration for Command ACME. Please use the Claims CLI command documentation for specific information on each parameter.
.\KeyfactorACMEConfig.exe claims --add `
Replace the following fields:
--claimtype ACCESS_TOKEN_CLAIM `
--claimvalue ACCESS_TOKEN_CLAIM_VALUE `
--roles EnrollmentUser `
--template COMMAND_CERTIFICATE_TEMPLATE
- ACCESS_TOKEN_CLAIM: The scope claim in JWT access tokens returned by your OAuth 2.0 authorization server. In most cases, this will be scp, but some authorization servers use scope.
- ACCESS_TOKEN_CLAIM_VALUE: The scope identified earlier. For example, acme:template:<template-name> or acme:<use case>.
- COMMAND_CERTIFICATE_TEMPLATE: The Template Short Name of the certificate template in Command.
- The output of the command in step 3 is similar to the following:
Adding claim with type: scp, value: acme-usecase-1, roles: EnrollmentUser, and template: tls1y
Updating claim's enrollment template to tls1y
Updating claim's roles.
Step 4: Fetch an EAB key
This section provides a PowerShell and Bash script to fetch an EAB key corresponding to the confidential client application whose client ID maps to a certificate template in Command. Depending on your preferred scripting language, create a new file and copy and paste the script of your choice. The top of each script contains predefined values required to create an access token and submit a request to the Key Management API. Populate these values with the following:
- COMMAND_ACME_API_URL: The URL to the Command ACME API including the scheme. E.g., https://enrollment.example.com/acme.
- TOKEN_URL: The token endpoint provided by the OAuth 2.0 authorization server.
- CLIENT_ID: The unique identifier assigned to a client application by the OAuth provider.
- CLIENT_SECRET: A confidential key used alongside the client ID.
- SCOPE: The scope value identified earlier in addition to any other scopes required by your OAuth provider.
- AUDIENCE: A specific value required by your OAuth provider. This guide doesn't configure Command ACME to have any opinion on the contents of the audience claim in access tokens, and it's purely up to your OAuth authorization server to determine what audience is requested. In most cases, the audience will be blank.
Powershell script
# Initialize variables
$COMMAND_ACME_API_URL = ""
$TOKEN_URL = ""
$CLIENT_ID = ""
$CLIENT_SECRET = ""
$SCOPE = ""
$AUDIENCE = ""
# Prompt for variables if they are empty
if (-not $COMMAND_ACME_API_URL)
{
$COMMAND_ACME_API_URL = Read-Host "Enter the URL to Command ACME"
}
if (-not $TOKEN_URL)
{
$TOKEN_URL = Read-Host "Enter Token URL"
}
if (-not $CLIENT_ID)
{
$CLIENT_ID = Read-Host "Enter Client ID"
}
if (-not $CLIENT_SECRET)
{
# Prompt as SecureString to avoid displaying it in plain text
$secureClientSecret = Read-Host "Enter Client Secret" -AsSecureString
# Convert the SecureString to plain text for usage in the POST body
$CLIENT_SECRET = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureClientSecret)
)
}
if (-not $SCOPE)
{
$SCOPE = Read-Host "Enter Scope (enter for empty)"
}
if (-not $AUDIENCE)
{
$AUDIENCE = Read-Host "Enter Audience (enter for empty)"
}
function Get-Token
{
# Construct the POST body
$tokenBody = "client_id=$($CLIENT_ID)&client_secret=$($CLIENT_SECRET)&grant_type=client_credentials"
if ($SCOPE)
{
$tokenBody += "&scope=$($SCOPE)"
}
if ($AUDIENCE)
{
$tokenBody += "&audience=$($AUDIENCE)"
}
# Call the token endpoint
try
{
$tokenResponse = Invoke-RestMethod `
-Uri $TOKEN_URL `
-Method Post `
-ContentType 'application/x-www-form-urlencoded' `
-Body $tokenBody
} catch
{
Write-Error "Failed to request access token."
throw
}
$accessToken = $tokenResponse.access_token
if (-not $accessToken)
{
Write-Error "Failed to parse 'access_token' from response."
Write-Host $tokenResponse
exit 1
}
return $accessToken
}
# Fetch the EAB key from Command ACME
$token = Get-Token
$url = "$($COMMAND_ACME_API_URL)/KeyManagement"
Write-Host "GET $url"
# Invoke the API endpoint with the Bearer token
try
{
$response = Invoke-RestMethod -Uri $url -Headers @{ "Authorization" = "Bearer $token" } -Method Get
$response
} catch
{
Write-Error "Failed to invoke $url"
throw
}
Bash script
#!/bin/bash
COMMAND_ACME_API_URL=""
TOKEN_URL=""
CLIENT_ID=""
CLIENT_SECRET=""
SCOPE=""
AUDIENCE=""
if [[ -z "$COMMAND_ACME_API_URL" ]]; then
read -p "Enter the URL to Command ACME: " COMMAND_ACME_API_URL
fi
if [[ -z "$TOKEN_URL" ]]; then
read -p "Enter Token URL: " TOKEN_URL
fi
if [[ -z "$CLIENT_ID" ]]; then
read -p "Enter Client ID: " CLIENT_ID
fi
if [[ -z "$CLIENT_SECRET" ]]; then
read -s -p "Enter Client Secret: " CLIENT_SECRET
echo # Move to a new line after hidden input
fi
if [[ -z "$SCOPE" ]]; then
read -p "Enter Scope (enter for empty): " SCOPE
fi
if [[ -z "$AUDIENCE" ]]; then
read -p "Enter Audience (enter for empty): " AUDIENCE
fi
fetch_token () {
local token_body="client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&grant_type=client_credentials"
if [ ! -z $SCOPE ]; then
token_body="$token_body&scope=$SCOPE"
fi
if [ ! -z $AUDIENCE ]; then
token_body="$token_body&audience=$AUDIENCE"
fi
# Send the request with the correct content type
local token_response=$(curl --request POST \
--url "$TOKEN_URL" \
--header 'content-type: application/x-www-form-urlencoded' \
--data "$token_body")
local access_token="$(echo "$token_response" | jq -r '.access_token')"
if [[ -z "$access_token" || "$access_token" == "null" ]]; then
echo "failed to request access token" >&2
echo "$token_response" >&2
exit 1
fi
echo "$access_token"
}
# Fetch the EAB key from Command ACME
token=$(fetch_token)
url="$COMMAND_ACME_API_URL/KeyManagement"
echo "GET $url"
curl --url "$url" \
-H "Authorization: Bearer $(echo "$token")" \
--header "Authorization: Bearer $(echo "$token")"
The output of the API call contains a keyId and keyValue:
- keyId is a string containing the reference GUID for the EAB key issued to the external account (user) within the Keyfactor ACME server. This identifier allows the server to recognize which external account is associated with a given ACME request.
- keyValue is a string containing the HMAC key value. This cryptographic key is used to sign the ACME request for external account binding, ensuring that the request is authenticated and that the ACME client is authorized to register with the Keyfactor ACME server.
These values should be used when configuring the ACME client. For example, cert-manager documents usage of External Account Binding here.
Add comment
Please sign in to leave a comment.