namespace DodoSSH.Domain; /// /// A container of encrypted items sharing one vault key. /// /// /// The vault name is plaintext, unlike item names. A user has to be able to pick a vault before /// anything is decrypted, and vault names are few and low-signal compared with a full host /// inventory. /// public sealed class Vault { /// Primary key. public Guid Id { get; set; } /// Display name. public string Name { get; set; } = string.Empty; /// Whether this belongs to a user or a team. public VaultOwnerKind OwnerKind { get; set; } /// Owning user, for a personal vault. public Guid? OwnerUserId { get; set; } /// Owning user, for a personal vault. public UserAccount? OwnerUser { get; set; } /// Owning team, for a team vault. public Guid? TeamId { get; set; } /// Owning team, for a team vault. public Team? Team { get; set; } /// /// Current key generation. Bumped on rekey, and part of every item's AAD, so a server cannot /// roll a row back to a superseded generation. /// public int KeyGeneration { get; set; } = 1; /// Whether a membership or key change has left this vault needing a rekey. public bool RekeyRequired { get; set; } /// Why a rekey is pending. public RekeyReason RekeyReason { get; set; } /// Creation timestamp. public DateTimeOffset CreatedAtUtc { get; set; } /// Last modification timestamp. public DateTimeOffset UpdatedAtUtc { get; set; } /// Soft-delete marker. public DateTimeOffset? DeletedAtUtc { get; set; } /// Wrapped vault keys, one per recipient per generation. public ICollection KeyGrants { get; } = []; /// Hosts in this vault. public ICollection Hosts { get; } = []; } /// /// A vault key wrapped to one recipient, for one key generation. /// /// /// /// The server stores verbatim and cannot verify that it is the correct /// vault key. A malicious granter can seal garbage; the recipient detects it on first unwrap as a /// tag failure, and names who did it. Detectable and attributable is the /// right failure mode here — silent is not achievable, since verification would require the /// server to hold the key. /// /// /// and are recorded so a recipient /// can check both who wrapped this and what view of the key log they held at the time. /// /// public sealed class VaultKeyGrant { /// Primary key. public Guid Id { get; set; } /// The vault. public Guid VaultId { get; set; } /// The vault. public Vault? Vault { get; set; } /// Key generation this grant is for. public int KeyGeneration { get; set; } /// Why this grant exists: a member, a recovery key, or escrow. public GrantKind Kind { get; set; } /// Recipient, for a member grant. public Guid? RecipientUserId { get; set; } /// Recipient, for a member grant. public UserAccount? RecipientUser { get; set; } /// /// Fingerprint of the exact public key this was wrapped to, so a later key rotation /// invalidates the grant explicitly rather than silently. /// public byte[] RecipientKeyFingerprint { get; set; } = []; /// The vault key, sealed to the recipient. Opaque. public byte[] WrappedKey { get; set; } = []; /// Who created this grant. public Guid GranterUserId { get; set; } /// Fingerprint of the granter's identity key. public byte[] GranterKeyFingerprint { get; set; } = []; /// Key log head the granter observed. Enables fork detection. public byte[]? KeyLogHead { get; set; } /// /// Ed25519 signature by the granter over the canonical grant tuple. Verified by clients, not /// by the server: server-side verification would be a convenience, never the boundary. /// public byte[] Signature { get; set; } = []; /// Grant state. public GrantState State { get; set; } /// Creation timestamp. public DateTimeOffset CreatedAtUtc { get; set; } /// Revocation timestamp. public DateTimeOffset? RevokedAtUtc { get; set; } }