Phase 1

Establish the account foundation

Identify the account and organization and create the setup service identity and credentials.

Phase details

Workstream
Shared foundation · Step 1 of 2
Time
25 minutes
Setup
One-time setup
Goal
Create a service user with limited roles, capture its API token, and create its client authentication certificate.
Inputs
bootstrap_api_tokenaccount_nameorganization_nameapproved_role_namesclient_auth_certificate_expiration
Expected outputs
account_idorganization_idservice_user_idapi_token_idservice_api_tokenclient_auth_certificate_idclient.crtclient.key

In this phase, you identify the target account and organization and find the required role names in your tenant. You then create the setup service user and both authentication factors required later by DigiCert® Software Trust Manager.

Phase prerequisites

Make sure you have:

  • A bootstrap user with an API token for the target tenant, the Manage users permission in DigiCert® Account Manager, and access to the target account, organization, and assignable roles. Make the token available as BOOTSTRAP_API_TOKEN for cURL or bootstrap_api_token for Python.
  • The exact target account and organization names.
  • Approved roles for the other products that contain the exact permissions listed in Before you begin.
  • A secure destination for the service-user API token, client certificate, and private key.
  • An approved UTC expiration timestamp for the client authentication credential.

Endpoints used

MethodPathPurpose
GET/account/api/v1/accountList accessible accounts.
GET/account/api/v1/roleList assignable roles by product.
GET/account/api/v1/organizationList organizations in the target account.
POST/account/api/v1/userCreate the service user and return its API token once.
POST/account/api/v1/client-auth-certificateSign a certificate signing request (CSR) and create a client authentication certificate for the authenticated service user.
GET/account/api/v1/user/me on the clientauth. hostVerify the new certificate authenticates as the service user.

Step 1.1: Select the target account

GET /account/api/v1/account returns an array. Select the intended active account explicitly. Do not assume the response contains only one account.

ACCOUNT_NAME="Example account"

ACCOUNT_ID="$(
  curl --fail-with-body --silent --show-error \
    "https://demo.one.digicert.com/account/api/v1/account" \
    -H "x-api-key: ${BOOTSTRAP_API_TOKEN}" |
  jq -er --arg name "${ACCOUNT_NAME}" '
    [.[] | select(.name == $name and .active == true)] |
    if length == 1 then .[0].id
    else error("expected one active target account")
    end'
)"
export ACCOUNT_ID
printf '%s\n' "${ACCOUNT_ID}" > account-id.txt
printf 'Selected account: %s (%s)\n' "${ACCOUNT_NAME}" "${ACCOUNT_ID}"
import requests

base_url = "https://demo.one.digicert.com"

response = requests.get(
    f"{base_url}/account/api/v1/account",
    headers={"x-api-key": bootstrap_api_token},
    timeout=30,
)
response.raise_for_status()

accounts = response.json()
matches = [item for item in accounts if item["name"] == "TARGET_ACCOUNT_NAME" and item["active"]]
if len(matches) != 1:
    raise RuntimeError(f"Expected one active target account. Found {len(matches)}")

account_id = matches[0]["id"]
print(f"Selected account: {matches[0]['name']} ({account_id})")

The cURL example exports ACCOUNT_ID. The Python example assigns account_id.

Step 1.2: Find the required role names

Role names are tenant-dependent. Retrieve the roles and select the minimum set that grants the DigiCert® Private CA, DigiCert® Device Trust Manager, and Software Trust Manager operations listed in Before you begin.

curl --fail-with-body --silent --show-error \
  "https://demo.one.digicert.com/account/api/v1/role?account_id=${ACCOUNT_ID}" \
  -H "x-api-key: ${BOOTSTRAP_API_TOKEN}" |
  jq '{ca_manager, device_trust_manager, secure_software_manager}'

Record the exact name values. Do not copy example role names from the API reference or another account.

Step 1.3: Select an organization

Software Trust Manager private trust certificate profiles associate issued certificates with an organization. Select the intended active organization by exact name.

ORGANIZATION_NAME="Example organization"

ORGANIZATION_ID="$(
  curl --fail-with-body --silent --show-error \
    "https://demo.one.digicert.com/account/api/v1/organization?account_id=${ACCOUNT_ID}" \
    -H "x-api-key: ${BOOTSTRAP_API_TOKEN}" |
  jq -er --arg name "${ORGANIZATION_NAME}" '
    [.[] | select(.name == $name and .active == true)] |
    if length == 1 then .[0].id
    else error("expected one active target organization")
    end'
)"
export ORGANIZATION_ID
printf '%s\n' "${ORGANIZATION_ID}" > organization-id.txt
printf 'Selected organization: %s (%s)\n' "${ORGANIZATION_NAME}" "${ORGANIZATION_ID}"

The example fails unless the approved name identifies exactly one active organization. It exports ORGANIZATION_ID as the shell equivalent of organization_id.

Step 1.4: Create the setup service user

Set ROLE_NAMES_JSON to an array of exact role name values from Find the required role names. The API returns api_token.token only once. Write the response to a protected file, and do not print the token in logs.

umask 077

ROLE_NAMES_JSON='[
  "<private-ca-role-name>",
  "<device-trust-role-name>",
  "<software-trust-role-name>"
]'

