--- title: "Enroll and verify a device" description: "Create passcode enrollment configuration and validate an issued device certificate." source_url: https://dev.digicert.com/solutions/private-trust-stack/phase-4-device-enrollment.html --- # Enroll and verify a device Create passcode enrollment configuration and validate an issued device certificate. ## Phase metadata - Phase: 4 - Workstream: Device identity — Step 2 of 2 - Product: Device Trust Manager - Goal: Attach a passcode authentication policy to a device group, issue a device certificate, and validate its key and chain. - Estimated time: 25 minutes - Inputs: account_id, service_api_token, division_id, certificate_policy_id, passcode, root-ca.pem, device-issuing-ca.pem - Prerequisite: [Configure device certificate infrastructure](https://dev.digicert.com/md/solutions/private-trust-stack/phase-3-device-infrastructure.md) In this phase, you use DigiCert® Device Trust Manager to create an authentication policy and add a passcode to it. You assign the authentication policy and certificate policy to a device group, then request a device certificate using passcode authentication. Finally, you verify that the returned private key matches the certificate and that the certificate path terminates at the root trust anchor prepared for the private trust domain. ## 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 `division_id` and `certificate_policy_id` from [Configure the device certificate infrastructure](https://dev.digicert.com/md/solutions/private-trust-stack/phase-3-device-infrastructure.md). - `root-ca.pem` and `device-issuing-ca.pem` 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** for administrative setup. - A passcode that contains six to 64 characters and is generated and stored through an approved secret-management process. - A secure destination for the server-generated device private key. ## Endpoints used | Method | Path | Purpose | |--------|------|---------| | POST | `/devicetrustmanager/authentication-service/api/v1/authentication-policy` | Create the authentication policy. | | POST | `/devicetrustmanager/authentication-service/api/v1/passcode` | Create the enrollment passcode. | | POST | `/devicetrustmanager/api/v4/device-group` | Create the group and policy assignment. | | GET | `/devicetrustmanager/api/v4/device-group` | Retrieve the group ID and inspect the assignment. | | POST | `/devicetrustmanager/certificate-issuance-service/api/v2/certificate` | Request and register a device certificate. | | GET | `/devicetrustmanager/certificate-issuance-service/api/v2/certificate-request/{certificate_request_id}/status` | Poll an enrollment that requires approval. | | POST | `/devicetrustmanager/certificate-issuance-service/api/v2/certificate/{certificate_id}/download-approved-certificate` | Download an approved certificate and server-generated key. | ## Step 4.1: Create an authentication policy ```bash jq -n \ --arg account_id "${ACCOUNT_ID}" ' { name: "Private device enrollment", account_id: $account_id, description: "Passcode authentication for the private device REST policy" }' | curl --fail-with-body --silent --show-error \ -X POST \ "https://demo.one.digicert.com/devicetrustmanager/authentication-service/api/v1/authentication-policy" \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ -H "Content-Type: application/json" \ --data-binary @- \ -o authentication-policy-response.json AUTHENTICATION_POLICY_ID="$(jq -er '.id' authentication-policy-response.json)" export AUTHENTICATION_POLICY_ID jq '{id, name, account_id, status}' authentication-policy-response.json ``` Save the response `id` as `authentication_policy_id`. ## Step 4.2: Create a passcode credential The API sets the default value of `enable_username_for_passcode` to `true` and requires a username when that setting is enabled. This workflow does not use a username, so set the property to `false` explicitly. Supply a passcode with six to 64 characters through a protected prompt, and do not put it in shell history or logs. ```bash read -rsp "Enrollment passcode: " ENROLLMENT_PASSCODE echo if (( ${#ENROLLMENT_PASSCODE} < 6 || ${#ENROLLMENT_PASSCODE} > 64 )); then echo "The enrollment passcode must contain 6-64 characters." >&2 return 1 2>/dev/null || exit 1 fi jq -n \ --arg name "Private device enrollment passcode" \ --arg account_id "${ACCOUNT_ID}" \ --arg authentication_policy_id "${AUTHENTICATION_POLICY_ID}" \ --arg passcode "${ENROLLMENT_PASSCODE}" \ '{ name: $name, account_id: $account_id, authentication_policy_id: $authentication_policy_id, enable_username_for_passcode: false, passcode: $passcode, usage_limit: 10 }' | curl --fail-with-body --silent --show-error \ -X POST \ "https://demo.one.digicert.com/devicetrustmanager/authentication-service/api/v1/passcode" \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ -H "Content-Type: application/json" \ --data-binary @- \ -o passcode-response.json chmod 600 passcode-response.json PASSCODE_ID="$(jq -er '.id' passcode-response.json)" export PASSCODE_ID jq '{id, name, authentication_policy_id, enable_username_for_passcode, passcode_hint}' \ passcode-response.json ``` Your tenant's API might return the passcode in the response. Do not depend on that behavior. Treat the entire response as a secret, do not print the `passcode` property, and continue using the original `ENROLLMENT_PASSCODE` value from your secret store. Save the response `id` as `passcode_id`. ## Step 4.3: Create and retrieve the device group The `policies` array contains policy-assignment objects, not policy-ID strings. The assignment below marks the certificate policy as the bootstrap certificate policy and applies the authentication-policy override documented for device groups. ```bash DEVICE_GROUP_NAME="Private trust test devices" curl --fail-with-body --silent --show-error \ -X POST "https://demo.one.digicert.com/devicetrustmanager/api/v4/device-group" \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"${DEVICE_GROUP_NAME}\", \"description\": \"Validation group for private device certificates\", \"division_id\": \"${DIVISION_ID}\", \"account_id\": \"${ACCOUNT_ID}\", \"is_static\": true, \"policies\": [ { \"policy_id\": \"${CERTIFICATE_POLICY_ID}\", \"assignment_name\": \"Private device bootstrap certificate\", \"type\": \"bootstrapCertificate\", \"auth_policy_id\": \"${AUTHENTICATION_POLICY_ID}\", \"status\": \"ACTIVE\" } ] }" curl --fail-with-body --silent --show-error --get \ "https://demo.one.digicert.com/devicetrustmanager/api/v4/device-group" \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ --data-urlencode "account_id=${ACCOUNT_ID}" \ --data-urlencode "division_id=${DIVISION_ID}" \ --data-urlencode "name=${DEVICE_GROUP_NAME}" \ --data-urlencode "include_assignment=true" | jq --arg name "${DEVICE_GROUP_NAME}" \ '[.records[] | select(.name == $name and .status == "ACTIVE")] | if length == 1 then .[0] else error("expected one active device group") end' \ > device-group.json DEVICE_GROUP_ID="$(jq -er '.id' device-group.json)" export DEVICE_GROUP_ID jq '{id, name, status, policies}' device-group.json ``` Save the exact matching record's `id` as `device_group_id`. Confirm its policy assignment contains the intended `certificate_policy_id` and `authentication_policy_id` and reports `status: ACTIVE`. ## Step 4.4: Request the device certificate with the passcode Passcode authentication is an alternative to authentication with an API token for this endpoint. Send `x-passcode`, and omit `x-api-key`. Include `device_group_id` so Device Trust Manager applies the group policy assignment and its authentication-policy override. This demo uses the server-side key-generation policy created when you [configure the device certificate infrastructure](https://dev.digicert.com/md/solutions/private-trust-stack/phase-3-device-infrastructure.md#step-34-create-the-rest-certificate-policy). For production devices with protected key storage, configure client-side generation and submit a certificate signing request (CSR) instead. ```bash umask 077 jq -n \ --arg certificate_policy_id "${CERTIFICATE_POLICY_ID}" \ --arg device_group_id "${DEVICE_GROUP_ID}" \ --arg common_name "device-001.example.internal" \ '{ certificate_policy_id: $certificate_policy_id, device_group_id: $device_group_id, certificate_profile_attributes: [ {id: "subject.common_name", value: $common_name} ], server_side_key_gen: true, key_type: "rsa_2048", private_key_format: "pem", private_key_syntax: "pkcs8", response_with_certificate_only: false, include_certificate_chain: true, split_certificate_response: true }' | curl --fail-with-body --silent --show-error \ -X POST \ "https://demo.one.digicert.com/devicetrustmanager/certificate-issuance-service/api/v2/certificate" \ -H "x-passcode: ${ENROLLMENT_PASSCODE}" \ -H "Content-Type: application/json" \ --data-binary @- \ -o device-enrollment-response.json chmod 600 device-enrollment-response.json jq '{certificate_request_id, status, result, certificate_id, device}' \ device-enrollment-response.json CERTIFICATE_REQUEST_ID="$(jq -er '.certificate_request_id' device-enrollment-response.json)" DEVICE_CERTIFICATE_ID="$(jq -er '.certificate_id' device-enrollment-response.json)" export CERTIFICATE_REQUEST_ID DEVICE_CERTIFICATE_ID ``` With `require_approval_for_enroll: false`, a successful response reports `AUTO_APPROVED` and `SUCCESS` and includes the issued certificate. ### If enrollment requires approval If your policy requires approval: 1. Poll `GET /devicetrustmanager/certificate-issuance-service/api/v2/certificate-request/{certificate_request_id}/status` with the same passcode. 1. Stop polling after the configured timeout or when the request reaches a final status. 1. If the status becomes `APPROVED` or `AUTO_APPROVED`, send a `POST` request to `/devicetrustmanager/certificate-issuance-service/api/v2/certificate/{certificate_id}/download-approved-certificate` with the required output-format body. 1. If the status becomes `REJECTED` or `CANCELED`, stop without downloading the certificate. > **Warning** > > If the service returns an empty `400` response after a long delay, do not retry immediately. Follow [Enrollment returns an empty 400 response](https://dev.digicert.com/md/solutions/private-trust-stack/troubleshoot.md#enrollment-returns-an-empty-400-response) to check for resources created by the request and preserve the information needed for escalation. ## Step 4.5: Validate the key and certificate path Extract the sensitive response fields without printing them: ```bash jq -r '.pem' device-enrollment-response.json > device.pem jq -r '.private_key' device-enrollment-response.json > device.key chmod 600 device.pem device.key ``` Confirm the private key matches the end-entity certificate and validate its path to the approved root: ```bash test "$(openssl x509 -in device.pem -pubkey -noout | openssl sha256)" = \ "$(openssl pkey -in device.key -pubout | openssl sha256)" openssl verify \ -CAfile root-ca.pem \ -untrusted device-issuing-ca.pem \ device.pem openssl x509 -in device.pem -noout -subject -issuer -serial -dates -purpose ``` Confirm that the subject matches the requested device identity and that the certificate purpose and extensions match the approved device template. Store the private key in the device's protected key store and remove intermediate response files according to your secret-handling policy. ## Phase 4 checkpoint - [ ] The passcode is attached to `authentication_policy_id`, with username mode explicitly disabled. - [ ] The device group's active policy assignment contains the certificate and authentication policy IDs. - [ ] Enrollment used `x-passcode` without an API token and included `device_group_id`. - [ ] The response reports a successful final status and includes `device_certificate_id`. - [ ] The private key matches the certificate. - [ ] The certificate path validates to `root-ca.pem` through `device-issuing-ca.pem`, and its purpose is appropriate for the device workload. The device enrollment and certificate-validation path is validated. - If code signing is incomplete, [configure and test private code signing](https://dev.digicert.com/md/solutions/private-trust-stack/phase-5-code-signing.md). - If both workstreams are validated, [prepare the solution for production operations](https://dev.digicert.com/md/solutions/private-trust-stack/operate.md). ## Outputs and handoff | Output | Produced in | Used by | | --- | --- | --- | | `authentication_policy_id` | Create an authentication policy: response `id` | Operations, audit, or cleanup | | `passcode_id` | Create a passcode credential: response `id` | Operations, audit, or cleanup | | `device_group_id` | Create and retrieve the device group: list response `records[].id` | Operations, audit, or cleanup | | `device_certificate_id` | Request the device certificate with the passcode: response `certificate_id` | Operations, audit, or cleanup | | `certificate_request_id` | Request the device certificate with the passcode: response `certificate_request_id` | Operations, audit, or cleanup |