Skip to main content

Migrate GPG keypairs

This chapter provides scripts for migrating GPG master keypairs and their secret keyrings, including the master key and all associated subkeys, between Signing Manager accounts or environments.

참고

These migration scripts support only GPG disk-based keypairs. They do not support HSM-based keypairs.

Contact your DigiCert account manager to obtain the migration scripts.

중요

Team-restricted keys: GPG secret keyring export is refused outright while the master key belongs to a team, because team membership triggers a multi-person approval flow (1, 2, 3, or more approvers, depending on team configuration). If your keypairs are team-restricted, run Step 0 first as laid out in the below section.

These Windows PoweShell scripts are intended specifically for GPG keypairs and support migrating GPG master keypairs and their secret keyring.

The migration follows a strict five-step workflow across three personas.

Step

Script

Persona

Environment

Purpose

0 (Optional

0_remove_from_teams.ps1

Requester/Admin

Source

Open the key to skip the multi-person team approval flow (falls back to single-approver)

1

1_request_export.ps1

Requester

Source

Resolve alias, verify eligibility, request export

2

2_approve_export.ps1

Approver (different user)

Source

Approve (or reject) the export request

3

3_download_secring.ps1

Requester (same as step 1)

Source

One-shot download of the encrypted secret keyring

4

4_import_secring.ps1

Importer

Destination

Inspect, derive aliases, import master with all subkeys

You supply only the master keypair's UUID. The scripts resolve the alias, discover every subkey, and derive deterministic import aliases automatically. No manual subkey bookkeeping is required.

  • PowerShell 5.1+ (included with Windows 10/11. No curl, jq, openssl required. ). The scripts use native .NET System.Net.Http.HttpClient and System.Security.Cryptography.X509Certificates.

  • Client certificate (.p12/.pfx) and API key for each persona (requester, approver, importer — these can be the same person only where noted below).

  • Permissions per persona:

    Requester: VIEW_SM_KEYPAIR (alias lookup) and REQUEST_SM_KEYPAIR_EXPORT.

    Approver: APPROVE_SM_KEYPAIR_EXPORT. Must be a different user from the requester, unless the account has exactly one eligible administrator.

    Importer: IMPORT_SM_KEYPAIR. VIEW_SM_KEYPAIR is optional — when present it enables the alias-availability pre-check and lets the ledger record the target keypair id; without it those checks degrade gracefully.

Execution policy

If you get an error about script execution being disabled, run the following command in an elevated (Admin) PowerShell:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Create a CSV with a single column, master_keypair_id.

master_keypair_id
aafc011e-f7ff-433b-94ae-8673996fe121
8733dd09-a5e6-421c-ac24-48bedb4be8c9

Only master keypair ids are accepted. If you supply a subkey id, step 1 refuses it locally with a message identifying the parent master key.

How to find your master keypair id

Use the Signing Manager UI under GPG Keypairs, or use the API:

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\path\to\cert.p12", "<password>")
Invoke-RestMethod -Uri "https://<clientauth-host>/signingmanager/api/v1/gpg-keypairs?account_id=<account-id>" `
    -Headers @{"x-api-key"="<your-api-key>"} -Certificate $cert | ConvertTo-Json

Every script accepts:

-ClientCert <path-to-cert.p12> -ClientCertPass <p12-password>

Step 0 (optional) Remove Master Keypairs from Teams

When Teams is enabled, a GPG master key can be restricted to a team. Actions performed on a team-restricted master key are subject to the team’s configured multi-person approval policy. Depending on the team configuration, approval may be required from one or more team members before an action can proceed.

During migration, this approval requirement can add complexity to an already multi-step workflow. To simplify the process, the migration temporarily changes the master key only from team-restricted to open. Other keys associated with the team and the team configuration itself are not affected.

After the master key is opened:

  • The team’s multi-person approval policy no longer applies to the master key.

  • The migration uses the simpler single-approver, cross-account approval process.

  • Any one eligible account administrator can approve the export request.

  • No additional team approval is required for the master key during the remaining migration steps.

    중요

    Opening the master key is not temporary. The migration process does not automatically restore the key’s team restriction or add the key back to its team after migration. If your organization requires the master key to remain team-restricted, you must reapply the appropriate team restriction through the UI or API after the migration is complete.

    Master keypair access details

    Item

    Details

    Who performs this step

    An account administrator in the source environment.

    When to perform this step

    Only when the master keypair is assigned to a team. If your keys are already open, skip this step.

    What this step does

    Remove team restrictions from a keypair:

    • Retrieve the keypair details (GET /gpg-keypairs/{id}?account_id=)

    • Remove the team restriction (PUT /gpg-keypairs/{id}/access-management)

    • Use the keypair's current team.id as the team_id {"team_id": "<current team id>;","limited": false}

    참고

    The team_id in the request must match the keypair's current team ID.

    The server removes the keypair from its team only when:

    - team_idis included in the request body, and

    - either the team is being changed or limited is set to false.

    If the key is not mapped to any team, it is recorded as already open.

.\0_remove_from_teams.ps1 `
    -InputCsv input.csv `
    -BaseUrl "https://clientauth.dcone.cluster.local" `
    -AccountId "16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889" `
    -ApiKey "<admin-api-key>" `
    -ClientCert "C:\path\to\admin_cert.p12" `
    -ClientCertPass "<p12-password>"

Output CSV: output_teams.csv (master_keypair_id, team_status). When resumed, the process skips rows with an OPEN status and retries rows with a FAILEDstatus.

Step 1: Request Export

Item

Details

Who performs this step

Requester

What this step does

For each master keypair ID:

  • Retrieve the keypair details (GET /gpg-keypairs/{id}?account_id=). Retrieve the keypair alias and determine whether the keypair is eligible for export.

  • Validate the keypair's eligibility.

    Reject the keypair locally with a specific reason if it is:

    - A subkey

    - HSM-backed

    - A TEST-type key

    - In a status that does not allow export

    - Restricted to a team

  • Request an export. (POST /gpg-keypairs/secring/export. Set {"all_admins": true}

    This sends the export request to all eligible administrators for approval. You cannot target a specific approver.

.\1_request_export.ps1 `
    -InputCsv input.csv `
    -BaseUrl "https://clientauth.dcone.cluster.local" `
    -AccountId "16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889" `
    -ApiKey "<requester-api-key>" `
    -ClientCert "C:\path\to\requester_cert.p12" `
    -ClientCertPass "<p12-password>"

Expected output:

===============================================================
 Step 1: Requesting GPG Secret Keyring Export
 Account: 16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889
 Input:   input.csv
===============================================================

[2] aafc011e-f7ff-433b-94ae-8673996fe121
    Alias resolved: master_only (DISK, ONLINE)
    [OK] export_request_id: ab147fea-bcdd-4c64-8229-93389d3a39e6

===============================================================
 Done! Requested: 1 | Already done: 0 | Failed/Refused: 0
 Output written to: output_export_requests.csv
 Share this file with the APPROVER for step 2.
 Approval emails have been sent to all eligible administrators.
===============================================================

Output CSV: output_export_requests.csv (master_keypair_id, master_alias, export_request_id). Share the file with the approver. On resume, rows with a valid export_request_id are skipped, while FAILED and REFUSEDrows are retried.

Step 2: Approve export

Item

Details

Who performs this step

Approver. A different administrator on the source account (unless it is the account's only eligible administrator).

What this step does

GET /gpg-keypairs/secring/export/{id}?account_id= to show the master name, requester, and requested date, then PUT /gpg-keypairs/secring/export/{id} with {"approved": true} (or {"approved": false} with --Reject).

.\2_approve_export.ps1 `
    -InputCsv output_export_requests.csv `
    -BaseUrl "https://clientauth.dcone.cluster.local" `
    -AccountId "16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889" `
    -ApiKey "<approver-api-key>" `
    -ClientCert "C:\path\to\approver_cert.p12" `
    -ClientCertPass "<p12-password>"

Expected output:

===============================================================
 Step 2: Deciding GPG Secret Keyring Export Requests
 Decision: APPROVED
 Account:  16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889
 Input:    output_export_requests.csv
===============================================================

[2] master_only (request ab147fea-bcdd-4c64-8229-93389d3a39e6)
    Master keypair : master_only
    Requested by   : lead01 In
    Requested on   : 09/11/2026 08:55:12
    [OK] APPROVED

===============================================================
 Done! APPROVED: 1 | Already done: 0 | Skipped/Failed: 0
 Output written to: output_approved.csv
 Return this file to the REQUESTER for step 3 (download).
===============================================================

Output CSV: output_approved.csv (master_keypair_id,master_alias,export_request_id,approval_status) → return to the Requester.

Pass -Reject to deny the request instead of approving it.

참고

An export request can only be decided once. Terminal states APPROVED/REJECTED are skipped on resume; FAILED rows are retried. If a decision genuinely fails partway through a batch, continue to step 3 with the succeeded rows, or restart from step 1 for the full list.

Step 3: Download secret keyring

Item

Details

Who performs this step

Requester. The original requester only (enforced by API).

What this step does

The server marks the export request EXPORTED as soon as it responds. A keyring cannot be downloaded twice. Recovery from a lost download requires a brand-new export request and a new human approval. For this reason the script writes the retrieved material to the output CSV as its very first action, before any parsing — secret material is never at risk of being lost even if a later step fails. No .asc sidecar file is written to disk on purpose; the CSV (secring_base64 column) is the single point of truth for the keyring.

.\3_download_secring.ps1 `
    -InputCsv output_approved.csv `
    -BaseUrl "https://clientauth.dcone.cluster.local" `
    -AccountId "16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889" `
    -ApiKey "<requester-api-key>" `
    -ClientCert "C:\path\to\requester_cert.p12" `
    -ClientCertPass "<p12-password>"

Expected output:

===============================================================
 Step 3: Downloading Approved GPG Secret Keyrings
 Account: 16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889
 Input:   output_approved.csv
===============================================================

 WARNING: each download is ONE-SHOT and cannot be repeated.
 WARNING: output_download.csv will contain SECRET KEY MATERIAL and passphrases.

[2] Downloading master_only (request ab147fea-bcdd-4c64-8229-93389d3a39e6)
    [OK] keyring captured to output_download.csv

===============================================================
 Done! Downloaded: 1 | Already done: 0 | Skipped/Failed: 0
 Output written to: output_download.csv

 *** output_download.csv CONTAINS SECRET KEY MATERIAL AND PASSPHRASES ***
 Transfer it to the IMPORTER over an encrypted channel only, and
 destroy every copy once step 4 has completed successfully.
===============================================================

Output CSV: output_download.csv (master_keypair_id,master_alias,export_request_id,secring_base64,passphrase,download_status) → transfer securely to the Importer.

Resume skips rows recorded as DOWNLOADED and FAILED by default (a failure may mean the server already consumed the one-shot request); pass --retry-failed to force a retry of FAILED rows.

This file contains the secret keyring and its passphrase together. 

Encrypt it in transit and delete every copy once step 4 has completed.

Step 4: Import secret keyring

Item

Details

Who performs this step

Importer. The user on the target environment. Requires IMPORT_SM_KEYPAIR; VIEW_SM_KEYPAIR is optional (enables extra checks).

What this step does

  1. Decodes the base64 keyring to a temporary .asc file.

  2. POST /gpg-keypairs/inspect-sec-ring (multipart: file, password, account.id) to discover the master's own fingerprint and every subkey's fingerprint, algorithm, and validity.

  3. Derives the alias actually used for import (see below).

  4. Refuses the whole keyring if any key (master or subkey) is invalid and nothing partially imports.

  5. Best-effort checks whether the derived master alias is already taken in the target account (degrades gracefully without VIEW_SM_KEYPAIR).

  6. POST /gpg-keypairs/import-sec-ring with the master key pair, the full subkey list, the passphrase, and the keyring bytes. Success is judged by HTTP status alone, the endpoint returns 200 with an empty body

  7. Resolves the new target master keypair id for the audit trail (needs VIEW_SM_KEYPAIR; degrades to a blank id and note otherwise).

Alias derivation

  • Master alias: First 91 characters of the CSVmaster_alias , plus _ + the first 8 hex characters of the master key's own fingerprint, upper-cased. Example: CSV alias master_only + fingerprint 3500A69F79C1B93B... → imported as master_only_3500A69F.

    Suffixing the master's own fingerprint (not just subkeys) is required because alias uniqueness on this server is enforced globally across every account — without the suffix, an alias already used in the source account (or any other account) would collide.

  • Subkey alias: First 87 characters of the derived master alias, plus _sub__sub_ + the first 8 hex characters of the subkey's own fingerprint, upper-cased. Example: master_only_3500A69F + subkey fingerprint 8A8ABA2A6D07ED10... → master_only_3500A69F_sub_8A8ABA2A.

    If two subkeys in the same ring happen to share their first 8 fingerprint characters, the script automatically widens every subkey suffix in that ring to 16 hex characters and prints a note.Both derivations are a pure function of each key's own fingerprint, so resuming or re-running always produces identical aliases.

  • Both derivations are pure functions of the input alias and fingerprint, so dry-run and real import always agree on the resulting aliases.

Dry run

Pass --dry-run to inspect the keyring, print the full alias plan (master + every subkey), and run the best-effort alias availability check — without writing the ledger or any import:

.\4_import_secring.ps1 `
    -InputCsv output_download.csv `
    -BaseUrl "https://clientauth.one.stage.az.digicert.net" `
    -AccountId "492569b0-9b1b-4d6e-ba2b-d30fe725feb2" `
    -ApiKey "<importer-api-key>" `
    -ClientCert "C:\path\to\importer_cert.p12" `
    -ClientCertPass "<p12-password>" `
    -DryRun

Real import

Drop --dry-run to perform the import:

.\4_import_secring.ps1 `
    -InputCsv output_download.csv `
    -BaseUrl "https://clientauth.one.stage.az.digicert.net" `
    -AccountId "492569b0-9b1b-4d6e-ba2b-d30fe725feb2" `
    -ApiKey "<importer-api-key>" `
    -ClientCert "C:\path\to\importer_cert.p12" `
    -ClientCertPass "<p12-password>"

Expected output (master + 3 subkeys):

===============================================================
 Step 4: Importing GPG Secret Keyrings
 Target account: 492569b0-9b1b-4d6e-ba2b-d30fe725feb2
 Target URL:     https://clientauth.one.stage.az.digicert.net
 Input:          output_download.csv
===============================================================

[2] master_01
    Inspecting keyring content...
    -> Master key
         Fingerprint : 6A5046352D0F7C456B0F58D618FADCBF05502F0E
         Algorithm   : RSA
         Will import as alias : 'master_01_6A504635'  (from 'master_01' + fingerprint suffix)
         Validity    : VALID (usable for import)
    -> Subkeys found: 3
       Subkey #1
         Fingerprint : 8A8ABA2A6D07ED10AF076465B287034EEA11B5DE
         Algorithm   : RSA
         Will import as alias : 'master_01_sub_8A8ABA2A'
         Validity    : VALID (usable for import)
       ...

    Checking whether alias 'master_01_6A504635' is free in the target account...
    ALIAS CHECK: 'master_01_6A504635' not found in the TARGET account (looks available).
    [OK] keyring imported: 1 master + 3 subkey(s) = 4 keypair(s) total
    [OK] target master keypair id (in target account): ce818f10-fc1d-481a-b1ad-be2937239129

===============================================================
 Done! Keyrings imported: 1 (= 4 individual keypair(s) counting master+subkeys)
       Already done (skipped): 0 | Refused: 0 | Failed: 0
 Output written to: output_import.csv

 Migration complete. Securely destroy output_download.csv and any temp files.
===============================================================

Output CSV: output_import.csv (master_keypair_id,master_alias,master_fingerprint,imported_master_alias,import_status,imported_count,target_master_id,sub_keys,detail)

The summary line distinguishes keyrings imported (one row = one master + all its subkeys) from individual keypairs (master + every subkey counted separately) to avoid ambiguity — e.g. "Keyrings imported: 1 (= 4 individual keypair(s)...)" means one master-keypair row succeeded, representing 4 distinct GPG keypairs now present in the target account.

Resume skips rows recorded as IMPORTEDI or REFUSED; FAILED rows are retried.

참고

An import rejected with gpg.alias.duplicate — the server rolls the entire keyring back (all-or-nothing). Edit the master_alias cell of that row in the input CSV (output_download.csv) and re-run; only the master_alias cell of a non-terminal row should ever be hand-edited.

Error / symptom

Cause

Solution

?accountId= (camelCase) silently returns the caller's own default account

The GET lookup endpoints (/gpg-keypairs/{id}, /gpg-keypairs/secring/export/{id}) require the snake_case query parameter account_id; camelCase does not error, it silently resolves the wrong account

Always use account_id=(snake_case) on GET lookups.

this is a SUBKEY... Supply the MASTER key id instead

A subkey UUID was supplied as master_keypair_id

Look up the subkey's parent_gpg_keypair_id and use that instead.

test keys cannot be exported

The master key is key_type: TEST

TEST-type keys are not eligible for export/migration.

HSM-backed keys cannot be exported

The master key's key_storage is HSM

HSM-backed keys cannot leave the HSM; not migratable via this tool.

keypair is restricted to a team...

Master key belongs to a team

Run 0_remove_from_teams.ps1 first.

Step 2 fails with invalid_status ...status of the Request is APPROVED

The request was already approved (e.g. on a prior run)

Treat the row as approved and continue to step 3.

Step 3 fails, reason mentions status/exported

This export request was already downloaded once.

Recovery requires a brand-new export request (step 1) and a new approval (step 2).

Step 3 fails, reason mentions expired

The export request expired before download

New export request with new approval.

Inspect (step 4) rejects the file at ~100 KB

Keyring exceeds the 102,400-byte inspection ceiling

Not fixable client-side; keyring is too large for this API.

gpg.alias.duplicate at import

The derived master alias already exists somewhere on the server (aliases are global)

Edit the master_alias cell in the input CSV and re-run.

Alias-availability check reports "skipped"

Importer lacks VIEW_SM_KEYPAIR

Informational only — the import itself still succeeds or fails on the server's own uniqueness check

The SSL connection could not be established on every call, even with -Insecur

Custom certificate-validation scriptblocks fail because HttpClient invokes them on a thread with no PowerShell runspace

Not an operator-facing issue in the shipped scripts — used DangerousAcceptAnyServerCertificateValidator instead of a scriptblock; if you have modified the script, avoid ServerCertificateCustomValidationCallback = { ... }.

URLs built with "$var?account_id=..." silently drop the variable

PowerShell string interpolation misparses $var? — the ? breaks variable name resolution

Not an operator-facing issue in the shipped scripts — used ${var}?account_id=... brace syntax; if you customize a URL, always brace-delimit variables immediately followed by ?

[FAIL] HTTP 403 ... feature_not_enabled

Transient server-side blip, or GPG feature genuinely not enabled on the account/package

Retry; if persistent, confirm the account package includes GPG keypair support

Script execution disabled

PowerShell execution policy

Run Set-ExecutionPolicy RemoteSigned -Scope CurrentUser.

  • Two-person authorization: Export requires a requester and a separate approver (except single-admin accounts). No single person can export a secret keyring alone.

  • mTLS: All API calls require mutual TLS using client certificates.

  • One-shot download: The secret keyring can be retrieved exactly once per approved export request; the server marks the request consumed the instant it responds.

  • Passphrase-protected: The exported keyring is encrypted with a one-time passphrase generated by the server; both are required for import.

  • Sensitive artifact protection: output_download.csv is the most sensitive artifact in this workflow. It holds the base64 keyring and its passphrase in the same row. Transfer it only over an encrypted channel and destroy every copy immediately after step 4 succeeds.

  • Audit trail: Every export/approve/download/import operation is logged in Signing Manager's audit log; output_import.csv additionally records the resulting target keypair id for reconciliation.

If you encounter issues not covered above, contact DigiCert support with:

  • The full script output (or the relevant [FAIL] block)

  • The HTTP status code and Reason/Response lines

  • Your account ID (and not API key)

  • The environment/host you are using

These bash scripts are intended specifically for GPG keypairs and support migrating GPG master keypairs and their secret keyring.

The migration follows a strict five-step workflow across three personas.

Step

Script

Persona

Environment

Purpose

0 (Optional

0_remove_from_teams.sh

Requester/Admin

Source

Open the key to skip the multi-person team approval flow (falls back to single-approver)

1

1_request_export.sh

Requester

Source

Resolve alias, verify eligibility, request export

2

2_approve_export.sh

Approver (different user)

Source

Approve (or reject) the export request

3

3_download_secring.sh

Requester (same as step 1)

Source

One-shot download of the encrypted secret keyring

4

4_import_secring.sh

Importer

Source

Inspect, derive aliases, import master with all subkeys

You supply only the master keypair's UUID — the scripts resolve the alias, discover every subkey, and derive deterministic import aliases automatically. No manual subkey bookkeeping is required.

  • curl, jq, openssl on the machine running the scripts (all standard on macOS/Linux; install using your package manager if missing, e.g. brew install jq or apt-get install jq)).

  • Client certificate (.p12/.pfx) and API key for each persona (requester, approver, importer — these can be the same person only where noted below).

  • Permissions per persona:

    Requester: VIEW_SM_KEYPAIR (alias lookup) and REQUEST_SM_KEYPAIR_EXPORT.

    Approver: APPROVE_SM_KEYPAIR_EXPORT. Must be a different user from the requester, unless the account has exactly one eligible administrator.

    Importer: IMPORT_SM_KEYPAIR. VIEW_SM_KEYPAIR is optional — when present it enables the alias-availability pre-check and lets the ledger record the target keypair id; without it those checks degrade gracefully.

Create a CSV with a single column, master_keypair_id (header (optional) is auto-detected and skipped):

master_keypair_id
aafc011e-f7ff-433b-94ae-8673996fe121
8733dd09-a5e6-421c-ac24-48bedb4be8c9

Only master keypair ids are accepted. If you supply a subkey id, step 1 refuses it locally with a message identifying the parent master key.

How to find your master keypair id

Use the Signing Manager UI under GPG Keypairs, or use the API:

curl -s -k --cert /tmp/requester.pem \
  -X GET "https://<clientauth-host>/signingmanager/api/v1/gpg-keypairs?account_id=<account-id>" \
  -H "x-api-key: <your-api-key>" | jq .

Every script accepts:

--client-cert <path-to-cert.p12-or-.pem> --client-cert-pass <p12-password>
  • .p12/.pfx files are converted on the fly to a temporary PEM using openssl pkcs12 and shredded (via trap ... EXIT) when the script exits.

  • .pem files are used directly (no password needed).

  • Pass --insecure to skip TLS certificate verification (test/staging environments with self-signed certs only — never in production).

Step 0 (optional) Remove Master Keypairs from Teams

When Teams is enabled, a GPG master key can be restricted to a team. Actions performed on a team-restricted master key are subject to the team’s configured multi-person approval policy. Depending on the team configuration, approval may be required from one or more team members before an action can proceed.

During migration, this approval requirement can add complexity to an already multi-step workflow. To simplify the process, the migration temporarily changes the master key only from team-restricted to open. Other keys associated with the team and the team configuration itself are not affected.

After the master key is opened:

  • he team’s multi-person approval policy no longer applies to the master key.

  • The migration uses the simpler single-approver, cross-account approval process.

  • Any one eligible account administrator can approve the export request.

  • No additional team approval is required for the master key during the remaining migration steps.

    중요

    Opening the master key is not temporary. The migration process does not automatically restore the key’s team restriction or add the key back to its team after migration. If your organization requires the master key to remain team-restricted, you must reapply the appropriate team restriction through the UI or API after the migration is complete.

    Master keypair access details

    Item

    Details

    Who performs this step

    An account administrator in the source environment.

    When to perform this step

    Only when the master keypair is assigned to a team.

    What this step does

    Remove team restrictions from a keypair:

    • Retrieve the keypair details (GET /gpg-keypairs/{id}?account_id=)

    • Remove the team restriction (PUT /gpg-keypairs/{id}/access-management)

    • Use the keypair's current team.id as the team_id {"team_id": "<current team id>;","limited": false}

    참고

    The team_id in the request must match the keypair's current team ID.

    The server removes the keypair from its team only when:

    - team_idis included in the request body, and

    - either the team is being changed or limited is set to false.

    If the keypair is already open

    No update is required.

./0_remove_from_teams.sh \
    --input input.csv \
    --base-url "https://clientauth.dcone.cluster.local" \
    --account-id "16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889" \
    --api-key "<admin-api-key>" \
    --client-cert "/path/to/admin_cert.p12" \
    --client-cert-pass "<p12-password>"

Output CSV: output_teams.csv (master_keypair_id, team_status). When resumed, the process skips rows with an OPEN status and retries rows with a FAILED status.

Step 1: Request Export

Item

Details

Who performs this step

Requester

What this step does

For each master keypair ID:

  • Retrieve the keypair details (HTTPGET /gpg-keypairs/{id}?account_id=). Retrieve the keypair alias and determine whether the keypair is eligible for export.

  • Validate the keypair's eligibility.

    Reject the keypair locally with a specific reason if it is:

    - A subkey

    - HSM-backed

    - A TEST-type key

    - In a status that does not allow export

    - Restricted to a team

  • Request an export. (POST /gpg-keypairs/secring/export. Set {"all_admins": true}

    This sends the export request to all eligible administrators for approval.

    ou cannot target a specific approver.

./1_request_export.sh \
    --input input.csv \
    --base-url "https://clientauth.dcone.cluster.local" \
    --account-id "16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889" \
    --api-key "<requester-api-key>" \
    --client-cert "/path/to/requester_cert.p12" \
    --client-cert-pass "<p12-password>"

Expected output:

===============================================================
 Step 1: Requesting GPG Secret Keyring Export
 Account: 16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889
 Input:   input.csv
===============================================================

[2] aafc011e-f7ff-433b-94ae-8673996fe121
    Alias resolved: master_only (DISK, ONLINE)
    [OK] export_request_id: ab147fea-bcdd-4c64-8229-93389d3a39e6

===============================================================
 Done! Requested: 1 | Already done: 0 | Failed/Refused: 0
 Output written to: output_export_requests.csv
 Share this file with the APPROVER for step 2.
 Approval emails have been sent to all eligible administrators.
===============================================================

Output CSV: output_export_requests.csv (master_keypair_id, master_alias, export_request_id). Share the file with the approver. On resume, rows with a valid export_request_id are skipped, while FAILED and REFUSEDrows are retried.

Step 2: Approve export

Item

Details

Who performs this step

Approver. A different administrator on the source account (unless it is the account's only eligible administrator).

What this step does

GET /gpg-keypairs/secring/export/{id}?account_id= to show the master name, requester, and requested date, then PUT /gpg-keypairs/secring/export/{id} with {"approved": true} (or {"approved": false} with --reject).

./2_approve_export.sh \
    --input output_export_requests.csv \
    --base-url "https://clientauth.dcone.cluster.local" \
    --account-id "16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889" \
    --api-key "<approver-api-key>" \
    --client-cert "/path/to/approver_cert.p12" \
    --client-cert-pass "<p12-password>"

Expected output:

===============================================================
 Step 2: Deciding GPG Secret Keyring Export Requests
 Decision: APPROVED
 Account:  16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889
 Input:    output_export_requests.csv
===============================================================

[2] master_only (request ab147fea-bcdd-4c64-8229-93389d3a39e6)
    Master keypair : master_only
    Requested by   : lead01 In
    Requested on   : 09/11/2026 08:55:12
    [OK] APPROVED

===============================================================
 Done! APPROVED: 1 | Already done: 0 | Skipped/Failed: 0
 Output written to: output_approved.csv
 Return this file to the REQUESTER for step 3 (download).
===============================================================

Output CSV: output_approved.csv (master_keypair_id,master_alias,export_request_id,approval_status) → return to the Requester.

참고

An export request can only be decided once. Terminal states APPROVED/REJECTED are skipped on resume; FAILED rows are retried. If a decision genuinely fails partway through a batch, continue to step 3 with the succeeded rows, or restart from step 1 for the full list.

Step 3: Download secret keyring

Item

Details

Who performs this step

Requester. The original requester only — the API enforces this.

What this step does

The server marks the export request EXPORTED as soon as it responds. A keyring cannot be downloaded twice. Recovery from a lost download requires a brand-new export request and a new human approval. For this reason the script writes the retrieved material to the output CSV as its very first action, before any parsing — secret material is never at risk of being lost even if a later step fails. No .asc sidecar file is written to disk on purpose; the CSV (secring_base64 column) is the single point of truth for the keyring.

./3_download_secring.sh \
    --input output_approved.csv \
    --base-url "https://clientauth.dcone.cluster.local" \
    --account-id "16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889" \
    --api-key "<requester-api-key>" \
    --client-cert "/path/to/requester_cert.p12" \
    --client-cert-pass "<p12-password>"

Expected output:

===============================================================
 Step 3: Downloading Approved GPG Secret Keyrings
 Account: 16d0b4f4-d1cc-4dbc-add8-8fcffc8c4889
 Input:   output_approved.csv
===============================================================

 WARNING: each download is ONE-SHOT and cannot be repeated.
 WARNING: output_download.csv will contain SECRET KEY MATERIAL and passphrases.

[2] Downloading master_only (request ab147fea-bcdd-4c64-8229-93389d3a39e6)
    [OK] keyring captured to output_download.csv

===============================================================
 Done! Downloaded: 1 | Already done: 0 | Skipped/Failed: 0
 Output written to: output_download.csv

 *** output_download.csv CONTAINS SECRET KEY MATERIAL AND PASSPHRASES ***
 Transfer it to the IMPORTER over an encrypted channel only, and
 destroy every copy once step 4 has completed successfully.
===============================================================

Output CSV: output_download.csv (master_keypair_id,master_alias,export_request_id,secring_base64,passphrase,download_status) → transfer securely to the Importer.

Resume skips rows recorded as DOWNLOADED and FAILED by default (a failure may mean the server already consumed the one-shot request); pass --retry-failed to force a retry of FAILED rows.

This file contains the secret keyring and its passphrase together. Encrypt it in transit and delete every copy once step 4 has completed.

Step 4: Import secret keyring

Item

Details

Who performs this step

Importer. The user on the target environment. Requires IMPORT_SM_KEYPAIR; VIEW_SM_KEYPAIR is optional (enables extra checks).

What this step does

  1. Decodes the base64 keyring to a temporary .asc file.

  2. POST /gpg-keypairs/inspect-sec-ring (multipart: file, password, account.id) to discover the master's own fingerprint and every subkey's fingerprint, algorithm, and validity.

  3. Derives the alias actually used for import (see below).

  4. Refuses the whole keyring if any key (master or subkey) is invalid and nothing partially imports.

  5. Best-effort checks whether the derived master alias is already taken in the target account (degrades gracefully without VIEW_SM_KEYPAIR).

  6. POST /gpg-keypairs/import-sec-ring with the master key pair, the full subkey list, the passphrase, and the keyring bytes. Success is judged by HTTP status alone, the endpoint returns 200 with an empty body

  7. Resolves the new target master keypair id for the audit trail (needs VIEW_SM_KEYPAIR; degrades to a blank id and note otherwise).

  8. Shreds the temporary .asc file on exit using trap, regardless of outcome.

Alias derivation

  • Master alias: First 91 characters of the CSVmaster_alias , plus _ + the first 8 hex characters of the master key's own fingerprint, upper-cased. Example: CSV alias master_only + fingerprint 3500A69F79C1B93B... → imported as master_only_3500A69F.

    Suffixing the master's own fingerprint (not just subkeys) is required because alias uniqueness on this server is enforced globally across every account — without the suffix, an alias already used in the source account (or any other account) would collide.

  • Subkey alias: First 87 characters of the derived master alias, plus _sub__sub_ + the first 8 hex characters of the subkey's own fingerprint, upper-cased. Example: master_only_3500A69F + subkey fingerprint 8A8ABA2A6D07ED10... → master_only_3500A69F_sub_8A8ABA2A.

    If two subkeys in the same ring happen to share their first 8 fingerprint characters, the script automatically widens every subkey suffix in that ring to 16 hex characters and prints a note.Both derivations are a pure function of each key's own fingerprint, so resuming or re-running always produces identical aliases.

  • Both derivations are pure functions of the input alias and fingerprint, so dry-run and real import always agree on the resulting aliases.

Dry run

Pass --dry-run to inspect the keyring, print the full alias plan (master + every subkey), and run the best-effort alias availability check — without writing the ledger or importing anything:

./4_import_secring.sh \
    --input output_download.csv \
    --base-url "https://clientauth.one.stage.az.digicert.net" \
    --account-id "492569b0-9b1b-4d6e-ba2b-d30fe725feb2" \
    --api-key "<importer-api-key>" \
    --client-cert "/path/to/importer_cert.p12" \
    --client-cert-pass "<p12-password>" \
    --dry-run

Real import

Drop --dry-run to perform the import:

./4_import_secring.sh \
    --input output_download.csv \
    --base-url "https://clientauth.one.stage.az.digicert.net" \
    --account-id "492569b0-9b1b-4d6e-ba2b-d30fe725feb2" \
    --api-key "<importer-api-key>" \
    --client-cert "/path/to/importer_cert.p12" \
    --client-cert-pass "<p12-password>"

Expected output (master + 3 subkeys):

==============================================================
 Step 4: Importing GPG Secret Keyrings
 Target account: 492569b0-9b1b-4d6e-ba2b-d30fe725feb2
 Target URL:     https://clientauth.one.stage.az.digicert.net
 Input:          output_download.csv
===============================================================

[2] master_01
    Inspecting keyring content...
    -> Master key
         Fingerprint : 6A5046352D0F7C456B0F58D618FADCBF05502F0E
         Algorithm   : RSA
         Will import as alias : 'master_01_6A504635'  (from 'master_01' + fingerprint suffix)
         Validity    : VALID (usable for import)
    -> Subkeys found: 3
       Subkey #1
         Fingerprint : 8A8ABA2A6D07ED10AF076465B287034EEA11B5DE
         Algorithm   : RSA
         Will import as alias : 'master_01_sub_8A8ABA2A'
         Validity    : VALID (usable for import)
       ...

    Checking whether alias 'master_01_6A504635' is free in the target account...
    ALIAS CHECK: 'master_01_6A504635' not found in the TARGET account (looks available).
    [OK] keyring imported: 1 master + 3 subkey(s) = 4 keypair(s) total
    [OK] target master keypair id (in target account): ce818f10-fc1d-481a-b1ad-be2937239129

===============================================================
 Done! Keyrings imported: 1 (= 4 individual keypair(s) counting master+subkeys)
       Already done (skipped): 0 | Refused: 0 | Failed: 0
 Output written to: output_import.csv

 Migration complete. Securely destroy output_download.csv and any temp files.
===============================================================

Output CSV: output_import.csv (master_keypair_id,master_alias,master_fingerprint,imported_master_alias,import_status,imported_count,target_master_id,sub_keys,detail

The summary line distinguishes keyrings imported (one row = one master + all its subkeys) from individual keypairs (master + every subkey counted separately) to avoid ambiguity — e.g. "Keyrings imported: 1 (= 4 individual keypair(s)...)" means one master-keypair row succeeded, representing 4 distinct GPG keypairs now present in the target account.

Resume skips rows recorded as IMPORTEDI or REFUSED; FAILED rows are retried.

참고

An import rejected with gpg.alias.duplicate — the server rolls the entire keyring back (all-or-nothing). Edit the master_alias cell of that row in the input CSV (output_download.csv) and re-run; only the master_alias cell of a non-terminal row should ever be hand-edited.

Error / symptom

Cause

Solution

?accountId= (camelCase) silently returns the caller's own default account

The GET lookup endpoints (/gpg-keypairs/{id}, /gpg-keypairs/secring/export/{id}) require the snake_case query parameter account_id; camelCase does not error, it silently resolves the wrong account

Always use account_id=

this is a SUBKEY... Supply the MASTER key id instead

A subkey UUID was supplied as master_keypair_id

Look up the subkey's parent_gpg_keypair_id and use that instead

test keys cannot be exported

The master key is key_type: TEST

TEST-type keys are not eligible for export/migration

HSM-backed keys cannot be exported

The master key's key_storage is HSM

HSM-backed keys cannot leave the HSM; not migratable via this tool

keypair is restricted to a team...

Master key belongs to a team

Run 0_remove_from_teams.sh first

Step 2 fails with invalid_status ...status of the Request is APPROVED

The request was already approved (e.g. on a prior run)

Treat the row as approved and continue to step 3

Step 3 fails, reason mentions status/exported

This export request was already downloaded once (one-shot)

Recovery requires a brand-new export request (step 1) and a new approval (step 2)

Step 3 fails, reason mentions expired

The export request expired before download

New export request and new approval required.

Inspect (step 4) rejects the file at ~100 KB

Keyring exceeds the 102,400-byte inspection ceiling

Not fixable client-side; keyring is too large for this API

gpg.alias.duplicate at import

The derived master alias already exists somewhere on the server (aliases are global)

Edit the master_alias cell in the input CSV and re-run

Alias-availability check reports "skipped"

Importer lacks VIEW_SM_KEYPAIR

Informational only — the import itself still succeeds or fails on the server's own uniqueness check

[FAIL] HTTP 403 ... feature_not_enabled

Transient server-side blip, or GPG feature genuinely not enabled on the account/package

Retry; if persistent, confirm the account package includes GPG keypair support

  • Two-person authorization: Export requires a requester and a separate approver (except single-admin accounts). No single person can export a secret keyring alone.

  • mTLS: All API calls require mutual TLS using client certificates.

  • One-shot download: The secret keyring can be retrieved exactly once per approved export request; the server marks the request consumed the instant it responds.

  • Passphrase-protected: The exported keyring is encrypted with a one-time passphrase generated by the server; both are required for import.

  • Sensitive artifact protection: output_download.csv is the most sensitive artifact in this workflow. It holds the base64 keyring and its passphrase in the same row. Transfer it only over an encrypted channel and destroy every copy immediately after step 4 succeeds.

  • Audit trail: Every export/approve/download/import operation is logged in Signing Manager's audit log; output_import.csv additionally records the resulting target keypair id for reconciliation.

If you encounter issues not covered above, contact DigiCert support with:

  • The full script output (or the relevant [FAIL] block)

  • The HTTP status code and Reason/Response lines

  • Your account ID (and not API key)

  • The environment/host you are using