Create a keypair and sign a file hash

Cryptographically prove the authenticity and integrity of your software artifacts.

Keypairs are the foundation of code signing in Software Trust Manager. A keypair consists of a private key securely stored in an HSM (Hardware Security Module) and a corresponding public key used to verify signatures. By signing file hashes with your keypair, you cryptographically prove the authenticity and integrity of your software artifacts.

In this tutorial, you will:

  • Create a signing keypair in Software Trust Manager.
  • Sign a file hash using your new keypair.
  • Verify the signature was recorded successfully.

Before you begin

Before you begin, make sure you have:

A DigiCert ONE account with Software Trust Manager access.

A user account with one of the following roles:

  • SSM_SIGNER - Sign files with assigned keypairs
  • SSM_BUILD_ENGINEER - Sign files as part of build processes
A client authentication certificate (.p12 file) for mTLS authentication.
OpenSSL installed for generating file hashes. Use openssl version to verify.

Endpoint overview

MethodPathDescription
POST/api/v1/keypairsCreate a new signing keypair
POST/api/v1/keypairs/{keypair_id}/signSign a file hash
GET/api/v1/signatures/{signature_id}Get signature details

Key algorithms

Select a key algorithm based on your signing requirements:

AlgorithmDescriptionUse case
RSARSA algorithm, requires key_size propertyWindows Authenticode, general signing
ECDSAElliptic Curve DSA, requires curve propertyModern applications, smaller signatures
EdDSAEdwards-curve DSA, requires curve propertyHigh-performance signing

Signature algorithms

Select a signature algorithm compatible with your key algorithm:

Key AlgorithmSignature Algorithms
RSASHA256WithRSA, SHA384WithRSA, SHA512WithRSA
ECDSASHA256WithECDSA, SHA384WithECDSA, SHA512WithECDSA
EdDSAED25519

Step 1: Create a signing keypair

Create a keypair that will be used to sign your files. The private key is generated and stored securely in Software Trust Manager—it never leaves the HSM.

Request:

curl -X POST "https://demo.one.digicert.com/api/v1/keypairs" \
  -H "x-api-key: MTLS" \
  -H "Content-Type: application/json" \
  -d '{
  "alias": "my_new_keypair",
  "key_alg": "KEY_ALG_VALUE",
  "key_storage": "KEY_STORAGE_VALUE",
  "key_type": "KEY_TYPE_VALUE",
  "properties": {
    "key_size": 0,
    "curve": "curve_value"
  },
  "status": "STATUS_VALUE"
}' \
  | jq '.'
import requests

response = requests.post(
    "https://demo.one.digicert.com/api/v1/keypairs",
    headers={"x-api-key": "MTLS", "Content-Type": "application/json"},
    json={
        "alias": "my_new_keypair",
        "key_alg": "KEY_ALG_VALUE",
        "key_storage": "KEY_STORAGE_VALUE",
        "key_type": "KEY_TYPE_VALUE",
        "properties": {
            "key_size": 0,
            "curve": "curve_value"
        },
        "status": "STATUS_VALUE"
    }
)

print(f"Status: {response.status_code}")
if response.status_code != 204:
    print(response.json())
import java.net.http.*;
import java.net.URI;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://demo.one.digicert.com/api/v1/keypairs"))
    .header("x-api-key", "MTLS")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString("{\"alias\": \"my_new_keypair\", \"key_alg\": \"KEY_ALG_VALUE\", \"key_storage\": \"KEY_STORAGE_VALUE\", \"key_type\": \"KEY_TYPE_VALUE\", \"properties\": {\"key_size\": 0, \"curve\": \"curve_value\"}, \"status\": \"STATUS_VALUE\"}"))
    .build();

HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());

System.out.println("Status: " + response.statusCode());
System.out.println(response.body());
using System.Net.Http;
using System.Text;
using System.Text.Json;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "MTLS");

