Phase 4

Enroll and verify a device

Create passcode enrollment configuration and validate an issued device certificate.

Phase details

Workstream
Device identity · Step 2 of 2
Time
25 minutes
Setup
Mixed setup and verification
Goal
Attach a passcode authentication policy to a device group, issue a device certificate, and validate its key and chain.
Inputs
account_idservice_api_tokendivision_idcertificate_policy_idpasscoderoot-ca.pemdevice-issuing-ca.pem
Expected outputs
authentication_policy_idpasscode_iddevice_group_iddevice_certificate_idcertificate_request_id

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:

Endpoints used

MethodPathPurpose
POST/devicetrustmanager/authentication-service/api/v1/authentication-policyCreate the authentication policy.
POST/devicetrustmanager/authentication-service/api/v1/passcodeCreate the enrollment passcode.
POST/devicetrustmanager/api/v4/device-groupCreate the group and policy assignment.
GET/devicetrustmanager/api/v4/device-groupRetrieve the group ID and inspect the assignment.
POST/devicetrustmanager/certificate-issuance-service/api/v2/certificateRequest and register a device certificate.
GET/devicetrustmanager/certificate-issuance-service/api/v2/certificate-request/{certificate_request_id}/statusPoll an enrollment that requires approval.
POST/devicetrustmanager/certificate-issuance-service/api/v2/certificate/{certificate_id}/download-approved-certificateDownload an approved certificate and server-generated key.

Step 4.1: Create an authentication policy

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.

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.

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. For production devices with protected key storage, configure client-side generation and submit a certificate signing request (CSR) instead.

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.
  2. Stop polling after the configured timeout or when the request reaches a final status.
  3. 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.
  4. If the status becomes REJECTED or CANCELED, stop without downloading the certificate.
If the service returns an empty 400 response after a long delay, do not retry immediately. Follow 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:

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:

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.