--- title: "Configure device certificate infrastructure" description: "Create the division, device certificate profile, and certificate policy." source_url: https://dev.digicert.com/solutions/private-trust-stack/phase-3-device-infrastructure.html --- # Configure device certificate infrastructure Create the division, device certificate profile, and certificate policy. ## Phase metadata - Phase: 3 - Workstream: Device identity — Step 1 of 2 - Product: Device Trust Manager - Goal: Select a device template, create a profile limited to one division, and bind the device issuing CA in a REST certificate policy. - Estimated time: 20 minutes - Inputs: account_id, service_api_token, device_ica_id, device_template_name, primary_rendezvous_zone_name - Prerequisite: [Prepare the private trust domain](https://dev.digicert.com/md/solutions/private-trust-stack/phase-2-private-trust-anchor.md) 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](https://dev.digicert.com/md/solutions/private-trust-stack/phase-4-device-enrollment.md), you add passcode authentication through the device group's policy assignment. ## Phase prerequisites Make sure you have: - `account_id` and `service_api_token` from [Establish the account foundation](https://dev.digicert.com/md/solutions/private-trust-stack/phase-1-account-foundation.md) and `device_ica_id` from [Prepare the private trust domain](https://dev.digicert.com/md/solutions/private-trust-stack/phase-2-private-trust-anchor.md). - 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_2048` for 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. ```bash 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`. **cURL** ```bash 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.json ``` **Python** ```python import 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](#step-32-create-and-retrieve-the-division). Adjust the validity and renewal values to match your certificate policy. **cURL** ```bash 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.json ``` **Python** ```python response = 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](https://dev.digicert.com/md/solutions/private-trust-stack/phase-4-device-enrollment.md#step-44-request-the-device-certificate-with-the-passcode). For production devices with secure key stores, prefer device-generated keys and certificate signing requests (CSRs). **cURL** ```bash 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.json ``` **Python** ```python response = 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.id` matches `device_certificate_profile_id`. - `ica.id` matches the `device_ica_id` selected when you prepared the private trust domain. - `division_id` matches `division_id`. - `certificate_management_methods` includes `SINGLE`. - `ca_connector_type` identifies the DigiCert ONE CA connection. ## Phase 3 checkpoint - [ ] An active division exists and `division_id` was 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_id` and is available to the division. - [ ] The certificate policy binds the profile, division, and `device_ica_id` and enables `SINGLE` enrollment. - [ ] The policy supports the server-side key-generation request used to enroll and verify a device. Next, [enroll and verify a device](https://dev.digicert.com/md/solutions/private-trust-stack/phase-4-device-enrollment.md). ## Outputs and handoff | Output | Produced in | Used by | | --- | --- | --- | | `device_certificate_template_id` | Select the device certificate template: record `id` | Operations, audit, or cleanup | | `primary_rendezvous_zone_id` | Create and retrieve the division: rendezvous-zone record `id` | Operations, audit, or cleanup | | `division_id` | Create and retrieve the division: list response `records[].id` | [Enroll and verify a device](https://dev.digicert.com/md/solutions/private-trust-stack/phase-4-device-enrollment.md) | | `device_certificate_profile_id` | Create the device certificate profile: response `id` | Operations, audit, or cleanup | | `certificate_policy_id` | Create the REST certificate policy: response `id` | [Enroll and verify a device](https://dev.digicert.com/md/solutions/private-trust-stack/phase-4-device-enrollment.md) |