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.
| Category | Operations |
|---|---|
| Lifecycle | C_Initialize, C_Finalize, C_InitToken, C_InitPIN, C_SetPIN |
| Session | C_Login, C_Logout |
| Key material | C_GenerateKey, C_GenerateKeyPair, C_WrapKey, C_UnwrapKey, C_DeriveKey, C_CreateObject, C_DestroyObject |
| Cryptographic operations | C_Sign, C_Verify, C_Encrypt, C_Decrypt, C_Digest (when over non-public data) |
| Random | C_GenerateRandom |
| Self-test | POST success and POST failure transitions |
| Integrity | Software integrity test results |
| Fork detection | A 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:
| Field | Type | Description |
|---|---|---|
timestamp | integer (ns since Unix epoch) | Monotonic where the OS supports it; falls back to wall clock |
session_handle | integer | PKCS#11 session handle; 0 for module-level events |
operation | object | Discriminated union; one key per operation type |
mechanism | integer (when applicable) | CK_MECHANISM_TYPE value |
fips_approved | boolean (crypto ops only) | Per IG 2.4.C, true when the operation used a FIPS-approved algorithm |
result | enum (Success / Failure) | Plus a generic error code on failure; internal state is never leaked |
previous_hash | 32-byte hex string | SHA-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
rsyslogorjournaldwithimfile. 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_handlepairs 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
- Freeze rotation so the active log is not modified by the scheduler.
- Run
craton-hsm-admin audit verifyon the active log and on every rotated archive that might cover the incident window. - Export the relevant window to a sealed bucket (object-lock enabled).
- Correlate with OS-level audit (
auditd, Windows Event Log) on the host and with application logs on the client side. - Reset PINs and re-issue credentials for any role whose log entries look inconsistent.
- Attach the verified slice of the log to the incident report; include
the
audit verifyoutput proving the chain is intact across the incident window.
Further Reading
- policy — formal security policy
- hardening — file permissions and SIEM exposure
- ../fips/fips-mode — the
fips_approvedflag in each entry - ../operations/runbook — day-to-day operations