Evidence Report Signing
Cryptographically sign evidence reports to prove they have not been tampered with after generation. Auditors can independently verify the signature using the public key.
Why Sign Evidence Reports?
Unsigned evidence is an assertion. Signed evidence is proof.
| Without signing | With signing |
|---|---|
| Auditor must trust the operator produced the report | Auditor can verify the report independently |
| Reports can be modified after generation | Any modification invalidates the signature |
| No chain of custody | Signing key binds report to a specific identity |
| May not satisfy SOX tamper-evidence requirements | SHA-256 checksums accepted by SOX auditors |
How It Works
1. Run validation checks → collect results
2. Serialize to canonical JSON (sorted keys, no whitespace)
3. Compute SHA-256 of canonical JSON → report_sha256
4. Sign the canonical JSON with ECDSA-P256 private key → signature
5. Store: report.json + report.pdf + report.sig
The .sig file is a detached signature — the JSON report and signature are stored separately. This allows auditors to hash the JSON file themselves and verify it matches.
Generating Signing Keys
Using OpenSSL
# Generate an ECDSA P-256 private key in PKCS#8 format
$ openssl ecparam -genkey -name prime256v1 -noout | \
openssl pkcs8 -topk8 -nocrypt -out signing-key.pem
# Extract the public key
$ openssl ec -in signing-key.pem -pubout -out signing-key-pub.pem
The private key must be in PKCS#8 format (begins with -----BEGIN PRIVATE KEY-----). If your key begins with -----BEGIN EC PRIVATE KEY-----, convert it:
$ openssl pkcs8 -topk8 -nocrypt -in ec-key.pem -out pkcs8-key.pem
Key Storage Best Practices
| Environment | Recommendation |
|---|---|
| Development | Local file with restricted permissions (chmod 600) |
| Docker/VM | Mount as a read-only volume |
| Kubernetes | Kubernetes Secret or external secret operator (Vault, AWS Secrets Manager) |
| CI/CD | Injected as a pipeline secret, never committed to git |
Configuring Signing
Add to your validation.yaml:
evidence:
signing:
enabled: true
private_key_path: "/etc/kafka-backup/signing-key.pem"
public_key_path: "/etc/kafka-backup/signing-key-pub.pem" # Optional
When signing is enabled, the stored JSON report uses canonical (compact) serialization to ensure the stored bytes exactly match what was signed.
Verifying Signatures
Using the CLI
$ kafka-backup validation evidence-verify \
--report evidence-report.json \
--signature evidence-report.sig \
--public-key signing-key-pub.pem
Output:
Report ID: validation-9275b4aa-2aeb-4910-a3a6-9e4aa1dc016a
Algorithm: ECDSA-P256-SHA256
Report SHA-256: 2482bbdfa113146e39a4884767002554a622ecc573f16f74e183a532de94590a
SHA-256 checksum: VALID
ECDSA signature: VALID
Evidence report integrity: VERIFIED
Manual Verification
Auditors can verify without the kafka-backup CLI:
# 1. Compute SHA-256 of the JSON report
$ sha256sum evidence-report.json
2482bbdfa113146e39a4884767002554... evidence-report.json
# 2. Compare against the SHA-256 in the .sig file
$ cat evidence-report.sig
-----BEGIN KAFKA BACKUP EVIDENCE SIGNATURE-----
Algorithm: ECDSA-P256-SHA256
Report-ID: validation-9275b4aa
Report-SHA256: 2482bbdfa113146e39a4884767002554...
Signature: MEUCIQDk...
-----END KAFKA BACKUP EVIDENCE SIGNATURE-----
# 3. If SHA-256 matches, the report has not been modified
Signature File Format
The .sig file uses a simple text format:
-----BEGIN KAFKA BACKUP EVIDENCE SIGNATURE-----
Algorithm: ECDSA-P256-SHA256
Report-ID: weekly-compliance-check-20260406-020000
Report-SHA256: e3b0c44298fc1c149afb4c8996fb924...
Signature: MEUCIQDk8nGq... (base64-encoded ECDSA signature)
-----END KAFKA BACKUP EVIDENCE SIGNATURE-----
| Field | Description |
|---|---|
Algorithm | Always ECDSA-P256-SHA256 |
Report-ID | Unique validation run identifier |
Report-SHA256 | SHA-256 hex digest of the canonical JSON report |
Signature | Base64-encoded ECDSA signature over the canonical JSON bytes |
Integrating with Your PKI
cert-manager (Kubernetes)
If your cluster uses cert-manager, you can use a Certificate resource to provision signing keys:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: kafka-backup-signing
spec:
secretName: kafka-backup-signing-key
issuerRef:
name: internal-ca
kind: ClusterIssuer
privateKey:
algorithm: ECDSA
size: 256
usages:
- digital signature
Mount the secret into the kafka-backup pod and reference the key path in your validation config.
HashiCorp Vault
# Store the signing key in Vault
$ vault kv put secret/kafka-backup/signing private_key=@signing-key.pem
# Retrieve at runtime
$ vault kv get -field=private_key secret/kafka-backup/signing > /tmp/signing-key.pem
Troubleshooting
"Failed to load signing key"
The private key is not in PKCS#8 format. Convert it:
$ openssl pkcs8 -topk8 -nocrypt -in your-key.pem -out pkcs8-key.pem
"Invalid ECDSA signature" during verification
- Ensure the JSON report file has not been reformatted (the bytes must match exactly what was signed)
- Verify you are using the correct public key that corresponds to the private key used for signing
SHA-256 mismatch
The report file was modified after signing. This could mean:
- The file was pretty-printed or reformatted
- The file was opened and saved by an editor that changed whitespace
- The file was genuinely tampered with
Next Steps
- Backup Validation Guide — complete validation walkthrough
- Validation Config Reference — all configuration options
- SOX Compliance Example — end-to-end signed evidence for SOX