Craton HSM

Audit Scope

Audit Scope

Craton HSM records every security-relevant operation in a tamper-evident append-only audit log. The log is the primary tool for answering "what happened, when, and by whom" during incident response and for satisfying the audit requirements of FIPS 140-3, PCI DSS, and SOC 2. This page describes what is logged, how tamper evidence works, how to rotate and export the log, and how to verify the chain.

Logged Operations

Every entry is written synchronously before the PKCS#11 call returns. The log is not fire-and-forget; a write failure aborts the operation with CKR_GENERAL_ERROR.

CategoryOperations
LifecycleC_Initialize, C_Finalize, C_InitToken, C_InitPIN, C_SetPIN
SessionC_Login, C_Logout
Key materialC_GenerateKey, C_GenerateKeyPair, C_WrapKey, C_UnwrapKey, C_DeriveKey, C_CreateObject, C_DestroyObject
Cryptographic operationsC_Sign, C_Verify, C_Encrypt, C_Decrypt, C_Digest (when over non-public data)
RandomC_GenerateRandom
Self-testPOST success and POST failure transitions
IntegritySoftware integrity test results
Fork detectionA forked child detected and rejected

Object reads (C_GetAttributeValue, C_FindObjects) are not logged by default to keep the log volume tractable; enable verbose object-access logging only in deployments where every read matters.

Entry Structure

Each entry is a JSON object with the following fields:

FieldTypeDescription
timestampinteger (ns since Unix epoch)Monotonic where the OS supports it; falls back to wall clock
session_handleintegerPKCS#11 session handle; 0 for module-level events
operationobjectDiscriminated union; one key per operation type
mechanisminteger (when applicable)CK_MECHANISM_TYPE value
fips_approvedboolean (crypto ops only)Per IG 2.4.C, true when the operation used a FIPS-approved algorithm
resultenum (Success / Failure)Plus a generic error code on failure; internal state is never leaked
previous_hash32-byte hex stringSHA-256 over the serialised previous entry

Example entry for a successful signature:

{
  "timestamp": 1743379200000000000,
  "session_handle": 42,
  "operation": {
    "Sign": {
      "mechanism": 7,
      "fips_approved": true
    }
  },
  "result": "Success",
  "previous_hash": "3e1f...c7a2"
}

Failure entries carry the mapped CK_RV code, not the internal HsmError:

{
  "timestamp": 1743379201000000000,
  "session_handle": 42,
  "operation": { "Login": { "user_type": 1 } },
  "result": { "Failure": "CKR_PIN_INCORRECT" },
  "previous_hash": "c7a2...9f18"
}

PINs, plaintext, ciphertext, signatures, and key bytes never appear in the log. Usernames in the enterprise build are SHA-256 hashed so that an operator can correlate without exposing PII.

Tamper Evidence

Entries are chained by hash:

Entry[0].previous_hash = [0; 32]                          // genesis
Entry[1].previous_hash = SHA-256(serialize(Entry[0]))
Entry[2].previous_hash = SHA-256(serialize(Entry[1]))
...

Any modification, reordering, or deletion breaks the chain at the point of change. The chain is verifiable offline from the raw log file with the craton-hsm-admin audit verify command:

craton-hsm-admin audit verify --log /var/log/craton-hsm/audit.log

The verifier walks the log from the genesis entry, recomputing previous_hash for each entry and comparing against the stored value. The first mismatch identifies both the tampered entry and the entry it was chained from.

Verification should run on a schedule (daily is a reasonable default) and before any entry is exported to a SIEM or archived.

Storage Layout

By default the log is written to audit.log in the state directory. The entries are newline-delimited JSON, one entry per line, with a trailing newline. The file is opened with O_APPEND so that concurrent writers never interleave partial entries; only one process holds the log open at a time (the same exclusive lock that guards the object store).

For sites that require structured storage, the enterprise build can stream entries to:

  • A gRPC sink consumed by the SIEM of choice.
  • A file in a custom directory, with per-day rotation.
  • A Kafka topic (via the enterprise cluster feature) for cross-node aggregation.

The in-process writer and every external sink share the same chain; the previous_hash field is the ground truth regardless of transport.

Rotation

The log is rotated by the operator, not the module. Suggested policy:

  • Rotate daily at a fixed UTC time, or when the file reaches 256 MiB, whichever comes first.
  • After rotation, compress the closed file with zstd --long=27. Do not recompress older archives — the chain is verifiable regardless of compression state.
  • Archive to append-only storage (object storage with object-lock, or WORM media).
  • Retain for at least one year; longer where regulation requires.

A rotation must preserve the final entry of the closed file and the first entry of the new file so that chain verification can span the boundary. The supplied logrotate snippet does this automatically:

/var/log/craton-hsm/audit.log {
    daily
    rotate 365
    missingok
    compress
    compresscmd /usr/bin/zstd
    compressext .zst
    postrotate
        /opt/craton-hsm/bin/craton-hsm-admin audit rotate \
            --log /var/log/craton-hsm/audit.log
    endscript
}

The audit rotate subcommand coordinates with the daemon so that no entry is written to the closed file after rotation begins.

Exporting to a SIEM

Forward the log to a SIEM (Splunk, Elastic, Chronicle, etc.) using the transport the SIEM supports. Recommended options:

  • Filebeat / Vector / Fluent Bit tailing audit.log. All three handle newline-delimited JSON natively and survive rotation.
  • Syslog via rsyslog or journald with imfile. Ensure the structured JSON survives the transport.
  • Direct gRPC from the enterprise daemon to a custom sink.

Before the SIEM consumes an entry it should receive the previous hash too, so that the SIEM side can run its own chain verification and distinguish loss of a log line from tampering. The enterprise daemon's SIEM sink forwards both fields; a simple tail must include the full JSON line, which already contains previous_hash.

Correlation identifiers:

  • session_handle pairs entries produced within the same session.
  • In the enterprise build, the authenticated identity (hashed) is attached as a separate field for SIEM filtering.

Incident Response Workflow

  1. Freeze rotation so the active log is not modified by the scheduler.
  2. Run craton-hsm-admin audit verify on the active log and on every rotated archive that might cover the incident window.
  3. Export the relevant window to a sealed bucket (object-lock enabled).
  4. Correlate with OS-level audit (auditd, Windows Event Log) on the host and with application logs on the client side.
  5. Reset PINs and re-issue credentials for any role whose log entries look inconsistent.
  6. Attach the verified slice of the log to the incident report; include the audit verify output proving the chain is intact across the incident window.

Further Reading