Phase 3
Configure device certificate infrastructure
Create the division, device certificate profile, and certificate policy.
Phase details
- Workstream
- Device identity · Step 1 of 2
- Product
- Device Trust Manager
- Time
- 20 minutes
- Setup
- One-time setup
- Goal
- Select a device template, create a profile limited to one division, and bind the device issuing CA in a REST certificate policy.
- Inputs
account_idservice_api_tokendevice_ica_iddevice_template_nameprimary_rendezvous_zone_name- Expected outputs
device_certificate_template_idprimary_rendezvous_zone_iddivision_iddevice_certificate_profile_idcertificate_policy_id- Prerequisite
- Prepare the private trust domain
In this phase, you select a certificate template from DigiCert® Device Trust Manager and create the resources that define device-certificate issuance. The certificate policy binds that profile to the device issuing CA and enables the SINGLE REST enrollment method.
When you enroll and verify a device, you add passcode authentication through the device group’s policy assignment.
Phase prerequisites
Make sure you have:
account_idandservice_api_tokenfrom Establish the account foundation anddevice_ica_idfrom Prepare the private trust domain.- A Device Trust Manager role that contains Solution administrator.
- An active custom X.509 end-entity template available to the target account.
- The exact name of the approved device certificate template.
- The exact name of an enabled rendezvous zone assigned to the account for primary usage.
- A template that requires the client authentication extended key usage (EKU), permits a user-supplied common name, and permits
rsa_2048for this server-side key-generation example.
Endpoints used
| Method | Path | Purpose |
|---|---|---|
| GET | /devicetrustmanager/certificate-configuration-service/api/v1/certificate-template | Select and validate the device template from Device Trust Manager. |
| GET | /devicetrustmanager/api/v4/rendezvous-zone | Select the division’s primary rendezvous zone. |
| POST | /devicetrustmanager/api/v4/division | Create the division. |
| GET | /devicetrustmanager/api/v4/division | Retrieve the created division ID. |
| POST | /devicetrustmanager/certificate-configuration-service/api/v1/certificate-profile | Create the device certificate profile. |
| POST | /devicetrustmanager/certificate-configuration-service/api/v2/certificate-policy | Create the REST certificate policy and bind the issuing CA. |
Step 3.1: Select the device certificate template
List the templates available to the account. The response stores template records in records, not items. Your tenant’s API uses a record offset and accepts at most 100 records per page for this endpoint.
The eligibility check supports both template formats found in the current API reference and API responses: the key_types list and the key_gen object with allowed key types and an RSA size range.
printf '[]\n' > device-template-records.json
record_offset=0
while :; do
curl --fail-with-body --silent --show-error --get \
"https://demo.one.digicert.com/devicetrustmanager/certificate-configuration-service/api/v1/certificate-template" \
-H "x-api-key: ${SERVICE_API_TOKEN}" \
--data-urlencode "account_id=${ACCOUNT_ID}" \
--data-urlencode "name=${DEVICE_TEMPLATE_NAME}" \
--data-urlencode "status=ACTIVE" \
--data-urlencode "type=custom" \
--data-urlencode "format=x509" \
--data-urlencode "limit=100" \
--data-urlencode "offset=${record_offset}" \
-o device-template-page.json
jq -s '.[0] + .[1].records' \
device-template-records.json device-template-page.json \
> device-template-records.next.json
mv device-template-records.next.json device-template-records.json
total="$(jq -er '.total' device-template-page.json)"
page_count="$(jq -er '.records | length' device-template-page.json)"
collected="$(jq -er 'length' device-template-records.json)"
if (( collected >= total )); then
break
fi
if (( page_count == 0 )); then
echo "Device Trust pagination ended before total records were collected." >&2
exit 1
fi
record_offset=$((record_offset + page_count))
done
jq --arg name "${DEVICE_TEMPLATE_NAME}" --arg account_id "${ACCOUNT_ID}" '
[.[] |
select(
.name == $name and
.status == "ACTIVE" and
.type == "custom" and
.format == "x509" and
.certificate_type == "end_entity" and
(.body.issue_types | index("client_authentication")) and
(
(((.body.key_types // []) | index("rsa_2048")) != null) or
(
.body.key_gen.enabled == true and
(((.body.key_gen.key_type.allowed_types // []) |
map(ascii_downcase) | index("rsa")) != null) and
((.body.key_gen.rsa_key_size.min_bits // 2147483647) <= 2048) and
((.body.key_gen.rsa_key_size.max_bits // 0) >= 2048)
)
) and
any(.body.subject.attributes[];
.type == "common_name" and
(.allowed_source | index("user_supplied"))) and
any(.body.extensions.extended_key_usage.required_usages[];
.oid == "client_authentication") and
((.limit_by_accounts == false) or
any(.accounts[]; .id == $account_id))
)] |
if length == 1 then .[0]
else error("expected one eligible device certificate template")
end |
{id, name, status, type, format, certificate_type, accounts, body}' \
device-template-records.json > device-template.json
DEVICE_CERTIFICATE_TEMPLATE_ID="$(jq -er '.id' device-template.json)"
export DEVICE_CERTIFICATE_TEMPLATE_ID
jq '{id, name, status, type, format, certificate_type, accounts, body}' device-template.json
Save the selected id as device_certificate_template_id. Do not use a template ID returned by DigiCert® Private CA or another product. Each product uses separate template resources, so the IDs are not interchangeable.
Step 3.2: Create and retrieve the division
Each division requires a primary rendezvous zone. Find an enabled zone that is assigned to the account for primary usage before you create the division. The create response contains status information but not the new division ID. Create the division, then search by account and exact name and read the ID from the matching item in records.
DIVISION_NAME="Private trust devices"
PRIMARY_RENDEZVOUS_ZONE_NAME="Example primary rendezvous zone"
PRIMARY_RENDEZVOUS_ZONE_ID="$(
curl --fail-with-body --silent --show-error --get \
"https://demo.one.digicert.com/devicetrustmanager/api/v4/rendezvous-zone" \
-H "x-api-key: ${SERVICE_API_TOKEN}" \
--data-urlencode "account_id=${ACCOUNT_ID}" \
--data-urlencode "name=${PRIMARY_RENDEZVOUS_ZONE_NAME}" \
--data-urlencode "status=ENABLED" \
--data-urlencode "is_primary_usage=true" \
--data-urlencode "limit=100" |
jq -er --arg name "${PRIMARY_RENDEZVOUS_ZONE_NAME}" '
[.records[] |
select(.name == $name and .status == "ENABLED" and .is_primary_usage == true)] |
if length == 1 then .[0].id
else error("expected one enabled primary rendezvous zone")
end'
)"
export PRIMARY_RENDEZVOUS_ZONE_ID
curl --fail-with-body --silent --show-error \
-X POST "https://demo.one.digicert.com/devicetrustmanager/api/v4/division" \
-H "x-api-key: ${SERVICE_API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"${DIVISION_NAME}\",
\"description\": \"Devices enrolled from the private trust stack\",
\"account_id\": \"${ACCOUNT_ID}\",
\"primary_rzone_id\": \"${PRIMARY_RENDEZVOUS_ZONE_ID}\"
}"
curl --fail-with-body --silent --show-error --get \
"https://demo.one.digicert.com/devicetrustmanager/api/v4/division" \
-H "x-api-key: ${SERVICE_API_TOKEN}" \
--data-urlencode "account_id=${ACCOUNT_ID}" \
--data-urlencode "name=${DIVISION_NAME}" |
jq --arg name "${DIVISION_NAME}" \
'[.records[] | select(.name == $name and .status == "ACTIVE")] |
if length == 1 then .[0] else error("expected one active division") end' \
> division.json
DIVISION_ID="$(jq -er '.id' division.json)"
export DIVISION_ID
jq '{id, name, status, account}' division.jsonimport requests
base_url = "https://demo.one.digicert.com"
headers = {"x-api-key": service_api_token}
division_name = "Private trust devices"
primary_rendezvous_zone_name = "Example primary rendezvous zone"
response = requests.get(
f"{base_url}/devicetrustmanager/api/v4/rendezvous-zone",
headers=headers,
params={
"account_id": account_id,
"name": primary_rendezvous_zone_name,
"status": "ENABLED",
"is_primary_usage": True,
"limit": 100,
},
timeout=30,
)
response.raise_for_status()
matches = [
item for item in response.json()["records"]
if item["name"] == primary_rendezvous_zone_name
and item["status"] == "ENABLED"
and item["is_primary_usage"] is True
]
if len(matches) != 1:
raise RuntimeError(f"Expected one enabled primary rendezvous zone. Found {len(matches)}")
primary_rendezvous_zone_id = matches[0]["id"]
response = requests.post(
f"{base_url}/devicetrustmanager/api/v4/division",
headers=headers,
json={
"name": division_name,
"description": "Devices enrolled from the private trust stack",
"account_id": account_id,
"primary_rzone_id": primary_rendezvous_zone_id,
},
timeout=30,
)
response.raise_for_status()
response = requests.get(
f"{base_url}/devicetrustmanager/api/v4/division",
headers=headers,
params={"account_id": account_id, "name": division_name},
timeout=30,
)
response.raise_for_status()
matches = [
item for item in response.json()["records"]
if item["name"] == division_name and item["status"] == "ACTIVE"
]
if len(matches) != 1:
raise RuntimeError(f"Expected one active division. Found {len(matches)}")
division_id = matches[0]["id"]Save the selected rendezvous-zone id as primary_rendezvous_zone_id and the retrieved division id as division_id.
Step 3.3: Create the device certificate profile
The body property is an array of profile-attribute objects, not an array of strings. The API requires explicit key-type, validity, and renewal settings in addition to the user-supplied common name. This example limits the profile to RSA-2048 and uses a one-month validity period. It also enables the documented renewal settings and limits the profile to the division created when you create and retrieve the division. Adjust the validity and renewal values to match your certificate policy.
curl --fail-with-body --silent --show-error \
-X POST \
"https://demo.one.digicert.com/devicetrustmanager/certificate-configuration-service/api/v1/certificate-profile" \
-H "x-api-key: ${SERVICE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary "$(
jq -n \
--arg account_id "${ACCOUNT_ID}" \
--arg template_id "${DEVICE_CERTIFICATE_TEMPLATE_ID}" \
--arg division_id "${DIVISION_ID}" '
{
name: "Private device identity",
account_id: $account_id,
certificate_template_id: $template_id,
body: [
{key: "allow_any_key_type", optional: false, enabled: true,
sources: ["fixed_value"], value: "no"},
{key: "allowed_key_types", optional: false, enabled: true,
sources: ["fixed_value"], value: ["rsa_2048"]},
{key: "subject.common_name", optional: false, enabled: true,
sources: ["user_supplied"], value: ""},
{key: "validity.duration_unit", optional: false, enabled: true,
sources: ["fixed_value"], value: "months"},
{key: "validity.duration_value", optional: false, enabled: true,
sources: ["fixed_value"], value: 1},
{key: "renewal_settings.renew_valid_cert", optional: false, enabled: true,
sources: ["fixed_value"], value: "anytime"},
{key: "renewal_settings.renew_expired_cert", optional: false, enabled: true,
sources: ["fixed_value"], value: "anytime"},
{key: "renewal_settings.renew_revoked_cert", optional: false, enabled: true,
sources: ["fixed_value"], value: true},
{key: "renewal_settings.renewal_key_pair", optional: false, enabled: true,
sources: ["fixed_value"], value: "optional"}
],
divisions: [$division_id]
}'
)" \
-o device-profile-response.json
DEVICE_CERTIFICATE_PROFILE_ID="$(jq -er '.id' device-profile-response.json)"
export DEVICE_CERTIFICATE_PROFILE_ID
jq '{id, name, status, certificate_template, divisions}' device-profile-response.jsonresponse = requests.post(
f"{base_url}/devicetrustmanager/certificate-configuration-service/api/v1/certificate-profile",
headers=headers,
json={
"name": "Private device identity",
"account_id": account_id,
"certificate_template_id": device_certificate_template_id,
"body": [
{"key": "allow_any_key_type", "optional": False, "enabled": True,
"sources": ["fixed_value"], "value": "no"},
{"key": "allowed_key_types", "optional": False, "enabled": True,
"sources": ["fixed_value"], "value": ["rsa_2048"]},
{"key": "subject.common_name", "optional": False, "enabled": True,
"sources": ["user_supplied"], "value": ""},
{"key": "validity.duration_unit", "optional": False, "enabled": True,
"sources": ["fixed_value"], "value": "months"},
{"key": "validity.duration_value", "optional": False, "enabled": True,
"sources": ["fixed_value"], "value": 1},
{"key": "renewal_settings.renew_valid_cert", "optional": False,
"enabled": True, "sources": ["fixed_value"], "value": "anytime"},
{"key": "renewal_settings.renew_expired_cert", "optional": False,
"enabled": True, "sources": ["fixed_value"], "value": "anytime"},
{"key": "renewal_settings.renew_revoked_cert", "optional": False,
"enabled": True, "sources": ["fixed_value"], "value": True},
{"key": "renewal_settings.renewal_key_pair", "optional": False,
"enabled": True, "sources": ["fixed_value"], "value": "optional"},
],
"divisions": [division_id],
},
timeout=30,
)
response.raise_for_status()
profile = response.json()
device_certificate_profile_id = profile["id"]Confirm that the response references the intended template and division. Save the response id as device_certificate_profile_id.
Step 3.4: Create the REST certificate policy
Create a policy that uses the profile, division, issuing CA, and SINGLE enrollment method. The API requires single_cert_request_parameters for this method. This guide selects server-side RSA-2048 key generation for the demo request used to request the device certificate with the passcode. For production devices with secure key stores, prefer device-generated keys and certificate signing requests (CSRs).
curl --fail-with-body --silent --show-error \
-X POST \
"https://demo.one.digicert.com/devicetrustmanager/certificate-configuration-service/api/v2/certificate-policy" \
-H "x-api-key: ${SERVICE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary "$(
jq -n \
--arg division_id "${DIVISION_ID}" \
--arg profile_id "${DEVICE_CERTIFICATE_PROFILE_ID}" \
--arg ica_id "${DEVICE_ICA_ID}" '
{
certificate_policy: {
name: "Private device REST enrollment",
division_id: $division_id,
certificate_profile_id: $profile_id,
ica_id: $ica_id,
certificate_management_methods: ["SINGLE"],
key_generation_option: "server_side",
key_generation_type: "RSA_2048",
key_generation_allow_to_change: false,
require_approval_for_enroll: false,
require_approval_for_renew: false,
single_cert_request_parameters: {
key_generation_option: "server_side",
key_type: "rsa_2048",
key_generation_allowed_to_change: false,
allow_to_use_pregenerated_keys: false,
private_key_format: "pem",
rsa_private_key_syntax: "pkcs8",
allow_key_cache: false,
response_with_certificate_only: false,
split_certificate_response: true,
include_chain_option: "include_ica_and_root"
}
}
}'
)" \
-o certificate-policy-response.json
CERTIFICATE_POLICY_ID="$(jq -er '.certificate_policy.id' certificate-policy-response.json)"
export CERTIFICATE_POLICY_ID
jq '.certificate_policy |
{id, name, status, division_id, certificate_profile, ica,
ca_connector_type, certificate_management_methods, single_cert_request_parameters}' \
certificate-policy-response.jsonresponse = requests.post(
f"{base_url}/devicetrustmanager/certificate-configuration-service/api/v2/certificate-policy",
headers=headers,
json={
"certificate_policy": {
"name": "Private device REST enrollment",
"division_id": division_id,
"certificate_profile_id": device_certificate_profile_id,
"ica_id": device_ica_id,
"certificate_management_methods": ["SINGLE"],
"key_generation_option": "server_side",
"key_generation_type": "RSA_2048",
"key_generation_allow_to_change": False,
"require_approval_for_enroll": False,
"require_approval_for_renew": False,
"single_cert_request_parameters": {
"key_generation_option": "server_side",
"key_type": "rsa_2048",
"key_generation_allowed_to_change": False,
"allow_to_use_pregenerated_keys": False,
"private_key_format": "pem",
"rsa_private_key_syntax": "pkcs8",
"allow_key_cache": False,
"response_with_certificate_only": False,
"split_certificate_response": True,
"include_chain_option": "include_ica_and_root",
},
}
},
timeout=30,
)
response.raise_for_status()
policy = response.json()["certificate_policy"]
certificate_policy_id = policy["id"]Validate the response before continuing:
certificate_profile.idmatchesdevice_certificate_profile_id.ica.idmatches thedevice_ica_idselected when you prepared the private trust domain.division_idmatchesdivision_id.certificate_management_methodsincludesSINGLE.ca_connector_typeidentifies the DigiCert ONE CA connection.
Phase 3 checkpoint
- An active division exists and
division_idwas retrieved from the list response. - The division was created with the approved enabled primary rendezvous zone.
- The selected Device Trust Manager template is active, custom, X.509, and eligible for this client authentication request.
- The certificate profile uses
device_certificate_template_idand is available to the division. - The certificate policy binds the profile, division, and
device_ica_idand enablesSINGLEenrollment. - The policy supports the server-side key-generation request used to enroll and verify a device.
Next, enroll and verify a device.