jq -n \
  --arg account_id "${ACCOUNT_ID}" \
  --argjson roles "${ROLE_NAMES_JSON}" \
  '{
    user_type: "service",
    friendly_name: "Private trust stack setup",
    email: "pki-automation-owner@example.com",
    description: "Setup identity for the private trust solution",
    accounts: [$account_id],
    roles: $roles
  }' |
curl --fail-with-body --silent --show-error \
  -X POST "https://demo.one.digicert.com/account/api/v1/user" \
  -H "x-api-key: ${BOOTSTRAP_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data-binary @- \
  -o service-user-response.json

jq -r '.api_token.token' service-user-response.json > service-api-token.txt
jq -r '.id' service-user-response.json > service-user-id.txt
jq -r '.api_token.id' service-user-response.json > api-token-id.txt
chmod 600 service-api-token.txt service-user-id.txt api-token-id.txt service-user-response.json

SERVICE_USER_ID="$(<service-user-id.txt)"
API_TOKEN_ID="$(<api-token-id.txt)"
SERVICE_API_TOKEN="$(<service-api-token.txt)"
export SERVICE_USER_ID API_TOKEN_ID

The successful response is an object containing the service user id and an api_token object:

{
  "id": "5e0bd5fe-117f-4049-b686-1548b1ee5e14",
  "status": "ACTIVE",
  "friendly_name": "Private trust stack setup",
  "api_token": {
    "id": "cdfcf47d-b47f-4919-bd1b-c62935312cea",
    "token": "<returned-once; redacted>",
    "enabled": true
  }
}

Save the user id as service_user_id, api_token.id as api_token_id, and the returned token as service_api_token. You need both IDs to remove the credentials later and associate them with audit events.

Step 1.5: Create the service user’s client certificate

The client authentication certificate must belong to the same service user whose API token you use with Software Trust Manager. Generate the private key locally, submit only the CSR, and authenticate the request with the service-user API token returned when you create the setup service user. Send the token in the x-api-key header. The API associates the new certificate with the authenticated service user and never receives the private key.

Set CLIENT_AUTH_CERTIFICATE_EXPIRATION to an approved UTC timestamp no more than 397 days in the future. The Account Manager API requires the expiration_date property.

umask 077
CLIENT_AUTH_CERTIFICATE_NAME="Private trust stack client authentication"
CLIENT_AUTH_CERTIFICATE_EXPIRATION="2027-01-31T23:59:59Z"

openssl req -new -newkey rsa:2048 -nodes \
  -keyout client.key \
  -out client.csr \
  -subj "/CN=Private trust stack setup"
chmod 600 client.key client.csr

jq -n \
  --rawfile csr client.csr \
  --arg name "${CLIENT_AUTH_CERTIFICATE_NAME}" \
  --arg expiration_date "${CLIENT_AUTH_CERTIFICATE_EXPIRATION}" '
  {
    csr: $csr,
    name: $name,
    expiration_date: $expiration_date
  }' |
curl --fail-with-body --silent --show-error \
  -X POST "https://demo.one.digicert.com/account/api/v1/client-auth-certificate" \
  -H "x-api-key: ${SERVICE_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data-binary @- \
  -o client-auth-certificate-response.json

jq -er '.x509_cert' client-auth-certificate-response.json > client.crt
jq -er '.ca_cert' client-auth-certificate-response.json > client-auth-ca.pem
jq -er '.id' client-auth-certificate-response.json > client-auth-certificate-id.txt

CLIENT_AUTH_CERTIFICATE_ID="$(<client-auth-certificate-id.txt)"
export CLIENT_AUTH_CERTIFICATE_ID
chmod 600 \
  client.crt \
  client.key \
  client-auth-ca.pem \
  client-auth-certificate-id.txt \
  client-auth-certificate-response.json

Use the response end_date and the certificate’s notAfter value to determine the actual expiration. Your tenant’s API might adjust the requested expiration boundary instead of returning the exact expiration_date value. Confirm that the actual validity period complies with your credential policy before you use the certificate.

Confirm the certificate and private key match, validate the certificate against the returned CA certificate, and verify that mutual TLS (mTLS) authenticates the intended service user:

test "$(openssl x509 -in client.crt -pubkey -noout | openssl sha256)" = \
     "$(openssl pkey -in client.key -pubout | openssl sha256)"

openssl verify -CAfile client-auth-ca.pem client.crt

jq '{end_date}' client-auth-certificate-response.json
openssl x509 -in client.crt -noout -enddate

curl --fail-with-body --silent --show-error \
  --cert client.crt \
  --key client.key \
  "https://clientauth.demo.one.digicert.com/account/api/v1/user/me" |
  jq -e --arg user_id "${SERVICE_USER_ID}" '
    if .id == $user_id then true
    else error("client certificate belongs to an unexpected user")
    end' > /dev/null

Phase 1 checkpoint

  • account_id identifies the intended active account.
  • organization_id identifies the approved active organization.
  • The service user has only the required tenant role names.
  • service_api_token is stored in a secret manager or protected local file and does not appear in logs.
  • client.crt and client.key belong to the service user and match cryptographically.
  • The response end_date matches the certificate’s notAfter value and complies with the approved validity policy.

Next, prepare the private trust domain.