CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
105 lines
3.3 KiB
Go
105 lines
3.3 KiB
Go
package moduletrust
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// advisorySessionLockKey serialisiert Schluesselaenderungen der
|
|
// Sitzungstokens (QA-15 F-10) — eigener Schluessel, damit eine Rotation der
|
|
// Sitzungs- und der Modul-Signaturschluessel (advisoryLockKey) einander
|
|
// nicht gegenseitig blockiert.
|
|
const advisorySessionLockKey int64 = 0x4e5841524d534b // "NXARMSK"
|
|
|
|
// SessionPGKeyStore ist PGKeyStore fuer die Tabelle session_signing_keys
|
|
// (Migration 0010) — gleiche Logik wie PGKeyStore/module_signing_keys, aber
|
|
// eigene Tabelle und eigene Advisory-Lock-Kennung, weil Sitzungs- und
|
|
// Modul-Signaturschluessel getrennte Sicherheitsdomaenen sind.
|
|
type SessionPGKeyStore struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
func NewSessionPGKeyStore(pool *pgxpool.Pool) *SessionPGKeyStore {
|
|
return &SessionPGKeyStore{pool: pool}
|
|
}
|
|
|
|
const selectSessionKeys = `SELECT kid, public_key, private_key_ciphertext, created_at, active_from, retire_after
|
|
FROM session_signing_keys ORDER BY active_from, created_at`
|
|
|
|
func listSessionKeys(ctx context.Context, q querier) ([]StoredKey, error) {
|
|
rows, err := q.Query(ctx, selectSessionKeys)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []StoredKey
|
|
for rows.Next() {
|
|
var k StoredKey
|
|
var pub []byte
|
|
if err := rows.Scan(&k.KID, &pub, &k.PrivateCiphertext, &k.CreatedAt, &k.ActiveFrom, &k.RetireAfter); err != nil {
|
|
return nil, err
|
|
}
|
|
k.Public = pub
|
|
out = append(out, k)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *SessionPGKeyStore) Load(ctx context.Context) ([]StoredKey, error) {
|
|
return listSessionKeys(ctx, s.pool)
|
|
}
|
|
|
|
func (s *SessionPGKeyStore) Update(ctx context.Context, fn func(tx KeyTx) error) error {
|
|
tx, err := s.pool.Begin(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
if _, err := tx.Exec(ctx, `SELECT pg_advisory_xact_lock($1)`, advisorySessionLockKey); err != nil {
|
|
return err
|
|
}
|
|
if err := fn(&pgSessionKeyTx{tx: tx}); err != nil {
|
|
return err
|
|
}
|
|
return tx.Commit(ctx)
|
|
}
|
|
|
|
type pgSessionKeyTx struct {
|
|
tx pgx.Tx
|
|
}
|
|
|
|
func (t *pgSessionKeyTx) List(ctx context.Context) ([]StoredKey, error) {
|
|
return listSessionKeys(ctx, t.tx)
|
|
}
|
|
|
|
func (t *pgSessionKeyTx) Insert(ctx context.Context, k StoredKey) error {
|
|
_, err := t.tx.Exec(ctx, `INSERT INTO session_signing_keys (kid, public_key, private_key_ciphertext, created_at, active_from, retire_after)
|
|
VALUES ($1, $2, $3, $4, $5, $6)`, k.KID, []byte(k.Public), k.PrivateCiphertext, k.CreatedAt, k.ActiveFrom, k.RetireAfter)
|
|
return err
|
|
}
|
|
|
|
func (t *pgSessionKeyTx) SetRetireAfter(ctx context.Context, kid string, at time.Time) error {
|
|
_, err := t.tx.Exec(ctx, `UPDATE session_signing_keys SET retire_after = $2 WHERE kid = $1`, kid, at)
|
|
return err
|
|
}
|
|
|
|
func (t *pgSessionKeyTx) DeleteRetiredBefore(ctx context.Context, at time.Time) error {
|
|
_, err := t.tx.Exec(ctx, `DELETE FROM session_signing_keys WHERE retire_after IS NOT NULL AND retire_after <= $1`, at)
|
|
return err
|
|
}
|
|
|
|
func (t *pgSessionKeyTx) UpdateCiphertext(ctx context.Context, kid string, ciphertext []byte) error {
|
|
tag, err := t.tx.Exec(ctx, `UPDATE session_signing_keys SET private_key_ciphertext = $2 WHERE kid = $1`, kid, ciphertext)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return fmt.Errorf("kid %q unbekannt", kid)
|
|
}
|
|
return nil
|
|
}
|