Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
keeper — Simple gardien sécurisé de secrets | Kitploit
Outils/GitHubGitHub/agberohq/keeper
Authentification et AutorisationOutils de Chiffrement/DéchiffrementCryptographieDétection de Secrets
GitHubagberohq/keeper

keeper

Simple gardien sécurisé de secrets

Voir le dépôt
1204il y a 4 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

keeper

Keeper est un coffre-fort cryptographique pour Go. Il chiffre des données brutes arbitraires au repos en utilisant la dérivation de clé Argon2id et le chiffrement authentifié XChaCha20-Poly1305 (par défaut), et les stocke dans une base de données bbolt intégrée.

Il se décline en trois composants utilisables indépendamment :

  • Une bibliothèque Go — intégrez un coffre-fort de secrets durci directement dans votre processus, avec quatre niveaux de sécurité, un isolement des DEK par bucket, et une chaîne d'audit inviolable.
  • Un gestionnaire HTTP (x/keephandler) — montez des endpoints keeper sur n'importe quel multiplexeur net/http en un seul appel, avec des hooks, des gardes et des encodeurs de réponse enfichables pour le contrôle d'accès et la journalisation d'audit.
  • Une CLI (cmd/keeper) — une interface terminal avec une session REPL persistante, une saisie de secrets sans écho et une exposition zéro à l'historique du shell.

Keeper a été conçu comme la couche de gestion de secrets fondamentale pour le répartiteur de charge Agbero mais n'a aucune dépendance envers Agbero et fonctionne dans tout projet Go.


Contenu

  • Modèle de sécurité
  • Conception cryptographique
  • Hiérarchie des clés
  • Schéma de stockage
  • Chaîne d'audit
  • Intégration Jack
  • x/keepcmd — opérations CLI réutilisables
  • x/keephandler — gestionnaire HTTP
  • Référence de l'API
  • Catalogue d'erreurs
  • Décisions de sécurité
  • Dépendances

Modèle de sécurité

Keeper partitionne les secrets en buckets. Chaque bucket possède une BucketSecurityPolicy immuable qui régit la protection de sa clé de chiffrement de données (DEK). Quatre niveaux sont disponibles.

Schémas vs. Niveaux de sécurité