var content = new StringContent(@"{
  ""alias"": ""my_new_keypair"",
  ""key_alg"": ""KEY_ALG_VALUE"",
  ""key_storage"": ""KEY_STORAGE_VALUE"",
  ""key_type"": ""KEY_TYPE_VALUE"",
  ""properties"": {
    ""key_size"": 0,
    ""curve"": ""curve_value""
  },
  ""status"": ""STATUS_VALUE""
}",
    Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://demo.one.digicert.com/api/v1/keypairs", content);

Console.WriteLine($"Status: {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());

Configure the keypair with these fields:

  • alias - A unique name for your keypair (e.g., my-signing-key)
  • key_alg - Algorithm: RSA, ECDSA, or EdDSA
  • key_storage - Storage location: HSM (recommended) or DISK
  • key_type - Purpose: PRODUCTION or TEST
  • properties - Algorithm-specific settings (see example below)
  • status - Availability: ONLINE (ready to sign) or OFFLINE

For an RSA keypair, set properties to include key_size:

{
  "properties": {
    "key_size": 2048
  }
}

For an ECDSA keypair, set properties to include curve:

{
  "properties": {
    "curve": "P-256"
  }
}

Successful response (201 Created):

{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "status": "active",
  "_note": "Response schema not fully documented in API spec"
}

From the response, save the keypair id. You will need this in Step 2 when signing your file hash.

Step 2: Sign a file hash

Before signing, you need to compute the hash of your file. Software Trust Manager signs the hash, not the file itself. This approach is more efficient and secure—your file content never leaves your environment.

Generate the file hash:

openssl dgst -sha256 -binary myfile.exe | openssl base64

This outputs a base64-encoded SHA-256 hash of your file.

Request:

curl -X POST "https://demo.one.digicert.com/api/v1/keypairs/KEYPAIR_ID/sign" \
  -H "x-api-key: MTLS" \
  -H "Content-Type: application/json" \
  -d '{
  "hash": "eeUnw3ky7+hI7Kawc9NnZ4CCCenu8rDDfV+YTwNutt+JHXX3LZsVRRjBzViDUobR",
  "sig_alg": "SHA3-512WithRSA/PSS"
}' \
  | jq '.'
import requests

KEYPAIR_ID = "KEYPAIR_ID"

response = requests.post(
    f"https://demo.one.digicert.com/api/v1/keypairs/{KEYPAIR_ID}/sign",
    headers={"x-api-key": "MTLS", "Content-Type": "application/json"},
    json={
        "hash": "eeUnw3ky7+hI7Kawc9NnZ4CCCenu8rDDfV+YTwNutt+JHXX3LZsVRRjBzViDUobR",
        "sig_alg": "SHA3-512WithRSA/PSS"
    }
)

print(f"Status: {response.status_code}")
if response.status_code != 204:
    print(response.json())
import java.net.http.*;
import java.net.URI;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://demo.one.digicert.com/api/v1/keypairs/KEYPAIR_ID/sign"))
    .header("x-api-key", "MTLS")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString("{\"hash\": \"eeUnw3ky7+hI7Kawc9NnZ4CCCenu8rDDfV+YTwNutt+JHXX3LZsVRRjBzViDUobR\", \"sig_alg\": \"SHA3-512WithRSA/PSS\"}"))
    .build();

HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());

System.out.println("Status: " + response.statusCode());
System.out.println(response.body());
using System.Net.Http;
using System.Text;
using System.Text.Json;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "MTLS");

var content = new StringContent(@"{
  ""hash"": ""eeUnw3ky7+hI7Kawc9NnZ4CCCenu8rDDfV+YTwNutt+JHXX3LZsVRRjBzViDUobR"",
  ""sig_alg"": ""SHA3-512WithRSA/PSS""
}",
    Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://demo.one.digicert.com/api/v1/keypairs/KEYPAIR_ID/sign", content);

Console.WriteLine($"Status: {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());

Replace {keypair_id} with the keypair ID from Step 1.

Configure the signing request with:

  • hash - The base64-encoded file hash from the OpenSSL command
  • sig_alg - Signature algorithm matching your key type (e.g., SHA256WithRSA)

Successful response (200 OK):

{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "status": "active",
  "_note": "Response schema not fully documented in API spec"
}

From the response, save the signature id and the signature value. The id is used in Step 3 to verify the signature was recorded. The signature value is the cryptographic signature you can use to verify your file.

Step 3: Verify the signature

Retrieve the signature record to confirm it was created successfully and review the signing details.

Request:

curl -X GET "https://demo.one.digicert.com/api/v1/signatures/SIGNATURE_ID" \
  -H "x-api-key: MTLS" \
  | jq '.'
import requests

SIGNATURE_ID = "SIGNATURE_ID"

response = requests.get(
    f"https://demo.one.digicert.com/api/v1/signatures/{SIGNATURE_ID}",
    headers={"x-api-key": "MTLS"}
)

print(f"Status: {response.status_code}")
if response.status_code != 204:
    print(response.json())
import java.net.http.*;
import java.net.URI;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://demo.one.digicert.com/api/v1/signatures/SIGNATURE_ID"))
    .header("x-api-key", "MTLS")
    .GET()
    .build();

HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());

System.out.println("Status: " + response.statusCode());
System.out.println(response.body());
using System.Net.Http;
using System.Text;
using System.Text.Json;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "MTLS");

var response = await client.GetAsync("https://demo.one.digicert.com/api/v1/signatures/SIGNATURE_ID");

Console.WriteLine($"Status: {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());

Replace {signature_id} with the signature ID from Step 2.

Successful response (200 OK):

{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "keypair_id": "01234567-89ab-cdef-0123-456789abcdef",
  "key_pair": "KEY_PAIR_VALUE",
  "signature": "...(signature string)...",
  "sig_alg": "SIG_ALG_VALUE",
  "hash": "eeUnw3ky7+hI7Kawc9NnZ4CCCenu8rDDfV+YTwNutt+JHXX3LZsVRRjBzViDUobR",
  "signing_status": "SIGNING_STATUS_VALUE",
  "account": [
    "01234567-89ab-cdef-0123-456789abcdef"
  ],
  "created_on": "CREATED_ON_VALUE",
  "created_by": "CREATED_BY_VALUE",
  "created_by_user": "CREATED_BY_USER_VALUE",
  "modified_on": "MODIFIED_ON_VALUE",
  "modified_by": "MODIFIED_BY_VALUE",
  "modified_by_user": "MODIFIED_BY_USER_VALUE",
  "client_ip": "211.22.33.44",
  "certificate_id": "01234567-89ab-cdef-0123-456789abcdef",
  "client_type": "SigningManager-PKCS11/1.31.0 (linux; amd64)",
  "signature_type": "SIGNATURE_TYPE_VALUE"
}

Verify the response shows signing_status as SUCCESS. The response also includes:

  • keypair_id - Confirms which keypair was used
  • sig_alg - The signature algorithm used
  • hash - The original hash that was signed
  • created_on - Timestamp of when the signature was created
  • signature - The cryptographic signature value

Common errors and solutions

For general API errors (authentication, rate limits), see the API error reference.

Invalid key algorithm

{
  "errors": [
    {
      "code": "INVALID_KEY_ALG",
      "message": "Invalid key algorithm specified"
    }
  ]
}

The key_alg value is not valid. Use one of: RSA, ECDSA, or EdDSA.

Missing required properties

{
  "errors": [
    {
      "code": "INVALID_PROPERTIES",
      "message": "Key properties are required for the specified algorithm"
    }
  ]
}

You must include the properties object with algorithm-specific settings. RSA requires key_size, while ECDSA and EdDSA require curve.

Signature algorithm mismatch

{
  "errors": [
    {
      "code": "INVALID_SIG_ALG",
      "message": "Signature algorithm is not compatible with keypair algorithm"
    }
  ]
}

The signature algorithm must match the keypair’s key algorithm. For example, use SHA256WithRSA with RSA keys, not SHA256WithECDSA.

Keypair not found or not accessible

{
  "errors": [
    {
      "code": "KEYPAIR_NOT_FOUND",
      "message": "Keypair not found or you do not have access"
    }
  ]
}

The keypair ID is invalid or you don’t have permission to use it for signing. Verify the ID and ensure your user has the SSM_SIGNER or SSM_BUILD_ENGINEER role.