--- title: "Configure and test private code signing" description: "Issue a private code-signing certificate, sign a known hash, and verify the signature and certificate path." source_url: https://dev.digicert.com/solutions/private-trust-stack/phase-5-code-signing.html --- # Configure and test private code signing Issue a private code-signing certificate, sign a known hash, and verify the signature and certificate path. ## Phase metadata - Phase: 5 - Workstream: Code signing — Step 1 of 1 - Product: Software Trust Manager - Goal: Select a code-signing template, bind the signing issuer to a profile, and verify a signature. - Estimated time: 30 minutes - Inputs: account_id, organization_id, service_api_token, signing_ica_id, code_signing_template_name, client.crt, client.key, root-ca.pem, signing-issuing-ca.pem - Prerequisite: [Prepare the private trust domain](https://dev.digicert.com/md/solutions/private-trust-stack/phase-2-private-trust-anchor.md) In this phase, you select a code-signing template in DigiCert® Software Trust Manager and create a private trust certificate profile. You then generate a test keypair, issue a private code-signing certificate, and sign a known SHA-256 hash. After signing, you verify both the signature and the certificate path. Keypair creation and signing require multi-factor authentication. For those operations, send both factors: - The service user's API token in `x-api-key`. - The same service user's client certificate and private key over mutual TLS (mTLS). Use the `clientauth.` host for those protected operations. A client certificate without the API token is not sufficient for this Software Trust Manager workflow. ## Phase prerequisites Make sure you have: - `account_id`, `organization_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 `signing_ica_id` from [Prepare the private trust domain](https://dev.digicert.com/md/solutions/private-trust-stack/phase-2-private-trust-anchor.md). - `client.crt` and its matching `client.key` for the service user. - `root-ca.pem` and `signing-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 Software Trust Manager role with **Manage certificate profiles**, **View certificate template**, **View keypair**, **Generate keypair**, **Generate certificate**, and **Sign** permissions. - An active certificate template with `type: CUSTOM` and `template: TEST` available to the target account. The template must require the code-signing extended key usage (EKU). > **Important** > > Verify the template prerequisite before you create any private code-signing resources. In a hosted Software Trust Manager account, DigiCert controls certificate template creation and updates. If [Select the code-signing certificate template](#step-51-select-the-code-signing-certificate-template) finds no eligible template, ask DigiCert Support to create one. Do not use a `SYSTEM` or `PRODUCTION` template as a substitute for this test workflow. ## Endpoints used | Method | Path | Authentication | Purpose | |--------|------|----------------|---------| | GET | `/signingmanager/api/v1/certificate-templates` | API token | Select and validate the signing template from Software Trust Manager. | | POST | `/signingmanager/api/v1/certificate-profiles` | API token | Create the private trust certificate profile. | | POST | `/signingmanager/api/v1/keypairs` | API token and mTLS | Create the test signing keypair. | | POST | `/signingmanager/api/v1/keypairs/{keypair_id}/certificates` | API token | Issue a certificate onto the keypair. | | POST | `/signingmanager/api/v1/keypairs/{keypair_id}/sign` | API token and mTLS | Sign a base64-encoded hash. | ## Step 5.1: Select the code-signing certificate template List the templates in the target account. The response stores records in `items`. This endpoint uses a page index for `offset`, so the loop increments the value by one for each additional page. ```bash printf '[]\n' > signing-template-items.json page_index=0 while :; do curl --fail-with-body --silent --show-error --get \ "https://demo.one.digicert.com/signingmanager/api/v1/certificate-templates" \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ --data-urlencode "account_id=${ACCOUNT_ID}" \ --data-urlencode "name=${CODE_SIGNING_TEMPLATE_NAME}" \ --data-urlencode "status=ACTIVE" \ --data-urlencode "type=CUSTOM" \ --data-urlencode "template=TEST" \ --data-urlencode "limit=100" \ --data-urlencode "offset=${page_index}" \ -o signing-template-page.json jq -s '.[0] + .[1].items' \ signing-template-items.json signing-template-page.json \ > signing-template-items.next.json mv signing-template-items.next.json signing-template-items.json total="$(jq -er '.total' signing-template-page.json)" page_count="$(jq -er '.items | length' signing-template-page.json)" collected="$(jq -er 'length' signing-template-items.json)" if (( collected >= total )); then break fi if (( page_count == 0 )); then echo "Software Trust pagination ended before total records were collected." >&2 exit 1 fi page_index=$((page_index + 1)) done jq --arg name "${CODE_SIGNING_TEMPLATE_NAME}" --arg account_id "${ACCOUNT_ID}" ' [.[] | select( .name == $name and .status == "ACTIVE" and .type == "CUSTOM" and .template == "TEST" and (.body.issue_types | index("code_signing")) and (.body.extensions.extended_key_usage.required_usages | index("code_signing")) and ((.accounts | length) == 0 or any(.accounts[]; .id == $account_id)) )] | if length == 1 then .[0] else error("expected one eligible code-signing certificate template") end | {id, name, status, type, template, accounts, body}' \ signing-template-items.json > signing-template.json CODE_SIGNING_CERTIFICATE_TEMPLATE_ID="$(jq -er '.id' signing-template.json)" export CODE_SIGNING_CERTIFICATE_TEMPLATE_ID jq '{id, name, status, type, template, accounts, body}' signing-template.json ``` The example exports the selected `id` as `CODE_SIGNING_CERTIFICATE_TEMPLATE_ID`. Do not use a template ID from DigiCert® Private CA or DigiCert® Device Trust Manager. Each product uses separate template resources, so the IDs are not interchangeable. ## Step 5.2: Create the private trust certificate profile The current Software Trust Manager API request format represents the issuing CA as `ca_certificate_profile_request.ca.id`. Use that nested object rather than a top-level `ica_id` property. **cURL** ```bash jq -n \ --arg template_id "${CODE_SIGNING_CERTIFICATE_TEMPLATE_ID}" \ --arg organization_id "${ORGANIZATION_ID}" \ --arg ica_id "${SIGNING_ICA_ID}" ' { name: "Private code signing test", profile_type: "CA_PROFILE", auto_renewal: "DISABLED", rekey: "DISABLED", ca_certificate_profile_request: { certificate_template_id: $template_id, profile: "TEST", body: [], organization: {id: $organization_id}, ca: {id: $ica_id} } }' | curl --fail-with-body --silent --show-error \ -X POST "https://demo.one.digicert.com/signingmanager/api/v1/certificate-profiles" \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ -H "Content-Type: application/json" \ --data-binary @- \ -o signing-profile-response.json SIGNING_PROFILE_ID="$(jq -er '.id' signing-profile-response.json)" export SIGNING_PROFILE_ID jq '{id, name, status, type, ca_certificate_profile}' signing-profile-response.json ``` **Python** ```python import requests base_url = "https://demo.one.digicert.com" headers = {"x-api-key": service_api_token} response = requests.post( f"{base_url}/signingmanager/api/v1/certificate-profiles", headers=headers, json={ "name": "Private code signing test", "profile_type": "CA_PROFILE", "auto_renewal": "DISABLED", "rekey": "DISABLED", "ca_certificate_profile_request": { "certificate_template_id": code_signing_certificate_template_id, "profile": "TEST", "body": [], "organization": {"id": organization_id}, "ca": {"id": signing_ica_id}, }, }, timeout=30, ) response.raise_for_status() profile = response.json() signing_profile_id = profile["id"] ``` Confirm that the response is active, has type `CA_PROFILE`, and identifies the intended template, organization, and issuing CA for code signing. Save the response `id` as `signing_profile_id`. ## Step 5.3: Create a test keypair with both authentication factors This validation example uses a software-backed RSA-3072 test key. Follow your organization's signing policy for production signing-key storage and access controls. Use hardware security module (HSM)-backed keys when required. **cURL** ```bash curl --fail-with-body --silent --show-error \ -X POST "https://clientauth.demo.one.digicert.com/signingmanager/api/v1/keypairs" \ --cert client.crt \ --key client.key \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "alias": "private-trust-test-key", "key_alg": "RSA", "key_storage": "DISK", "key_type": "TEST", "properties": { "key_size": 3072 }, "status": "ONLINE", "expiry_type": "NO_EXPIRY" }' \ -o keypair-response.json KEYPAIR_ID="$(jq -er '.id' keypair-response.json)" export KEYPAIR_ID jq '{id, alias, key_alg, key_size, key_type, key_storage, key_status}' keypair-response.json ``` **Python** ```python client_certificate = ("client.crt", "client.key") response = requests.post( "https://clientauth.demo.one.digicert.com/signingmanager/api/v1/keypairs", headers=headers, cert=client_certificate, json={ "alias": "private-trust-test-key", "key_alg": "RSA", "key_storage": "DISK", "key_type": "TEST", "properties": {"key_size": 3072}, "status": "ONLINE", "expiry_type": "NO_EXPIRY", }, timeout=30, ) response.raise_for_status() keypair = response.json() keypair_id = keypair["id"] ``` Save the response `id` as `keypair_id`. ## Step 5.4: Issue the private code-signing certificate Reference the profile by ID and make the certificate the keypair's default certificate. ```bash umask 077 jq -n \ --arg profile_id "${SIGNING_PROFILE_ID}" ' { certificate_profile: {id: $profile_id}, alias: "private-trust-test-certificate", default_cert: true }' | curl --fail-with-body --silent --show-error \ -X POST \ "https://demo.one.digicert.com/signingmanager/api/v1/keypairs/${KEYPAIR_ID}/certificates" \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ -H "Content-Type: application/json" \ --data-binary @- \ -o signing-certificate-response.json chmod 600 signing-certificate-response.json SIGNING_CERTIFICATE_ID="$(jq -er '.id' signing-certificate-response.json)" export SIGNING_CERTIFICATE_ID jq '{id, alias, certificate_status, default_cert, enrollment_method, hierarchy, certificate_profile, valid_from, valid_to}' \ signing-certificate-response.json ``` Save the response `id` as `signing_certificate_id`. Extract the base64-encoded DER certificate and validate its path and purpose: ```bash jq -r '.cert' signing-certificate-response.json | openssl base64 -d -A -out signing-cert.der openssl x509 -inform DER -in signing-cert.der -out signing-cert.pem openssl verify -CAfile root-ca.pem -untrusted signing-issuing-ca.pem signing-cert.pem openssl x509 -in signing-cert.pem -noout -subject -issuer -serial -dates -purpose ``` Confirm that the certificate contains the code-signing EKU and chains through the expected issuing CA for code signing to `root-ca.pem`. ## Step 5.5: Sign and verify a known hash Create a test artifact and its binary SHA-256 digest. Base64-encode the digest for the API request. ```bash printf '%s' 'private-trust signature verification' > test-artifact.txt openssl dgst -sha256 -binary test-artifact.txt > test-artifact.sha256 HASH_BASE64="$(openssl base64 -A -in test-artifact.sha256)" ``` Sign the digest with both authentication factors: ```bash jq -n \ --arg hash "${HASH_BASE64}" \ '{hash: $hash, sig_alg: "SHA256WithRSA"}' | curl --fail-with-body --silent --show-error \ -X POST \ "https://clientauth.demo.one.digicert.com/signingmanager/api/v1/keypairs/${KEYPAIR_ID}/sign" \ --cert client.crt \ --key client.key \ -H "x-api-key: ${SERVICE_API_TOKEN}" \ -H "Content-Type: application/json" \ --data-binary @- \ -o signing-response.json jq '{id}' signing-response.json SIGNATURE_ID="$(jq -er '.id' signing-response.json)" export SIGNATURE_ID ``` Extract and verify the RSA PKCS#1 v1.5 signature over the SHA-256 digest: ```bash jq -r '.signature' signing-response.json | openssl base64 -d -A -out test-artifact.sig openssl x509 -in signing-cert.pem -pubkey -noout > signing-public-key.pem openssl pkeyutl -verify \ -pubin \ -inkey signing-public-key.pem \ -in test-artifact.sha256 \ -sigfile test-artifact.sig \ -pkeyopt digest:sha256 ``` A successful verification proves that the returned signature was produced by the private key corresponding to `signing-cert.pem`. The earlier `openssl verify` command independently proves that the signing certificate chains to the approved private root. > **Note** > > This is a low-level API verification, not a platform-specific signed artifact. Use `smctl`, KSP, PKCS#11, or the applicable signing integration to create and verify Authenticode, JAR, container, or other release artifacts. Timestamping and release-policy enforcement are separate production requirements. ## Phase 5 checkpoint - [ ] The selected Software Trust Manager template is active, custom, configured for testing, available to the account, and requires the code-signing EKU. - [ ] The `CA_PROFILE` response identifies the intended issuing CA for code signing, organization, and Software Trust Manager template. - [ ] Keypair creation used the API token and matching client certificate over the `clientauth.` host. - [ ] The signing certificate has the code-signing EKU and validates to `root-ca.pem` through `signing-issuing-ca.pem`. - [ ] Signing used both authentication factors. - [ ] OpenSSL verified the returned signature with the signing certificate's public key. The private signing and signature-validation path is validated. - If device identity is incomplete, [configure the device certificate infrastructure](https://dev.digicert.com/md/solutions/private-trust-stack/phase-3-device-infrastructure.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 | | --- | --- | --- | | `code_signing_certificate_template_id` | Select the code-signing certificate template: record `id` | Operations, audit, or cleanup | | `signing_profile_id` | Create the private trust certificate profile: response `id` | Operations, audit, or cleanup | | `keypair_id` | Create a test keypair with both authentication factors: response `id` | Operations, audit, or cleanup | | `signing_certificate_id` | Issue the private code-signing certificate: response `id` | Operations, audit, or cleanup | | `signature_id` | Sign and verify a known hash: response `id` | Operations, audit, or cleanup |