Un schéma est un préfixe URI qui regroupe des buckets connexes (vault://, certs://, space://, ou tout nom que vous enregistrez). Un niveau de sécurité est une propriété de la politique du bucket définie à la création et immuable par la suite.

Vous pouvez librement mélanger les niveaux de sécurité au sein d'un même schéma. Par exemple, vault://system pourrait être LevelPasswordOnly (déverrouillé automatiquement au démarrage), tandis que vault://admin est LevelAdminWrapped (nécessite des identifiants explicites).

LevelPasswordOnly

La DEK du bucket est dérivée de la clé maîtresse à l'aide de HKDF-SHA256 avec une chaîne d'information séparée par domaine pour chaque bucket (keeper-bucket-dek-v1:scheme:namespace). Tous les buckets LevelPasswordOnly sont déverrouillés automatiquement lorsque UnlockDatabase est appelée avec la bonne phrase de passe maîtresse. Aucun identifiant par bucket n'est nécessaire à l'exécution. Ce niveau convient aux secrets dont le processus a besoin au démarrage sans intervention humaine.

LevelAdminWrapped

Le bucket possède une DEK aléatoire de 32 octets unique à ce bucket. La DEK n'est jamais stockée en clair. Pour chaque administrateur autorisé, une clé de chiffrement de clé (KEK) est dérivée de HKDF(masterKey‖adminCred, dekSalt) et utilisée pour envelopper la DEK via XChaCha20-Poly1305. Le bucket est inaccessible jusqu'à ce qu'un administrateur appelle UnlockBucket avec ses identifiants. La phrase de passe maîtresse seule ne peut pas déchiffrer le bucket. La révocation d'un administrateur n'affecte aucune copie enveloppée d'un autre administrateur.

LevelHSM

La DEK du bucket est générée lors de CreateBucket et immédiatement enveloppée par un HSMProvider fourni par l'appelant. Le fournisseur effectue les opérations d'enveloppement et de déballage — keeper ne manipule jamais la DEK brute après l'avoir transmise au fournisseur. UnlockDatabase appelle automatiquement le fournisseur pour déballer et amorcer l' Enveloppe pour tous les buckets HSM enregistrés. La rotation de la clé maîtresse ne rechiffre pas ces buckets ; la DEK est contrôlée par le fournisseur.

Une implémentation intégrée SoftHSM basée sur une clé d'enveloppement protégée par memguard est disponible dans pkg/hsm pour les tests et les environnements CI. Ne l'utilisez pas en production.

LevelRemote

Identique à LevelHSM en termes de comportement de gestion des clés, mais le HSMProvider est implémenté par pkg/remote.Provider — un adaptateur HTTPS configurable qui délègue l'enveloppement et le déballage à n'importe quel service KMS distant via TLS. Des configurations pré-construites pour HashiCorp Vault Transit, AWS KMS et GCP Cloud KMS sont fournies dans pkg/remote. Pour une utilisation en production, configurez TLSClientCert et TLSClientKey pour activer l'authentification mutuelle TLS.


Conception cryptographique

Dérivation de la clé maîtresse```

salt ← random 32 bytes, generated once, stored as a versioned SaltStore (unencrypted) masterKey ← Argon2id(passphrase, salt, t=3, m=64 MiB, p=4) → 32 bytes

root@kitploit:~
Un hash de vérification est stocké lors de la première dérivation :```
verifyHash ← Argon2id(masterKey, "verification", t=1, m=64 MiB, p=4) → 32 bytes

Les appels ultérieurs à DeriveMaster recalculent ce hachage et le comparent avec crypto/subtle.ConstantTimeCompare. Une non-correspondance renvoie ErrInvalidPassphrase.

Le sel KDF est stocké non chiffré par conception. Il doit être lisible avant UnlockDatabase pour dériver la clé maîtresse — le chiffrer avec une clé dérivée de la clé maîtresse serait circulaire. Un sel KDF n'est pas un secret ; son but est l'unicité, pas la confidentialité.

Chiffrement des secrets

Chaque valeur en clair est chiffrée avec XChaCha20-Poly1305 en utilisant la DEK du compartiment :``` nonce ← random 24 bytes ciphertext ← XChaCha20-Poly1305.Seal(nonce, DEK, plaintext)

root@kitploit:~
L'enregistrement stocké est une structure `Secret` encodée en msgpack contenant le texte chiffré, les métadonnées chiffrées et la version du schéma. L'authentification est implicite : un texte chiffré déchiffré avec la mauvaise clé produit une échec d'authentification AEAD avant qu'aucun texte en clair ne soit renvoyé.

### Dérivation de KEK — LevelAdminWrapped```
salt ← random 32 bytes, generated at bucket creation, stored in policy
ikm ← masterKey ‖ adminCredential
KEK ← HKDF-SHA256(ikm, salt, info="keeper-kek-v1") → 32 bytes
wrappedDEK ← XChaCha20-Poly1305.Seal(nonce, KEK, DEK)

La KEK est dérivée à l'aide de HKDF plutôt que d'un second passage d'Argon2. La clé maîtresse a déjà été produite par une KDF à coût élevé ; une seconde invocation d'Argon2 ajouterait des centaines de millisecondes de latence à chaque appel UnlockBucket sans bénéfice de sécurité. HKDF-SHA256 opère en environ une microseconde.

Défense en profondeur : Un attaquant qui ne compromet que la base de données obtient la DEK enveloppée et le sel HKDF mais ne peut pas dériver la KEK sans la clé maîtresse. Un attaquant qui ne compromet que la clé maîtresse ne peut déchiffrer aucune DEK LevelAdminWrapped sans également connaître l'identifiant administrateur.

Chiffrement des métadonnées — secrets

Les métadonnées secrètes (heure de création, heure de mise à jour, nombre d'accès, version) sont chiffrées séparément du texte chiffré :``` metaKey ← HKDF-SHA256(bucketDEK, nil, info="keeper-metadata-v1") → 32 bytes encryptedMeta ← XChaCha20-Poly1305.Seal(nonce, metaKey, msgpack(metadata))

root@kitploit:~
Pour `LevelAdminWrapped`, `LevelHSM` et `LevelRemote` buckets, cela signifie que les métadonnées sont inaccessibles sans le credential du bucket, empêchant un attaquant ayant un accès en lecture au fichier de base de données d'apprendre les patterns d'accès ou les horodatages.

**Note sur le canal auxiliaire temporel :** XChaCha20-Poly1305 traite l'intégralité du ciphertext avant de renvoyer une erreur d'authentification. Le chemin de décryptage de secours (nouvelle DEK dérivée → ancienne master-key-as-DEK) prend le même temps horloge mural quelle que soit la clé qui réussit. Aucun canal auxiliaire temporel ne fuit l'état de migration d'un enregistrement.

### Chiffrement des métadonnées — politiques, WAL et audit

Toutes les métadonnées structurelles sont également chiffrées au repos. Deux clés sont dérivées de la clé maîtresse au moment de `UnlockDatabase` :```
policyEncKey ← HKDF-SHA256(masterKey, nil, info="keeper-policy-enc-v1") → 32 bytes
auditEncKey  ← HKDF-SHA256(masterKey, nil, info="keeper-audit-enc-v1")  → 32 bytes

policyEncKey chiffre : les valeurs de BucketSecurityPolicy et le WAL de rotation.

auditEncKey chiffre : les champs Scheme, Namespace et Details de chaque événement d'audit.

Les deux clés sont effacées de la mémoire lors de Lock(). Le chiffrement utilisé pour le chiffrement des métadonnées est la même interface configurable crypt.Cipher que celle utilisée pour les secrets — le choix de chiffrement de l'utilisateur (AES-256-GCM pour FIPS, XChaCha20-Poly1305 par défaut) s'applique automatiquement.

Format de fil pour tous les blobs de métadonnées chiffrés :``` nonce (cipher.NonceSize() bytes) || AEAD-ciphertext

root@kitploit:~
### Hachage des clés de compartiment de politique

Sur disque, les clés de politique sont des hachages opaques plutôt que des chaînes en texte clair `scheme:namespace`,
empêchant l'énumération hors ligne des noms de compartiments :```
base ← hex(SHA-256("scheme:namespace"))[:32]   // 32 hex chars = 128-bit key space
_policies/<base>          → encrypted BucketSecurityPolicy
_policies/<base>__hash__  → SHA-256(encrypted policy bytes)
_policies/<base>__hmac__  → HMAC-SHA256(policyKey, encrypted policy bytes)

Le schemeRegistry en mémoire continue d'utiliser "scheme:namespace" comme clé — seule la représentation sur disque change.

Authentification de la politique

Chaque enregistrement de politique porte deux balises d'intégrité écrites atomiquement dans une transaction bbolt :``` hash ← SHA-256(encryptedPolicyBytes) — unauthenticated, pre-unlock integrity policyKey ← HKDF-SHA256(masterKey, nil, info="keeper-policy-hmac-v1") → 32 bytes hmac ← HMAC-SHA256(policyKey, encryptedPolicyBytes) — authenticated, post-unlock integrity

root@kitploit:~
Avant `UnlockDatabase`, seul le hachage SHA-256 est disponible. Après le déverrouillage,
`loadPolicy` vérifie la balise HMAC. `UnlockDatabase` appelle `upgradePolicyHMACs`
pour remplir rétroactivement les balises HMAC sur les politiques créées avant l'existence de cette fonctionnalité.

### Signature HMAC d'audit```
auditKey ← HKDF-SHA256(masterKey, nil, info="keeper-audit-hmac-v1") → 32 bytes
HMAC ← HMAC-SHA256(auditKey, event fields including Seq)

La clé de signature est activée lors de UnlockDatabase et effacée lors de Lock. Lorsque la clé maître est permutée, Rotate ajoute un événement de point de contrôle de rotation de clé à chaque chaîne d'audit active, signé avec l'ancienne clé d'audit comme dernier événement de l'ancienne époque. L'historique n'est jamais réécrit ; le point de contrôle est le pont de confiance entre les époques.


Hiérarchie des clés```

passphrase │ └─ Argon2id(salt) ──→ masterKey (32 bytes, memguard Enclave) │ ├─ HKDF("keeper-audit-hmac-v1") ──→ auditKey (HMAC signing) ├─ HKDF("keeper-audit-enc-v1") ──→ auditEncKey (audit field encryption) ├─ HKDF("keeper-policy-hmac-v1") ──→ policyKey (policy HMAC) ├─ HKDF("keeper-policy-enc-v1") ──→ policyEncKey (policy/WAL encryption) │ ├─ [LevelPasswordOnly] │ └─ HKDF("keeper-bucket-dek-v1:scheme:ns") ──→ DEK │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ ├─ [LevelAdminWrapped] │ ├─ random 32 bytes ──→ DEK │ │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ │ │ └─ HKDF("keeper-kek-v1", masterKey‖adminCred, dekSalt) │ └─ KEK │ └─ XChaCha20-Poly1305(KEK, DEK) ──→ wrappedDEK │ └─ [LevelHSM / LevelRemote] ├─ random 32 bytes ──→ DEK │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ └─ HSMProvider.WrapDEK(DEK) ──→ wrappedDEK (stored; provider controls the wrapping key)

root@kitploit:~
All intermediate keys are zeroed immediately after use. The master key is
never written to disk in any form.

---

## Storage schema

The underlying database is bbolt. All buckets and their contents:

| bbolt bucket | Key | Value |
|---|---|---|
| `__meta__` | `salt` | msgpack — SaltStore (non chiffré ; dépendance circulaire si chiffré) |
| `__meta__` | `verify` | octets bruts — hachage de vérification Argon2id |
| `__meta__` | `rotation_wal` | `nonce‖AEAD(msgpack(RotationWAL))` |
| `__meta__` | `bucket_dek_done` | `"1"` — marqueur d'achèvement de migration DEK |
| `__policies__` | `hex(SHA-256(scheme:ns))[:32]` | `nonce‖AEAD(msgpack(BucketSecurityPolicy))` |
| `__policies__` | `<base>__hash__` | hex SHA-256 des octets de politique chiffrés |
| `__policies__` | `<base>__hmac__` | hex HMAC-SHA256(policyKey, encrypted policy bytes) |
| `__audit__/scheme/namespace` | event UUID | JSON — audit Event |
| `__audit__/scheme/namespace` | `__chain_index__` | JSON — chainIndex |
| `scheme/namespace` | key string | msgpack — Secret struct |

### Secret struct (msgpack)```go
type Secret struct {
    Ciphertext    []byte `msgpack:"ct"`
    EncryptedMeta []byte `msgpack:"em,omitempty"`
    SchemaVersion int    `msgpack:"sv"`  // always 1
}

Champs des événements d'audit

La structure Event utilise des champs de routage en texte clair séparés (Scheme, Namespace) ainsi que des champs de charge utile chiffrés (EncScheme, EncNamespace, EncDetails). Les sommes de contrôle sont calculées sur les champs de routage en texte clair et les octets chiffrés d'EncDetails, de sorte que l'intégrité de la chaîne peut être vérifiée à trois niveaux sans aucune clé :

NiveauPossèdePeut vérifier
PublicRienChaîne de somme de contrôle SHA-256 (détecte les altérations et insertions)
Détenteur de la clé d'audit

Exemple : Un auditeur de conformité ne reçoit que auditEncKey. Il peut vérifier la chaîne HMAC complète à travers les rotations de clés et lire tous les détails des événements, mais ne peut déchiffrer aucune valeur secrète. Un observateur public disposant uniquement du fichier de base de données peut toujours détecter si un événement a été modifié ou inséré après coup.

Magasin de sel versionné

Le sel KDF est stocké en tant que SaltStore encodé en msgpack sous la clé de métadonnées salt. Chaque rotation de sel ajoute une nouvelle SaltEntry et avance CurrentVersion. Les anciennes entrées sont conservées comme piste d'audit. Le SaltStore est stocké non chiffré — voir Décisions de sécurité.

WAL de rotation à l'épreuve des crashs

Rotate écrit un WAL avant de toucher à un enregistrement. Le WAL contient WrappedOldKey : la clé maître pré-rotation chiffrée avec la nouvelle clé maître. Après un crash, l'ancienne phrase de passe a disparu ; WrappedOldKey est la seule manière correcte de transporter l'ancienne clé à travers la frontière. Lors de UnlockDatabase, lorsqu'un WAL est présent, la nouvelle clé maître déchiffre WrappedOldKey et la rotation reprend à partir du curseur WAL. Le WAL lui-même est chiffré avec policyEncKey.


Chaîne d'audit

Chaque opération significative ajoute un événement inviolable à la chaîne d'audit du bucket. L'intégrité de la chaîne repose sur deux mécanismes.

Somme de contrôle. SHA-256 sur prevChecksum, ID, BucketID, Scheme, Namespace, EncDetails, EventType et Timestamp. L'utilisation de Scheme/Namespace en texte clair (toujours conservés aux côtés des formes chiffrées) garantit que la somme de contrôle est stable à travers les chemins de chargement. EncDetails fournit une intégrité sur la charge utile chiffrée.

HMAC. HMAC-SHA256 sur tous les champs, y compris Seq. Un attaquant qui peut écrire dans la base de données mais ne connaît pas la clé d'audit ne peut pas produire un HMAC valide. VerifyIntegrity vérifie les deux couches pour chaque événement.

Limite d'époque de rotation de clé. Lors de Rotate, un événement de point de contrôle est ajouté à chaque chaîne active portant les empreintes des clés d'audit sortante et entrante. Le point de contrôle est signé avec la clé sortante. Les auditeurs détenant une clé d'époque peuvent récupérer les clés d'époque suivantes à partir du champ wrapped_new_key et vérifier la continuité HMAC sur toute la chaîne.

Élagage automatique. Lorsque AuditPruneInterval est défini dans Config, un jack.Scheduler s'exécute périodiquement et appelle PruneEvents sur chaque bucket enregistré. Les buckets LevelHSM et LevelRemote ne sont jamais élagués, indépendamment de ce paramètre.


Intégration Jack

Jack est une bibliothèque optionnelle de supervision de processus. Lorsqu'un JackConfig est fourni via WithJack, keeper active automatiquement les composants d'arrière-plan :

  • Boucle de verrouillage automatique : Vérifie périodiquement l'horodatage de la dernière activité et supprime les DEKs du bucket LevelAdminWrapped après AutoLockInterval. Les buckets LevelPasswordOnly restent déverrouillés afin que les tâches d'arrière-plan se poursuivent sans interruption. Le modèle de verrouillage en écriture unique à l'intérieur de la tâche de la boucle élimine la condition de course RUnlock→Lock présente dans les conceptions antérieures.
  • Moissonneur DEK par bucket : Expiration basée sur TTL pour les DEKs LevelAdminWrapped.
  • Patients de surveillance de santé : Vérification de la latence de lecture bbolt et vérification aller-retour chiffrement/déchiffrement, toutes deux enregistrées auprès de jack.Doctor.
  • Planificateur d'élagage d'audit : PruneEvents périodique sur tous les buckets non HSM.
  • Pool d'événements asynchrones : Événements d'audit soumis sans bloquer l'opération principale.

Si JackConfig n'est pas fourni, keeper s'exécute sans ces tâches d'arrière-plan. Keeper n'appelle jamais pool.Shutdown — le cycle de vie du pool appartient à l'appelant.


x/keepcmd

x/keepcmd fournit des opérations keeper réutilisables découplées de tout framework CLI. Intégrez-le dans votre propre application pour obtenir une gestion de secrets typée et testable sans avoir à inclure le binaire CLI.```go import "github.com/agberohq/keeper/x/keepcmd"

cmds := &keepcmd.Commands{ Store: func() (*keeper.Keeper, error) { return security.KeeperOpen(cfg) // your own config }, Out: keepcmd.PlainOutput{}, NoClose: false, // true in REPL / session contexts }

cmds.List() // all keys: scheme://namespace/key cmds.List("vault") // all keys in scheme vault cmds.List("vault", "system") // all keys in vault://system cmds.Get("vault://system/jwt_secret") cmds.Set("vault://system/jwt_secret", "newsecret", keepcmd.SetOptions{}) cmds.Rotate(newPassphraseBytes) // caller resolved the passphrase — no prompter dependency cmds.RotateSalt(currentPassBytes) // same

root@kitploit:~
`keepcmd` n'appelle jamais `prompter` ni ne lit depuis stdin. La résolution de la phrase de passe est entièrement de la responsabilité de l'appelant — cela maintient le package en sécurité dans les contextes de serveur sans tête.

`NoClose: true` empêche `Commands` d'appeler `store.Close()` après chaque opération. Utilisez ceci dans les contextes REPL / session où un même store est partagé entre de nombreux appels.

---

## x/keephandler

`x/keephandler` monte les points de terminaison HTTP de keeper sur n'importe quel mux `net/http`. Aucune dépendance de routeur externe — il utilise le routage par méthode+patron de Go 1.22+ avec `http.ServeMux` de la stdlib.```go
import "github.com/agberohq/keeper/x/keephandler"

keephandler.Mount(mux, store,
    keephandler.WithPrefix("/api/keeper"),
    keephandler.WithGuard(func(w http.ResponseWriter, r *http.Request, route string) bool {
        if !acl.Allow(r.Header.Get("X-Principal"), route) {
            http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
            return false
        }
        return true
    }),
    keephandler.WithHooks(
        keephandler.Hook{
            Route:       keephandler.RouteGet,
            CaptureBody: false,
            After: func(r *http.Request, status int, _ []byte) {
                audit.Log(r.Context(), route, status)
            },
        },
    ),
    keephandler.WithEncoder(func(w http.ResponseWriter, route string, status int, data any) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(status)
        json.NewEncoder(w).Encode(map[string]any{
            "ok":    status < 400,
            "route": route,
            "data":  data,
        })
    }),
    keephandler.WithRoutes(func(m *http.ServeMux) {
        m.HandleFunc("POST /api/keeper/totp/{user}", myTOTPHandler)
    }),
)

Points d'accès

Contrat de hook

BeforeFunc retourne (allow bool, err error).

  • (true, nil) — laisser la requête se poursuivre.
  • (false, nil) — annuler; le hook a déjà écrit une réponse complète.
  • (false, err) — annuler; le framework écrit un 500 en utilisant err.Error(). Le hook ne doit pas avoir écrit quoi que ce soit dans w.

Hook.CaptureBody bool contrôle si AfterFunc reçoit le corps de la réponse. false (par défaut) coûte un wrapper léger statusWriter ; true met en mémoire tampon le corps complet dans un bytes.Buffer pour AfterFunc — une allocation par requête.

Les hooks sont exécutés dans l'ordre d'enregistrement. Plusieurs appels WithHooks sont additifs. Seul le premier hook enregistré pour un nom de route donné est utilisé—les enregistrements ultérieurs pour la même route sont ignorés.


Référence de l'API

Construction et déverrouillage```go

store, err := keeper.New(keeper.Config{ DBPath: "/var/lib/agbero/keeper.db", AutoLockInterval: 30 * time.Minute, EnableAudit: true, AuditPruneInterval: 24 * time.Hour, AuditPruneKeepLastN: 10_000, AuditPruneOlderThan: 90 * 24 * time.Hour, DBLatencyThreshold: 200 * time.Millisecond, Logger: logger, }, keeper.WithJack(keeper.JackConfig{ Pool: jackPool, Shutdown: jackShutdown, })) defer store.Close()

// Shorthand (wraps DeriveMaster + UnlockDatabase): if err := store.Unlock([]byte(os.Getenv("KEEPER_PASSPHRASE"))); err != nil { log.Fatal(err) // ErrInvalidPassphrase on wrong passphrase }

root@kitploit:~
`UnlockDatabase` effectue les opérations suivantes dans l'ordre :

1. Dérive et active la clé de signature HMAC d'audit
2. Dérive et active la clé HMAC de politique
3. Dérive et active `policyEncKey` et `auditEncKey`
4. Efface et recharge `schemeRegistry` (déchiffre tous les blobs de politique)
5. Reprend toute rotation WAL interrompue
6. Met à jour les tags HMAC de politique
7. Insère toutes les DEKs des compartiments `LevelPasswordOnly` dans l'Enveloppe
8. Démarre les tâches en arrière-plan (boucle de migration, verrouillage automatique, patients de santé)

### Compartiment LevelPasswordOnly — cycle de vie complet```go
err := store.CreateBucket("vault", "system", keeper.LevelPasswordOnly, "init")

store.Set("vault://system/jwt_secret", []byte("supersecret"))
val, err := store.Get("vault://system/jwt_secret")

// Namespaced convenience wrappers
store.SetNamespaced("admin", "jwt_secret", secretBytes)
val, err = store.GetNamespaced("admin", "jwt_secret")

LevelAdminWrapped bucket — cycle de vie complet```go

err := store.CreateBucket("finance", "payroll", keeper.LevelAdminWrapped, "ops-team") err = store.AddAdminToPolicy("finance", "payroll", "alice", []byte("alicepass"))

store.SetNamespacedFull("finance", "payroll", "salary_key", []byte("AES256..."))

store.LockBucket("finance", "payroll") err = store.UnlockBucket("finance", "payroll", "bob", []byte("bobpass")) // ErrAuthFailed — does not distinguish wrong password from unknown admin (CWE-204)

err = store.RevokeAdmin("finance", "payroll", "alice") err = store.RotateAdminWrappedDEK("finance", "payroll", "bob", []byte("bobpass"))

needs, err := store.NeedsAdminRekey("finance", "payroll")

root@kitploit:~
### LevelHSM / LevelRemote buckets```go
import (
    "github.com/agberohq/keeper/pkg/hsm"
    "github.com/agberohq/keeper/pkg/remote"
)

// SoftHSM — testing only
provider, _ := hsm.NewSoftHSM()
store.RegisterHSMProvider("secure", "keys", provider)
store.CreateBucket("secure", "keys", keeper.LevelHSM, "ops")

// Vault Transit
cfg := remote.VaultTransit("https://vault.corp:8200", vaultToken, "my-key")
cfg.TLSClientCert = "/etc/keeper/client.crt"
cfg.TLSClientKey  = "/etc/keeper/client.key"
provider, _ = remote.New(cfg)
store.RegisterHSMProvider("tenant", "secrets", provider)
store.CreateBucket("tenant", "secrets", keeper.LevelRemote, "ops")

Export de clé d'audit```go

// Export the audit encryption key to allow a third-party auditor to decrypt // event details without access to the master passphrase. auditKey, err := store.ExportAuditKey() defer zero.Bytes(auditKey)

events, err := auditStore.LoadChain("vault", "system", auditKey)

root@kitploit:~
### Rotation des clés```go
// Rotate passphrase — crash-safe WAL, resumes on next Unlock if interrupted
store.Rotate([]byte("new-passphrase"))

// Rotate KDF salt — re-derives master key, re-encrypts LevelPasswordOnly
store.RotateSalt([]byte("current-passphrase"))

Comparaison-échange```go

err := store.CompareAndSwapNamespacedFull("vault", "system", "counter", []byte("old"), []byte("new")) // ErrCASConflict if current value does not match old

root@kitploit:~
### Sauvegarde```go
f, _ := os.Create("keeper.db.bak")
info, err := store.Backup(f)
// info.Bytes, info.Timestamp, info.DBPath

Catalogue des erreurs

Toutes les erreurs sentinelles fonctionnent avec errors.Is et errors.As. Les traces d'appel sont capturées au moment de la création via github.com/olekukonko/errors.


Décisions de sécurité

ErrAuthFailed unifie tous les échecs de UnlockBucket (CWE-204 / CVSS 5.3). Un ID admin inconnu et un mauvais mot de passe renvoient tous deux ErrAuthFailed. Cela empêche l'énumération des ID admin par timing ou comparaison de chaînes d'erreur. RevokeAdmin conserve ErrAdminNotFound car il s'agit d'une opération administrative sur un magasin déjà déverrouillé. La comparaison en temps constant pour la présence de l'ID admin est intentionnellement omise. Un attaquant capable de mesurer des différences sub-microsecondes dans les recherches de bucket bbolt aurait besoin d'un accès local au système de fichiers — à ce stade, il peut lire directement le bucket de politique. Le modèle de menace suppose que le fichier de base de données peut être compromis ; la défense par timing contre l'énumération distante est la préoccupation principale.

Argon2id domine le timing. Argon2id prend 200 à 500 ms sur du matériel typique. Les différences de comparaison post-dérivation sont quatre ordres de grandeur ou plus inférieures et ne sont pas mesurables à distance. Aucune égalisation artificielle n'est appliquée.

DEK récupéré à l'intérieur de la limite de transaction CAS. CompareAndSwapNamespacedFull récupère le DEK du bucket à l'intérieur de la transaction d'écriture bbolt, éliminant la fenêtre où une rotation concurrente pourrait changer le DEK entre la récupération et l'utilisation.

La phrase de passe n'est jamais stockée sous forme de chaîne Go dans le gestionnaire HTTP. Les trois champs de phrase de passe (passphrase, new_passphrase) sont décodés du JSON directement en []byte via une extraction de carte brute, gardant le tableau de support de chaîne hors du tas à longue durée de vie. La copie []byte est effacée avec wipeBytes après utilisation.

Pas de drapeau --passphrase dans la CLI. Les drapeaux apparaissent dans la sortie ps et l'historique du shell. La CLI n'accepte la phrase de passe que depuis l'environnement KEEPER_PASSPHRASE ou une invite interactive sans écho.

Les valeurs secrètes du REPL ne sont jamais visibles. set <key> dans le REPL sans valeur en ligne utilise term.ReadPassword — elle n'apparaît pas dans le défilement du terminal, l'historique du shell ou ps. Une valeur en ligne (set key value) peut être fournie pour des données non sensibles lorsque c'est pratique.

SaltStore est intentionnellement non chiffré. Le sel KDF doit être lisible avant UnlockDatabase pour dériver la clé maîtresse. policyEncKey (utilisé pour tout autre chiffrement de métadonnées) est lui-même dérivé de la clé maîtresse — chiffrer le sel avec policyEncKey serait circulaire. Un sel KDF fournit l'unicité, pas la confidentialité ; il n'y a aucune valeur de sécurité à le chiffrer.

Les clés du bucket de politique sont hachées, pas en clair. Les clés de politique sur disque sont hex(SHA-256("scheme:namespace"))[:32] — 128 bits d'espace de clé — plutôt que des chaînes lisibles. Un attaquant hors ligne lisant le fichier bbolt ne peut pas énumérer les noms de bucket sans déchiffrer les blobs de politique.

Le chiffrement des métadonnées utilise la même interface de chiffrement que les secrets. Toutes les opérations policyEncKey et auditEncKey passent par s.config.NewCipher(key) — la même interface crypt.Cipher configurée pour les valeurs secrètes. Le choix de chiffrement de l'utilisateur (AES-256-GCM pour FIPS 140, XChaCha20-Poly1305 par défaut) se répercute automatiquement sur le chiffrement de la politique, du WAL et de l'audit. Aucun chemin de code ne code en dur un algorithme spécifique.

Les buckets LevelHSM et LevelRemote sont ignorés lors de la rotation de la clé maîtresse. reencryptAllWithKey et RotateSalt ignorent explicitement ces buckets. Le DEK est contrôlé par le fournisseur ; la rotation du sel maître ne l'affecte pas.

Rotation à sécurité crash avec WrappedOldKey. Rotate écrit un WAL avant de toucher à tout enregistrement. Le WAL contient WrappedOldKey : la clé maîtresse avant rotation chiffrée avec la nouvelle clé maîtresse. Après un crash, UnlockDatabase déchiffre WrappedOldKey en utilisant la nouvelle clé vérifiée et reprend la rotation à partir du curseur.


Dépendances

Télécharger l’outil
auditEncKey
Chaîne complète + déchiffrer Scheme/Namespace/Details
OpérateurPhrase de passe maîtreTout
MéthodeCheminDescription
POST{prefix}/unlockDéverrouiller le coffre avec une phrase de passe
POST{prefix}/lockVerrouiller le coffre
GET{prefix}/statusÉtat de verrouillage — sûr à interroger sans authentification
GET{prefix}/keysLister toutes les clés secrètes
GET{prefix}/keys/{key}Récupérer une valeur secrète
POST{prefix}/keysStocker un secret (JSON ou multipart)
DELETE{prefix}/keys/{key}Supprimer un secret
POST{prefix}/rotateFaire pivoter la phrase de passe maîtresse
POST{prefix}/rotate/saltFaire pivoter le sel KDF
GET{prefix}/backupDiffuser un instantané de la base de données
ErreurSignification
ErrStoreLockedOpération tentée alors que le magasin est verrouillé
ErrInvalidPassphraseMauvaise phrase de passe principale
ErrAuthFailedTout échec de UnlockBucket — ne distingue pas un mauvais mot de passe d'un ID admin inconnu (CWE-204)
ErrKeyNotFoundLa clé secrète n'existe pas
ErrBucketLockedLe bucket n'a pas été déverrouillé
ErrPolicyImmutableDeuxième politique pour un bucket existant
ErrPolicyNotFoundAucune politique pour le schéma/namespace donné
ErrAdminNotFoundL'ID admin n'est pas dans la politique — RevokeAdmin uniquement
ErrHSMProviderNilBucket HSM/Remote créé sans fournisseur enregistré
ErrCheckLatencyLa latence de lecture DB a dépassé DBLatencyThreshold
ErrCASConflictLa valeur actuelle ne correspond pas à celle attendue dans CompareAndSwap
ErrSecurityDowngradeDéplacement inter-bucket d'un niveau de sécurité supérieur à inférieur
ErrAlreadyUnlockedUnlockDatabase appelé sur un magasin déjà déverrouillé
ErrMasterRequiredUnlockDatabase appelé avec Master nil ou détruit
ErrChainBrokenLa vérification d'intégrité de la chaîne d'audit a échoué
ErrMetadataDecryptLes métadonnées chiffrées n'ont pas pu être déchiffrées
ErrPolicySignatureLa vérification HMAC de la politique a échoué — l'enregistrement a été falsifié
PaquetObjectif
go.etcd.io/bboltMagasin de clés-valeurs embarqué
golang.org/x/cryptoArgon2id, XChaCha20-Poly1305, HKDF, scrypt
github.com/awnumar/memguardEnclave de clé sécurisée en mémoire (clé maîtresse, DEKs)
github.com/vmihailenco/msgpack/v5Sérialisation binaire pour les secrets et politiques
github.com/olekukonko/jackSupervision des processus (intégration Jack optionnelle)
github.com/olekukonko/llJournalisation structurée
github.com/olekukonko/errorsErreurs sentinelles avec traces d'appel
github.com/olekukonko/zeroMise à zéro sécurisée de tranches d'octets
github.com/olekukonko/prompterInvites de terminal sans écho (CLI uniquement)
github.com/integrii/flaggyAnalyse des drapeaux CLI (cmd/keeper uniquement)
golang.org/x/termDétection TTY et lecture de mot de passe brut (CLI uniquement)