Skip to content

Credentials

This guide covers credential management - creating, rotating, and revoking certificate bundles with coupled lifecycle management.

A credential is a managed bundle of private key(s) + certificate(s) with coupled lifecycle management. All certificates in a credential are created, renewed, and revoked together.

Traditional PKI tools manage keys and certificates separately, requiring manual coordination for:

  • Key generation → CSR → Certificate issuance → Deployment
  • Renewal (repeat the full cycle)
  • Revocation (track which certs belong together)

Credentials encapsulate this end-entity workflow:

  • Single command to enroll (key + cert created together)
  • Atomic rotation (all certs renewed at once)
  • Grouped revocation (all certs added to CRL together)
  • Multi-algorithm support (classical + PQC in one bundle)
AspectCertificateCredential
ScopeSingle certificateBundle of related certificates
KeysSeparate managementIntegrated lifecycle
RenewalManual per-certRotate all at once
RevocationIndividualAll certs together
Multi-algorithmOne algorithmMultiple profiles

Credentials use a versioned structure with separate directories for keys and certificates (matching CA structure):

credentials/<credential-id>/
├── credential.meta.json # Metadata (versions, active, etc.)
└── versions/
└── v1/
├── keys/
│ ├── credential.ecdsa-p384.key # Private key (encrypted)
│ └── credential.ml-dsa-87.key
└── certs/
├── credential.ecdsa-p384.pem # Certificate
└── credential.ml-dsa-87.pem

After rotation, credentials have multiple versions:

credentials/<credential-id>/
├── credential.meta.json # Points to active version (status is computed, not stored)
└── versions/
├── v1/ # archived
│ ├── keys/
│ │ └── credential.ecdsa-p384.key
│ └── certs/
│ └── credential.ecdsa-p384.pem
└── v2/ # active
├── keys/
│ ├── credential.ecdsa-p384.key
│ └── credential.ml-dsa-87.key
└── certs/
├── credential.ecdsa-p384.pem
└── credential.ml-dsa-87.pem

Version Status (computed, not stored):

StatusCondition
activeVersion ID matches active field in credential.meta.json
pendingVersion exists but not active (no archived_at timestamp)
archivedVersion has archived_at timestamp set

Status is derived at runtime from:

  • active field in credential.meta.json → determines which version is active
  • archived_at timestamp in version → marks archived versions
RoleDescription
signatureStandard signature certificate
signature-classicalClassical signature in hybrid-separate mode
signature-pqcPQC signature in hybrid-separate mode
encryptionStandard encryption certificate
encryption-classicalClassical encryption in hybrid-separate mode
encryption-pqcPQC encryption in hybrid-separate mode
StatusDescription
pendingCredential created but not yet active
validCredential is active and usable
expiredValidity period has ended
revokedCredential was revoked (all certs added to CRL)
┌─────────────┐
│ ENROLL │
│ (pending) │
└──────┬──────┘
┌─────────────┐ ┌─────────────┐
│ VALID │────►│ EXPIRED │
│ │ │ (automatic) │
└──────┬──────┘ └─────────────┘
│ revoke
┌─────────────┐
│ REVOKED │
│ (on CRL) │
└─────────────┘

Create a new credential with key(s) and certificate(s).

Terminal window
qpki credential enroll [flags]

Flags:

FlagShortDefaultDescription
--profile-PrequiredProfile to use (repeatable for multi-profile)
--varVariable value (e.g., cn=example.com). Repeatable.
--var-fileYAML file with variable values
--ca-dir-d./caCA directory (for signing)
--cred-dir-c./credentialsCredentials directory
--idautoCustom credential ID
--passphrase-p""Passphrase for private keys

Output:

Terminal window
qpki credential enroll --profile ec/tls-client --var cn=Alice
# ├── credential.meta.json # Metadata
# └── private-keys.pem # Private key(s)

Examples:

Terminal window
# Basic enrollment (single profile)
qpki credential enroll --profile ec/tls-client \
--var cn=alice@example.com --var email=alice@example.com
qpki credential enroll --profile ec/client --profile ml/client \
--var cn=alice@example.com
qpki credential enroll --profile hybrid/catalyst/tls-client \
--var cn=alice@example.com --var email=alice@example.com
qpki credential enroll --profile ec/tls-server \
--var cn=server.example.com \
--var dns_names=server.example.com,www.example.com
qpki credential enroll --profile ec/tls-client \
--var cn=alice@example.com --id alice-prod
qpki credential enroll --profile hybrid/catalyst/tls-client \
--var cn=alice@example.com --passphrase "secret"
qpki credential enroll --ca-dir ./myca --cred-dir ./myca/credentials \
--profile ec/tls-server --var cn=server.example.com

