Documentation
¶
Overview ¶
Package crypto provides encryption utilities for sensitive data.
Index ¶
- Variables
- func ComputeAuditChainHash(prevHash, auditLogID, payload string, ts time.Time) string
- func HashToken(token string) string
- func HashTokenBytes(token string) []byte
- func HashTokenPeppered(token, pepper string) string
- func IsNoOp(e Encryptor) bool
- func VerifyTokenHash(token, storedHash string) bool
- func VerifyTokenHashAny(token, storedHash, pepper string) bool
- type Cipher
- type Encryptor
- type NoOpEncryptor
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidKey is returned when the encryption key is invalid. ErrInvalidKey = errors.New("crypto: invalid encryption key") // ErrInvalidCiphertext is returned when the ciphertext is malformed. ErrInvalidCiphertext = errors.New("crypto: invalid ciphertext") // ErrDecryptionFailed is returned when decryption fails. ErrDecryptionFailed = errors.New("crypto: decryption failed") )
Functions ¶
func ComputeAuditChainHash ¶ added in v0.2.0
ComputeAuditChainHash returns the SHA-256 hex digest that pins a single audit_log row into the tamper-evident chain for its tenant.
The hash covers four pieces of data in a fixed order:
prevHash | auditLogID | payload | timestamp (RFC3339Nano, UTC)
Why each part matters:
- prevHash links this entry to the previous one; any mutation of an earlier row invalidates every subsequent hash.
- auditLogID ties the chain row to a specific audit_logs PK, so reinserting an audit log with a new id cannot slot in silently.
- payload should be a deterministic serialisation of the audit_log row contents (actor, action, resource, changes, etc.). Callers normally pass a canonical JSON of the log or a concatenation of the scalar columns.
- timestamp binds the hash to real time so reordering chain rows is detectable.
An empty prevHash is valid — it marks the first entry per tenant. The returned string is lower-case hex, 64 characters. The function is pure (no I/O, no time.Now) so it is trivially unit-testable.
func HashToken ¶
HashToken returns the SHA256 hash of a token as a hex string. This is used for secure token storage (bootstrap tokens, API keys, etc.). The original token should never be stored; only its hash should be persisted.
SECURITY NOTE on CodeQL go/weak-cryptographic-algorithm:
CodeQL flags this function because a parameter literally named "token" feeding into SHA-256 matches its password-hashing-with-weak-algorithm rule. That rule exists because user-chosen PASSWORDS are low-entropy and need a computationally expensive KDF (bcrypt, argon2, pbkdf2) to slow offline brute force. This function is NOT used for passwords — OpenCTEM passwords go through bcrypt via pkg/password. HashToken only ever sees:
- API keys: 32 bytes from crypto/rand (256 bits of entropy)
- Agent bootstrap tokens: similar high-entropy random
- Session tokens: 32 bytes crypto/rand
At 256 bits of entropy, an attacker with a leaked hash would need ~2^255 SHA-256 evaluations on average to brute-force. No KDF slows that further than the input space already does — a slow KDF on a cryptographically-random 256-bit input is cargo-cult.
F-9: This unkeyed hash is retained only for backward compatibility with pre-existing rows in the DB. NEW writes should use HashTokenPeppered (HMAC-SHA256 with a server-side pepper) so that a DB-only leak cannot be brute-forced via rainbow table — see agent/service.go hashAgentAPIKey for the canonical caller.
Action for reviewers: dismiss the CodeQL alert as "Won't fix — false positive; input is cryptographically random, not a password".
func HashTokenBytes ¶
HashTokenBytes returns the SHA256 hash of a token as bytes. See HashToken's doc-comment for the CodeQL false-positive rationale; same applies here.
func HashTokenPeppered ¶ added in v0.2.0
HashTokenPeppered returns HMAC-SHA256(pepper, token) as a lowercase hex string. When `pepper` is empty, the function falls back to the plain SHA-256 (HashToken) behaviour so callers that do not yet have a pepper configured still produce a stable, deterministic hash.
F-9: use this for any new API-key persistence. The pepper should be the platform-wide APP_ENCRYPTION_KEY (or a dedicated secret derived from it). Because the output is deterministic, existing unique-index lookups keep working. Because the pepper lives in application config (not the DB), a database-only leak no longer yields material that can be brute-forced with generic rainbow tables / hashcat against leaked `key_hash` columns.
func IsNoOp ¶ added in v0.2.0
IsNoOp returns true when the given Encryptor is a NoOpEncryptor. Helper for callers that want to refuse to persist sensitive data when encryption is effectively disabled. Returns false on real Cipher instances (or any other implementation).
func VerifyTokenHash ¶
VerifyTokenHash checks if a plaintext token matches a stored hash. This uses constant-time comparison to prevent timing attacks.
func VerifyTokenHashAny ¶ added in v0.2.0
VerifyTokenHashAny checks a raw token against a stored hash using either the peppered (HMAC-SHA256) variant OR the legacy plain SHA-256 variant. Callers that cannot yet re-hash every row on write use this to keep old rows working while new writes produce peppered hashes.
Both comparisons use constant-time compare to avoid timing leaks. If `pepper` is empty the peppered branch is skipped.
Types ¶
type Cipher ¶
type Cipher struct {
// contains filtered or unexported fields
}
Cipher provides AES-256-GCM encryption and decryption.
func NewCipher ¶
NewCipher creates a new Cipher with the given key. The key must be exactly 32 bytes for AES-256.
func NewCipherFromBase64 ¶
NewCipherFromBase64 creates a new Cipher from a base64-encoded key.
func NewCipherFromHex ¶
NewCipherFromHex creates a new Cipher from a hex-encoded key.
func (*Cipher) DecryptString ¶
DecryptString decrypts base64-encoded ciphertext and returns a string.
type Encryptor ¶
type Encryptor interface {
// EncryptString encrypts plaintext and returns base64-encoded ciphertext.
EncryptString(plaintext string) (string, error)
// DecryptString decrypts base64-encoded ciphertext and returns plaintext.
DecryptString(encoded string) (string, error)
}
Encryptor provides encryption and decryption capabilities.
func NewNoOpEncryptor ¶
func NewNoOpEncryptor() Encryptor
NewNoOpEncryptor creates a no-op encryptor for development/testing.
type NoOpEncryptor ¶
type NoOpEncryptor struct{}
NoOpEncryptor is an Encryptor that does not encrypt — used ONLY in development sandboxes where APP_ENCRYPTION_KEY is intentionally unset. Callers that handle long-lived secret material (integration credentials, OAuth tokens) should type-check for *NoOpEncryptor and reject — having credential rows persisted in plaintext is a data breach waiting to happen if the DB is later ever leaked.
func (*NoOpEncryptor) DecryptString ¶
func (n *NoOpEncryptor) DecryptString(encoded string) (string, error)
DecryptString returns the encoded string as-is (no decryption).
func (*NoOpEncryptor) EncryptString ¶
func (n *NoOpEncryptor) EncryptString(plaintext string) (string, error)
EncryptString returns the plaintext as-is (no encryption).