Phase 5
Configure and test private code signing
Issue a private code-signing certificate, sign a known hash, and verify the signature and certificate path.
Phase details
- Workstream
- Code signing · Step 1 of 1
- Product
- Software Trust Manager
- Time
- 30 minutes
- Setup
- Mixed setup and verification
- Goal
- Select a code-signing template, bind the signing issuer to a profile, and verify a signature.
- Inputs
account_idorganization_idservice_api_tokensigning_ica_idcode_signing_template_nameclient.crtclient.keyroot-ca.pemsigning-issuing-ca.pem- Expected outputs
code_signing_certificate_template_idsigning_profile_idkeypair_idsigning_certificate_idsignature_id- Prerequisite
- Prepare the private trust domain
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, andservice_api_tokenfrom Establish the account foundation andsigning_ica_idfrom Prepare the private trust domain.client.crtand its matchingclient.keyfor the service user.root-ca.pemandsigning-issuing-ca.pemfrom Prepare the private trust domain.- 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: CUSTOMandtemplate: TESTavailable to the target account. The template must require the code-signing extended key usage (EKU).
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.
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.
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.jsonimport 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 --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.jsonclient_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.
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:
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.
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:
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:
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.
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_PROFILEresponse 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.pemthroughsigning-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.
- If both workstreams are validated, prepare the solution for production operations.