mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
v2 — Sharing & Recovery: - Shared vaults (org/team) with member access control - Multi-device enrollment with per-device wrapping keys - Per-collection sharing (recipient decrypts only shared collection) - Threshold recovery (XOR split, Shamir in v3) v3 — Provider Marketplace: - SyncProvider, StorageProvider, RecoveryProvider interfaces - Local providers (filesystem, content-addressed, share storage) - Export/Import portable vault bundles (encrypted snapshot + oplog) - Usage metering (puts, gets, syncs, anchors per vault) v4 — Confidential Compute: - ConfidentialEngine interface (opt-in FHE/ZK, NOT default) - Local engine (decrypt-compute-prove baseline) - PolicyEngine with capability-based access control (grant/check/revoke/expiry) - Append-only audit log with hash-chained entries + merkle root 50 tests total. Zero failures. Zero external dependencies. Multi-instance HA: each instance runs its own SQLite shard, CRDT sync handles consistency. Encryption at rest via DEK/KEK hierarchy.
118 lines
3.1 KiB
Go
118 lines
3.1 KiB
Go
// Copyright (C) 2020-2026, Hanzo AI Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package vault
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Bundle is a portable vault export: JSON envelope with encrypted data + oplog + metadata.
|
|
type Bundle struct {
|
|
Version int `json:"version"`
|
|
UserID string `json:"userId"`
|
|
OrgID string `json:"orgId"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
Snapshot json.RawMessage `json:"snapshot"` // encrypted store entries
|
|
Oplog []Op `json:"oplog"`
|
|
Metadata BundleMetadata `json:"metadata"`
|
|
}
|
|
|
|
// BundleMetadata holds non-sensitive metadata about the bundle.
|
|
type BundleMetadata struct {
|
|
KeyCount int `json:"keyCount"`
|
|
OpCount int `json:"opCount"`
|
|
Format string `json:"format"` // "vault-bundle-v3"
|
|
}
|
|
|
|
// ExportVault exports the session's encrypted state as a portable JSON bundle.
|
|
// The bundle contains ciphertext only — the DEK is NOT included.
|
|
// The caller must supply the DEK separately to import.
|
|
func ExportVault(session *Session) ([]byte, error) {
|
|
session.mu.RLock()
|
|
defer session.mu.RUnlock()
|
|
|
|
if session.store == nil {
|
|
return nil, fmt.Errorf("vault: session is closed")
|
|
}
|
|
|
|
// Serialize the encrypted store as a map of key → hex-encoded ciphertext.
|
|
snapshot := make(map[string][]byte, len(session.store))
|
|
for k, v := range session.store {
|
|
snapshot[k] = v
|
|
}
|
|
|
|
snapshotJSON, err := json.Marshal(snapshot)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("vault: marshal snapshot: %w", err)
|
|
}
|
|
|
|
oplog := make([]Op, len(session.oplog))
|
|
copy(oplog, session.oplog)
|
|
|
|
bundle := Bundle{
|
|
Version: 3,
|
|
UserID: session.userID,
|
|
OrgID: session.orgID,
|
|
Timestamp: time.Now().Unix(),
|
|
Snapshot: snapshotJSON,
|
|
Oplog: oplog,
|
|
Metadata: BundleMetadata{
|
|
KeyCount: len(snapshot),
|
|
OpCount: len(oplog),
|
|
Format: "vault-bundle-v3",
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(bundle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("vault: marshal bundle: %w", err)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
// ImportVault imports a vault from a portable bundle.
|
|
// The caller must provide the correct DEK to decrypt values.
|
|
func ImportVault(data []byte, dek []byte) (*Session, error) {
|
|
if len(dek) != 32 {
|
|
return nil, fmt.Errorf("vault: DEK must be 32 bytes")
|
|
}
|
|
|
|
var bundle Bundle
|
|
if err := json.Unmarshal(data, &bundle); err != nil {
|
|
return nil, fmt.Errorf("vault: unmarshal bundle: %w", err)
|
|
}
|
|
|
|
if bundle.Version != 3 {
|
|
return nil, fmt.Errorf("vault: unsupported bundle version %d", bundle.Version)
|
|
}
|
|
|
|
// Deserialize the encrypted store.
|
|
var snapshot map[string][]byte
|
|
if err := json.Unmarshal(bundle.Snapshot, &snapshot); err != nil {
|
|
return nil, fmt.Errorf("vault: unmarshal snapshot: %w", err)
|
|
}
|
|
|
|
// Rebuild version map from oplog.
|
|
version := make(map[string]uint64)
|
|
for _, op := range bundle.Oplog {
|
|
if op.Seq > version[op.NodeID] {
|
|
version[op.NodeID] = op.Seq
|
|
}
|
|
}
|
|
|
|
session := &Session{
|
|
userID: bundle.UserID,
|
|
orgID: bundle.OrgID,
|
|
dek: make([]byte, 32),
|
|
store: snapshot,
|
|
oplog: bundle.Oplog,
|
|
version: version,
|
|
}
|
|
copy(session.dek, dek)
|
|
|
|
return session, nil
|
|
}
|