crypto

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 23, 2026 License: GPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package crypto provides encryption utilities for sensitive data.

Index

Constants

This section is empty.

Variables

View Source
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

func ComputeAuditChainHash(prevHash, auditLogID, payload string, ts time.Time) string

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

func HashToken(token string) string

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

func HashTokenBytes(token string) []byte

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

func HashTokenPeppered(token, pepper string) string

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

func IsNoOp(e Encryptor) bool

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

func VerifyTokenHash(token, storedHash string) bool

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

func VerifyTokenHashAny(token, storedHash, pepper string) bool

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

func NewCipher(key []byte) (*Cipher, error)

NewCipher creates a new Cipher with the given key. The key must be exactly 32 bytes for AES-256.

func NewCipherFromBase64

func NewCipherFromBase64(b64Key string) (*Cipher, error)

NewCipherFromBase64 creates a new Cipher from a base64-encoded key.

func NewCipherFromHex

func NewCipherFromHex(hexKey string) (*Cipher, error)

NewCipherFromHex creates a new Cipher from a hex-encoded key.

func (*Cipher) Decrypt

func (c *Cipher) Decrypt(encoded string) ([]byte, error)

Decrypt decrypts base64-encoded ciphertext and returns plaintext.

func (*Cipher) DecryptString

func (c *Cipher) DecryptString(encoded string) (string, error)

DecryptString decrypts base64-encoded ciphertext and returns a string.

func (*Cipher) Encrypt

func (c *Cipher) Encrypt(plaintext []byte) (string, error)

Encrypt encrypts plaintext and returns base64-encoded ciphertext. The ciphertext includes the nonce prepended to it.

func (*Cipher) EncryptString

func (c *Cipher) EncryptString(plaintext string) (string, error)

EncryptString encrypts a string and returns base64-encoded ciphertext.

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL