Phase 2

Prepare the private trust domain

Assign the issuing CAs and download the CA certificates used by both workloads.

Phase details

Workstream
Shared foundation · Step 2 of 2
Time
25 minutes
Setup
One-time setup
Goal
Identify the root and workload-specific issuing CAs, assign each issuer to the account, and download the CA certificates.
Inputs
account_idservice_api_tokenroot_ca_namedevice_ica_namesigning_ica_name
Expected outputs
root_ca_iddevice_ica_idsigning_ica_idroot-ca.pemdevice-issuing-ca.pemsigning-issuing-ca.pem

In this phase, you use DigiCert® Private CA to identify the private root CA and the issuing CA for each workload. You assign the issuers to the target account and download the CA certificates used for path validation. You can select the same issuing CA for both workloads when your public key infrastructure (PKI) policy permits it.

The root CA certificate is the trust anchor. The issuing CAs sign end-entity certificates. Certificate-template selection happens in the consuming product because DigiCert® Device Trust Manager and DigiCert® Software Trust Manager expose separate template resources and identifiers.

Phase prerequisites

Make sure you have:

  • The account_id and service_api_token from Establish the account foundation.
  • An active private root CA and one or two active issuing CAs beneath that root.
  • The exact approved names for the root CA, device issuing CA, and signing issuing CA.
  • A service-user role with View CM CA (VIEW_CM_CA) and Manage CM CA Accounts (MANAGE_CM_CA_ACCOUNTS) permissions in DigiCert Private CA.
  • The approved SHA-256 fingerprint for the root CA certificate.

Endpoints used

MethodPathPurpose
GET/certificate-authority/api/v1/caList accessible CAs and inspect account assignments.
POST/certificate-authority/api/v1/ca/{id}/accountsAssign an issuing CA to the account.
GET/certificate-authority/api/v1/ca/{id}/download?format=pemDownload a CA certificate in PEM format.

Step 2.1: Select the root and issuing CAs

GET /certificate-authority/api/v1/ca returns an object with pagination metadata and stores CA records in items. The API reference does not document pagination parameters for this endpoint. Before selecting a CA by its approved exact name or ID, confirm that total equals the number of returned items. If the values differ, stop and confirm the pagination parameters supported by your tenant’s API.

curl --fail-with-body --silent --show-error \
  "https://demo.one.digicert.com/certificate-authority/api/v1/ca" \
  -H "x-api-key: ${SERVICE_API_TOKEN}" \
  -o ca-inventory.json

jq -e '
  if .total == (.items | length) then true
  else error("CA list is incomplete; confirm deployed paging parameters")
  end' ca-inventory.json > /dev/null

select_active_ca_id() {
  jq -er --arg name "$1" '
    [.items[] | select(.name == $name and .status == "active")] |
    if length == 1 then .[0].id
    else error("expected one active CA named " + $name)
    end' ca-inventory.json
}

ROOT_CA_ID="$(select_active_ca_id "${ROOT_CA_NAME}")"
DEVICE_ICA_ID="$(select_active_ca_id "${DEVICE_ICA_NAME}")"
SIGNING_ICA_ID="$(select_active_ca_id "${SIGNING_ICA_NAME}")"
export ROOT_CA_ID DEVICE_ICA_ID SIGNING_ICA_ID

