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
- Product
- DigiCert Private CA
- 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- Prerequisite
- Establish the account foundation
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_idandservice_api_tokenfrom 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
| Method | Path | Purpose |
|---|---|---|
| GET | /certificate-authority/api/v1/ca | List accessible CAs and inspect account assignments. |
| POST | /certificate-authority/api/v1/ca/{id}/accounts | Assign an issuing CA to the account. |
| GET | /certificate-authority/api/v1/ca/{id}/download?format=pem | Download 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.jsonimport 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_typeorca_type. - Each issuer has an intermediate
cert_typeorca_type, and itsconfiguration.issue_end_entitiesvalue istrue. - Each issuer’s
issuer.idequals 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
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_ididentifies the expected active root CA. -
device_ica_idandsigning_ica_ididentify active intermediates beneath that root. - Each distinct issuing CA is assigned to
account_id. -
device-issuing-ca.pemandsigning-issuing-ca.pemvalidate toroot-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.