ML-KEM (encryption) profiles:

For ML-KEM profiles, a signature profile must be listed first (RFC 9883 proof of possession):

Terminal window
# Correct: signature profile before KEM profile
qpki credential enroll --profile ec/client --profile ml-kem/client \
--var cn=alice@example.com
qpki credential enroll --profile ml-kem/client --var cn=alice@example.com

List all credentials.

Terminal window
qpki credential list [flags]

Flags:

FlagShortDefaultDescription
--cred-dir-c./credentialsCredentials directory

Example:

Terminal window
qpki credential list
qpki credential list --cred-dir ./myca/credentials

Show details of a specific credential.

Terminal window
qpki credential info <credential-id> [flags]

Flags:

FlagShortDefaultDescription
--cred-dir-c./credentialsCredentials directory

Example:

Terminal window
qpki credential info alice-20250115-abc123

Rotate a credential with new certificates. Creates a PENDING version that must be activated.

Terminal window
qpki credential rotate <credential-id> [flags]

Flags:

FlagShortDefaultDescription
--ca-dir-d./caCA directory (for signing)
--cred-dir-c./credentialsCredentials directory
--profile-PReplace all profiles (overrides add/remove)
--add-profileAdd profile(s) to current set
--remove-profileRemove profile(s) from current set
--keep-keysfalseReuse existing keys (certificate renewal only)
--passphrase-p""Passphrase for private keys
--hsm-configHSM configuration file for key generation
--key-labelHSM key label prefix

Workflow:

After rotation, the new version must be explicitly activated:

Terminal window
qpki credential rotate <credential-id>
qpki credential activate <credential-id> --version v20260105_abc123

This allows:

  • Review before activation
  • Gradual rollout
  • Rollback possibility

Examples:

Terminal window
# Simple rotation (generates new keys)
qpki credential rotate alice-xxx
# Output: Version v20260105_abc123 (PENDING)
qpki credential rotate alice-xxx --keep-keys
qpki credential rotate alice-xxx --add-profile ml/tls-client
qpki credential rotate alice-xxx --remove-profile ec/tls-client
qpki credential rotate alice-xxx \
--profile ec/tls-client --profile ml/tls-client
qpki credential rotate alice-xxx --ca-dir ./myca --cred-dir ./myca/credentials

Activate a pending or archived credential version. Use this after rotation to make a new version active, or to rollback to a previous version.

Terminal window
qpki credential activate <credential-id> [flags]

Flags:

FlagShortDefaultDescription
--cred-dir-c./credentialsCredentials directory
--version(required)Version to activate

Examples:

Terminal window
# Activate a pending version after rotation
qpki credential activate alice-xxx --version v20260105_abc123
# Rollback to a previous (archived) version
qpki credential versions alice-xxx
# Shows: v1 (archived), v2 (active)
qpki credential activate alice-xxx --version v1
# v1 becomes active, v2 becomes archived

Rollback behavior:

  • When activating an archived version, its archived_at timestamp is cleared
  • The previously active version becomes archived
  • All certificates in the reactivated version become usable again

List all versions of a credential.

Terminal window
qpki credential versions <credential-id> [flags]

Flags:

FlagShortDefaultDescription
--cred-dir-c./credentialsCredentials directory

Example:

Terminal window
qpki credential versions alice-xxx

Output:

Credential: alice-xxx
VERSION STATUS PROFILES CREATED
------- ------ -------- -------
v20260101_abc123 archived ec/tls-client 2026-01-01
v20260105_def456 active ec/tls-client, ml/tls-client 2026-01-05

Revoke all certificates in a credential.

Terminal window
qpki credential revoke <credential-id> [flags]

Flags:

FlagShortDefaultDescription
--ca-dir-d./caCA directory (for CRL/index update)
--cred-dir-c./credentialsCredentials directory
--reason-runspecifiedRevocation reason

Revocation Reasons:

ReasonDescription
unspecifiedNo specific reason
keyCompromisePrivate key was compromised
caCompromiseCA key was compromised
affiliationChangedSubject’s affiliation changed
supersededReplaced by new certificate
cessationCertificate no longer needed
holdTemporary hold

Example:

Terminal window
qpki credential revoke alice-20250115-abc123 --reason keyCompromise

Export credential certificates.

Terminal window
qpki credential export <credential-id> [flags]

Flags:

FlagShortDefaultDescription
--ca-dir-d./caCA directory (for chain bundle)
--cred-dir-c./credentialsCredentials directory
--out-ostdoutOutput file
--format-fpemOutput format: pem, der
--bundle-bcertBundle type: cert, chain, all
--version-vExport specific version
--allfalseExport all versions

Bundle types:

BundleDescription
certCertificate(s) only (default)
chainCertificates + issuing CA chain
allAll certificates from all algorithm families

Examples:

Terminal window
# Export active certificates as PEM
qpki credential export alice-xxx
qpki credential export alice-xxx --format der --out alice.der
qpki credential export alice-xxx --bundle chain --out alice-chain.pem
qpki credential export alice-xxx --version v20260105_abc123
qpki credential export alice-xxx --all --out alice

Terminal window
# 1. Enroll server credential
qpki credential enroll --profile ec/tls-server \
--var cn=server.example.com \
--var dns_names=server.example.com,www.example.com
# 2. Export certificate and key for deployment
qpki credential export <id> --out /etc/ssl/server.crt
# For the private key, copy from the versioned directory:
cp ./credentials/<id>/versions/v1/keys/credential.ecdsa-p384.key /etc/ssl/server.key
# 3. Rotate when needed
qpki credential rotate <id>
qpki credential activate <id> --version <new-version>
Terminal window
# 1. Create CA
qpki ca init --profile ec/root-ca --ca-dir ./mtls-ca --var cn="mTLS CA"
qpki credential enroll --ca-dir ./mtls-ca --cred-dir ./mtls-ca/credentials \
--profile ec/tls-server \
--var cn=server.local --var dns_names=server.local
qpki credential enroll --ca-dir ./mtls-ca --cred-dir ./mtls-ca/credentials \
--profile ec/tls-client \
--var cn=client-a@example.com --id client-a
qpki credential enroll --ca-dir ./mtls-ca --cred-dir ./mtls-ca/credentials \
--profile ec/tls-client \
--var cn=client-b@example.com --id client-b
# ssl_certificate server.crt;
# ssl_client_certificate mtls-ca/ca.crt;
Terminal window
# 1. Enroll code signing credential
qpki credential enroll --profile ec/code-signing \
--var cn="My Company Code Signing" \
--var organization="My Company"
# 2. Sign using qpki cms (recommended)
qpki cms sign --data binary.exe --credential <id> --out binary.exe.sig
# Or using openssl with exported files:
qpki credential export <id> --out signer.pem
openssl cms -sign -in binary.exe \
-signer signer.pem \
-inkey ./credentials/<id>/versions/v1/keys/credential.ecdsa-p384.key \
-out binary.exe.sig -binary
# 3. Verify
openssl cms -verify -in binary.exe.sig \
-content binary.exe -CAfile ./ca/ca.crt
Terminal window
# 1. Check credential expiration
qpki credential info <credential-id>
qpki credential rotate <credential-id>
qpki credential versions <credential-id>
qpki credential activate <credential-id> --version v20260105_abc123
qpki credential revoke <old-credential-id> --reason superseded

For detailed migration scenarios, see Crypto-Agility.

Terminal window
# Start with classical certificates
qpki credential enroll --profile ec/client --var cn=alice@example.com
qpki credential rotate alice-xxx --add-profile ml/client
qpki credential activate alice-xxx --version <new-version>
qpki credential rotate alice-xxx --remove-profile ec/client
qpki credential activate alice-xxx --version <new-version>

The --credential flag allows loading certificate and key directly from the credential store:

Terminal window
# CMS signing
qpki cms sign --data doc.pdf --credential signer --out doc.p7s
qpki tsa sign --data doc.pdf --credential tsa --out doc.tsr
qpki ocsp sign --serial 0A1B2C --status good --ca ca.crt \
--credential ocsp-responder --out response.ocsp

Credentials are particularly well-suited for long-running servers because the rotate → activate workflow enables certificate renewal without service interruption:

Terminal window
# Start server with credential
qpki tsa serve --port 8318 --credential tsa-server
# or
qpki ocsp serve --port 8080 --ca-dir ./ca --credential ocsp-responder
qpki credential rotate tsa-server
qpki credential versions tsa-server
qpki credential activate tsa-server --version v2

When using --credential with cms decrypt, QPKI automatically searches all versions of the credential to find a matching decryption key. This is essential after key rotation: data encrypted with an old key can still be decrypted.

Terminal window
# Encrypt with current active key
qpki cms encrypt --recipient ./credentials/bob/certificates.pem \
--in secret.txt --out secret.p7m
qpki credential rotate bob
qpki credential activate bob --version v2
qpki cms decrypt --credential bob --in secret.p7m --out secret.txt