jq -e --arg root_id "${ROOT_CA_ID}" \
      --arg device_id "${DEVICE_ICA_ID}" \
      --arg signing_id "${SIGNING_ICA_ID}" '
  def selected($id): first(.items[] | select(.id == $id));
  selected($root_id) as $root |
  selected($device_id) as $device |
  selected($signing_id) as $signing |
  if
    ((($root.cert_type // $root.ca_type // "") | ascii_downcase) == "root") and
    ((($device.cert_type // $device.ca_type // "") | ascii_downcase) == "intermediate") and
    ($device.configuration.issue_end_entities == true) and
    ($device.issuer.id == $root.id) and
    ((($signing.cert_type // $signing.ca_type // "") | ascii_downcase) == "intermediate") and
    ($signing.configuration.issue_end_entities == true) and
    ($signing.issuer.id == $root.id)
  then true
  else error("selected CAs do not form an end-entity issuing hierarchy")
  end
' ca-inventory.json > /dev/null

jq --arg root_id "${ROOT_CA_ID}" \
   --arg device_id "${DEVICE_ICA_ID}" \
   --arg signing_id "${SIGNING_ICA_ID}" '
  [.items[] |
    select(.id == $root_id or .id == $device_id or .id == $signing_id) |
    {
      id,
      name,
      status,
      ca_type,
      cert_type,
      issue_end_entities: .configuration.issue_end_entities,
      issuer,
      account_assignments
    }]
' ca-inventory.json
import requests

base_url = "https://demo.one.digicert.com"
headers = {"x-api-key": service_api_token}
response = requests.get(
    f"{base_url}/certificate-authority/api/v1/ca",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
page = response.json()
ca_records = page["items"]
if page["total"] != len(ca_records):
    raise RuntimeError("CA list is incomplete. Confirm deployed paging parameters")

active_cas = [item for item in ca_records if item.get("status") == "active"]

def select_one_ca(name):
    matches = [item for item in active_cas if item.get("name") == name]
    if len(matches) != 1:
        raise RuntimeError(f"Expected one active CA named {name}. Found {len(matches)}")
    return matches[0]

root = select_one_ca(root_ca_name)
device_issuer = select_one_ca(device_ica_name)
signing_issuer = select_one_ca(signing_ica_name)

root_ca_id = root["id"]
device_ica_id = device_issuer["id"]
signing_ica_id = signing_issuer["id"]

def ca_kind(ca):
    return (ca.get("cert_type") or ca.get("ca_type") or "").lower()

if ca_kind(root) != "root":
    raise RuntimeError("The selected trust anchor is not a root CA")

for label, issuer in (("device", device_issuer), ("signing", signing_issuer)):
    if ca_kind(issuer) != "intermediate":
        raise RuntimeError(f"The selected {label} issuer is not an intermediate CA")
    if (issuer.get("configuration") or {}).get("issue_end_entities") is not True:
        raise RuntimeError(f"The selected {label} issuer cannot issue end entities")
    if (issuer.get("issuer") or {}).get("id") != root_ca_id:
        raise RuntimeError(f"The selected {label} issuer is not beneath the selected root")

Before continuing, confirm the following facts from the selected records:

  • The root has a root cert_type or ca_type.
  • Each issuer has an intermediate cert_type or ca_type, and its configuration.issue_end_entities value is true.
  • Each issuer’s issuer.id equals the selected root CA ID.
  • The CA status is active.

Do not infer code-signing eligibility from a top-level issue_types value. The end-entity template’s extended key usage controls that purpose, and you validate it when you select the code-signing certificate template.

Step 2.2: Assign each issuer to the account

Inspect each issuer’s account_assignments before adding the account. If an assignment is already present, do not repeat the request that adds it.

assign_ica_if_needed() {
  local ica_id="$1"
  if jq -e --arg ica_id "${ica_id}" --arg account_id "${ACCOUNT_ID}" '
    any(.items[];
      .id == $ica_id and
      any(.account_assignments[]?;
        (if type == "object" then .id else . end) == $account_id))
  ' ca-inventory.json > /dev/null; then
    printf 'ICA %s is already assigned to account %s.\n' "${ica_id}" "${ACCOUNT_ID}"
    return
  fi

  curl --fail-with-body --silent --show-error \
    -X POST \
    "https://demo.one.digicert.com/certificate-authority/api/v1/ca/${ica_id}/accounts" \
    -H "x-api-key: ${SERVICE_API_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "{\"accounts\": [\"${ACCOUNT_ID}\"]}"
}

assign_ica_if_needed "${DEVICE_ICA_ID}"
if [[ "${SIGNING_ICA_ID}" != "${DEVICE_ICA_ID}" ]]; then
  assign_ica_if_needed "${SIGNING_ICA_ID}"
fi

For a new assignment, a successful response returns 204 No Content. Include each distinct, unassigned issuing CA ID only once. List the CAs again, and confirm that each selected issuer’s account_assignments contains account_id.

Step 2.3: Download and validate the CA certificates

Download the root and workload-specific issuing CA certificates. Install the root certificate as the trust anchor in relying-party trust stores. Provide the appropriate issuing CA as an intermediate when a verifier needs it to construct the path.

With format=pem, save the response as a PEM certificate. Stop if a downloaded file is not parseable by OpenSSL. Never automate trust-store distribution without validating the certificate and its approved fingerprint.

curl --fail-with-body --silent --show-error \
  "https://demo.one.digicert.com/certificate-authority/api/v1/ca/${ROOT_CA_ID}/download?format=pem" \
  -H "x-api-key: ${SERVICE_API_TOKEN}" \
  --output root-ca.pem

curl --fail-with-body --silent --show-error \
  "https://demo.one.digicert.com/certificate-authority/api/v1/ca/${DEVICE_ICA_ID}/download?format=pem" \
  -H "x-api-key: ${SERVICE_API_TOKEN}" \
  --output device-issuing-ca.pem

curl --fail-with-body --silent --show-error \
  "https://demo.one.digicert.com/certificate-authority/api/v1/ca/${SIGNING_ICA_ID}/download?format=pem" \
  -H "x-api-key: ${SERVICE_API_TOKEN}" \
  --output signing-issuing-ca.pem

Inspect and validate the certificates before distribution:

openssl x509 -in root-ca.pem -noout -subject -issuer -serial -fingerprint -sha256
openssl x509 -in device-issuing-ca.pem -noout -subject -issuer -serial -fingerprint -sha256
openssl x509 -in signing-issuing-ca.pem -noout -subject -issuer -serial -fingerprint -sha256
openssl verify -CAfile root-ca.pem device-issuing-ca.pem
openssl verify -CAfile root-ca.pem signing-issuing-ca.pem
Distribute root-ca.pem through your approved configuration-management process and verify its SHA-256 fingerprint through a separate trusted channel. Do not trust a downloaded certificate until you validate its identity.

Phase 2 checkpoint

  • root_ca_id identifies the expected active root CA.
  • device_ica_id and signing_ica_id identify active intermediates beneath that root.
  • Each distinct issuing CA is assigned to account_id.
  • device-issuing-ca.pem and signing-issuing-ca.pem validate to root-ca.pem.
  • The root fingerprint matches the value approved for distribution.

The shared foundation is complete. Next, begin the device identity workstream, begin the code-signing workstream, or work on both in parallel. Both workstreams are required.